Vendor things

This commit is contained in:
John Doty 2024-03-08 11:03:01 -08:00
parent 5deceec006
commit 977e3c17e5
19434 changed files with 10682014 additions and 0 deletions

View file

@ -0,0 +1 @@
{"files":{"Cargo.lock":"f94dbe69b6b59ff7d1d9491ce0c80d9b585a3931ae0cb69ec4526b924984ebdf","Cargo.toml":"fbc51bf500578be1bf058c68073028f218670e63b8aae1fcaf65d3e6041dc196","LICENSE-APACHE":"0178e21322b0e88aa3aeb3146f6a9611bc1f8df6d98bdfb34be28b9dd56a8107","LICENSE-MIT":"ad41be6cc6538b29b9346648f41432b5e460bad6be073b5eeaa41320ea2921dc","README.md":"226f277f49ce319de28eb27d222cfcdd511318aaa3994723c4048d6a46b808ab","examples/d3d12-buffer-winrs.rs":"1d9baa1b69ba130a71afdb94336dfcb590c4d74c98026fd07123746a9e07b4bb","examples/d3d12-buffer.rs":"2162d9186f55b54510672d67dee58da78b2fff01ee0cd455ad96a453da556b62","examples/d3d12-visualization/dxil/imgui.ps.dxil":"8aa54c288d633b3f7d7649ef20967841ba5accbf5f23e96c83bf96274e90d77f","examples/d3d12-visualization/dxil/imgui.vs.dxil":"3a0de3911a8959e26167a040a8b5f9a3dc1a7e7edc1e2e5ce16fe99c3e002bfc","examples/d3d12-visualization/imgui_renderer.rs":"7cabe99bbee0f16174ed5a5fa355518efc1758e8de7e837691f6e4f5fe3fce08","examples/d3d12-visualization/main.rs":"28a1fe11cf63b479a80f51b1ae4810be0af033b1ab3f0440927fdd7f7aca3d7b","examples/d3d12-visualization/shaders/imgui.ps.hlsl":"a6b8c3730f38f699519c3b1a0874b2629a0c5b2169530dd82dd00bede9380c8f","examples/d3d12-visualization/shaders/imgui.vs.hlsl":"7f0aee77b1b6c928523c908cb48584f606007925ad0e397623f075333b6efce3","examples/vulkan-buffer.rs":"25447d2f7e6be9f83499ca27509be9e1486fbad9b025c2a02827c84422de9032","examples/vulkan-visualization/helper.rs":"b294f2d1d3dedb91ee52c15134962a2f181de4e98ac5fcafbbf03c84b218704b","examples/vulkan-visualization/imgui_renderer.rs":"2da146c7c7d820d40a1a73296885ecc05786403d479cb00a8a1604b630c5f8d4","examples/vulkan-visualization/main.rs":"4d6546bb2bdc77a6c774a732fa3411704fdb84a5bcde861984cba0b0e2915f50","examples/vulkan-visualization/shaders/imgui.ps.hlsl":"a6b8c3730f38f699519c3b1a0874b2629a0c5b2169530dd82dd00bede9380c8f","examples/vulkan-visualization/shaders/imgui.vs.hlsl":"7f0aee77b1b6c928523c908cb48584f606007925ad0e397623f075333b6efce3","examples/vulkan-visualization/spirv/imgui.ps.spv":"b45a364e6bf6545c5fd34fe060910cdfc6f7608af6f343f6b437da385b8d3e71","examples/vulkan-visualization/spirv/imgui.vs.spv":"6316b49bfb84608bcfad308a5e3c50b6a28b0fed8ff07fb04c2ed4e93265762b","src/allocator/dedicated_block_allocator/mod.rs":"34010b593e2c62b2fb3625ea5486f145865639114d593e28abd1c6af8fbc3e71","src/allocator/dedicated_block_allocator/visualizer.rs":"b31eb2b451d183d89db49c1533da86efb6e973771fc403aad9b8ad359123d7e6","src/allocator/free_list_allocator/mod.rs":"f8e38245825805c152a3fc42df04e8de63882f4af0f5aa0b1f6ffba2e082117f","src/allocator/free_list_allocator/visualizer.rs":"2762eb1f95180b39c926aec32936c2b11ca82c74bf6073207eec5a4c40c2b1b7","src/allocator/mod.rs":"1a3a342af286291da4ae3a395cb5dfaa9a4f3a0f6577e21cc3e1964d929fdffa","src/d3d12/mod.rs":"fdc0bea2c21b2134f218a88d9610d2b227d49c524f2bc72f300e71a14fb585a8","src/d3d12/visualizer.rs":"83dac0ef330103baad46002b5a7172ac8713aa2a850fec07543c0baaca2ba08e","src/lib.rs":"cf68b68271393d49c765aa752868316eb38ff78943185d7f903d31b23508012a","src/result.rs":"aaa573d8b9b976c31cd61fdae7b18f509f3015cc93708373497cc681daa15163","src/visualizer/mod.rs":"196a066ad451936c13577567040bcd1a8ecf20f67dda5e8fdf544cba44a8e81b","src/vulkan/mod.rs":"7c6556afa2069b7b8a2f601d1c0bdfcdadd5837ae69f56c4bd5559b2733ad548","src/vulkan/visualizer.rs":"0be8670cc33574aa74ae333b5bbba397706c1f6b8be04ec308a087cb68081ea3"},"package":"ce95f9e2e11c2c6fadfce42b5af60005db06576f231f5c92550fdded43c423e8"}

1582
third-party/vendor/gpu-allocator/Cargo.lock generated vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,172 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2018"
name = "gpu-allocator"
version = "0.22.0"
authors = ["Traverse Research <opensource@traverseresearch.nl>"]
include = [
"/README.md",
"/LICENSE-*",
"/src",
"/examples",
]
description = "Memory allocator for GPU memory in Vulkan and DirectX 12"
homepage = "https://github.com/Traverse-Research/gpu-allocator"
documentation = "https://docs.rs/gpu-allocator/"
readme = "README.md"
keywords = [
"vulkan",
"memory",
"allocator",
]
categories = [
"rendering",
"rendering::graphics-api",
]
license = "MIT OR Apache-2.0"
repository = "https://github.com/Traverse-Research/gpu-allocator"
[[example]]
name = "vulkan-buffer"
required-features = [
"vulkan",
"ash/loaded",
]
[[example]]
name = "vulkan-visualization"
required-features = [
"vulkan",
"ash/loaded",
"visualizer",
]
[[example]]
name = "d3d12-buffer"
required-features = [
"d3d12",
"public-winapi",
]
[[example]]
name = "d3d12-buffer-winrs"
required-features = ["d3d12"]
[[example]]
name = "d3d12-visualization"
required-features = [
"d3d12",
"public-winapi",
"visualizer",
]
[dependencies.ash]
version = ">=0.34, <=0.37"
features = ["debug"]
optional = true
default-features = false
[dependencies.backtrace]
version = "0.3"
[dependencies.imgui]
version = "0.10"
features = ["tables-api"]
optional = true
[dependencies.log]
version = "0.4"
[dependencies.thiserror]
version = "1.0"
[dev-dependencies.ash]
version = "0.37"
features = [
"debug",
"loaded",
]
default-features = false
[dev-dependencies.ash-window]
version = "0.12"
[dev-dependencies.env_logger]
version = "0.10"
[dev-dependencies.raw-window-handle]
version = "0.5"
[dev-dependencies.winit]
version = "0.27"
features = [
"x11",
"wayland",
]
[features]
d3d12 = ["windows"]
default = [
"d3d12",
"vulkan",
]
public-winapi = ["dep:winapi"]
visualizer = ["imgui"]
vulkan = ["ash"]
[target."cfg(windows)".dependencies.winapi]
version = "0.3.9"
features = [
"d3d12",
"winerror",
"impl-default",
"impl-debug",
]
optional = true
[target."cfg(windows)".dependencies.windows]
version = "0.44"
features = [
"Win32_Foundation",
"Win32_Graphics",
"Win32_Graphics_Direct3D",
"Win32_Graphics_Direct3D12",
"Win32_Graphics_Dxgi",
"Win32_Graphics_Dxgi_Common",
]
optional = true
[target."cfg(windows)".dev-dependencies.winapi]
version = "0.3.9"
features = [
"d3d12",
"d3d12sdklayers",
"dxgi1_6",
"winerror",
"impl-default",
"impl-debug",
"winuser",
"windowsx",
"libloaderapi",
]
[target."cfg(windows)".dev-dependencies.windows]
version = "0.44"
features = [
"Win32_Foundation",
"Win32_Graphics",
"Win32_Graphics_Direct3D",
"Win32_Graphics_Direct3D12",
"Win32_Graphics_Dxgi",
"Win32_Graphics_Dxgi_Common",
]

View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2021 Traverse Research B.V.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1,7 @@
Copyright (c) 2021 Traverse Research B.V.
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:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
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.

View file

@ -0,0 +1,147 @@
# 📒 gpu-allocator
[![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)
[![Latest version](https://img.shields.io/crates/v/gpu-allocator.svg?logo=rust)](https://crates.io/crates/gpu-allocator)
[![Docs](https://img.shields.io/docsrs/gpu-allocator?logo=docs.rs)](https://docs.rs/gpu-allocator/)
[![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE-MIT)
[![LICENSE](https://img.shields.io/badge/license-apache-blue.svg?logo=apache)](LICENSE-APACHE)
[![Contributor Covenant](https://img.shields.io/badge/contributor%20covenant-v1.4%20adopted-ff69b4.svg)](../main/CODE_OF_CONDUCT.md)
[![Banner](banner.png)](https://traverseresearch.nl)
```toml
[dependencies]
gpu-allocator = "0.22.0"
```
This crate provides a fully written in Rust memory allocator for Vulkan and DirectX 12.
### [Windows-rs] and [winapi]
`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.
[Windows-rs]: https://github.com/microsoft/windows-rs
[winapi]: https://github.com/retep998/winapi-rs
### Setting up the Vulkan memory allocator
```rust
use gpu_allocator::vulkan::*;
let mut allocator = Allocator::new(&AllocatorCreateDesc {
instance,
device,
physical_device,
debug_settings: Default::default(),
buffer_device_address: true, // Ideally, check the BufferDeviceAddressFeatures struct.
});
```
### Simple Vulkan allocation example
```rust
use gpu_allocator::vulkan::*;
use gpu_allocator::MemoryLocation;
// Setup vulkan info
let vk_info = vk::BufferCreateInfo::builder()
.size(512)
.usage(vk::BufferUsageFlags::STORAGE_BUFFER);
let buffer = unsafe { device.create_buffer(&vk_info, None) }.unwrap();
let requirements = unsafe { device.get_buffer_memory_requirements(buffer) };
let allocation = allocator
.allocate(&AllocationCreateDesc {
name: "Example allocation",
requirements,
location: MemoryLocation::CpuToGpu,
linear: true, // Buffers are always linear
allocation_scheme: AllocationScheme::GpuAllocatorManaged,
}).unwrap();
// Bind memory to the buffer
unsafe { device.bind_buffer_memory(buffer, allocation.memory(), allocation.offset()).unwrap() };
// Cleanup
allocator.free(allocation).unwrap();
unsafe { device.destroy_buffer(buffer, None) };
```
### Setting up the D3D12 memory allocator
```rust
use gpu_allocator::d3d12::*;
let mut allocator = Allocator::new(&AllocatorCreateDesc {
device,
debug_settings: Default::default(),
});
```
### Simple d3d12 allocation example
```rust
use gpu_allocator::d3d12::*;
use gpu_allocator::MemoryLocation;
let buffer_desc = Direct3D12::D3D12_RESOURCE_DESC {
Dimension: Direct3D12::D3D12_RESOURCE_DIMENSION_BUFFER,
Alignment: 0,
Width: 512,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
Format: Dxgi::Common::DXGI_FORMAT_UNKNOWN,
SampleDesc: Dxgi::Common::DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Layout: Direct3D12::D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
Flags: Direct3D12::D3D12_RESOURCE_FLAG_NONE,
};
let allocation_desc = AllocationCreateDesc::from_d3d12_resource_desc(
&allocator.device(),
&buffer_desc,
"Example allocation",
MemoryLocation::GpuOnly,
);
let allocation = allocator.allocate(&allocation_desc).unwrap();
let mut resource: Option<Direct3D12::ID3D12Resource> = None;
let hr = unsafe {
device.CreatePlacedResource(
allocation.heap(),
allocation.offset(),
&buffer_desc,
Direct3D12::D3D12_RESOURCE_STATE_COMMON,
None,
&mut resource,
)
}?;
// Cleanup
drop(resource);
allocator.free(allocation).unwrap();
```
### License
Licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](../master/LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](../master/LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
### Alternative libraries
* [vk-mem-rs](https://github.com/gwihlidal/vk-mem-rs)
### Contribution
Unless you explicitly state otherwise, any contribution intentionally
submitted for inclusion in the work by you, as defined in the Apache-2.0
license, shall be dual licensed as above, without any additional terms or
conditions.

View file

@ -0,0 +1,237 @@
//! Example showcasing [`gpu-allocator`] with types and functions from the [`windows`] crate.
use gpu_allocator::d3d12::{
AllocationCreateDesc, Allocator, AllocatorCreateDesc, ResourceCategory,
};
use gpu_allocator::MemoryLocation;
use log::*;
use windows::core::{Interface, Result};
use windows::Win32::{
Foundation::E_NOINTERFACE,
Graphics::{
Direct3D::{D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_12_0},
Direct3D12::{
D3D12CreateDevice, ID3D12Device, ID3D12Resource,
D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT, D3D12_RESOURCE_DESC,
D3D12_RESOURCE_DIMENSION_BUFFER, D3D12_RESOURCE_FLAG_NONE, D3D12_RESOURCE_STATE_COMMON,
D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
},
Dxgi::Common::{DXGI_FORMAT_UNKNOWN, DXGI_SAMPLE_DESC},
Dxgi::{
CreateDXGIFactory2, IDXGIAdapter4, IDXGIFactory6, DXGI_ADAPTER_FLAG3_SOFTWARE,
DXGI_ERROR_NOT_FOUND,
},
},
};
fn create_d3d12_device(dxgi_factory: &IDXGIFactory6) -> Option<ID3D12Device> {
for idx in 0.. {
// TODO: Might as well return Result<> from this function
let adapter1 = match unsafe { dxgi_factory.EnumAdapters1(idx) } {
Ok(a) => a,
Err(e) if e.code() == DXGI_ERROR_NOT_FOUND => break,
Err(e) => panic!("{:?}", e),
};
let adapter4: IDXGIAdapter4 = adapter1.cast().unwrap();
let mut desc = Default::default();
unsafe { adapter4.GetDesc3(&mut desc) }.unwrap();
// Skip software adapters
// Vote for https://github.com/microsoft/windows-rs/issues/793!
if (desc.Flags & DXGI_ADAPTER_FLAG3_SOFTWARE) == DXGI_ADAPTER_FLAG3_SOFTWARE {
continue;
}
let feature_levels = [
(D3D_FEATURE_LEVEL_11_0, "D3D_FEATURE_LEVEL_11_0"),
(D3D_FEATURE_LEVEL_11_1, "D3D_FEATURE_LEVEL_11_1"),
(D3D_FEATURE_LEVEL_12_0, "D3D_FEATURE_LEVEL_12_0"),
];
let device =
feature_levels
.iter()
.rev()
.find_map(|&(feature_level, feature_level_name)| {
let mut device = None;
match unsafe { D3D12CreateDevice(&adapter4, feature_level, &mut device) } {
Ok(()) => {
info!("Using D3D12 feature level: {}", feature_level_name);
Some(device.unwrap())
}
Err(e) if e.code() == E_NOINTERFACE => {
error!("ID3D12Device interface not supported");
None
}
Err(e) => {
info!(
"D3D12 feature level {} not supported: {}",
feature_level_name, e
);
None
}
}
});
if device.is_some() {
return device;
}
}
None
}
fn main() -> Result<()> {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace")).init();
let dxgi_factory = unsafe { CreateDXGIFactory2(0) }?;
let device = create_d3d12_device(&dxgi_factory).expect("Failed to create D3D12 device.");
// Setting up the allocator
let mut allocator = Allocator::new(&AllocatorCreateDesc {
device: device.clone(),
debug_settings: Default::default(),
})
.unwrap();
// Test allocating Gpu Only memory
{
let test_buffer_desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
Alignment: D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT as u64,
Width: 512,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
Format: DXGI_FORMAT_UNKNOWN,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
Flags: D3D12_RESOURCE_FLAG_NONE,
};
let allocation_desc = AllocationCreateDesc::from_d3d12_resource_desc(
allocator.device(),
&test_buffer_desc,
"Test allocation (Gpu only)",
MemoryLocation::GpuOnly,
);
let allocation = allocator.allocate(&allocation_desc).unwrap();
let mut resource: Option<ID3D12Resource> = None;
unsafe {
device.CreatePlacedResource(
allocation.heap(),
allocation.offset(),
&test_buffer_desc,
D3D12_RESOURCE_STATE_COMMON,
None,
&mut resource,
)
}?;
drop(resource);
allocator.free(allocation).unwrap();
info!("Allocation and deallocation of GpuOnly memory was successful.");
}
// Test allocating Cpu to Gpu memory
{
let test_buffer_desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
Alignment: D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT as u64,
Width: 512,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
Format: DXGI_FORMAT_UNKNOWN,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
Flags: D3D12_RESOURCE_FLAG_NONE,
};
let alloc_info = unsafe { device.GetResourceAllocationInfo(0, &[test_buffer_desc]) };
let allocation = allocator
.allocate(&AllocationCreateDesc {
name: "Test allocation (Cpu To Gpu)",
location: MemoryLocation::CpuToGpu,
size: alloc_info.SizeInBytes,
alignment: alloc_info.Alignment,
resource_category: ResourceCategory::Buffer,
})
.unwrap();
let mut resource: Option<ID3D12Resource> = None;
unsafe {
device.CreatePlacedResource(
allocation.heap(),
allocation.offset(),
&test_buffer_desc,
D3D12_RESOURCE_STATE_COMMON,
None,
&mut resource,
)
}?;
drop(resource);
allocator.free(allocation).unwrap();
info!("Allocation and deallocation of CpuToGpu memory was successful.");
}
// Test allocating Gpu to Cpu memory
{
let test_buffer_desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
Alignment: D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT as u64,
Width: 512,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
Format: DXGI_FORMAT_UNKNOWN,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
Flags: D3D12_RESOURCE_FLAG_NONE,
};
let alloc_info = unsafe { device.GetResourceAllocationInfo(0, &[test_buffer_desc]) };
let allocation = allocator
.allocate(&AllocationCreateDesc {
name: "Test allocation (Gpu to Cpu)",
location: MemoryLocation::GpuToCpu,
size: alloc_info.SizeInBytes,
alignment: alloc_info.Alignment,
resource_category: ResourceCategory::Buffer,
})
.unwrap();
let mut resource: Option<ID3D12Resource> = None;
unsafe {
device.CreatePlacedResource(
allocation.heap(),
allocation.offset(),
&test_buffer_desc,
D3D12_RESOURCE_STATE_COMMON,
None,
&mut resource,
)
}?;
drop(resource);
allocator.free(allocation).unwrap();
info!("Allocation and deallocation of CpuToGpu memory was successful.");
}
Ok(())
}

View file

@ -0,0 +1,282 @@
//! Example showcasing [`winapi`] interop with [`gpu-allocator`] which is driven by the [`windows`] crate.
use winapi::shared::{dxgiformat, winerror};
use winapi::um::{d3d12, d3dcommon};
use winapi::Interface;
mod all_dxgi {
pub use winapi::shared::{dxgi1_3::*, dxgi1_6::*, dxgitype::*};
}
use log::*;
use gpu_allocator::d3d12::{
AllocationCreateDesc, Allocator, AllocatorCreateDesc, ResourceCategory, ToWinapi, ToWindows,
};
use gpu_allocator::MemoryLocation;
fn create_d3d12_device(
dxgi_factory: *mut all_dxgi::IDXGIFactory6,
) -> Option<*mut d3d12::ID3D12Device> {
for idx in 0.. {
let mut adapter4: *mut all_dxgi::IDXGIAdapter4 = std::ptr::null_mut();
let hr = unsafe {
dxgi_factory.as_ref().unwrap().EnumAdapters1(
idx,
<*mut *mut all_dxgi::IDXGIAdapter4>::cast(&mut adapter4),
)
};
if hr == winerror::DXGI_ERROR_NOT_FOUND {
break;
}
assert_eq!(hr, winerror::S_OK);
let mut desc = all_dxgi::DXGI_ADAPTER_DESC3::default();
let hr = unsafe { adapter4.as_ref().unwrap().GetDesc3(&mut desc) };
if hr != winerror::S_OK {
error!("Failed to get adapter description for adapter");
continue;
}
// Skip software adapters
if (desc.Flags & all_dxgi::DXGI_ADAPTER_FLAG3_SOFTWARE)
== all_dxgi::DXGI_ADAPTER_FLAG3_SOFTWARE
{
continue;
}
let feature_levels = [
(d3dcommon::D3D_FEATURE_LEVEL_11_0, "D3D_FEATURE_LEVEL_11_0"),
(d3dcommon::D3D_FEATURE_LEVEL_11_1, "D3D_FEATURE_LEVEL_11_1"),
(d3dcommon::D3D_FEATURE_LEVEL_12_0, "D3D_FEATURE_LEVEL_12_0"),
];
let device =
feature_levels
.iter()
.rev()
.find_map(|&(feature_level, feature_level_name)| {
let mut device: *mut d3d12::ID3D12Device = std::ptr::null_mut();
let hr = unsafe {
d3d12::D3D12CreateDevice(
adapter4.cast(),
feature_level,
&d3d12::ID3D12Device::uuidof(),
<*mut *mut d3d12::ID3D12Device>::cast(&mut device),
)
};
match hr {
winapi::shared::winerror::S_OK => {
info!("Using D3D12 feature level: {}.", feature_level_name);
Some(device)
}
winapi::shared::winerror::E_NOINTERFACE => {
error!("ID3D12Device interface not supported.");
None
}
_ => {
info!(
"D3D12 feature level: {} not supported: {:x}",
feature_level_name, hr
);
None
}
}
});
if device.is_some() {
return device;
}
}
None
}
fn main() {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace")).init();
let dxgi_factory = {
let mut dxgi_factory: *mut all_dxgi::IDXGIFactory6 = std::ptr::null_mut();
let hr = unsafe {
all_dxgi::CreateDXGIFactory2(
0,
&all_dxgi::IID_IDXGIFactory6,
<*mut *mut all_dxgi::IDXGIFactory6>::cast(&mut dxgi_factory),
)
};
assert_eq!(
hr,
winapi::shared::winerror::S_OK,
"Failed to create DXGI factory",
);
dxgi_factory
};
let device = create_d3d12_device(dxgi_factory).expect("Failed to create D3D12 device.");
// Setting up the allocator
let mut allocator = Allocator::new(&AllocatorCreateDesc {
device: device.as_windows().clone(),
debug_settings: Default::default(),
})
.unwrap();
let device = unsafe { device.as_ref() }.unwrap();
// Test allocating Gpu Only memory
{
let test_buffer_desc = d3d12::D3D12_RESOURCE_DESC {
Dimension: d3d12::D3D12_RESOURCE_DIMENSION_BUFFER,
Alignment: d3d12::D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT as u64,
Width: 512,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
Format: dxgiformat::DXGI_FORMAT_UNKNOWN,
SampleDesc: all_dxgi::DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Layout: d3d12::D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
Flags: d3d12::D3D12_RESOURCE_FLAG_NONE,
};
let allocation_desc = AllocationCreateDesc::from_winapi_d3d12_resource_desc(
device,
&test_buffer_desc,
"Test allocation (Gpu Only)",
MemoryLocation::GpuOnly,
);
let allocation = allocator.allocate(&allocation_desc).unwrap();
let mut resource: *mut d3d12::ID3D12Resource = std::ptr::null_mut();
let hr = unsafe {
device.CreatePlacedResource(
allocation.heap().as_winapi() as *mut _,
allocation.offset(),
&test_buffer_desc,
d3d12::D3D12_RESOURCE_STATE_COMMON,
std::ptr::null(),
&d3d12::IID_ID3D12Resource,
<*mut *mut d3d12::ID3D12Resource>::cast(&mut resource),
)
};
if hr != winerror::S_OK {
panic!("Failed to create placed resource.");
}
unsafe { resource.as_ref().unwrap().Release() };
allocator.free(allocation).unwrap();
info!("Allocation and deallocation of GpuOnly memory was successful.");
}
// Test allocating Cpu to Gpu memory
{
let test_buffer_desc = d3d12::D3D12_RESOURCE_DESC {
Dimension: d3d12::D3D12_RESOURCE_DIMENSION_BUFFER,
Alignment: d3d12::D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT as u64,
Width: 512,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
Format: dxgiformat::DXGI_FORMAT_UNKNOWN,
SampleDesc: all_dxgi::DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Layout: d3d12::D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
Flags: d3d12::D3D12_RESOURCE_FLAG_NONE,
};
let alloc_info = unsafe { device.GetResourceAllocationInfo(0, 1, &test_buffer_desc) };
let allocation = allocator
.allocate(&AllocationCreateDesc {
name: "Test allocation (Cpu to Gpu)",
location: MemoryLocation::CpuToGpu,
size: alloc_info.SizeInBytes,
alignment: alloc_info.Alignment,
resource_category: ResourceCategory::Buffer,
})
.unwrap();
let mut resource: *mut d3d12::ID3D12Resource = std::ptr::null_mut();
let hr = unsafe {
device.CreatePlacedResource(
allocation.heap().as_winapi() as *mut _,
allocation.offset(),
&test_buffer_desc,
d3d12::D3D12_RESOURCE_STATE_COMMON,
std::ptr::null(),
&d3d12::IID_ID3D12Resource,
<*mut *mut d3d12::ID3D12Resource>::cast(&mut resource),
)
};
if hr != winerror::S_OK {
panic!("Failed to create placed resource.");
}
unsafe { resource.as_ref().unwrap().Release() };
allocator.free(allocation).unwrap();
info!("Allocation and deallocation of CpuToGpu memory was successful.");
}
// Test allocating Gpu to Cpu memory
{
let test_buffer_desc = d3d12::D3D12_RESOURCE_DESC {
Dimension: d3d12::D3D12_RESOURCE_DIMENSION_BUFFER,
Alignment: d3d12::D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT as u64,
Width: 512,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
Format: dxgiformat::DXGI_FORMAT_UNKNOWN,
SampleDesc: all_dxgi::DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Layout: d3d12::D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
Flags: d3d12::D3D12_RESOURCE_FLAG_NONE,
};
let alloc_info = unsafe { device.GetResourceAllocationInfo(0, 1, &test_buffer_desc) };
let allocation = allocator
.allocate(&AllocationCreateDesc {
name: "Test allocation (Gpu to Cpu)",
location: MemoryLocation::GpuToCpu,
size: alloc_info.SizeInBytes,
alignment: alloc_info.Alignment,
resource_category: ResourceCategory::Buffer,
})
.unwrap();
let mut resource: *mut d3d12::ID3D12Resource = std::ptr::null_mut();
let hr = unsafe {
device.CreatePlacedResource(
allocation.heap().as_winapi() as *mut _,
allocation.offset(),
&test_buffer_desc,
d3d12::D3D12_RESOURCE_STATE_COMMON,
std::ptr::null(),
&d3d12::IID_ID3D12Resource,
<*mut *mut d3d12::ID3D12Resource>::cast(&mut resource),
)
};
if hr != winerror::S_OK {
panic!("Failed to create placed resource.");
}
unsafe { resource.as_ref().unwrap().Release() };
allocator.free(allocation).unwrap();
info!("Allocation and deallocation of CpuToGpu memory was successful.");
}
drop(allocator); // Explicitly drop before destruction of device.
unsafe { device.Release() };
unsafe { dxgi_factory.as_ref().unwrap().Release() };
}

View file

@ -0,0 +1,990 @@
mod all_dxgi {
pub use winapi::shared::{
dxgi::*, dxgi1_2::*, dxgi1_3::*, dxgi1_4::*, dxgi1_6::*, dxgiformat::*, dxgitype::*,
};
}
use gpu_allocator::d3d12::ToWinapi;
use winapi::um::d3d12::*;
use winapi::um::d3dcommon::*;
use winapi::Interface;
use winapi::shared::winerror::FAILED;
use gpu_allocator::d3d12::{Allocation, AllocationCreateDesc, Allocator};
use gpu_allocator::MemoryLocation;
use super::transition_resource;
#[repr(C)]
#[derive(Clone, Copy)]
struct ImGuiCBuffer {
scale: [f32; 2],
translation: [f32; 2],
}
pub struct ImGuiRenderer {
root_signature: *mut ID3D12RootSignature,
pipeline: *mut ID3D12PipelineState,
vb_capacity: u64,
ib_capacity: u64,
vb_allocation: Allocation,
ib_allocation: Allocation,
vb_pointer: *mut u8,
ib_pointer: *mut u8,
vertex_buffer: *mut ID3D12Resource,
index_buffer: *mut ID3D12Resource,
cb_allocation: Allocation,
cb_pointer: *mut u8,
constant_buffer: *mut ID3D12Resource,
font_image: *mut ID3D12Resource,
font_image_memory: Allocation,
font_image_srv_index: usize,
font_image_upload_buffer: *mut ID3D12Resource,
font_image_upload_buffer_memory: Allocation,
}
impl ImGuiRenderer {
pub(crate) fn new(
imgui: &mut imgui::Context,
device: &mut ID3D12Device,
allocator: &mut Allocator,
descriptor_heap: &mut ID3D12DescriptorHeap,
descriptor_heap_counter: &mut usize,
cmd: &mut ID3D12GraphicsCommandList,
) -> Self {
let root_signature = {
let mut root_parameters = [
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_CBV,
ShaderVisibility: D3D12_SHADER_VISIBILITY_ALL,
..Default::default()
},
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
ShaderVisibility: D3D12_SHADER_VISIBILITY_ALL,
..Default::default()
},
];
let ranges = [D3D12_DESCRIPTOR_RANGE {
RangeType: D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
NumDescriptors: 1,
BaseShaderRegister: 2,
RegisterSpace: 0,
OffsetInDescriptorsFromTableStart: 0,
}];
unsafe {
root_parameters[0].u.Descriptor_mut().ShaderRegister = 0;
root_parameters[0].u.Descriptor_mut().RegisterSpace = 0;
root_parameters[1]
.u
.DescriptorTable_mut()
.NumDescriptorRanges = ranges.len() as u32;
root_parameters[1].u.DescriptorTable_mut().pDescriptorRanges = ranges.as_ptr();
}
let static_samplers = [D3D12_STATIC_SAMPLER_DESC {
Filter: D3D12_FILTER_MIN_MAG_MIP_LINEAR,
AddressU: D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
AddressV: D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
AddressW: D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
MipLODBias: 0.0,
MaxAnisotropy: 0,
ComparisonFunc: D3D12_COMPARISON_FUNC_ALWAYS,
BorderColor: D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK,
MinLOD: 0.0,
MaxLOD: 0.0,
ShaderRegister: 1,
RegisterSpace: 0,
ShaderVisibility: D3D12_SHADER_VISIBILITY_ALL,
}];
let rsig_desc = D3D12_ROOT_SIGNATURE_DESC {
NumParameters: root_parameters.len() as u32,
pParameters: root_parameters.as_ptr(),
NumStaticSamplers: static_samplers.len() as u32,
pStaticSamplers: static_samplers.as_ptr(),
Flags: D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT,
};
let mut blob = std::ptr::null_mut();
let mut error_blob = std::ptr::null_mut();
let hr = unsafe {
D3D12SerializeRootSignature(
&rsig_desc,
D3D_ROOT_SIGNATURE_VERSION_1,
&mut blob,
&mut error_blob,
)
};
if FAILED(hr) {
let error = unsafe {
let error_blob = error_blob.as_ref().unwrap();
std::slice::from_raw_parts(
error_blob.GetBufferPointer() as *const u8,
error_blob.GetBufferSize(),
)
};
panic!(
"Failed to serialize root signature: '{}'. hr: {:#x}",
std::str::from_utf8(error).unwrap(),
hr
);
}
let blob = unsafe { blob.as_ref() }.unwrap();
let mut rsig = std::ptr::null_mut();
let hr = unsafe {
device.CreateRootSignature(
0,
blob.GetBufferPointer(),
blob.GetBufferSize(),
&ID3D12RootSignature::uuidof(),
<*mut *mut ID3D12RootSignature>::cast(&mut rsig),
)
};
if FAILED(hr) {
panic!("Failed to create root signature. hr: {:#x}", hr);
}
unsafe { rsig.as_mut() }.unwrap()
};
let pipeline = unsafe {
let vs = include_bytes!("./dxil/imgui.vs.dxil");
let ps = include_bytes!("./dxil/imgui.ps.dxil");
let input_elements = [
D3D12_INPUT_ELEMENT_DESC {
SemanticName: b"POSITION\0".as_ptr().cast(),
SemanticIndex: 0,
Format: all_dxgi::DXGI_FORMAT_R32G32_FLOAT,
InputSlot: 0,
AlignedByteOffset: 0,
InputSlotClass: D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
},
D3D12_INPUT_ELEMENT_DESC {
SemanticName: b"TEXCOORD\0".as_ptr().cast(),
SemanticIndex: 0,
Format: all_dxgi::DXGI_FORMAT_R32G32_FLOAT,
InputSlot: 0,
AlignedByteOffset: 8,
InputSlotClass: D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
},
D3D12_INPUT_ELEMENT_DESC {
SemanticName: b"COLOR\0".as_ptr().cast(),
SemanticIndex: 0,
Format: all_dxgi::DXGI_FORMAT_R8G8B8A8_UNORM,
InputSlot: 0,
AlignedByteOffset: 16,
InputSlotClass: D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
},
];
let desc = D3D12_GRAPHICS_PIPELINE_STATE_DESC {
pRootSignature: root_signature,
VS: D3D12_SHADER_BYTECODE {
pShaderBytecode: vs.as_ptr().cast(),
BytecodeLength: vs.len(),
},
PS: D3D12_SHADER_BYTECODE {
pShaderBytecode: ps.as_ptr().cast(),
BytecodeLength: ps.len(),
},
BlendState: D3D12_BLEND_DESC {
AlphaToCoverageEnable: 0,
IndependentBlendEnable: 0,
RenderTarget: [
D3D12_RENDER_TARGET_BLEND_DESC {
BlendEnable: 1,
LogicOpEnable: 0,
SrcBlend: D3D12_BLEND_SRC_ALPHA,
DestBlend: D3D12_BLEND_INV_SRC_ALPHA,
BlendOp: D3D12_BLEND_OP_ADD,
SrcBlendAlpha: D3D12_BLEND_ONE,
DestBlendAlpha: D3D12_BLEND_ZERO,
BlendOpAlpha: D3D12_BLEND_OP_ADD,
LogicOp: D3D12_LOGIC_OP_NOOP,
RenderTargetWriteMask: D3D12_COLOR_WRITE_ENABLE_ALL as u8,
},
D3D12_RENDER_TARGET_BLEND_DESC::default(),
D3D12_RENDER_TARGET_BLEND_DESC::default(),
D3D12_RENDER_TARGET_BLEND_DESC::default(),
D3D12_RENDER_TARGET_BLEND_DESC::default(),
D3D12_RENDER_TARGET_BLEND_DESC::default(),
D3D12_RENDER_TARGET_BLEND_DESC::default(),
D3D12_RENDER_TARGET_BLEND_DESC::default(),
],
},
SampleMask: !0u32,
RasterizerState: D3D12_RASTERIZER_DESC {
FillMode: D3D12_FILL_MODE_SOLID,
CullMode: D3D12_CULL_MODE_NONE,
FrontCounterClockwise: 0,
DepthBias: 0,
DepthBiasClamp: 0.0,
SlopeScaledDepthBias: 0.0,
DepthClipEnable: 0,
MultisampleEnable: 0,
AntialiasedLineEnable: 0,
ForcedSampleCount: 1,
ConservativeRaster: D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF,
},
DepthStencilState: D3D12_DEPTH_STENCIL_DESC {
DepthEnable: 0,
DepthWriteMask: D3D12_DEPTH_WRITE_MASK_ZERO,
DepthFunc: D3D12_COMPARISON_FUNC_ALWAYS,
StencilEnable: 0,
StencilReadMask: 0,
StencilWriteMask: 0,
FrontFace: D3D12_DEPTH_STENCILOP_DESC::default(),
BackFace: D3D12_DEPTH_STENCILOP_DESC::default(),
},
InputLayout: D3D12_INPUT_LAYOUT_DESC {
pInputElementDescs: input_elements.as_ptr(),
NumElements: input_elements.len() as u32,
},
PrimitiveTopologyType: D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
NumRenderTargets: 1,
RTVFormats: [
all_dxgi::DXGI_FORMAT_R8G8B8A8_UNORM,
all_dxgi::DXGI_FORMAT_UNKNOWN,
all_dxgi::DXGI_FORMAT_UNKNOWN,
all_dxgi::DXGI_FORMAT_UNKNOWN,
all_dxgi::DXGI_FORMAT_UNKNOWN,
all_dxgi::DXGI_FORMAT_UNKNOWN,
all_dxgi::DXGI_FORMAT_UNKNOWN,
all_dxgi::DXGI_FORMAT_UNKNOWN,
],
SampleDesc: all_dxgi::DXGI_SAMPLE_DESC {
Quality: 0,
Count: 1,
},
..Default::default()
};
let mut pipeline: *mut ID3D12PipelineState = std::ptr::null_mut();
let hr = device.CreateGraphicsPipelineState(
&desc,
&ID3D12PipelineState::uuidof(),
<*mut *mut ID3D12PipelineState>::cast(&mut pipeline),
);
if FAILED(hr) {
panic!("Failed to create imgui pipeline: {:#x}", hr);
}
pipeline.as_mut().unwrap()
};
let (
font_image,
font_image_memory,
font_image_srv_index,
font_image_upload_buffer,
font_image_upload_buffer_memory,
) = {
let fonts = imgui.fonts();
let font_atlas = fonts.build_rgba32_texture();
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Alignment: 0,
Width: font_atlas.width as u64,
Height: font_atlas.height,
DepthOrArraySize: 1,
MipLevels: 1,
Format: all_dxgi::DXGI_FORMAT_R8G8B8A8_UNORM,
SampleDesc: all_dxgi::DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Layout: D3D12_TEXTURE_LAYOUT_UNKNOWN,
Flags: D3D12_RESOURCE_FLAG_NONE,
};
let font_image_memory = allocator
.allocate(&AllocationCreateDesc::from_winapi_d3d12_resource_desc(
device,
&desc,
"font_image",
MemoryLocation::GpuOnly,
))
.unwrap();
let font_image = unsafe {
let mut font_image: *mut ID3D12Resource = std::ptr::null_mut();
let hr = device.CreatePlacedResource(
font_image_memory.heap().as_winapi() as *mut _,
font_image_memory.offset(),
&desc,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
std::ptr::null(),
&ID3D12Resource::uuidof(),
<*mut *mut ID3D12Resource>::cast(&mut font_image),
);
if FAILED(hr) {
panic!("Failed to create font image. hr: {:#x}", hr);
}
font_image
};
//Create SRV
let srv_index = unsafe {
let srv_index = *descriptor_heap_counter;
*descriptor_heap_counter += 1;
let srv_size = device
.GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV)
as usize;
let desc_heap_handle = descriptor_heap.GetCPUDescriptorHandleForHeapStart();
let desc_heap_handle = D3D12_CPU_DESCRIPTOR_HANDLE {
ptr: desc_heap_handle.ptr + srv_index * srv_size,
};
const fn d3d12_encode_shader_4_component_mapping(
src0: u32,
src1: u32,
src2: u32,
src3: u32,
) -> u32 {
(src0 & D3D12_SHADER_COMPONENT_MAPPING_MASK)
| ((src1 & D3D12_SHADER_COMPONENT_MAPPING_MASK)
<< (D3D12_SHADER_COMPONENT_MAPPING_SHIFT))
| ((src2 & D3D12_SHADER_COMPONENT_MAPPING_MASK)
<< (D3D12_SHADER_COMPONENT_MAPPING_SHIFT * 2))
| ((src3 & D3D12_SHADER_COMPONENT_MAPPING_MASK)
<< (D3D12_SHADER_COMPONENT_MAPPING_SHIFT * 3))
| D3D12_SHADER_COMPONENT_MAPPING_ALWAYS_SET_BIT_AVOIDING_ZEROMEM_MISTAKES
}
const fn d3d12_default_shader_4_component_mapping() -> u32 {
d3d12_encode_shader_4_component_mapping(0, 1, 2, 3)
}
let mut srv_desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: all_dxgi::DXGI_FORMAT_R8G8B8A8_UNORM,
ViewDimension: D3D12_SRV_DIMENSION_TEXTURE2D,
Shader4ComponentMapping: d3d12_default_shader_4_component_mapping(),
..Default::default()
};
srv_desc.u.Texture2D_mut().MostDetailedMip = 0;
srv_desc.u.Texture2D_mut().MipLevels = 1;
srv_desc.u.Texture2D_mut().PlaneSlice = 0;
srv_desc.u.Texture2D_mut().ResourceMinLODClamp = 0.0;
device.CreateShaderResourceView(font_image, &srv_desc, desc_heap_handle);
srv_index
};
let mut layouts = [D3D12_PLACED_SUBRESOURCE_FOOTPRINT::default()];
let mut num_rows: u32 = 0;
let mut row_size_in_bytes: u64 = 0;
let mut total_bytes: u64 = 0;
unsafe {
device.GetCopyableFootprints(
&font_image.as_ref().unwrap().GetDesc(),
0, // first sub
layouts.len() as u32, // num sub
0, // intermediate offset
layouts.as_mut_ptr(),
&mut num_rows,
&mut row_size_in_bytes,
&mut total_bytes,
)
};
// Create upload buffer
let (upload_buffer, upload_buffer_memory) = {
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
Alignment: 0,
Width: total_bytes,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
Format: all_dxgi::DXGI_FORMAT_UNKNOWN,
SampleDesc: all_dxgi::DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
Flags: D3D12_RESOURCE_FLAG_NONE,
};
let upload_buffer_memory = allocator
.allocate(&AllocationCreateDesc::from_winapi_d3d12_resource_desc(
device,
&desc,
"font_image upload buffer",
MemoryLocation::CpuToGpu,
))
.unwrap();
let mut upload_buffer: *mut ID3D12Resource = std::ptr::null_mut();
let hr = unsafe {
device.CreatePlacedResource(
upload_buffer_memory.heap().as_winapi() as *mut _,
upload_buffer_memory.offset(),
&desc,
D3D12_RESOURCE_STATE_GENERIC_READ,
std::ptr::null(),
&ID3D12Resource::uuidof(),
<*mut *mut ID3D12Resource>::cast(&mut upload_buffer),
)
};
if FAILED(hr) {
panic!("Failed to create font image upload buffer. hr: {:x}.", hr);
}
(upload_buffer, upload_buffer_memory)
};
unsafe {
let mut mapped_ptr = std::ptr::null_mut();
upload_buffer
.as_ref()
.unwrap()
.Map(0, std::ptr::null(), &mut mapped_ptr);
let mapped_ptr = mapped_ptr.cast::<u8>();
let layout = &layouts[0];
let mapped_ptr = mapped_ptr.add(layout.Offset as usize);
let source_ptr = font_atlas.data.as_ptr();
for y in 0..layout.Footprint.Height {
let mapped_ptr = mapped_ptr.add((y * layout.Footprint.RowPitch) as usize);
let source_ptr = source_ptr.add((y * font_atlas.width * 4) as usize);
std::ptr::copy_nonoverlapping(
source_ptr,
mapped_ptr,
(layout.Footprint.Width * 4) as usize,
);
}
upload_buffer.as_ref().unwrap().Unmap(0, std::ptr::null())
};
let mut dst = D3D12_TEXTURE_COPY_LOCATION {
pResource: font_image,
Type: D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
..Default::default()
};
unsafe { *dst.u.SubresourceIndex_mut() = 0 };
let mut src = D3D12_TEXTURE_COPY_LOCATION {
pResource: upload_buffer,
Type: D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
..Default::default()
};
unsafe { *src.u.PlacedFootprint_mut() = layouts[0] };
unsafe {
let barriers = [transition_resource(
font_image,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_STATE_COPY_DEST,
)];
cmd.ResourceBarrier(barriers.len() as u32, barriers.as_ptr());
cmd.CopyTextureRegion(&dst, 0, 0, 0, &src, std::ptr::null())
};
(
font_image,
font_image_memory,
srv_index,
upload_buffer,
upload_buffer_memory,
)
};
let (constant_buffer, cb_allocation, cb_pointer) = {
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
Alignment: 0,
Width: std::mem::size_of::<ImGuiCBuffer>() as u64,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
Format: all_dxgi::DXGI_FORMAT_UNKNOWN,
SampleDesc: all_dxgi::DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
Flags: D3D12_RESOURCE_FLAG_NONE,
};
let allocation = allocator
.allocate(&AllocationCreateDesc::from_winapi_d3d12_resource_desc(
device,
&desc,
"ImGui Constant buffer",
MemoryLocation::CpuToGpu,
))
.unwrap();
let mut buffer: *mut ID3D12Resource = std::ptr::null_mut();
let hr = unsafe {
device.CreatePlacedResource(
allocation.heap().as_winapi() as *mut _,
allocation.offset(),
&desc,
D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER,
std::ptr::null(),
&ID3D12Resource::uuidof(),
<*mut *mut ID3D12Resource>::cast(&mut buffer),
)
};
if FAILED(hr) {
panic!("Failed to create constant buffer. hr: {:x}.", hr);
}
let mut mapped_ptr: *mut u8 = std::ptr::null_mut();
unsafe {
buffer.as_ref().unwrap().Map(
0,
std::ptr::null(),
<*mut *mut u8>::cast(&mut mapped_ptr),
)
};
(buffer, allocation, mapped_ptr)
};
let vb_capacity = 1024 * 1024;
let (vertex_buffer, vb_allocation, vb_pointer) = {
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
Alignment: 0,
Width: vb_capacity,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
Format: all_dxgi::DXGI_FORMAT_UNKNOWN,
SampleDesc: all_dxgi::DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
Flags: D3D12_RESOURCE_FLAG_NONE,
};
let allocation = allocator
.allocate(&AllocationCreateDesc::from_winapi_d3d12_resource_desc(
device,
&desc,
"ImGui Vertex buffer",
MemoryLocation::CpuToGpu,
))
.unwrap();
let mut buffer: *mut ID3D12Resource = std::ptr::null_mut();
let hr = unsafe {
device.CreatePlacedResource(
allocation.heap().as_winapi() as *mut _,
allocation.offset(),
&desc,
D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER,
std::ptr::null(),
&ID3D12Resource::uuidof(),
<*mut *mut ID3D12Resource>::cast(&mut buffer),
)
};
if FAILED(hr) {
panic!("Failed to create vertex buffer. hr: {:x}.", hr);
}
let mut mapped_ptr: *mut u8 = std::ptr::null_mut();
unsafe {
buffer.as_ref().unwrap().Map(
0,
std::ptr::null(),
<*mut *mut u8>::cast(&mut mapped_ptr),
)
};
(buffer, allocation, mapped_ptr)
};
let ib_capacity = 1024 * 1024;
let (index_buffer, ib_allocation, ib_pointer) = {
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
Alignment: 0,
Width: ib_capacity,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
Format: all_dxgi::DXGI_FORMAT_UNKNOWN,
SampleDesc: all_dxgi::DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
Flags: D3D12_RESOURCE_FLAG_NONE,
};
let allocation = allocator
.allocate(&AllocationCreateDesc::from_winapi_d3d12_resource_desc(
device,
&desc,
"ImGui Vertex buffer",
MemoryLocation::CpuToGpu,
))
.unwrap();
let mut buffer: *mut ID3D12Resource = std::ptr::null_mut();
let hr = unsafe {
device.CreatePlacedResource(
allocation.heap().as_winapi() as *mut _,
allocation.offset(),
&desc,
D3D12_RESOURCE_STATE_INDEX_BUFFER,
std::ptr::null(),
&ID3D12Resource::uuidof(),
<*mut *mut ID3D12Resource>::cast(&mut buffer),
)
};
if FAILED(hr) {
panic!("Failed to create vertex buffer. hr: {:x}.", hr);
}
let mut mapped_ptr: *mut u8 = std::ptr::null_mut();
unsafe {
buffer.as_ref().unwrap().Map(
0,
std::ptr::null(),
<*mut *mut u8>::cast(&mut mapped_ptr),
)
};
(buffer, allocation, mapped_ptr)
};
Self {
root_signature,
pipeline,
vb_capacity,
ib_capacity,
vb_allocation,
ib_allocation,
vb_pointer,
ib_pointer,
vertex_buffer,
index_buffer,
cb_allocation,
cb_pointer,
constant_buffer,
font_image,
font_image_memory,
font_image_srv_index,
font_image_upload_buffer,
font_image_upload_buffer_memory,
}
}
pub(crate) fn render(
&mut self,
imgui_draw_data: &imgui::DrawData,
device: &mut ID3D12Device,
window_width: u32,
window_height: u32,
descriptor_heap: &mut ID3D12DescriptorHeap,
cmd: &mut ID3D12GraphicsCommandList,
) {
// Update constant buffer
{
let left = imgui_draw_data.display_pos[0];
let right = imgui_draw_data.display_pos[0] + imgui_draw_data.display_size[0];
let top = imgui_draw_data.display_pos[1] + imgui_draw_data.display_size[1];
let bottom = imgui_draw_data.display_pos[1];
let cbuffer_data = ImGuiCBuffer {
scale: [(2.0 / (right - left)), (2.0 / (bottom - top))],
translation: [
(right + left) / (left - right),
(top + bottom) / (top - bottom),
],
};
unsafe { std::ptr::copy_nonoverlapping(&cbuffer_data, self.cb_pointer.cast(), 1) };
}
let (vtx_count, idx_count) =
imgui_draw_data
.draw_lists()
.fold((0, 0), |(vtx_count, idx_count), draw_list| {
(
vtx_count + draw_list.vtx_buffer().len(),
idx_count + draw_list.idx_buffer().len(),
)
});
let vtx_size = (vtx_count * std::mem::size_of::<imgui::DrawVert>()) as u64;
if vtx_size > self.vb_capacity {
// reallocate vertex buffer
todo!();
}
let idx_size = (idx_count * std::mem::size_of::<imgui::DrawIdx>()) as u64;
if idx_size > self.ib_capacity {
// reallocate index buffer
todo!();
}
let mut vb_offset = 0;
let mut ib_offset = 0;
unsafe {
let viewports = [D3D12_VIEWPORT {
TopLeftX: 0.0,
TopLeftY: 0.0,
Width: window_width as f32,
Height: window_height as f32,
MinDepth: 0.0,
MaxDepth: 1.0,
}];
cmd.RSSetViewports(viewports.len() as u32, viewports.as_ptr());
cmd.SetPipelineState(self.pipeline);
cmd.SetGraphicsRootSignature(self.root_signature);
cmd.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
{
let constant_buffer = self.constant_buffer.as_mut().unwrap();
let addr = constant_buffer.GetGPUVirtualAddress();
cmd.SetGraphicsRootConstantBufferView(0, addr);
}
{
let srv_stride =
device.GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
let heap_handle = descriptor_heap.GetGPUDescriptorHandleForHeapStart();
let heap_handle = D3D12_GPU_DESCRIPTOR_HANDLE {
ptr: heap_handle.ptr + srv_stride as u64 * self.font_image_srv_index as u64,
};
cmd.SetGraphicsRootDescriptorTable(1, heap_handle);
}
}
for draw_list in imgui_draw_data.draw_lists() {
let vertices = draw_list.vtx_buffer();
let indices = draw_list.idx_buffer();
let vbv = {
let vertex_buffer = unsafe { self.vertex_buffer.as_mut().unwrap() };
let stride = std::mem::size_of::<imgui::DrawVert>();
let address =
unsafe { vertex_buffer.GetGPUVirtualAddress() } + (vb_offset * stride) as u64;
D3D12_VERTEX_BUFFER_VIEW {
BufferLocation: address,
SizeInBytes: (vertices.len() * stride) as u32,
StrideInBytes: stride as u32,
}
};
let ibv = {
let index_buffer = unsafe { self.index_buffer.as_mut().unwrap() };
let stride = std::mem::size_of::<u16>();
let address =
unsafe { index_buffer.GetGPUVirtualAddress() } + (ib_offset * stride) as u64;
D3D12_INDEX_BUFFER_VIEW {
BufferLocation: address,
SizeInBytes: (indices.len() * stride) as u32,
Format: all_dxgi::DXGI_FORMAT_R16_UINT,
}
};
// Upload vertices
unsafe {
let stride = std::mem::size_of::<imgui::DrawVert>();
let dst_ptr = self
.vb_pointer
.add(vb_offset * stride)
.cast::<imgui::DrawVert>();
std::ptr::copy_nonoverlapping(vertices.as_ptr(), dst_ptr, vertices.len());
}
vb_offset += vertices.len();
// Upload indices
unsafe {
let stride = std::mem::size_of::<u16>();
let dst_ptr = self.ib_pointer.add(ib_offset * stride).cast();
std::ptr::copy_nonoverlapping(indices.as_ptr(), dst_ptr, indices.len());
}
ib_offset += indices.len();
unsafe {
cmd.IASetVertexBuffers(0, 1, &vbv);
cmd.IASetIndexBuffer(&ibv);
};
for command in draw_list.commands() {
match command {
imgui::DrawCmd::Elements { count, cmd_params } => {
let scissor_rect = D3D12_RECT {
left: cmd_params.clip_rect[0] as i32,
top: cmd_params.clip_rect[1] as i32,
right: cmd_params.clip_rect[2] as i32,
bottom: cmd_params.clip_rect[3] as i32,
};
unsafe {
cmd.RSSetScissorRects(1, &scissor_rect);
cmd.DrawIndexedInstanced(
count as u32,
1,
cmd_params.idx_offset as u32,
cmd_params.vtx_offset as i32,
0,
);
};
}
_ => todo!(),
}
}
}
}
pub(crate) fn destroy(self, allocator: &mut Allocator) {
unsafe { self.pipeline.as_ref().unwrap().Release() };
unsafe { self.font_image_upload_buffer.as_ref().unwrap().Release() };
unsafe { self.vertex_buffer.as_ref().unwrap().Release() };
unsafe { self.index_buffer.as_ref().unwrap().Release() };
unsafe { self.constant_buffer.as_ref().unwrap().Release() };
unsafe { self.font_image.as_ref().unwrap().Release() };
allocator
.free(self.font_image_upload_buffer_memory)
.unwrap();
allocator.free(self.vb_allocation).unwrap();
allocator.free(self.ib_allocation).unwrap();
allocator.free(self.cb_allocation).unwrap();
allocator.free(self.font_image_memory).unwrap();
}
}
pub(crate) fn handle_imgui_event(
io: &mut imgui::Io,
window: &winit::window::Window,
event: &winit::event::Event<'_, ()>,
) -> bool {
use winit::event::{
DeviceEvent, ElementState, Event, KeyboardInput, MouseButton, MouseScrollDelta, TouchPhase,
VirtualKeyCode, WindowEvent,
};
match event {
Event::WindowEvent { event, window_id } if *window_id == window.id() => match *event {
WindowEvent::Resized(physical_size) => {
io.display_size = [physical_size.width as f32, physical_size.height as f32];
false
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(key),
state,
..
},
..
} => {
let pressed = state == ElementState::Pressed;
io.keys_down[key as usize] = pressed;
match key {
VirtualKeyCode::LShift | VirtualKeyCode::RShift => io.key_shift = pressed,
VirtualKeyCode::LControl | VirtualKeyCode::RControl => io.key_ctrl = pressed,
VirtualKeyCode::LAlt | VirtualKeyCode::RAlt => io.key_alt = pressed,
VirtualKeyCode::LWin | VirtualKeyCode::RWin => io.key_super = pressed,
_ => (),
}
io.want_capture_keyboard
}
WindowEvent::ReceivedCharacter(ch) => {
io.add_input_character(ch);
io.want_capture_keyboard
}
WindowEvent::CursorMoved { position, .. } => {
io.mouse_pos = [position.x as f32, position.y as f32];
io.want_capture_mouse
}
WindowEvent::MouseWheel {
delta,
phase: TouchPhase::Moved,
..
} => {
match delta {
MouseScrollDelta::LineDelta(h, v) => {
io.mouse_wheel_h = h;
io.mouse_wheel = v;
}
MouseScrollDelta::PixelDelta(pos) => {
match pos.x.partial_cmp(&0.0) {
Some(std::cmp::Ordering::Greater) => io.mouse_wheel_h += 1.0,
Some(std::cmp::Ordering::Less) => io.mouse_wheel_h -= 1.0,
_ => (),
}
match pos.y.partial_cmp(&0.0) {
Some(std::cmp::Ordering::Greater) => io.mouse_wheel += 1.0,
Some(std::cmp::Ordering::Less) => io.mouse_wheel -= 1.0,
_ => (),
}
}
}
io.want_capture_mouse
}
WindowEvent::MouseInput { state, button, .. } => {
let pressed = state == ElementState::Pressed;
match button {
MouseButton::Left => io.mouse_down[0] = pressed,
MouseButton::Right => io.mouse_down[1] = pressed,
MouseButton::Middle => io.mouse_down[2] = pressed,
MouseButton::Other(idx @ 0..=4) => io.mouse_down[idx as usize] = pressed,
MouseButton::Other(_) => (),
}
io.want_capture_mouse
}
_ => false,
},
// Track key release events outside our window. If we don't do this,
// we might never see the release event if some other window gets focus.
Event::DeviceEvent {
event:
DeviceEvent::Key(KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
..
}),
..
} => {
io.keys_down[*key as usize] = false;
match *key {
VirtualKeyCode::LShift | VirtualKeyCode::RShift => io.key_shift = false,
VirtualKeyCode::LControl | VirtualKeyCode::RControl => io.key_ctrl = false,
VirtualKeyCode::LAlt | VirtualKeyCode::RAlt => io.key_alt = false,
VirtualKeyCode::LWin | VirtualKeyCode::RWin => io.key_super = false,
_ => (),
}
io.want_capture_keyboard
}
_ => false,
}
}

View file

@ -0,0 +1,556 @@
#![windows_subsystem = "windows"]
//! Example showcasing [`winapi`] interop with [`gpu-allocator`] which is driven by the [`windows`] crate.
use log::info;
use raw_window_handle::HasRawWindowHandle;
use gpu_allocator::d3d12::{Allocator, AllocatorCreateDesc, ToWindows};
mod all_dxgi {
pub use winapi::shared::{
dxgi::*, dxgi1_2::*, dxgi1_3::*, dxgi1_4::*, dxgi1_6::*, dxgiformat::*, dxgitype::*,
};
}
use winapi::um::d3d12::*;
use winapi::um::d3dcommon::*;
use winapi::um::winuser;
use winapi::shared::minwindef::UINT;
use winapi::shared::winerror;
use winapi::shared::winerror::{FAILED, SUCCEEDED};
use winapi::Interface;
mod imgui_renderer;
use imgui_renderer::{handle_imgui_event, ImGuiRenderer};
const ENABLE_DEBUG_LAYER: bool = true;
const FRAMES_IN_FLIGHT: usize = 2;
struct BackBuffer {
resource: *mut ID3D12Resource,
rtv_handle: D3D12_CPU_DESCRIPTOR_HANDLE,
}
fn find_hardware_adapter(
dxgi_factory: &all_dxgi::IDXGIFactory6,
) -> Option<*mut all_dxgi::IDXGIAdapter4> {
let mut adapter: *mut all_dxgi::IDXGIAdapter4 = std::ptr::null_mut();
for adapter_index in 0.. {
let hr = unsafe {
dxgi_factory.EnumAdapters1(
adapter_index,
<*mut *mut all_dxgi::IDXGIAdapter4>::cast(&mut adapter),
)
};
if hr == winerror::DXGI_ERROR_NOT_FOUND {
break;
}
let mut desc = Default::default();
unsafe { adapter.as_ref().unwrap().GetDesc3(&mut desc) };
if (desc.Flags & all_dxgi::DXGI_ADAPTER_FLAG_SOFTWARE) != 0 {
continue;
}
let hr = unsafe {
D3D12CreateDevice(
adapter.cast(),
D3D_FEATURE_LEVEL_12_0,
&IID_ID3D12Device,
std::ptr::null_mut(),
)
};
if SUCCEEDED(hr) {
return Some(adapter);
}
}
None
}
fn enable_d3d12_debug_layer() -> bool {
use winapi::um::d3d12sdklayers::ID3D12Debug;
let mut debug: *mut ID3D12Debug = std::ptr::null_mut();
let hr = unsafe {
D3D12GetDebugInterface(
&ID3D12Debug::uuidof(),
<*mut *mut ID3D12Debug>::cast(&mut debug),
)
};
if FAILED(hr) {
return false;
}
let debug = unsafe { debug.as_mut().unwrap() };
unsafe { debug.EnableDebugLayer() };
unsafe { debug.Release() };
true
}
fn create_d3d12_device(adapter: &mut all_dxgi::IDXGIAdapter4) -> *mut ID3D12Device {
unsafe {
let mut device: *mut ID3D12Device = std::ptr::null_mut();
let hr = D3D12CreateDevice(
<*mut all_dxgi::IDXGIAdapter4>::cast(adapter),
D3D_FEATURE_LEVEL_12_0,
&ID3D12Device::uuidof(),
<*mut *mut ID3D12Device>::cast(&mut device),
);
if FAILED(hr) {
panic!("Failed to create ID3D12Device.");
}
device
}
}
#[must_use]
fn transition_resource(
resource: *mut ID3D12Resource,
before: D3D12_RESOURCE_STATES,
after: D3D12_RESOURCE_STATES,
) -> D3D12_RESOURCE_BARRIER {
let mut barrier = D3D12_RESOURCE_BARRIER {
Type: D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
Flags: D3D12_RESOURCE_BARRIER_FLAG_NONE,
..D3D12_RESOURCE_BARRIER::default()
};
unsafe {
barrier.u.Transition_mut().pResource = resource;
barrier.u.Transition_mut().StateBefore = before;
barrier.u.Transition_mut().StateAfter = after;
barrier.u.Transition_mut().Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
}
barrier
}
fn main() {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace")).init();
// Disable automatic DPI scaling by windows
unsafe { winuser::SetProcessDPIAware() };
let event_loop = winit::event_loop::EventLoop::new();
let window_width = 1920;
let window_height = 1080;
let window = winit::window::WindowBuilder::new()
.with_title("gpu-allocator d3d12 visualization")
.with_inner_size(winit::dpi::PhysicalSize::new(
window_width as f64,
window_height as f64,
))
.with_resizable(false)
.build(&event_loop)
.unwrap();
let (event_send, event_recv) = std::sync::mpsc::sync_channel(1);
let quit_send = event_loop.create_proxy();
std::thread::spawn(move || {
let mut dxgi_factory_flags = 0;
if ENABLE_DEBUG_LAYER && enable_d3d12_debug_layer() {
info!("Enabled D3D12 debug layer");
dxgi_factory_flags |= all_dxgi::DXGI_CREATE_FACTORY_DEBUG;
}
let dxgi_factory = unsafe {
let mut factory: *mut all_dxgi::IDXGIFactory6 = std::ptr::null_mut();
let hr = all_dxgi::CreateDXGIFactory2(
dxgi_factory_flags,
&all_dxgi::IID_IDXGIFactory6,
<*mut *mut all_dxgi::IDXGIFactory6>::cast(&mut factory),
);
if FAILED(hr) {
panic!("Failed to create dxgi factory");
}
factory.as_mut().unwrap()
};
let adapter = find_hardware_adapter(dxgi_factory).unwrap();
let adapter = unsafe { adapter.as_mut().unwrap() };
let device = create_d3d12_device(adapter);
let device = unsafe { device.as_mut().unwrap() };
let queue = unsafe {
let desc = D3D12_COMMAND_QUEUE_DESC {
Type: D3D12_COMMAND_LIST_TYPE_DIRECT,
Priority: 0, // ?
Flags: D3D12_COMMAND_QUEUE_FLAG_NONE,
NodeMask: 0,
};
let mut queue: *mut ID3D12CommandQueue = std::ptr::null_mut();
let hr = device.CreateCommandQueue(
&desc,
&ID3D12CommandQueue::uuidof(),
<*mut *mut ID3D12CommandQueue>::cast(&mut queue),
);
if FAILED(hr) {
panic!("Failed to create command queue.");
}
queue.as_mut().unwrap()
};
let swapchain = unsafe {
let mut swapchain: *mut all_dxgi::IDXGISwapChain3 = std::ptr::null_mut();
let swap_chain_desc = all_dxgi::DXGI_SWAP_CHAIN_DESC1 {
BufferCount: FRAMES_IN_FLIGHT as UINT,
Width: window_width,
Height: window_height,
Format: all_dxgi::DXGI_FORMAT_R8G8B8A8_UNORM,
BufferUsage: all_dxgi::DXGI_USAGE_RENDER_TARGET_OUTPUT,
SwapEffect: all_dxgi::DXGI_SWAP_EFFECT_FLIP_DISCARD,
SampleDesc: all_dxgi::DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
..all_dxgi::DXGI_SWAP_CHAIN_DESC1::default()
};
let raw_window_haver: &dyn HasRawWindowHandle = &window;
let hwnd = if let raw_window_handle::RawWindowHandle::Win32(handle) =
raw_window_haver.raw_window_handle()
{
handle.hwnd
} else {
panic!("Failed to get HWND.")
};
let hr = dxgi_factory.CreateSwapChainForHwnd(
<*mut ID3D12CommandQueue>::cast(queue),
hwnd.cast(),
&swap_chain_desc,
std::ptr::null(),
std::ptr::null_mut(),
<*mut *mut all_dxgi::IDXGISwapChain3>::cast(&mut swapchain),
);
if FAILED(hr) {
panic!("Failed to create swapchain. hr: {:#x}", hr);
}
swapchain.as_mut().unwrap()
};
let rtv_heap = unsafe {
let desc = D3D12_DESCRIPTOR_HEAP_DESC {
Type: D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
NumDescriptors: 2,
Flags: D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
NodeMask: 0,
};
let mut heap: *mut ID3D12DescriptorHeap = std::ptr::null_mut();
let hr = device.CreateDescriptorHeap(
&desc,
&IID_ID3D12DescriptorHeap,
<*mut *mut ID3D12DescriptorHeap>::cast(&mut heap),
);
if FAILED(hr) {
panic!("Failed to create RTV Descriptor heap");
}
heap.as_mut().unwrap()
};
let backbuffers = unsafe {
(0..FRAMES_IN_FLIGHT)
.map(|i| {
let mut resource: *mut ID3D12Resource = std::ptr::null_mut();
let hr = swapchain.GetBuffer(
i as u32,
&ID3D12Resource::uuidof(),
<*mut *mut ID3D12Resource>::cast(&mut resource),
);
if FAILED(hr) {
panic!("Failed to access swapchain buffer {}", i);
}
let mut u = D3D12_RENDER_TARGET_VIEW_DESC_u::default();
let t2d = u.Texture2D_mut();
t2d.MipSlice = 0;
t2d.PlaneSlice = 0;
let rtv_stride = device
.GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV)
as usize;
let rtv_handle = D3D12_CPU_DESCRIPTOR_HANDLE {
ptr: rtv_heap.GetCPUDescriptorHandleForHeapStart().ptr + i * rtv_stride,
};
let mut rtv_desc = D3D12_RENDER_TARGET_VIEW_DESC {
Format: all_dxgi::DXGI_FORMAT_R8G8B8A8_UNORM,
ViewDimension: D3D12_RTV_DIMENSION_TEXTURE2D,
..Default::default()
};
rtv_desc.u.Texture2D_mut().MipSlice = 0;
rtv_desc.u.Texture2D_mut().PlaneSlice = 0;
device.CreateRenderTargetView(resource, &rtv_desc, rtv_handle);
BackBuffer {
resource,
rtv_handle,
}
})
.collect::<Vec<_>>()
};
let command_allocator = unsafe {
let mut command_allocator: *mut ID3D12CommandAllocator = std::ptr::null_mut();
let hr = device.CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE_DIRECT,
&ID3D12CommandAllocator::uuidof(),
<*mut *mut ID3D12CommandAllocator>::cast(&mut command_allocator),
);
if FAILED(hr) {
panic!("Failed to create command allocator");
}
command_allocator.as_mut().unwrap()
};
let descriptor_heap = unsafe {
let desc = D3D12_DESCRIPTOR_HEAP_DESC {
Type: D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
NumDescriptors: 4096,
Flags: D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE,
NodeMask: 0,
};
let mut heap: *mut ID3D12DescriptorHeap = std::ptr::null_mut();
let hr = device.CreateDescriptorHeap(
&desc,
&IID_ID3D12DescriptorHeap,
<*mut *mut ID3D12DescriptorHeap>::cast(&mut heap),
);
if FAILED(hr) {
panic!("Failed to create descriptor heap.");
}
heap.as_mut().unwrap()
};
let command_list = unsafe {
let mut command_list: *mut ID3D12GraphicsCommandList = std::ptr::null_mut();
let hr = device.CreateCommandList(
0,
D3D12_COMMAND_LIST_TYPE_DIRECT,
command_allocator,
std::ptr::null_mut(),
&ID3D12GraphicsCommandList::uuidof(),
<*mut *mut ID3D12GraphicsCommandList>::cast(&mut command_list),
);
if FAILED(hr) {
panic!("Failed to create command list.");
}
command_list.as_mut().unwrap()
};
let mut allocator = Allocator::new(&AllocatorCreateDesc {
device: device.as_windows().clone(),
debug_settings: Default::default(),
})
.unwrap();
let mut descriptor_heap_counter = 0;
let mut imgui = imgui::Context::create();
imgui.io_mut().display_size = [window_width as f32, window_height as f32];
let mut imgui_renderer = ImGuiRenderer::new(
&mut imgui,
device,
&mut allocator,
descriptor_heap,
&mut descriptor_heap_counter,
command_list,
);
let fence = unsafe {
let mut fence: *mut ID3D12Fence = std::ptr::null_mut();
let hr = device.CreateFence(
0,
D3D12_FENCE_FLAG_NONE,
&ID3D12Fence::uuidof(),
<*mut *mut ID3D12Fence>::cast(&mut fence),
);
if FAILED(hr) {
panic!("Failed to create fence");
}
fence.as_mut().unwrap()
};
let mut fence_value = 0_u64;
unsafe { command_list.Close() };
// Submit and wait idle
unsafe {
let lists = [<*mut ID3D12GraphicsCommandList>::cast(command_list)];
queue.ExecuteCommandLists(lists.len() as u32, lists.as_ptr());
fence_value += 1;
queue.Signal(fence, fence_value);
while fence.GetCompletedValue() < fence_value {}
};
let mut visualizer = gpu_allocator::d3d12::AllocatorVisualizer::new();
loop {
let event = event_recv.recv().unwrap();
handle_imgui_event(imgui.io_mut(), &window, &event);
let mut should_quit = false;
if let winit::event::Event::WindowEvent { event, .. } = event {
match event {
winit::event::WindowEvent::KeyboardInput { input, .. } => {
if let Some(winit::event::VirtualKeyCode::Escape) = input.virtual_keycode {
should_quit = true;
}
}
winit::event::WindowEvent::CloseRequested => {
should_quit = true;
}
_ => {}
}
}
if should_quit {
quit_send.send_event(()).unwrap();
break;
}
let buffer_index = unsafe { swapchain.GetCurrentBackBufferIndex() };
let current_backbuffer = &backbuffers[buffer_index as usize];
let ui = imgui.frame();
visualizer.render(&allocator, ui, None);
let imgui_draw_data = imgui.render();
unsafe {
command_allocator.Reset();
command_list.Reset(command_allocator, std::ptr::null_mut());
{
let barriers = [transition_resource(
current_backbuffer.resource,
D3D12_RESOURCE_STATE_PRESENT,
D3D12_RESOURCE_STATE_RENDER_TARGET,
)];
command_list.ResourceBarrier(barriers.len() as u32, barriers.as_ptr());
}
command_list.ClearRenderTargetView(
current_backbuffer.rtv_handle,
&[1.0, 1.0, 0.0, 0.0],
0,
std::ptr::null_mut(),
);
let rtv_handles = [current_backbuffer.rtv_handle];
command_list.OMSetRenderTargets(
rtv_handles.len() as u32,
rtv_handles.as_ptr(),
0,
std::ptr::null_mut(),
);
{
let scissor_rects = [D3D12_RECT {
left: 0,
top: 0,
right: window_width as i32,
bottom: window_height as i32,
}];
command_list
.RSSetScissorRects(scissor_rects.len() as u32, scissor_rects.as_ptr());
}
let mut heaps: [*mut _; 1] = [descriptor_heap];
command_list.SetDescriptorHeaps(heaps.len() as u32, heaps.as_mut_ptr());
imgui_renderer.render(
imgui_draw_data,
device,
window_width,
window_height,
descriptor_heap,
command_list,
);
{
let barriers = [transition_resource(
current_backbuffer.resource,
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_PRESENT,
)];
command_list.ResourceBarrier(barriers.len() as u32, barriers.as_ptr());
}
command_list.Close();
let lists = [<*mut ID3D12GraphicsCommandList>::cast(command_list)];
queue.ExecuteCommandLists(lists.len() as u32, lists.as_ptr());
}
unsafe { swapchain.Present(0, 0) };
unsafe {
fence_value += 1;
queue.Signal(fence, fence_value);
loop {
if fence_value == fence.GetCompletedValue() {
break;
}
}
}
}
unsafe {
fence_value += 1;
queue.Signal(fence, fence_value);
while fence.GetCompletedValue() < fence_value {}
}
imgui_renderer.destroy(&mut allocator);
unsafe {
for b in backbuffers {
b.resource.as_ref().unwrap().Release();
}
fence.Release();
command_list.Release();
command_allocator.Release();
swapchain.Release();
queue.Release();
device.Release();
adapter.Release();
dxgi_factory.Release();
}
});
event_loop.run(move |event, _, control_flow| {
*control_flow = winit::event_loop::ControlFlow::Wait;
if event == winit::event::Event::UserEvent(()) {
*control_flow = winit::event_loop::ControlFlow::Exit;
} else if let Some(event) = event.to_static() {
let _ = event_send.send(event);
} else {
*control_flow = winit::event_loop::ControlFlow::Exit;
}
});
}

View file

@ -0,0 +1,14 @@
SamplerState g_sampler : register(s1, space0);
Texture2D g_texture : register(t2, space0);
struct VertexInput
{
float4 position : SV_POSITION;
float2 texCoord: TEXCOORD0;
float4 color: COLOR;
};
float4 main(VertexInput input) : SV_Target0
{
return input.color * g_texture.Sample(g_sampler, input.texCoord);
}

View file

@ -0,0 +1,30 @@
struct VertexInput
{
float2 pos : POSITION;
float2 texCoord : TEXCOORD0;
float4 color: COLOR;
};
struct VertexOutput
{
float4 position : SV_POSITION;
float2 texCoord: TEXCOORD0;
float4 color: COLOR;
};
struct Constants
{
float2 scale;
float2 translation;
};
ConstantBuffer<Constants> g_constants : register(b0, space0);
VertexOutput main(VertexInput vertex)
{
VertexOutput o;
o.position = float4(vertex.pos * g_constants.scale + g_constants.translation, 0.0, 1.0);
o.texCoord = vertex.texCoord;
o.color = vertex.color;
return o;
}

View file

@ -0,0 +1,206 @@
use ash::vk;
use log::info;
use std::default::Default;
use std::ffi::CString;
use gpu_allocator::vulkan::{
AllocationCreateDesc, AllocationScheme, Allocator, AllocatorCreateDesc,
};
use gpu_allocator::MemoryLocation;
fn main() {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace")).init();
let entry = unsafe { ash::Entry::load() }.unwrap();
// Create Vulkan instance
let instance = {
let app_name = CString::new("Vulkan gpu-allocator test").unwrap();
let appinfo = vk::ApplicationInfo::builder()
.application_name(&app_name)
.application_version(0)
.engine_name(&app_name)
.engine_version(0)
.api_version(vk::make_api_version(0, 1, 0, 0));
let layer_names = [CString::new("VK_LAYER_KHRONOS_validation").unwrap()];
let layers_names_raw: Vec<*const i8> = layer_names
.iter()
.map(|raw_name| raw_name.as_ptr())
.collect();
let extensions_names_raw = vec![];
let create_info = vk::InstanceCreateInfo::builder()
.application_info(&appinfo)
.enabled_layer_names(&layers_names_raw)
.enabled_extension_names(&extensions_names_raw);
unsafe {
entry
.create_instance(&create_info, None)
.expect("Instance creation error")
}
};
// Look for vulkan physical device
let (pdevice, queue_family_index) = {
let pdevices = unsafe {
instance
.enumerate_physical_devices()
.expect("Physical device error")
};
pdevices
.iter()
.find_map(|pdevice| {
unsafe { instance.get_physical_device_queue_family_properties(*pdevice) }
.iter()
.enumerate()
.find_map(|(index, &info)| {
let supports_graphics = info.queue_flags.contains(vk::QueueFlags::GRAPHICS);
if supports_graphics {
Some((*pdevice, index))
} else {
None
}
})
})
.expect("Couldn't find suitable device.")
};
// Create vulkan device
let device = {
let device_extension_names_raw = vec![];
let features = vk::PhysicalDeviceFeatures {
shader_clip_distance: 1,
..Default::default()
};
let priorities = [1.0];
let queue_info = vk::DeviceQueueCreateInfo::builder()
.queue_family_index(queue_family_index as u32)
.queue_priorities(&priorities);
let create_info = vk::DeviceCreateInfo::builder()
.queue_create_infos(std::slice::from_ref(&queue_info))
.enabled_extension_names(&device_extension_names_raw)
.enabled_features(&features);
unsafe { instance.create_device(pdevice, &create_info, None).unwrap() }
};
// Setting up the allocator
let mut allocator = Allocator::new(&AllocatorCreateDesc {
instance: instance.clone(),
device: device.clone(),
physical_device: pdevice,
debug_settings: Default::default(),
buffer_device_address: false,
})
.unwrap();
// Test allocating Gpu Only memory
{
let test_buffer_info = vk::BufferCreateInfo::builder()
.size(512)
.usage(vk::BufferUsageFlags::STORAGE_BUFFER)
.sharing_mode(vk::SharingMode::EXCLUSIVE);
let test_buffer = unsafe { device.create_buffer(&test_buffer_info, None) }.unwrap();
let requirements = unsafe { device.get_buffer_memory_requirements(test_buffer) };
let location = MemoryLocation::GpuOnly;
let allocation = allocator
.allocate(&AllocationCreateDesc {
requirements,
location,
linear: true,
allocation_scheme: AllocationScheme::GpuAllocatorManaged,
name: "Test allocation (Gpu Only)",
})
.unwrap();
unsafe {
device
.bind_buffer_memory(test_buffer, allocation.memory(), allocation.offset())
.unwrap()
};
allocator.free(allocation).unwrap();
unsafe { device.destroy_buffer(test_buffer, None) };
info!("Allocation and deallocation of GpuOnly memory was successful.");
}
// Test allocating Cpu to Gpu memory
{
let test_buffer_info = vk::BufferCreateInfo::builder()
.size(512)
.usage(vk::BufferUsageFlags::STORAGE_BUFFER)
.sharing_mode(vk::SharingMode::EXCLUSIVE);
let test_buffer = unsafe { device.create_buffer(&test_buffer_info, None) }.unwrap();
let requirements = unsafe { device.get_buffer_memory_requirements(test_buffer) };
let location = MemoryLocation::CpuToGpu;
let allocation = allocator
.allocate(&AllocationCreateDesc {
requirements,
location,
linear: true,
allocation_scheme: AllocationScheme::GpuAllocatorManaged,
name: "Test allocation (Cpu to Gpu)",
})
.unwrap();
unsafe {
device
.bind_buffer_memory(test_buffer, allocation.memory(), allocation.offset())
.unwrap()
};
allocator.free(allocation).unwrap();
unsafe { device.destroy_buffer(test_buffer, None) };
info!("Allocation and deallocation of CpuToGpu memory was successful.");
}
// Test allocating Gpu to Cpu memory
{
let test_buffer_info = vk::BufferCreateInfo::builder()
.size(512)
.usage(vk::BufferUsageFlags::STORAGE_BUFFER)
.sharing_mode(vk::SharingMode::EXCLUSIVE);
let test_buffer = unsafe { device.create_buffer(&test_buffer_info, None) }.unwrap();
let requirements = unsafe { device.get_buffer_memory_requirements(test_buffer) };
let location = MemoryLocation::GpuToCpu;
let allocation = allocator
.allocate(&AllocationCreateDesc {
requirements,
location,
linear: true,
allocation_scheme: AllocationScheme::GpuAllocatorManaged,
name: "Test allocation (Gpu to Cpu)",
})
.unwrap();
unsafe {
device
.bind_buffer_memory(test_buffer, allocation.memory(), allocation.offset())
.unwrap()
};
allocator.free(allocation).unwrap();
unsafe { device.destroy_buffer(test_buffer, None) };
info!("Allocation and deallocation of GpuToCpu memory was successful.");
}
drop(allocator); // Explicitly drop before destruction of device and instance.
unsafe { device.destroy_device(None) };
unsafe { instance.destroy_instance(None) };
}

View file

@ -0,0 +1,47 @@
use ash::vk;
#[allow(clippy::too_many_arguments)]
pub(crate) fn record_and_submit_command_buffer<F: FnOnce(&ash::Device, vk::CommandBuffer)>(
device: &ash::Device,
command_buffer: vk::CommandBuffer,
command_buffer_reuse_fence: vk::Fence,
submit_queue: vk::Queue,
wait_mask: &[vk::PipelineStageFlags],
wait_semaphores: &[vk::Semaphore],
signal_semaphores: &[vk::Semaphore],
f: F,
) {
unsafe { device.wait_for_fences(&[command_buffer_reuse_fence], true, u64::MAX) }.unwrap();
unsafe { device.reset_fences(&[command_buffer_reuse_fence]) }.unwrap();
unsafe {
device.reset_command_buffer(
command_buffer,
vk::CommandBufferResetFlags::RELEASE_RESOURCES,
)
}
.unwrap();
let command_buffer_begin_info =
vk::CommandBufferBeginInfo::builder().flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT);
unsafe { device.begin_command_buffer(command_buffer, &command_buffer_begin_info) }.unwrap();
f(device, command_buffer);
unsafe { device.end_command_buffer(command_buffer) }.unwrap();
let command_buffers = [command_buffer];
let submit_info = vk::SubmitInfo::builder()
.wait_semaphores(wait_semaphores)
.wait_dst_stage_mask(wait_mask)
.command_buffers(&command_buffers)
.signal_semaphores(signal_semaphores);
unsafe {
device.queue_submit(
submit_queue,
std::slice::from_ref(&submit_info),
command_buffer_reuse_fence,
)
}
.unwrap();
}

View file

@ -0,0 +1,953 @@
use ash::vk;
use crate::helper::record_and_submit_command_buffer;
use gpu_allocator::vulkan::{Allocation, AllocationCreateDesc, AllocationScheme, Allocator};
use gpu_allocator::MemoryLocation;
#[repr(C)]
#[derive(Clone, Copy)]
struct ImGuiCBuffer {
scale: [f32; 2],
translation: [f32; 2],
}
pub struct ImGuiRenderer {
sampler: vk::Sampler,
vb_capacity: u64,
ib_capacity: u64,
vb_allocation: Allocation,
ib_allocation: Allocation,
vertex_buffer: vk::Buffer,
index_buffer: vk::Buffer,
cb_allocation: Allocation,
constant_buffer: vk::Buffer,
font_image: vk::Image,
font_image_memory: Allocation,
font_image_view: vk::ImageView,
descriptor_sets: Vec<vk::DescriptorSet>,
vs_module: vk::ShaderModule,
ps_module: vk::ShaderModule,
descriptor_set_layouts: Vec<vk::DescriptorSetLayout>,
pipeline_layout: vk::PipelineLayout,
pub(crate) render_pass: vk::RenderPass,
pipeline: vk::Pipeline,
}
impl ImGuiRenderer {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
imgui: &mut imgui::Context,
device: &ash::Device,
descriptor_pool: vk::DescriptorPool,
render_target_format: vk::Format,
allocator: &mut Allocator,
cmd: vk::CommandBuffer,
cmd_reuse_fence: vk::Fence,
queue: vk::Queue,
) -> Result<Self, vk::Result> {
let (pipeline_layout, descriptor_set_layouts) = {
let bindings = [
vk::DescriptorSetLayoutBinding {
binding: 0,
descriptor_type: vk::DescriptorType::UNIFORM_BUFFER,
descriptor_count: 1,
stage_flags: vk::ShaderStageFlags::VERTEX,
p_immutable_samplers: std::ptr::null(),
},
vk::DescriptorSetLayoutBinding {
binding: 1,
descriptor_type: vk::DescriptorType::SAMPLER,
descriptor_count: 1,
stage_flags: vk::ShaderStageFlags::FRAGMENT,
p_immutable_samplers: std::ptr::null(),
},
vk::DescriptorSetLayoutBinding {
binding: 2,
descriptor_type: vk::DescriptorType::SAMPLED_IMAGE,
descriptor_count: 1,
stage_flags: vk::ShaderStageFlags::FRAGMENT,
p_immutable_samplers: std::ptr::null(),
},
];
let set_layout_infos =
[vk::DescriptorSetLayoutCreateInfo::builder().bindings(&bindings)];
let set_layouts = set_layout_infos
.iter()
.map(|info| unsafe { device.create_descriptor_set_layout(info, None) })
.collect::<Result<Vec<_>, vk::Result>>()?;
let layout_info = vk::PipelineLayoutCreateInfo::builder().set_layouts(&set_layouts);
let pipeline_layout = unsafe { device.create_pipeline_layout(&layout_info, None) }?;
(pipeline_layout, set_layouts)
};
let render_pass = {
let attachments = vk::AttachmentDescription::builder()
.format(render_target_format)
.samples(vk::SampleCountFlags::TYPE_1)
.load_op(vk::AttachmentLoadOp::CLEAR)
.store_op(vk::AttachmentStoreOp::STORE)
.final_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
let subpass_attachment = vk::AttachmentReference::builder()
.attachment(0)
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
let subpass_description = vk::SubpassDescription::builder()
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
.color_attachments(std::slice::from_ref(&subpass_attachment));
let dependencies = vk::SubpassDependency::builder()
.src_subpass(vk::SUBPASS_EXTERNAL)
.src_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
.dst_access_mask(
vk::AccessFlags::COLOR_ATTACHMENT_READ
| vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
)
.dst_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT);
let render_pass_create_info = vk::RenderPassCreateInfo::builder()
.attachments(std::slice::from_ref(&attachments))
.subpasses(std::slice::from_ref(&subpass_description))
.dependencies(std::slice::from_ref(&dependencies));
unsafe { device.create_render_pass(&render_pass_create_info, None) }.unwrap()
};
let vs_module = {
let vs = include_bytes!("./spirv/imgui.vs.spv");
#[allow(clippy::cast_ptr_alignment)]
let shader_info = vk::ShaderModuleCreateInfo::builder().code(unsafe {
assert_eq!(vs.len() % 4, 0);
std::slice::from_raw_parts(vs.as_ptr().cast(), vs.len() / 4)
});
unsafe { device.create_shader_module(&shader_info, None) }?
};
let ps_module = {
let ps = include_bytes!("./spirv/imgui.ps.spv");
#[allow(clippy::cast_ptr_alignment)]
let shader_info = vk::ShaderModuleCreateInfo::builder().code(unsafe {
assert_eq!(ps.len() % 4, 0);
std::slice::from_raw_parts(ps.as_ptr().cast(), ps.len() / 4)
});
unsafe { device.create_shader_module(&shader_info, None) }?
};
let pipeline = {
let vertex_stage = vk::PipelineShaderStageCreateInfo::builder()
.stage(vk::ShaderStageFlags::VERTEX)
.module(vs_module)
.name(std::ffi::CStr::from_bytes_with_nul(b"main\0").unwrap());
let fragment_stage = vk::PipelineShaderStageCreateInfo::builder()
.stage(vk::ShaderStageFlags::FRAGMENT)
.module(ps_module)
.name(std::ffi::CStr::from_bytes_with_nul(b"main\0").unwrap());
let stages = [vertex_stage.build(), fragment_stage.build()];
let vertex_binding_descriptions = [vk::VertexInputBindingDescription {
binding: 0,
stride: std::mem::size_of::<imgui::DrawVert>() as u32,
input_rate: vk::VertexInputRate::VERTEX,
}];
let vertex_attribute_descriptions = [
vk::VertexInputAttributeDescription {
location: 0,
binding: 0,
format: vk::Format::R32G32_SFLOAT,
offset: 0,
},
vk::VertexInputAttributeDescription {
location: 1,
binding: 0,
format: vk::Format::R32G32_SFLOAT,
offset: 8,
},
vk::VertexInputAttributeDescription {
location: 2,
binding: 0,
format: vk::Format::R8G8B8A8_UNORM,
offset: 16,
},
];
let vertex_input_state = vk::PipelineVertexInputStateCreateInfo::builder()
.vertex_binding_descriptions(&vertex_binding_descriptions)
.vertex_attribute_descriptions(&vertex_attribute_descriptions);
let input_assembly_state = vk::PipelineInputAssemblyStateCreateInfo::builder()
.topology(vk::PrimitiveTopology::TRIANGLE_LIST);
let viewport_state = vk::PipelineViewportStateCreateInfo::builder()
.viewport_count(1)
.scissor_count(1);
let rasterization_state = vk::PipelineRasterizationStateCreateInfo::builder()
.polygon_mode(vk::PolygonMode::FILL)
.cull_mode(vk::CullModeFlags::NONE)
.front_face(vk::FrontFace::CLOCKWISE)
.depth_bias_enable(false)
.line_width(1.0);
let multisample_state = vk::PipelineMultisampleStateCreateInfo::builder()
.rasterization_samples(vk::SampleCountFlags::TYPE_1);
let noop_stencil_state = vk::StencilOpState {
fail_op: vk::StencilOp::KEEP,
pass_op: vk::StencilOp::KEEP,
depth_fail_op: vk::StencilOp::KEEP,
compare_op: vk::CompareOp::ALWAYS,
..Default::default()
};
let depth_stencil_state = vk::PipelineDepthStencilStateCreateInfo::builder()
.depth_test_enable(false)
.depth_write_enable(false)
.depth_compare_op(vk::CompareOp::ALWAYS)
.depth_bounds_test_enable(false)
.stencil_test_enable(false)
.front(noop_stencil_state)
.back(noop_stencil_state)
.max_depth_bounds(1.0);
let attachments = vk::PipelineColorBlendAttachmentState::builder()
.blend_enable(true)
.src_color_blend_factor(vk::BlendFactor::SRC_ALPHA)
.dst_color_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
.color_blend_op(vk::BlendOp::ADD)
.src_alpha_blend_factor(vk::BlendFactor::ZERO)
.dst_alpha_blend_factor(vk::BlendFactor::ZERO)
.alpha_blend_op(vk::BlendOp::ADD)
.color_write_mask({
vk::ColorComponentFlags::R
| vk::ColorComponentFlags::G
| vk::ColorComponentFlags::B
| vk::ColorComponentFlags::A
});
let color_blend_state = vk::PipelineColorBlendStateCreateInfo::builder()
.logic_op(vk::LogicOp::CLEAR)
.attachments(std::slice::from_ref(&attachments));
let dynamic_state = vk::PipelineDynamicStateCreateInfo::builder()
.dynamic_states(&[vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR]);
let pipeline_create_info = vk::GraphicsPipelineCreateInfo::builder()
.stages(&stages)
.vertex_input_state(&vertex_input_state)
.input_assembly_state(&input_assembly_state)
.viewport_state(&viewport_state)
.rasterization_state(&rasterization_state)
.multisample_state(&multisample_state)
.depth_stencil_state(&depth_stencil_state)
.color_blend_state(&color_blend_state)
.dynamic_state(&dynamic_state)
.layout(pipeline_layout)
.render_pass(render_pass)
.subpass(0);
unsafe {
device.create_graphics_pipelines(
vk::PipelineCache::null(),
std::slice::from_ref(&pipeline_create_info),
None,
)
}
.unwrap()[0]
};
let (font_image, font_image_memory, font_image_view) = {
let fonts = imgui.fonts();
let font_atlas = fonts.build_rgba32_texture();
// Create image
let image_usage = vk::ImageUsageFlags::SAMPLED
| vk::ImageUsageFlags::TRANSFER_DST
| vk::ImageUsageFlags::TRANSFER_SRC;
let create_info = vk::ImageCreateInfo::builder()
.image_type(vk::ImageType::TYPE_2D)
.format(vk::Format::R8G8B8A8_UNORM)
.extent(vk::Extent3D {
width: font_atlas.width,
height: font_atlas.height,
depth: 1,
})
.mip_levels(1)
.array_layers(1)
.samples(vk::SampleCountFlags::TYPE_1)
.tiling(vk::ImageTiling::OPTIMAL)
.usage(image_usage)
.initial_layout(vk::ImageLayout::UNDEFINED);
let image = unsafe { device.create_image(&create_info, None) }?;
// Allocate and bind memory to image
let requirements = unsafe { device.get_image_memory_requirements(image) };
let allocation = allocator
.allocate(&AllocationCreateDesc {
name: "ImGui font image",
requirements,
location: MemoryLocation::GpuOnly,
linear: false,
allocation_scheme: AllocationScheme::GpuAllocatorManaged,
})
.unwrap();
unsafe { device.bind_image_memory(image, allocation.memory(), allocation.offset()) }
.unwrap();
// Create image view
let view_create_info = vk::ImageViewCreateInfo::builder()
.image(image)
.view_type(vk::ImageViewType::TYPE_2D)
.format(vk::Format::R8G8B8A8_UNORM)
.components(vk::ComponentMapping {
r: vk::ComponentSwizzle::R,
g: vk::ComponentSwizzle::G,
b: vk::ComponentSwizzle::B,
a: vk::ComponentSwizzle::A,
})
.subresource_range(vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::COLOR,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
});
let image_view = unsafe { device.create_image_view(&view_create_info, None) }?;
// Create upload buffer
let (upload_buffer, upload_buffer_memory) = {
let create_info = vk::BufferCreateInfo::builder()
.size((font_atlas.width * font_atlas.height * 4) as u64)
.usage(vk::BufferUsageFlags::TRANSFER_SRC);
let buffer = unsafe { device.create_buffer(&create_info, None) }?;
let requirements = unsafe { device.get_buffer_memory_requirements(buffer) };
let buffer_memory = allocator
.allocate(&AllocationCreateDesc {
name: "ImGui font image upload buffer",
requirements,
location: MemoryLocation::CpuToGpu,
linear: true,
allocation_scheme: AllocationScheme::GpuAllocatorManaged,
})
.unwrap();
unsafe {
device.bind_buffer_memory(
buffer,
buffer_memory.memory(),
buffer_memory.offset(),
)
}?;
(buffer, buffer_memory)
};
// Copy font data to upload buffer
let dst = upload_buffer_memory.mapped_ptr().unwrap().cast().as_ptr();
unsafe {
std::ptr::copy_nonoverlapping(
font_atlas.data.as_ptr(),
dst,
(font_atlas.width * font_atlas.height * 4) as usize,
);
}
// Copy upload buffer to image
record_and_submit_command_buffer(
device,
cmd,
cmd_reuse_fence,
queue,
&[],
&[],
&[],
|device, cmd| {
{
let layout_transition_barriers = vk::ImageMemoryBarrier::builder()
.image(image)
.dst_access_mask(vk::AccessFlags::TRANSFER_WRITE)
.new_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
.old_layout(vk::ImageLayout::UNDEFINED)
.subresource_range(vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::COLOR,
base_mip_level: 0,
level_count: vk::REMAINING_MIP_LEVELS,
base_array_layer: 0,
layer_count: vk::REMAINING_ARRAY_LAYERS,
});
unsafe {
device.cmd_pipeline_barrier(
cmd,
vk::PipelineStageFlags::BOTTOM_OF_PIPE,
vk::PipelineStageFlags::TRANSFER,
vk::DependencyFlags::empty(),
&[],
&[],
std::slice::from_ref(&layout_transition_barriers),
)
};
}
let regions = vk::BufferImageCopy::builder()
.buffer_offset(0)
.buffer_row_length(font_atlas.width)
.buffer_image_height(font_atlas.height)
.image_subresource(vk::ImageSubresourceLayers {
aspect_mask: vk::ImageAspectFlags::COLOR,
mip_level: 0,
base_array_layer: 0,
layer_count: 1,
})
.image_offset(vk::Offset3D { x: 0, y: 0, z: 0 })
.image_extent(vk::Extent3D {
width: font_atlas.width,
height: font_atlas.height,
depth: 1,
});
unsafe {
device.cmd_copy_buffer_to_image(
cmd,
upload_buffer,
image,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
std::slice::from_ref(&regions),
)
};
{
let layout_transition_barriers = vk::ImageMemoryBarrier::builder()
.image(image)
.dst_access_mask(vk::AccessFlags::SHADER_READ)
.new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
.subresource_range(vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::COLOR,
base_mip_level: 0,
level_count: vk::REMAINING_MIP_LEVELS,
base_array_layer: 0,
layer_count: vk::REMAINING_ARRAY_LAYERS,
});
unsafe {
device.cmd_pipeline_barrier(
cmd,
vk::PipelineStageFlags::BOTTOM_OF_PIPE,
vk::PipelineStageFlags::FRAGMENT_SHADER,
vk::DependencyFlags::empty(),
&[],
&[],
std::slice::from_ref(&layout_transition_barriers),
)
};
}
},
);
unsafe { device.queue_wait_idle(queue) }?;
// Free upload buffer
unsafe { device.destroy_buffer(upload_buffer, None) };
allocator.free(upload_buffer_memory).unwrap();
(image, allocation, image_view)
};
let sampler = {
let create_info = vk::SamplerCreateInfo::builder()
.mag_filter(vk::Filter::NEAREST)
.min_filter(vk::Filter::NEAREST)
.mipmap_mode(vk::SamplerMipmapMode::NEAREST)
.address_mode_u(vk::SamplerAddressMode::REPEAT)
.address_mode_v(vk::SamplerAddressMode::REPEAT)
.address_mode_w(vk::SamplerAddressMode::REPEAT)
.mip_lod_bias(0.0)
.anisotropy_enable(false)
.compare_enable(false)
.unnormalized_coordinates(false);
unsafe { device.create_sampler(&create_info, None) }?
};
let (vertex_buffer, vb_allocation, vb_capacity) = {
let capacity = 1024 * 1024;
let create_info = vk::BufferCreateInfo::builder()
.size(capacity)
.usage(vk::BufferUsageFlags::VERTEX_BUFFER)
.sharing_mode(vk::SharingMode::EXCLUSIVE);
let buffer = unsafe { device.create_buffer(&create_info, None) }?;
let requirements = unsafe { device.get_buffer_memory_requirements(buffer) };
let allocation = allocator
.allocate(&AllocationCreateDesc {
name: "ImGui Vertex buffer",
requirements,
location: MemoryLocation::CpuToGpu,
linear: true,
allocation_scheme: AllocationScheme::GpuAllocatorManaged,
})
.unwrap();
unsafe { device.bind_buffer_memory(buffer, allocation.memory(), allocation.offset()) }?;
(buffer, allocation, capacity)
};
let (index_buffer, ib_allocation, ib_capacity) = {
let capacity = 1024 * 1024;
let create_info = vk::BufferCreateInfo::builder()
.size(capacity)
.usage(vk::BufferUsageFlags::INDEX_BUFFER)
.sharing_mode(vk::SharingMode::EXCLUSIVE);
let buffer = unsafe { device.create_buffer(&create_info, None) }?;
let requirements = unsafe { device.get_buffer_memory_requirements(buffer) };
let allocation = allocator
.allocate(&AllocationCreateDesc {
name: "ImGui Index buffer",
requirements,
location: MemoryLocation::CpuToGpu,
linear: true,
allocation_scheme: AllocationScheme::GpuAllocatorManaged,
})
.unwrap();
unsafe { device.bind_buffer_memory(buffer, allocation.memory(), allocation.offset()) }?;
(buffer, allocation, capacity)
};
let (constant_buffer, cb_allocation) = {
let create_info = vk::BufferCreateInfo::builder()
.size(std::mem::size_of::<ImGuiCBuffer>() as u64)
.usage(vk::BufferUsageFlags::UNIFORM_BUFFER)
.sharing_mode(vk::SharingMode::EXCLUSIVE);
let buffer = unsafe { device.create_buffer(&create_info, None) }?;
let requirements = unsafe { device.get_buffer_memory_requirements(buffer) };
let allocation = allocator
.allocate(&AllocationCreateDesc {
name: "ImGui Constant buffer",
requirements,
location: MemoryLocation::CpuToGpu,
linear: true,
allocation_scheme: AllocationScheme::GpuAllocatorManaged,
})
.unwrap();
unsafe { device.bind_buffer_memory(buffer, allocation.memory(), allocation.offset()) }?;
(buffer, allocation)
};
let descriptor_sets = {
let alloc_info = vk::DescriptorSetAllocateInfo::builder()
.descriptor_pool(descriptor_pool)
.set_layouts(&descriptor_set_layouts);
let descriptor_sets = unsafe { device.allocate_descriptor_sets(&alloc_info) }?;
let buffer_info = vk::DescriptorBufferInfo::builder()
.buffer(constant_buffer)
.offset(0)
.range(std::mem::size_of::<ImGuiCBuffer>() as u64);
let uniform_buffer = vk::WriteDescriptorSet::builder()
.dst_set(descriptor_sets[0])
.dst_binding(0)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.buffer_info(std::slice::from_ref(&buffer_info));
let image_info = vk::DescriptorImageInfo::builder().sampler(sampler);
let sampler = vk::WriteDescriptorSet::builder()
.dst_set(descriptor_sets[0])
.dst_binding(1)
.descriptor_type(vk::DescriptorType::SAMPLER)
.image_info(std::slice::from_ref(&image_info));
let image_info = vk::DescriptorImageInfo::builder()
.image_view(font_image_view)
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL);
let sampled_image = vk::WriteDescriptorSet::builder()
.dst_set(descriptor_sets[0])
.dst_binding(2)
.descriptor_type(vk::DescriptorType::SAMPLED_IMAGE)
.image_info(std::slice::from_ref(&image_info));
unsafe {
device.update_descriptor_sets(
&[
uniform_buffer.build(),
sampler.build(),
sampled_image.build(),
],
&[],
)
};
descriptor_sets
};
Ok(Self {
sampler,
vb_capacity,
ib_capacity,
vb_allocation,
ib_allocation,
vertex_buffer,
index_buffer,
cb_allocation,
constant_buffer,
font_image,
font_image_memory,
font_image_view,
descriptor_sets,
vs_module,
ps_module,
descriptor_set_layouts,
pipeline_layout,
render_pass,
pipeline,
})
}
pub(crate) fn render(
&mut self,
imgui_draw_data: &imgui::DrawData,
device: &ash::Device,
window_width: u32,
window_height: u32,
framebuffer: vk::Framebuffer,
cmd: vk::CommandBuffer,
) {
// Update constant buffer
{
let left = imgui_draw_data.display_pos[0];
let right = imgui_draw_data.display_pos[0] + imgui_draw_data.display_size[0];
let top = imgui_draw_data.display_pos[1];
let bottom = imgui_draw_data.display_pos[1] + imgui_draw_data.display_size[1];
let cbuffer_data = ImGuiCBuffer {
scale: [(2.0 / (right - left)), (2.0 / (bottom - top))],
translation: [
(right + left) / (left - right),
(top + bottom) / (top - bottom),
],
};
unsafe {
std::ptr::copy_nonoverlapping(
&cbuffer_data,
self.cb_allocation.mapped_ptr().unwrap().cast().as_ptr(),
1,
)
};
}
let render_pass_begin_info = vk::RenderPassBeginInfo::builder()
.render_pass(self.render_pass)
.framebuffer(framebuffer)
.render_area(vk::Rect2D {
offset: vk::Offset2D { x: 0, y: 0 },
extent: vk::Extent2D {
width: window_width,
height: window_height,
},
})
.clear_values(&[vk::ClearValue {
color: vk::ClearColorValue {
float32: [1.0, 0.5, 1.0, 0.0],
},
}]);
unsafe {
device.cmd_begin_render_pass(cmd, &render_pass_begin_info, vk::SubpassContents::INLINE)
};
unsafe { device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, self.pipeline) };
let viewport = vk::Viewport::builder()
.x(0.0)
.y(0.0)
.width(window_width as f32)
.height(window_height as f32);
unsafe { device.cmd_set_viewport(cmd, 0, std::slice::from_ref(&viewport)) };
{
let scissor_rect = vk::Rect2D {
offset: vk::Offset2D { x: 0, y: 0 },
extent: vk::Extent2D {
width: window_width,
height: window_height,
},
};
unsafe { device.cmd_set_scissor(cmd, 0, &[scissor_rect]) };
}
unsafe {
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
self.pipeline_layout,
0,
&self.descriptor_sets,
&[],
)
};
let (vtx_count, idx_count) =
imgui_draw_data
.draw_lists()
.fold((0, 0), |(vtx_count, idx_count), draw_list| {
(
vtx_count + draw_list.vtx_buffer().len(),
idx_count + draw_list.idx_buffer().len(),
)
});
let vtx_size = (vtx_count * std::mem::size_of::<imgui::DrawVert>()) as u64;
if vtx_size > self.vb_capacity {
// reallocate vertex buffer
todo!();
}
let idx_size = (idx_count * std::mem::size_of::<imgui::DrawIdx>()) as u64;
if idx_size > self.ib_capacity {
// reallocate index buffer
todo!();
}
let mut vb_offset = 0;
let mut ib_offset = 0;
for draw_list in imgui_draw_data.draw_lists() {
unsafe {
device.cmd_bind_vertex_buffers(
cmd,
0,
&[self.vertex_buffer],
&[vb_offset as u64 * std::mem::size_of::<imgui::DrawVert>() as u64],
)
};
unsafe {
device.cmd_bind_index_buffer(
cmd,
self.index_buffer,
ib_offset as u64 * std::mem::size_of::<imgui::DrawIdx>() as u64,
vk::IndexType::UINT16,
)
};
{
let vertices = draw_list.vtx_buffer();
let dst_ptr = self
.vb_allocation
.mapped_ptr()
.unwrap()
.cast::<imgui::DrawVert>()
.as_ptr();
let dst_ptr = unsafe { dst_ptr.offset(vb_offset) };
unsafe {
std::ptr::copy_nonoverlapping(vertices.as_ptr(), dst_ptr, vertices.len())
};
vb_offset += vertices.len() as isize;
}
{
let indices = draw_list.idx_buffer();
let dst_ptr = self
.ib_allocation
.mapped_ptr()
.unwrap()
.cast::<imgui::DrawIdx>()
.as_ptr();
let dst_ptr = unsafe { dst_ptr.offset(ib_offset) };
unsafe { std::ptr::copy_nonoverlapping(indices.as_ptr(), dst_ptr, indices.len()) };
ib_offset += indices.len() as isize;
}
for command in draw_list.commands() {
match command {
imgui::DrawCmd::Elements { count, cmd_params } => {
let scissor_rect = vk::Rect2D {
offset: vk::Offset2D {
x: cmd_params.clip_rect[0] as i32,
y: cmd_params.clip_rect[1] as i32,
},
extent: vk::Extent2D {
width: (cmd_params.clip_rect[2] - cmd_params.clip_rect[0]) as u32,
height: (cmd_params.clip_rect[3] - cmd_params.clip_rect[1]) as u32,
},
};
unsafe { device.cmd_set_scissor(cmd, 0, &[scissor_rect]) };
unsafe {
device.cmd_draw_indexed(
cmd,
count as u32,
1,
cmd_params.idx_offset as u32,
cmd_params.vtx_offset as i32,
0,
)
};
}
_ => todo!(),
}
}
}
unsafe { device.cmd_end_render_pass(cmd) };
}
pub(crate) fn destroy(self, device: &ash::Device, allocator: &mut Allocator) {
unsafe { device.destroy_buffer(self.constant_buffer, None) };
allocator.free(self.cb_allocation).unwrap();
unsafe { device.destroy_buffer(self.index_buffer, None) };
allocator.free(self.ib_allocation).unwrap();
unsafe { device.destroy_buffer(self.vertex_buffer, None) };
allocator.free(self.vb_allocation).unwrap();
unsafe {
device.destroy_sampler(self.sampler, None);
}
unsafe {
device.destroy_image_view(self.font_image_view, None);
}
unsafe {
device.destroy_image(self.font_image, None);
}
allocator.free(self.font_image_memory).unwrap();
unsafe { device.destroy_shader_module(self.ps_module, None) };
unsafe { device.destroy_shader_module(self.vs_module, None) };
unsafe { device.destroy_pipeline(self.pipeline, None) };
unsafe { device.destroy_render_pass(self.render_pass, None) };
unsafe {
device.destroy_pipeline_layout(self.pipeline_layout, None);
}
for &layout in self.descriptor_set_layouts.iter() {
unsafe { device.destroy_descriptor_set_layout(layout, None) };
}
}
}
pub(crate) fn handle_imgui_event(
io: &mut imgui::Io,
window: &winit::window::Window,
event: &winit::event::Event<'_, ()>,
) -> bool {
use winit::event::{
DeviceEvent, ElementState, Event, KeyboardInput, MouseButton, MouseScrollDelta, TouchPhase,
VirtualKeyCode, WindowEvent,
};
match event {
Event::WindowEvent { event, window_id } if *window_id == window.id() => match *event {
WindowEvent::Resized(physical_size) => {
io.display_size = [physical_size.width as f32, physical_size.height as f32];
false
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(key),
state,
..
},
..
} => {
let pressed = state == ElementState::Pressed;
io.keys_down[key as usize] = pressed;
match key {
VirtualKeyCode::LShift | VirtualKeyCode::RShift => io.key_shift = pressed,
VirtualKeyCode::LControl | VirtualKeyCode::RControl => io.key_ctrl = pressed,
VirtualKeyCode::LAlt | VirtualKeyCode::RAlt => io.key_alt = pressed,
VirtualKeyCode::LWin | VirtualKeyCode::RWin => io.key_super = pressed,
_ => (),
}
io.want_capture_keyboard
}
WindowEvent::ReceivedCharacter(ch) => {
io.add_input_character(ch);
io.want_capture_keyboard
}
WindowEvent::CursorMoved { position, .. } => {
io.mouse_pos = [position.x as f32, position.y as f32];
io.want_capture_mouse
}
WindowEvent::MouseWheel {
delta,
phase: TouchPhase::Moved,
..
} => {
match delta {
MouseScrollDelta::LineDelta(h, v) => {
io.mouse_wheel_h = h;
io.mouse_wheel = v;
}
MouseScrollDelta::PixelDelta(pos) => {
match pos.x.partial_cmp(&0.0) {
Some(std::cmp::Ordering::Greater) => io.mouse_wheel_h += 1.0,
Some(std::cmp::Ordering::Less) => io.mouse_wheel_h -= 1.0,
_ => (),
}
match pos.y.partial_cmp(&0.0) {
Some(std::cmp::Ordering::Greater) => io.mouse_wheel += 1.0,
Some(std::cmp::Ordering::Less) => io.mouse_wheel -= 1.0,
_ => (),
}
}
}
io.want_capture_mouse
}
WindowEvent::MouseInput { state, button, .. } => {
let pressed = state == ElementState::Pressed;
match button {
MouseButton::Left => io.mouse_down[0] = pressed,
MouseButton::Right => io.mouse_down[1] = pressed,
MouseButton::Middle => io.mouse_down[2] = pressed,
MouseButton::Other(idx @ 0..=4) => io.mouse_down[idx as usize] = pressed,
MouseButton::Other(_) => (),
}
io.want_capture_mouse
}
_ => false,
},
// Track key release events outside our window. If we don't do this,
// we might never see the release event if some other window gets focus.
Event::DeviceEvent {
event:
DeviceEvent::Key(KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(key),
..
}),
..
} => {
io.keys_down[*key as usize] = false;
match *key {
VirtualKeyCode::LShift | VirtualKeyCode::RShift => io.key_shift = false,
VirtualKeyCode::LControl | VirtualKeyCode::RControl => io.key_ctrl = false,
VirtualKeyCode::LAlt | VirtualKeyCode::RAlt => io.key_alt = false,
VirtualKeyCode::LWin | VirtualKeyCode::RWin => io.key_super = false,
_ => (),
}
io.want_capture_keyboard
}
_ => false,
}
}

View file

@ -0,0 +1,440 @@
use std::default::Default;
use std::ffi::CString;
use ash::vk;
use gpu_allocator::vulkan::{Allocator, AllocatorCreateDesc};
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
mod imgui_renderer;
use imgui_renderer::{handle_imgui_event, ImGuiRenderer};
mod helper;
use helper::record_and_submit_command_buffer;
fn main() -> ash::prelude::VkResult<()> {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace")).init();
let entry = unsafe { ash::Entry::load() }.unwrap();
let event_loop = winit::event_loop::EventLoop::new();
let window_width = 1920;
let window_height = 1080;
let window = winit::window::WindowBuilder::new()
.with_title("gpu-allocator Vulkan visualization")
.with_inner_size(winit::dpi::PhysicalSize::new(
window_width as f64,
window_height as f64,
))
.with_resizable(false)
.build(&event_loop)
.unwrap();
// Create Vulkan instance
let instance = {
let app_name = CString::new("gpu-allocator examples vulkan-visualization").unwrap();
let appinfo = vk::ApplicationInfo::builder()
.application_name(&app_name)
.application_version(0)
.engine_name(&app_name)
.engine_version(0)
.api_version(vk::make_api_version(0, 1, 0, 0));
let layer_names: &[CString] = &[CString::new("VK_LAYER_KHRONOS_validation").unwrap()];
let layers_names_raw: Vec<*const i8> = layer_names
.iter()
.map(|raw_name| raw_name.as_ptr())
.collect();
let surface_extensions =
ash_window::enumerate_required_extensions(event_loop.raw_display_handle()).unwrap();
let create_info = vk::InstanceCreateInfo::builder()
.application_info(&appinfo)
.enabled_layer_names(&layers_names_raw)
.enabled_extension_names(surface_extensions);
unsafe {
entry
.create_instance(&create_info, None)
.expect("Instance creation error")
}
};
let surface = unsafe {
ash_window::create_surface(
&entry,
&instance,
window.raw_display_handle(),
window.raw_window_handle(),
None,
)
}
.unwrap();
let surface_loader = ash::extensions::khr::Surface::new(&entry, &instance);
// Look for Vulkan physical device
let (pdevice, queue_family_index) = {
let pdevices = unsafe {
instance
.enumerate_physical_devices()
.expect("Physical device error")
};
pdevices
.iter()
.find_map(|pdevice| {
unsafe { instance.get_physical_device_queue_family_properties(*pdevice) }
.iter()
.enumerate()
.find_map(|(index, &info)| {
let supports_graphics = info.queue_flags.contains(vk::QueueFlags::GRAPHICS);
let supports_surface = unsafe {
surface_loader.get_physical_device_surface_support(
*pdevice,
index as u32,
surface,
)
}
.unwrap();
if supports_graphics && supports_surface {
Some((*pdevice, index))
} else {
None
}
})
})
.expect("Couldn't find suitable device.")
};
// Create Vulkan device
let device = {
let device_extension_names_raw = [ash::extensions::khr::Swapchain::name().as_ptr()];
let features = vk::PhysicalDeviceFeatures {
shader_clip_distance: 1,
..Default::default()
};
let priorities = [1.0];
let queue_info = vk::DeviceQueueCreateInfo::builder()
.queue_family_index(queue_family_index as u32)
.queue_priorities(&priorities);
let create_info = vk::DeviceCreateInfo::builder()
.queue_create_infos(std::slice::from_ref(&queue_info))
.enabled_extension_names(&device_extension_names_raw)
.enabled_features(&features);
unsafe { instance.create_device(pdevice, &create_info, None) }.unwrap()
};
let present_queue = unsafe { device.get_device_queue(queue_family_index as u32, 0) };
let surface_format =
unsafe { surface_loader.get_physical_device_surface_formats(pdevice, surface) }.unwrap()[0];
let surface_capabilities =
unsafe { surface_loader.get_physical_device_surface_capabilities(pdevice, surface) }
.unwrap();
let mut desired_image_count = surface_capabilities.min_image_count + 1;
if surface_capabilities.max_image_count > 0
&& desired_image_count > surface_capabilities.max_image_count
{
desired_image_count = surface_capabilities.max_image_count;
}
let surface_resolution = match surface_capabilities.current_extent.width {
u32::MAX => vk::Extent2D {
width: window_width,
height: window_height,
},
_ => surface_capabilities.current_extent,
};
let pre_transform = if surface_capabilities
.supported_transforms
.contains(vk::SurfaceTransformFlagsKHR::IDENTITY)
{
vk::SurfaceTransformFlagsKHR::IDENTITY
} else {
surface_capabilities.current_transform
};
let present_modes =
unsafe { surface_loader.get_physical_device_surface_present_modes(pdevice, surface) }
.unwrap();
let present_mode = present_modes
.iter()
.cloned()
.find(|&mode| mode == vk::PresentModeKHR::MAILBOX)
.unwrap_or(vk::PresentModeKHR::FIFO);
let swapchain_loader = ash::extensions::khr::Swapchain::new(&instance, &device);
let swapchain_create_info = vk::SwapchainCreateInfoKHR::builder()
.surface(surface)
.min_image_count(desired_image_count)
.image_color_space(surface_format.color_space)
.image_format(surface_format.format)
.image_extent(surface_resolution)
.image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT)
.image_sharing_mode(vk::SharingMode::EXCLUSIVE)
.pre_transform(pre_transform)
.composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
.present_mode(present_mode)
.clipped(true)
.image_array_layers(1);
let swapchain =
unsafe { swapchain_loader.create_swapchain(&swapchain_create_info, None) }.unwrap();
let pool_create_info = vk::CommandPoolCreateInfo::builder()
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER)
.queue_family_index(queue_family_index as u32);
let command_pool = unsafe { device.create_command_pool(&pool_create_info, None) }.unwrap();
let command_buffer_allocate_info = vk::CommandBufferAllocateInfo::builder()
.command_buffer_count(2)
.command_pool(command_pool)
.level(vk::CommandBufferLevel::PRIMARY);
let command_buffers =
unsafe { device.allocate_command_buffers(&command_buffer_allocate_info) }.unwrap();
let setup_command_buffer = command_buffers[0];
let draw_command_buffer = command_buffers[1];
let present_images = unsafe { swapchain_loader.get_swapchain_images(swapchain) }.unwrap();
let mut present_image_views = present_images
.iter()
.map(|&image| {
let create_view_info = vk::ImageViewCreateInfo::builder()
.view_type(vk::ImageViewType::TYPE_2D)
.format(surface_format.format)
.components(vk::ComponentMapping {
r: vk::ComponentSwizzle::R,
g: vk::ComponentSwizzle::G,
b: vk::ComponentSwizzle::B,
a: vk::ComponentSwizzle::A,
})
.subresource_range(vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::COLOR,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
})
.image(image);
unsafe { device.create_image_view(&create_view_info, None) }.unwrap()
})
.collect::<Vec<_>>();
// Setting up the allocator
let mut allocator = Some(
Allocator::new(&AllocatorCreateDesc {
instance: instance.clone(),
device: device.clone(),
physical_device: pdevice,
debug_settings: Default::default(),
buffer_device_address: false,
})
.unwrap(),
);
let fence_create_info = vk::FenceCreateInfo::builder().flags(vk::FenceCreateFlags::SIGNALED);
let draw_commands_reuse_fence =
unsafe { device.create_fence(&fence_create_info, None) }.unwrap();
let setup_commands_reuse_fence =
unsafe { device.create_fence(&fence_create_info, None) }.unwrap();
let semaphore_create_info = vk::SemaphoreCreateInfo::default();
let present_complete_semaphore =
unsafe { device.create_semaphore(&semaphore_create_info, None) }.unwrap();
let rendering_complete_semaphore =
unsafe { device.create_semaphore(&semaphore_create_info, None) }.unwrap();
let mut imgui = imgui::Context::create();
imgui.io_mut().display_size = [window_width as f32, window_height as f32];
let descriptor_pool = {
let pool_sizes = [
vk::DescriptorPoolSize {
ty: vk::DescriptorType::UNIFORM_BUFFER,
descriptor_count: 1,
},
vk::DescriptorPoolSize {
ty: vk::DescriptorType::SAMPLED_IMAGE,
descriptor_count: 1,
},
vk::DescriptorPoolSize {
ty: vk::DescriptorType::SAMPLER,
descriptor_count: 1,
},
];
let create_info = vk::DescriptorPoolCreateInfo::builder()
.max_sets(1)
.pool_sizes(&pool_sizes);
unsafe { device.create_descriptor_pool(&create_info, None) }?
};
let mut imgui_renderer = Some(ImGuiRenderer::new(
&mut imgui,
&device,
descriptor_pool,
surface_format.format,
allocator.as_mut().unwrap(),
setup_command_buffer,
setup_commands_reuse_fence,
present_queue,
)?);
let mut framebuffers = present_image_views
.iter()
.map(|&view| {
let create_info = vk::FramebufferCreateInfo::builder()
.render_pass(imgui_renderer.as_ref().unwrap().render_pass)
.attachments(std::slice::from_ref(&view))
.width(window_width)
.height(window_height)
.layers(1);
unsafe { device.create_framebuffer(&create_info, None) }.unwrap()
})
.collect::<Vec<_>>();
let mut visualizer = Some(gpu_allocator::vulkan::AllocatorVisualizer::new());
event_loop.run(move |event, _, control_flow| {
*control_flow = winit::event_loop::ControlFlow::Wait;
handle_imgui_event(imgui.io_mut(), &window, &event);
let mut ready_for_rendering = false;
match event {
winit::event::Event::WindowEvent { event, .. } => match event {
winit::event::WindowEvent::CloseRequested
| winit::event::WindowEvent::KeyboardInput {
input:
winit::event::KeyboardInput {
virtual_keycode: Some(winit::event::VirtualKeyCode::Escape),
..
},
..
} => {
*control_flow = winit::event_loop::ControlFlow::Exit;
}
_ => {}
},
winit::event::Event::MainEventsCleared => ready_for_rendering = true,
_ => {}
}
if ready_for_rendering {
let (present_index, _) = unsafe {
swapchain_loader.acquire_next_image(
swapchain,
u64::MAX,
present_complete_semaphore,
vk::Fence::null(),
)
}
.unwrap();
// Start ImGui frame
let ui = imgui.frame();
// Submit visualizer ImGui commands
visualizer
.as_mut()
.unwrap()
.render(allocator.as_ref().unwrap(), ui, None);
// Finish ImGui Frame
let imgui_draw_data = imgui.render();
record_and_submit_command_buffer(
&device,
draw_command_buffer,
draw_commands_reuse_fence,
present_queue,
&[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT],
&[present_complete_semaphore],
&[rendering_complete_semaphore],
|device, cmd| {
// Render ImGui to swapchain image
imgui_renderer.as_mut().unwrap().render(
imgui_draw_data,
device,
window_width,
window_height,
framebuffers[present_index as usize],
cmd,
);
// Transition swapchain image to present state
let image_barriers = vk::ImageMemoryBarrier::builder()
.src_access_mask(
vk::AccessFlags::COLOR_ATTACHMENT_READ
| vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
)
.old_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
.new_layout(vk::ImageLayout::PRESENT_SRC_KHR)
.image(present_images[present_index as usize])
.subresource_range(vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::COLOR,
base_mip_level: 0,
level_count: vk::REMAINING_MIP_LEVELS,
base_array_layer: 0,
layer_count: vk::REMAINING_ARRAY_LAYERS,
});
unsafe {
device.cmd_pipeline_barrier(
cmd,
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
vk::PipelineStageFlags::BOTTOM_OF_PIPE,
vk::DependencyFlags::empty(),
&[],
&[],
std::slice::from_ref(&image_barriers),
)
};
},
);
let present_create_info = vk::PresentInfoKHR::builder()
.wait_semaphores(std::slice::from_ref(&rendering_complete_semaphore))
.swapchains(std::slice::from_ref(&swapchain))
.image_indices(std::slice::from_ref(&present_index));
unsafe { swapchain_loader.queue_present(present_queue, &present_create_info) }.unwrap();
} else if *control_flow == winit::event_loop::ControlFlow::Exit {
unsafe { device.queue_wait_idle(present_queue) }.unwrap();
visualizer.take();
for fb in framebuffers.drain(..) {
unsafe { device.destroy_framebuffer(fb, None) };
}
let mut allocator = allocator.take().unwrap();
imgui_renderer
.take()
.unwrap()
.destroy(&device, &mut allocator);
unsafe { device.destroy_descriptor_pool(descriptor_pool, None) };
unsafe { device.destroy_semaphore(rendering_complete_semaphore, None) };
unsafe { device.destroy_semaphore(present_complete_semaphore, None) };
unsafe { device.destroy_fence(setup_commands_reuse_fence, None) };
unsafe { device.destroy_fence(draw_commands_reuse_fence, None) };
drop(allocator);
for view in present_image_views.drain(..) {
unsafe { device.destroy_image_view(view, None) };
}
unsafe { device.free_command_buffers(command_pool, &command_buffers) };
unsafe { device.destroy_command_pool(command_pool, None) };
unsafe { swapchain_loader.destroy_swapchain(swapchain, None) };
unsafe { device.destroy_device(None) };
unsafe {
surface_loader.destroy_surface(surface, None);
}
unsafe { instance.destroy_instance(None) };
}
});
}

View file

@ -0,0 +1,14 @@
SamplerState g_sampler : register(s1, space0);
Texture2D g_texture : register(t2, space0);
struct VertexInput
{
float4 position : SV_POSITION;
float2 texCoord: TEXCOORD0;
float4 color: COLOR;
};
float4 main(VertexInput input) : SV_Target0
{
return input.color * g_texture.Sample(g_sampler, input.texCoord);
}

View file

@ -0,0 +1,30 @@
struct VertexInput
{
float2 pos : POSITION;
float2 texCoord : TEXCOORD0;
float4 color: COLOR;
};
struct VertexOutput
{
float4 position : SV_POSITION;
float2 texCoord: TEXCOORD0;
float4 color: COLOR;
};
struct Constants
{
float2 scale;
float2 translation;
};
ConstantBuffer<Constants> g_constants : register(b0, space0);
VertexOutput main(VertexInput vertex)
{
VertexOutput o;
o.position = float4(vertex.pos * g_constants.scale + g_constants.translation, 0.0, 1.0);
o.texCoord = vertex.texCoord;
o.color = vertex.color;
return o;
}

View file

@ -0,0 +1,132 @@
#![deny(unsafe_code, clippy::unwrap_used)]
#[cfg(feature = "visualizer")]
pub(crate) mod visualizer;
use super::{resolve_backtrace, AllocationReport, AllocationType, SubAllocator, SubAllocatorBase};
use crate::{AllocationError, Result};
use log::{log, Level};
#[derive(Debug)]
pub(crate) struct DedicatedBlockAllocator {
size: u64,
allocated: u64,
name: Option<String>,
backtrace: Option<backtrace::Backtrace>,
}
impl DedicatedBlockAllocator {
pub(crate) fn new(size: u64) -> Self {
Self {
size,
allocated: 0,
name: None,
backtrace: None,
}
}
}
impl SubAllocatorBase for DedicatedBlockAllocator {}
impl SubAllocator for DedicatedBlockAllocator {
fn allocate(
&mut self,
size: u64,
_alignment: u64,
_allocation_type: AllocationType,
_granularity: u64,
name: &str,
backtrace: Option<backtrace::Backtrace>,
) -> Result<(u64, std::num::NonZeroU64)> {
if self.allocated != 0 {
return Err(AllocationError::OutOfMemory);
}
if self.size != size {
return Err(AllocationError::Internal(
"DedicatedBlockAllocator size must match allocation size.".into(),
));
}
self.allocated = size;
self.name = Some(name.to_string());
self.backtrace = backtrace;
#[allow(clippy::unwrap_used)]
let dummy_id = std::num::NonZeroU64::new(1).unwrap();
Ok((0, dummy_id))
}
fn free(&mut self, chunk_id: Option<std::num::NonZeroU64>) -> Result<()> {
if chunk_id != std::num::NonZeroU64::new(1) {
Err(AllocationError::Internal("Chunk ID must be 1.".into()))
} else {
self.allocated = 0;
Ok(())
}
}
fn rename_allocation(
&mut self,
chunk_id: Option<std::num::NonZeroU64>,
name: &str,
) -> Result<()> {
if chunk_id != std::num::NonZeroU64::new(1) {
Err(AllocationError::Internal("Chunk ID must be 1.".into()))
} else {
self.name = Some(name.into());
Ok(())
}
}
fn report_memory_leaks(
&self,
log_level: Level,
memory_type_index: usize,
memory_block_index: usize,
) {
let empty = "".to_string();
let name = self.name.as_ref().unwrap_or(&empty);
let backtrace = resolve_backtrace(&self.backtrace);
log!(
log_level,
r#"leak detected: {{
memory type: {}
memory block: {}
dedicated allocation: {{
size: 0x{:x},
name: {},
backtrace: {}
}}
}}"#,
memory_type_index,
memory_block_index,
self.size,
name,
backtrace
)
}
fn report_allocations(&self) -> Vec<AllocationReport> {
vec![AllocationReport {
name: self
.name
.clone()
.unwrap_or_else(|| "<Unnamed Dedicated allocation>".to_owned()),
size: self.size,
backtrace: self.backtrace.clone(),
}]
}
fn size(&self) -> u64 {
self.size
}
fn allocated(&self) -> u64 {
self.allocated
}
fn supports_general_allocations(&self) -> bool {
false
}
}

View file

@ -0,0 +1,8 @@
use super::DedicatedBlockAllocator;
use crate::visualizer::SubAllocatorVisualizer;
impl SubAllocatorVisualizer for DedicatedBlockAllocator {
fn draw_base_info(&self, ui: &imgui::Ui) {
ui.text("Dedicated Block");
}
}

View file

@ -0,0 +1,413 @@
#![deny(unsafe_code, clippy::unwrap_used)]
#[cfg(feature = "visualizer")]
pub(crate) mod visualizer;
use super::{resolve_backtrace, AllocationReport, AllocationType, SubAllocator, SubAllocatorBase};
use crate::{AllocationError, Result};
use log::{log, Level};
use std::collections::{HashMap, HashSet};
const USE_BEST_FIT: bool = true;
fn align_down(val: u64, alignment: u64) -> u64 {
val & !(alignment - 1u64)
}
fn align_up(val: u64, alignment: u64) -> u64 {
align_down(val + alignment - 1u64, alignment)
}
#[derive(Debug)]
pub(crate) struct MemoryChunk {
pub(crate) chunk_id: std::num::NonZeroU64,
pub(crate) size: u64,
pub(crate) offset: u64,
pub(crate) allocation_type: AllocationType,
pub(crate) name: Option<String>,
pub(crate) backtrace: Option<backtrace::Backtrace>, // Only used if STORE_STACK_TRACES is true
next: Option<std::num::NonZeroU64>,
prev: Option<std::num::NonZeroU64>,
}
#[derive(Debug)]
pub(crate) struct FreeListAllocator {
size: u64,
allocated: u64,
pub(crate) chunk_id_counter: u64,
pub(crate) chunks: HashMap<std::num::NonZeroU64, MemoryChunk>,
free_chunks: HashSet<std::num::NonZeroU64>,
}
/// Test if two suballocations will overlap the same page.
fn is_on_same_page(offset_a: u64, size_a: u64, offset_b: u64, page_size: u64) -> bool {
let end_a = offset_a + size_a - 1;
let end_page_a = align_down(end_a, page_size);
let start_b = offset_b;
let start_page_b = align_down(start_b, page_size);
end_page_a == start_page_b
}
/// Test if two allocation types will be conflicting or not.
fn has_granularity_conflict(type0: AllocationType, type1: AllocationType) -> bool {
if type0 == AllocationType::Free || type1 == AllocationType::Free {
return false;
}
type0 != type1
}
impl FreeListAllocator {
pub(crate) fn new(size: u64) -> Self {
#[allow(clippy::unwrap_used)]
let initial_chunk_id = std::num::NonZeroU64::new(1).unwrap();
let mut chunks = HashMap::default();
chunks.insert(
initial_chunk_id,
MemoryChunk {
chunk_id: initial_chunk_id,
size,
offset: 0,
allocation_type: AllocationType::Free,
name: None,
backtrace: None,
prev: None,
next: None,
},
);
let mut free_chunks = HashSet::default();
free_chunks.insert(initial_chunk_id);
Self {
size,
allocated: 0,
// 0 is not allowed as a chunk ID, 1 is used by the initial chunk, next chunk is going to be 2.
// The system well take the counter as the ID, and the increment the counter.
chunk_id_counter: 2,
chunks,
free_chunks,
}
}
/// Generates a new unique chunk ID
fn get_new_chunk_id(&mut self) -> Result<std::num::NonZeroU64> {
if self.chunk_id_counter == u64::MAX {
// End of chunk id counter reached, no more allocations are possible.
return Err(AllocationError::OutOfMemory);
}
let id = self.chunk_id_counter;
self.chunk_id_counter += 1;
std::num::NonZeroU64::new(id).ok_or_else(|| {
AllocationError::Internal("New chunk id was 0, which is not allowed.".into())
})
}
/// Finds the specified `chunk_id` in the list of free chunks and removes if from the list
fn remove_id_from_free_list(&mut self, chunk_id: std::num::NonZeroU64) {
self.free_chunks.remove(&chunk_id);
}
/// Merges two adjacent chunks. Right chunk will be merged into the left chunk
fn merge_free_chunks(
&mut self,
chunk_left: std::num::NonZeroU64,
chunk_right: std::num::NonZeroU64,
) -> Result<()> {
// Gather data from right chunk and remove it
let (right_size, right_next) = {
let chunk = self.chunks.remove(&chunk_right).ok_or_else(|| {
AllocationError::Internal("Chunk ID not present in chunk list.".into())
})?;
self.remove_id_from_free_list(chunk.chunk_id);
(chunk.size, chunk.next)
};
// Merge into left chunk
{
let chunk = self.chunks.get_mut(&chunk_left).ok_or_else(|| {
AllocationError::Internal("Chunk ID not present in chunk list.".into())
})?;
chunk.next = right_next;
chunk.size += right_size;
}
// Patch pointers
if let Some(right_next) = right_next {
let chunk = self.chunks.get_mut(&right_next).ok_or_else(|| {
AllocationError::Internal("Chunk ID not present in chunk list.".into())
})?;
chunk.prev = Some(chunk_left);
}
Ok(())
}
}
impl SubAllocatorBase for FreeListAllocator {}
impl SubAllocator for FreeListAllocator {
fn allocate(
&mut self,
size: u64,
alignment: u64,
allocation_type: AllocationType,
granularity: u64,
name: &str,
backtrace: Option<backtrace::Backtrace>,
) -> Result<(u64, std::num::NonZeroU64)> {
let free_size = self.size - self.allocated;
if size > free_size {
return Err(AllocationError::OutOfMemory);
}
let mut best_fit_id: Option<std::num::NonZeroU64> = None;
let mut best_offset = 0u64;
let mut best_aligned_size = 0u64;
let mut best_chunk_size = 0u64;
for current_chunk_id in self.free_chunks.iter() {
let current_chunk = self.chunks.get(current_chunk_id).ok_or_else(|| {
AllocationError::Internal(
"Chunk ID in free list is not present in chunk list.".into(),
)
})?;
if current_chunk.size < size {
continue;
}
let mut offset = align_up(current_chunk.offset, alignment);
if let Some(prev_idx) = current_chunk.prev {
let previous = self.chunks.get(&prev_idx).ok_or_else(|| {
AllocationError::Internal("Invalid previous chunk reference.".into())
})?;
if is_on_same_page(previous.offset, previous.size, offset, granularity)
&& has_granularity_conflict(previous.allocation_type, allocation_type)
{
offset = align_up(offset, granularity);
}
}
let padding = offset - current_chunk.offset;
let aligned_size = padding + size;
if aligned_size > current_chunk.size {
continue;
}
if let Some(next_idx) = current_chunk.next {
let next = self.chunks.get(&next_idx).ok_or_else(|| {
AllocationError::Internal("Invalid next chunk reference.".into())
})?;
if is_on_same_page(offset, size, next.offset, granularity)
&& has_granularity_conflict(allocation_type, next.allocation_type)
{
continue;
}
}
if USE_BEST_FIT {
if best_fit_id.is_none() || current_chunk.size < best_chunk_size {
best_fit_id = Some(*current_chunk_id);
best_aligned_size = aligned_size;
best_offset = offset;
best_chunk_size = current_chunk.size;
};
} else {
best_fit_id = Some(*current_chunk_id);
best_aligned_size = aligned_size;
best_offset = offset;
best_chunk_size = current_chunk.size;
break;
}
}
let first_fit_id = best_fit_id.ok_or(AllocationError::OutOfMemory)?;
let chunk_id = if best_chunk_size > best_aligned_size {
let new_chunk_id = self.get_new_chunk_id()?;
let new_chunk = {
let free_chunk = self.chunks.get_mut(&first_fit_id).ok_or_else(|| {
AllocationError::Internal("Chunk ID must be in chunk list.".into())
})?;
let new_chunk = MemoryChunk {
chunk_id: new_chunk_id,
size: best_aligned_size,
offset: free_chunk.offset,
allocation_type,
name: Some(name.to_string()),
backtrace,
prev: free_chunk.prev,
next: Some(first_fit_id),
};
free_chunk.prev = Some(new_chunk.chunk_id);
free_chunk.offset += best_aligned_size;
free_chunk.size -= best_aligned_size;
new_chunk
};
if let Some(prev_id) = new_chunk.prev {
let prev_chunk = self.chunks.get_mut(&prev_id).ok_or_else(|| {
AllocationError::Internal("Invalid previous chunk reference.".into())
})?;
prev_chunk.next = Some(new_chunk.chunk_id);
}
self.chunks.insert(new_chunk_id, new_chunk);
new_chunk_id
} else {
let chunk = self
.chunks
.get_mut(&first_fit_id)
.ok_or_else(|| AllocationError::Internal("Invalid chunk reference.".into()))?;
chunk.allocation_type = allocation_type;
chunk.name = Some(name.to_string());
chunk.backtrace = backtrace;
self.remove_id_from_free_list(first_fit_id);
first_fit_id
};
self.allocated += best_aligned_size;
Ok((best_offset, chunk_id))
}
fn free(&mut self, chunk_id: Option<std::num::NonZeroU64>) -> Result<()> {
let chunk_id = chunk_id
.ok_or_else(|| AllocationError::Internal("Chunk ID must be a valid value.".into()))?;
let (next_id, prev_id) = {
let chunk = self.chunks.get_mut(&chunk_id).ok_or_else(|| {
AllocationError::Internal(
"Attempting to free chunk that is not in chunk list.".into(),
)
})?;
chunk.allocation_type = AllocationType::Free;
chunk.name = None;
chunk.backtrace = None;
self.allocated -= chunk.size;
self.free_chunks.insert(chunk.chunk_id);
(chunk.next, chunk.prev)
};
if let Some(next_id) = next_id {
if self.chunks[&next_id].allocation_type == AllocationType::Free {
self.merge_free_chunks(chunk_id, next_id)?;
}
}
if let Some(prev_id) = prev_id {
if self.chunks[&prev_id].allocation_type == AllocationType::Free {
self.merge_free_chunks(prev_id, chunk_id)?;
}
}
Ok(())
}
fn rename_allocation(
&mut self,
chunk_id: Option<std::num::NonZeroU64>,
name: &str,
) -> Result<()> {
let chunk_id = chunk_id
.ok_or_else(|| AllocationError::Internal("Chunk ID must be a valid value.".into()))?;
let chunk = self.chunks.get_mut(&chunk_id).ok_or_else(|| {
AllocationError::Internal(
"Attempting to rename chunk that is not in chunk list.".into(),
)
})?;
if chunk.allocation_type == AllocationType::Free {
return Err(AllocationError::Internal(
"Attempting to rename a freed allocation.".into(),
));
}
chunk.name = Some(name.into());
Ok(())
}
fn report_memory_leaks(
&self,
log_level: Level,
memory_type_index: usize,
memory_block_index: usize,
) {
for (chunk_id, chunk) in self.chunks.iter() {
if chunk.allocation_type == AllocationType::Free {
continue;
}
let empty = "".to_string();
let name = chunk.name.as_ref().unwrap_or(&empty);
let backtrace = resolve_backtrace(&chunk.backtrace);
log!(
log_level,
r#"leak detected: {{
memory type: {}
memory block: {}
chunk: {{
chunk_id: {},
size: 0x{:x},
offset: 0x{:x},
allocation_type: {:?},
name: {},
backtrace: {}
}}
}}"#,
memory_type_index,
memory_block_index,
chunk_id,
chunk.size,
chunk.offset,
chunk.allocation_type,
name,
backtrace
);
}
}
fn report_allocations(&self) -> Vec<AllocationReport> {
self.chunks
.iter()
.filter(|(_key, chunk)| chunk.allocation_type != AllocationType::Free)
.map(|(_key, chunk)| AllocationReport {
name: chunk
.name
.clone()
.unwrap_or_else(|| "<Unnamed FreeList allocation>".to_owned()),
size: chunk.size,
backtrace: chunk.backtrace.clone(),
})
.collect::<Vec<_>>()
}
fn size(&self) -> u64 {
self.size
}
fn allocated(&self) -> u64 {
self.allocated
}
fn supports_general_allocations(&self) -> bool {
true
}
}

View file

@ -0,0 +1,126 @@
use super::{resolve_backtrace, AllocationType, FreeListAllocator};
use crate::visualizer::{ColorScheme, SubAllocatorVisualizer};
impl SubAllocatorVisualizer for FreeListAllocator {
fn supports_visualization(&self) -> bool {
true
}
fn draw_base_info(&self, ui: &imgui::Ui) {
ui.text("free list sub-allocator");
ui.text(format!("chunk count: {}", self.chunks.len()));
ui.text(format!("chunk id counter: {}", self.chunk_id_counter));
}
fn draw_visualization(
&self,
color_scheme: &ColorScheme,
ui: &imgui::Ui,
bytes_per_unit: i32,
show_backtraces: bool,
) {
let draw_list = ui.get_window_draw_list();
let window_size = ui.window_size();
let base_pos = ui.cursor_screen_pos();
const LINE_HEIGHT: f32 = 10.0f32;
const LINE_SPACING: f32 = 1.0f32;
// Variables for keeping track of our own cursor.
let mut line_x = 0.0f32;
let mut line_y = 0.0f32;
let line_width = window_size[0];
struct LineMarker {
x: f32,
y: f32,
}
let mut line_markers = Vec::<LineMarker>::default();
let mut sorted_chunks = self.chunks.values().collect::<Vec<_>>();
sorted_chunks.sort_by(|a, b| a.offset.cmp(&b.offset));
// Draw each chunk in the memory block.
for chunk in sorted_chunks.iter() {
// Select a color based on the memory type.
let color = match chunk.allocation_type {
AllocationType::Free => color_scheme.free_color,
AllocationType::Linear => color_scheme.linear_color,
AllocationType::NonLinear => color_scheme.non_linear_color,
};
// Draw one or multiple bars based on the size of the chunk.
let mut bytes_to_draw = chunk.size as f32;
loop {
// Calculate how large the block should be. We take in account the size of the chunk,
// and the amount of space that is left on the line.
let units_to_draw = bytes_to_draw / bytes_per_unit as f32;
let units_left_on_line = line_width - line_x;
let units_to_draw = units_to_draw.min(units_left_on_line);
// Determine bounds of chunk line
let top_left = [base_pos[0] + line_x, base_pos[1] + line_y];
let bottom_right = [
base_pos[0] + line_x + units_to_draw,
base_pos[1] + line_y + LINE_HEIGHT,
];
if ui.is_rect_visible(top_left, bottom_right) {
// Draw chunk line.
draw_list
.add_rect(top_left, bottom_right, color)
.filled(true)
.build();
// Show chunk information in a tool tip when hovering over the chunk.
if ui.is_mouse_hovering_rect(top_left, bottom_right) {
ui.tooltip(|| {
ui.text(format!("chunk_id: {}", chunk.chunk_id));
ui.text(format!("size: 0x{:x}", chunk.size));
ui.text(format!("offset: 0x{:x}", chunk.offset));
ui.text(format!("allocation_type: {:?}", chunk.allocation_type));
if let Some(name) = &chunk.name {
ui.text(format!("name: {:?}", name));
}
if show_backtraces && chunk.backtrace.is_some() {
ui.text(format!(
"backtrace: {:}",
resolve_backtrace(&chunk.backtrace)
));
}
})
}
}
// Advance line counter.
line_x += units_to_draw;
// Go to next line if it reached the end.
if line_x >= line_width {
line_x = 0.0f32;
line_y += LINE_HEIGHT + LINE_SPACING;
}
// Calculate how many bytes have been drawn, and subtract that from the number of bytes left to draw
let bytes_drawn = units_to_draw * bytes_per_unit as f32;
bytes_to_draw -= bytes_drawn;
// Exit when there are no more bytes to draw.
if bytes_to_draw < 1.0f32 {
// Add a line marker to the end of the chunk.
line_markers.push(LineMarker {
x: bottom_right[0],
y: top_left[1],
});
// Exit the loop.
break;
}
}
}
// Draw the line markers after drawing all the chunks, so that chunks don't overlap the line markers
for line_marker in line_markers.iter() {
let top_left = [line_marker.x, line_marker.y];
let bottom_right = [line_marker.x, line_marker.y + LINE_HEIGHT];
if ui.is_rect_visible(top_left, bottom_right) {
// Draw a line to mark the end of the chunk.
draw_list
.add_line(top_left, bottom_right, 0xffff_ffff)
.thickness(1.0f32)
.build();
}
}
// Let ImGui know how much we drew using the draw list.
ui.set_cursor_pos([line_x, line_y + LINE_HEIGHT]);
}
}

View file

@ -0,0 +1,106 @@
use crate::result::*;
pub(crate) mod dedicated_block_allocator;
pub(crate) use dedicated_block_allocator::DedicatedBlockAllocator;
pub(crate) mod free_list_allocator;
pub(crate) use free_list_allocator::FreeListAllocator;
use log::*;
#[derive(PartialEq, Copy, Clone, Debug)]
#[repr(u8)]
pub(crate) enum AllocationType {
Free,
Linear,
NonLinear,
}
#[derive(Clone)]
pub(crate) struct AllocationReport {
pub(crate) name: String,
pub(crate) size: u64,
pub(crate) backtrace: Option<backtrace::Backtrace>,
}
pub(crate) fn resolve_backtrace(backtrace: &Option<backtrace::Backtrace>) -> String {
backtrace.as_ref().map_or_else(
|| "".to_owned(),
|bt| {
let mut bt = bt.clone();
bt.resolve();
format!("{:?}", bt)
},
)
}
#[cfg(feature = "visualizer")]
pub(crate) trait SubAllocatorBase: crate::visualizer::SubAllocatorVisualizer {}
#[cfg(not(feature = "visualizer"))]
pub(crate) trait SubAllocatorBase {}
pub(crate) trait SubAllocator: SubAllocatorBase + std::fmt::Debug + Sync + Send {
fn allocate(
&mut self,
size: u64,
alignment: u64,
allocation_type: AllocationType,
granularity: u64,
name: &str,
backtrace: Option<backtrace::Backtrace>,
) -> Result<(u64, std::num::NonZeroU64)>;
fn free(&mut self, chunk_id: Option<std::num::NonZeroU64>) -> Result<()>;
fn rename_allocation(
&mut self,
chunk_id: Option<std::num::NonZeroU64>,
name: &str,
) -> Result<()>;
fn report_memory_leaks(
&self,
log_level: Level,
memory_type_index: usize,
memory_block_index: usize,
);
fn report_allocations(&self) -> Vec<AllocationReport>;
#[must_use]
fn supports_general_allocations(&self) -> bool;
#[must_use]
fn size(&self) -> u64;
#[must_use]
fn allocated(&self) -> u64;
/// Helper function: reports how much memory is available in this suballocator
#[must_use]
fn available_memory(&self) -> u64 {
self.size() - self.allocated()
}
/// Helper function: reports if the suballocator is empty (meaning, having no allocations).
#[must_use]
fn is_empty(&self) -> bool {
self.allocated() == 0
}
}
pub(crate) const VISUALIZER_TABLE_MAX_ENTRY_NAME_LEN: usize = 40;
pub(crate) fn fmt_bytes(mut amount: u64) -> String {
const SUFFIX: [&str; 5] = ["B", "KB", "MB", "GB", "TB"];
let mut idx = 0;
let mut print_amount = amount as f64;
loop {
if amount < 1024 {
return format!("{:.2} {}", print_amount, SUFFIX[idx]);
}
print_amount = amount as f64 / 1024.0;
amount /= 1024;
idx += 1;
}
}

View file

@ -0,0 +1,970 @@
#![deny(clippy::unimplemented, clippy::unwrap_used, clippy::ok_expect)]
use std::fmt;
use log::{debug, warn, Level};
use windows::Win32::{Foundation::E_OUTOFMEMORY, Graphics::Direct3D12::*};
#[cfg(feature = "public-winapi")]
mod public_winapi {
use super::*;
pub use winapi::um::d3d12 as winapi_d3d12;
/// Trait similar to [`AsRef`]/[`AsMut`],
pub trait ToWinapi<T> {
fn as_winapi(&self) -> *const T;
fn as_winapi_mut(&mut self) -> *mut T;
}
/// [`windows`] types hold their pointer internally and provide drop semantics. As such this trait
/// is usually implemented on the _pointer type_ (`*const`, `*mut`) of the [`winapi`] object so that
/// a **borrow of** that pointer becomes a borrow of the [`windows`] type.
pub trait ToWindows<T> {
fn as_windows(&self) -> &T;
}
impl ToWinapi<winapi_d3d12::ID3D12Resource> for ID3D12Resource {
fn as_winapi(&self) -> *const winapi_d3d12::ID3D12Resource {
unsafe { std::mem::transmute_copy(self) }
}
fn as_winapi_mut(&mut self) -> *mut winapi_d3d12::ID3D12Resource {
unsafe { std::mem::transmute_copy(self) }
}
}
impl ToWinapi<winapi_d3d12::ID3D12Device> for ID3D12Device {
fn as_winapi(&self) -> *const winapi_d3d12::ID3D12Device {
unsafe { std::mem::transmute_copy(self) }
}
fn as_winapi_mut(&mut self) -> *mut winapi_d3d12::ID3D12Device {
unsafe { std::mem::transmute_copy(self) }
}
}
impl ToWindows<ID3D12Device> for *const winapi_d3d12::ID3D12Device {
fn as_windows(&self) -> &ID3D12Device {
unsafe { std::mem::transmute(self) }
}
}
impl ToWindows<ID3D12Device> for *mut winapi_d3d12::ID3D12Device {
fn as_windows(&self) -> &ID3D12Device {
unsafe { std::mem::transmute(self) }
}
}
impl ToWindows<ID3D12Device> for &mut winapi_d3d12::ID3D12Device {
fn as_windows(&self) -> &ID3D12Device {
unsafe { std::mem::transmute(self) }
}
}
impl ToWinapi<winapi_d3d12::ID3D12Heap> for ID3D12Heap {
fn as_winapi(&self) -> *const winapi_d3d12::ID3D12Heap {
unsafe { std::mem::transmute_copy(self) }
}
fn as_winapi_mut(&mut self) -> *mut winapi_d3d12::ID3D12Heap {
unsafe { std::mem::transmute_copy(self) }
}
}
}
#[cfg(feature = "public-winapi")]
pub use public_winapi::*;
#[cfg(feature = "visualizer")]
mod visualizer;
#[cfg(feature = "visualizer")]
pub use visualizer::AllocatorVisualizer;
use super::allocator;
use super::allocator::AllocationType;
use crate::{
allocator::fmt_bytes, AllocationError, AllocatorDebugSettings, MemoryLocation, Result,
};
/// [`ResourceCategory`] is used for supporting [`D3D12_RESOURCE_HEAP_TIER_1`].
/// [`ResourceCategory`] will be ignored if device supports [`D3D12_RESOURCE_HEAP_TIER_2`].
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ResourceCategory {
Buffer,
RtvDsvTexture,
OtherTexture,
}
#[derive(Clone, Copy)]
pub struct ResourceCreateDesc<'a> {
pub name: &'a str,
pub memory_location: MemoryLocation,
pub resource_category: ResourceCategory,
pub resource_desc: &'a D3D12_RESOURCE_DESC,
pub clear_value: Option<&'a D3D12_CLEAR_VALUE>,
pub initial_state: D3D12_RESOURCE_STATES,
pub resource_type: &'a ResourceType<'a>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum HeapCategory {
All,
Buffer,
RtvDsvTexture,
OtherTexture,
}
impl From<ResourceCategory> for HeapCategory {
fn from(resource_category: ResourceCategory) -> Self {
match resource_category {
ResourceCategory::Buffer => Self::Buffer,
ResourceCategory::RtvDsvTexture => Self::RtvDsvTexture,
ResourceCategory::OtherTexture => Self::OtherTexture,
}
}
}
impl From<&D3D12_RESOURCE_DESC> for ResourceCategory {
fn from(desc: &D3D12_RESOURCE_DESC) -> Self {
if desc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER {
Self::Buffer
} else if (desc.Flags
& (D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL))
!= D3D12_RESOURCE_FLAG_NONE
{
Self::RtvDsvTexture
} else {
Self::OtherTexture
}
}
}
#[cfg(feature = "public-winapi")]
impl From<&winapi_d3d12::D3D12_RESOURCE_DESC> for ResourceCategory {
fn from(desc: &winapi_d3d12::D3D12_RESOURCE_DESC) -> Self {
if desc.Dimension == winapi_d3d12::D3D12_RESOURCE_DIMENSION_BUFFER {
Self::Buffer
} else if (desc.Flags
& (winapi_d3d12::D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET
| winapi_d3d12::D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL))
!= 0
{
Self::RtvDsvTexture
} else {
Self::OtherTexture
}
}
}
#[derive(Clone, Debug)]
pub struct AllocationCreateDesc<'a> {
/// Name of the allocation, for tracking and debugging purposes
pub name: &'a str,
/// Location where the memory allocation should be stored
pub location: MemoryLocation,
/// Size of allocation, should be queried using [`ID3D12Device::GetResourceAllocationInfo()`]
pub size: u64,
/// Alignment of allocation, should be queried using [`ID3D12Device::GetResourceAllocationInfo()`]
pub alignment: u64,
/// Resource category based on resource dimension and flags. Can be created from a [`D3D12_RESOURCE_DESC`]
/// using the helper into function. The resource category is ignored when Resource Heap Tier 2 or higher
/// is supported.
pub resource_category: ResourceCategory,
}
impl<'a> AllocationCreateDesc<'a> {
/// Helper conversion function utilizing [`winapi`] types.
///
/// This function is also available for [`windows::Win32::Graphics::Direct3D12`]
/// types as [`from_d3d12_resource_desc()`][Self::from_d3d12_resource_desc()].
#[cfg(feature = "public-winapi")]
pub fn from_winapi_d3d12_resource_desc(
device: *const winapi_d3d12::ID3D12Device,
desc: &winapi_d3d12::D3D12_RESOURCE_DESC,
name: &'a str,
location: MemoryLocation,
) -> AllocationCreateDesc<'a> {
let device = device.as_windows();
// Raw structs are binary-compatible
let desc = unsafe { std::mem::transmute(desc) };
let allocation_info =
unsafe { device.GetResourceAllocationInfo(0, std::slice::from_ref(desc)) };
let resource_category: ResourceCategory = desc.into();
AllocationCreateDesc {
name,
location,
size: allocation_info.SizeInBytes,
alignment: allocation_info.Alignment,
resource_category,
}
}
/// Helper conversion function utilizing [`windows::Win32::Graphics::Direct3D12`] types.
///
/// This function is also available for `winapi` types as `from_winapi_d3d12_resource_desc()`
/// when the `public-winapi` feature is enabled.
pub fn from_d3d12_resource_desc(
device: &ID3D12Device,
desc: &D3D12_RESOURCE_DESC,
name: &'a str,
location: MemoryLocation,
) -> AllocationCreateDesc<'a> {
let allocation_info =
unsafe { device.GetResourceAllocationInfo(0, std::slice::from_ref(desc)) };
let resource_category: ResourceCategory = desc.into();
AllocationCreateDesc {
name,
location,
size: allocation_info.SizeInBytes,
alignment: allocation_info.Alignment,
resource_category,
}
}
}
#[derive(Debug)]
pub struct AllocatorCreateDesc {
pub device: ID3D12Device,
pub debug_settings: AllocatorDebugSettings,
}
pub enum ResourceType<'a> {
/// Allocation equivalent to Dx12's CommittedResource.
Committed {
heap_properties: &'a D3D12_HEAP_PROPERTIES,
heap_flags: D3D12_HEAP_FLAGS,
},
/// Allocation equivalent to Dx12's PlacedResource.
Placed,
}
#[derive(Debug)]
pub struct Resource {
name: String,
pub allocation: Option<Allocation>,
resource: Option<ID3D12Resource>,
pub memory_location: MemoryLocation,
memory_type_index: Option<usize>,
pub size: u64,
}
impl Resource {
pub fn resource(&self) -> &ID3D12Resource {
self.resource.as_ref().expect("Resource was already freed.")
}
}
impl Drop for Resource {
fn drop(&mut self) {
if self.resource.is_some() {
warn!("Dropping resource `{}` that was not freed. Call `Allocator::free_resource(resource)` instead.", self.name);
}
}
}
#[derive(Debug)]
pub struct CommittedAllocationStatistics {
pub num_allocations: usize,
pub total_size: u64,
}
#[derive(Debug)]
pub struct Allocation {
chunk_id: Option<std::num::NonZeroU64>,
offset: u64,
size: u64,
memory_block_index: usize,
memory_type_index: usize,
heap: ID3D12Heap,
name: Option<Box<str>>,
}
impl Allocation {
pub fn chunk_id(&self) -> Option<std::num::NonZeroU64> {
self.chunk_id
}
/// Returns the [`ID3D12Heap`] object that is backing this allocation.
/// This heap object can be shared with multiple other allocations and shouldn't be freed (or allocated from)
/// without this library, because that will lead to undefined behavior.
///
/// # Safety
/// The result of this function be safely passed into [`ID3D12Device::CreatePlacedResource()`].
/// It is exposed for this reason. Keep in mind to also pass [`Self::offset()`] along to it.
pub unsafe fn heap(&self) -> &ID3D12Heap {
&self.heap
}
/// Returns the offset of the allocation on the [`ID3D12Heap`].
/// When creating a placed resources, this offset needs to be supplied as well.
pub fn offset(&self) -> u64 {
self.offset
}
/// Returns the size of the allocation
pub fn size(&self) -> u64 {
self.size
}
pub fn is_null(&self) -> bool {
self.chunk_id.is_none()
}
}
#[derive(Debug)]
struct MemoryBlock {
heap: ID3D12Heap,
size: u64,
sub_allocator: Box<dyn allocator::SubAllocator>,
}
impl MemoryBlock {
fn new(
device: &ID3D12Device,
size: u64,
heap_properties: &D3D12_HEAP_PROPERTIES,
heap_category: HeapCategory,
dedicated: bool,
) -> Result<Self> {
let heap = {
let mut desc = D3D12_HEAP_DESC {
SizeInBytes: size,
Properties: *heap_properties,
Alignment: D3D12_DEFAULT_MSAA_RESOURCE_PLACEMENT_ALIGNMENT as u64,
..Default::default()
};
desc.Flags = match heap_category {
HeapCategory::All => D3D12_HEAP_FLAG_NONE,
HeapCategory::Buffer => D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS,
HeapCategory::RtvDsvTexture => D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES,
HeapCategory::OtherTexture => D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES,
};
let mut heap = None;
let hr = unsafe { device.CreateHeap(&desc, &mut heap) };
match hr {
Err(e) if e.code() == E_OUTOFMEMORY => Err(AllocationError::OutOfMemory),
Err(e) => Err(AllocationError::Internal(format!(
"ID3D12Device::CreateHeap failed: {}",
e
))),
Ok(()) => heap.ok_or_else(|| {
AllocationError::Internal(
"ID3D12Heap pointer is null, but should not be.".into(),
)
}),
}?
};
let sub_allocator: Box<dyn allocator::SubAllocator> = if dedicated {
Box::new(allocator::DedicatedBlockAllocator::new(size))
} else {
Box::new(allocator::FreeListAllocator::new(size))
};
Ok(Self {
heap,
size,
sub_allocator,
})
}
}
#[derive(Debug)]
struct MemoryType {
memory_blocks: Vec<Option<MemoryBlock>>,
committed_allocations: CommittedAllocationStatistics,
memory_location: MemoryLocation,
heap_category: HeapCategory,
heap_properties: D3D12_HEAP_PROPERTIES,
memory_type_index: usize,
active_general_blocks: usize,
}
const DEFAULT_DEVICE_MEMBLOCK_SIZE: u64 = 256 * 1024 * 1024;
const DEFAULT_HOST_MEMBLOCK_SIZE: u64 = 64 * 1024 * 1024;
impl MemoryType {
fn allocate(
&mut self,
device: &ID3D12Device,
desc: &AllocationCreateDesc<'_>,
backtrace: Option<backtrace::Backtrace>,
) -> Result<Allocation> {
let allocation_type = AllocationType::Linear;
let memblock_size = if self.heap_properties.Type == D3D12_HEAP_TYPE_DEFAULT {
DEFAULT_DEVICE_MEMBLOCK_SIZE
} else {
DEFAULT_HOST_MEMBLOCK_SIZE
};
let size = desc.size;
let alignment = desc.alignment;
// Create a dedicated block for large memory allocations
if size > memblock_size {
let mem_block = MemoryBlock::new(
device,
size,
&self.heap_properties,
self.heap_category,
true,
)?;
let block_index = self.memory_blocks.iter().position(|block| block.is_none());
let block_index = match block_index {
Some(i) => {
self.memory_blocks[i].replace(mem_block);
i
}
None => {
self.memory_blocks.push(Some(mem_block));
self.memory_blocks.len() - 1
}
};
let mem_block = self.memory_blocks[block_index]
.as_mut()
.ok_or_else(|| AllocationError::Internal("Memory block must be Some".into()))?;
let (offset, chunk_id) = mem_block.sub_allocator.allocate(
size,
alignment,
allocation_type,
1,
desc.name,
backtrace,
)?;
return Ok(Allocation {
chunk_id: Some(chunk_id),
size,
offset,
memory_block_index: block_index,
memory_type_index: self.memory_type_index,
heap: mem_block.heap.clone(),
name: Some(desc.name.into()),
});
}
let mut empty_block_index = None;
for (mem_block_i, mem_block) in self.memory_blocks.iter_mut().enumerate().rev() {
if let Some(mem_block) = mem_block {
let allocation = mem_block.sub_allocator.allocate(
size,
alignment,
allocation_type,
1,
desc.name,
backtrace.clone(),
);
match allocation {
Ok((offset, chunk_id)) => {
return Ok(Allocation {
chunk_id: Some(chunk_id),
offset,
size,
memory_block_index: mem_block_i,
memory_type_index: self.memory_type_index,
heap: mem_block.heap.clone(),
name: Some(desc.name.into()),
});
}
Err(AllocationError::OutOfMemory) => {} // Block is full, continue search.
Err(err) => return Err(err), // Unhandled error, return.
}
} else if empty_block_index.is_none() {
empty_block_index = Some(mem_block_i);
}
}
let new_memory_block = MemoryBlock::new(
device,
memblock_size,
&self.heap_properties,
self.heap_category,
false,
)?;
let new_block_index = if let Some(block_index) = empty_block_index {
self.memory_blocks[block_index] = Some(new_memory_block);
block_index
} else {
self.memory_blocks.push(Some(new_memory_block));
self.memory_blocks.len() - 1
};
self.active_general_blocks += 1;
let mem_block = self.memory_blocks[new_block_index]
.as_mut()
.ok_or_else(|| AllocationError::Internal("Memory block must be Some".into()))?;
let allocation = mem_block.sub_allocator.allocate(
size,
alignment,
allocation_type,
1,
desc.name,
backtrace,
);
let (offset, chunk_id) = match allocation {
Err(AllocationError::OutOfMemory) => Err(AllocationError::Internal(
"Allocation that must succeed failed. This is a bug in the allocator.".into(),
)),
a => a,
}?;
Ok(Allocation {
chunk_id: Some(chunk_id),
offset,
size,
memory_block_index: new_block_index,
memory_type_index: self.memory_type_index,
heap: mem_block.heap.clone(),
name: Some(desc.name.into()),
})
}
#[allow(clippy::needless_pass_by_value)]
fn free(&mut self, allocation: Allocation) -> Result<()> {
let block_idx = allocation.memory_block_index;
let mem_block = self.memory_blocks[block_idx]
.as_mut()
.ok_or_else(|| AllocationError::Internal("Memory block must be Some.".into()))?;
mem_block.sub_allocator.free(allocation.chunk_id)?;
if mem_block.sub_allocator.is_empty() {
if mem_block.sub_allocator.supports_general_allocations() {
if self.active_general_blocks > 1 {
let block = self.memory_blocks[block_idx].take();
if block.is_none() {
return Err(AllocationError::Internal(
"Memory block must be Some.".into(),
));
}
// Note that `block` will be destroyed on `drop` here
self.active_general_blocks -= 1;
}
} else {
let block = self.memory_blocks[block_idx].take();
if block.is_none() {
return Err(AllocationError::Internal(
"Memory block must be Some.".into(),
));
}
// Note that `block` will be destroyed on `drop` here
}
}
Ok(())
}
}
pub struct Allocator {
device: ID3D12Device,
debug_settings: AllocatorDebugSettings,
memory_types: Vec<MemoryType>,
}
impl Allocator {
pub fn device(&self) -> &ID3D12Device {
&self.device
}
pub fn new(desc: &AllocatorCreateDesc) -> Result<Self> {
// Perform AddRef on the device
let device = desc.device.clone();
// Query device for feature level
let mut options = Default::default();
unsafe {
device.CheckFeatureSupport(
D3D12_FEATURE_D3D12_OPTIONS,
<*mut D3D12_FEATURE_DATA_D3D12_OPTIONS>::cast(&mut options),
std::mem::size_of_val(&options) as u32,
)
}
.map_err(|e| {
AllocationError::Internal(format!("ID3D12Device::CheckFeatureSupport failed: {}", e))
})?;
let is_heap_tier1 = options.ResourceHeapTier == D3D12_RESOURCE_HEAP_TIER_1;
let heap_types = vec![
(
MemoryLocation::GpuOnly,
D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_DEFAULT,
..Default::default()
},
),
(
MemoryLocation::CpuToGpu,
D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_CUSTOM,
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE,
MemoryPoolPreference: D3D12_MEMORY_POOL_L0,
..Default::default()
},
),
(
MemoryLocation::GpuToCpu,
D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_CUSTOM,
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_WRITE_BACK,
MemoryPoolPreference: D3D12_MEMORY_POOL_L0,
..Default::default()
},
),
];
let heap_types = if is_heap_tier1 {
heap_types
.iter()
.flat_map(|(memory_location, heap_properties)| {
[
(HeapCategory::Buffer, *memory_location, *heap_properties),
(
HeapCategory::RtvDsvTexture,
*memory_location,
*heap_properties,
),
(
HeapCategory::OtherTexture,
*memory_location,
*heap_properties,
),
]
.to_vec()
})
.collect::<Vec<_>>()
} else {
heap_types
.iter()
.map(|(memory_location, heap_properties)| {
(HeapCategory::All, *memory_location, *heap_properties)
})
.collect::<Vec<_>>()
};
let memory_types = heap_types
.iter()
.enumerate()
.map(
|(i, &(heap_category, memory_location, heap_properties))| MemoryType {
memory_blocks: Vec::default(),
memory_location,
heap_category,
heap_properties,
memory_type_index: i,
active_general_blocks: 0,
committed_allocations: CommittedAllocationStatistics {
num_allocations: 0,
total_size: 0,
},
},
)
.collect::<Vec<_>>();
Ok(Self {
memory_types,
device,
debug_settings: desc.debug_settings,
})
}
pub fn allocate(&mut self, desc: &AllocationCreateDesc<'_>) -> Result<Allocation> {
let size = desc.size;
let alignment = desc.alignment;
let backtrace = if self.debug_settings.store_stack_traces {
Some(backtrace::Backtrace::new_unresolved())
} else {
None
};
if self.debug_settings.log_allocations {
debug!(
"Allocating `{}` of {} bytes with an alignment of {}.",
&desc.name, size, alignment
);
if self.debug_settings.log_stack_traces {
let backtrace = backtrace::Backtrace::new();
debug!("Allocation stack trace: {:?}", &backtrace);
}
}
if size == 0 || !alignment.is_power_of_two() {
return Err(AllocationError::InvalidAllocationCreateDesc);
}
// Find memory type
let memory_type = self
.memory_types
.iter_mut()
.find(|memory_type| {
let is_location_compatible = desc.location == MemoryLocation::Unknown
|| desc.location == memory_type.memory_location;
let is_category_compatible = memory_type.heap_category == HeapCategory::All
|| memory_type.heap_category == desc.resource_category.into();
is_location_compatible && is_category_compatible
})
.ok_or(AllocationError::NoCompatibleMemoryTypeFound)?;
memory_type.allocate(&self.device, desc, backtrace)
}
pub fn free(&mut self, allocation: Allocation) -> Result<()> {
if self.debug_settings.log_frees {
let name = allocation.name.as_deref().unwrap_or("<null>");
debug!("Freeing `{}`.", name);
if self.debug_settings.log_stack_traces {
let backtrace = backtrace::Backtrace::new();
debug!("Free stack trace: {:?}", backtrace);
}
}
if allocation.is_null() {
return Ok(());
}
self.memory_types[allocation.memory_type_index].free(allocation)?;
Ok(())
}
pub fn rename_allocation(&mut self, allocation: &mut Allocation, name: &str) -> Result<()> {
allocation.name = Some(name.into());
if allocation.is_null() {
return Ok(());
}
let mem_type = &mut self.memory_types[allocation.memory_type_index];
let mem_block = mem_type.memory_blocks[allocation.memory_block_index]
.as_mut()
.ok_or_else(|| AllocationError::Internal("Memory block must be Some.".into()))?;
mem_block
.sub_allocator
.rename_allocation(allocation.chunk_id, name)?;
Ok(())
}
pub fn report_memory_leaks(&self, log_level: Level) {
for (mem_type_i, mem_type) in self.memory_types.iter().enumerate() {
for (block_i, mem_block) in mem_type.memory_blocks.iter().enumerate() {
if let Some(mem_block) = mem_block {
mem_block
.sub_allocator
.report_memory_leaks(log_level, mem_type_i, block_i);
}
}
}
}
/// Create a resource according to the provided parameters.
/// Created resources should be freed at the end of their lifetime by calling [`Self::free_resource()`].
pub fn create_resource(&mut self, desc: &ResourceCreateDesc<'_>) -> Result<Resource> {
match desc.resource_type {
ResourceType::Committed {
heap_properties,
heap_flags,
} => {
let mut result: Option<ID3D12Resource> = None;
let clear_value: Option<*const D3D12_CLEAR_VALUE> =
desc.clear_value.map(|v| -> *const _ { v });
if let Err(err) = unsafe {
self.device.CreateCommittedResource(
*heap_properties,
*heap_flags,
desc.resource_desc,
desc.initial_state,
clear_value,
&mut result,
)
} {
Err(AllocationError::Internal(err.message().to_string()))
} else {
let resource =
result.expect("Allocation succeeded but no resource was returned?");
let allocation_info = unsafe {
self.device
.GetResourceAllocationInfo(0, &[*desc.resource_desc])
};
let memory_type = self
.memory_types
.iter_mut()
.find(|memory_type| {
let is_location_compatible = desc.memory_location
== MemoryLocation::Unknown
|| desc.memory_location == memory_type.memory_location;
let is_category_compatible = memory_type.heap_category
== HeapCategory::All
|| memory_type.heap_category == desc.resource_category.into();
is_location_compatible && is_category_compatible
})
.ok_or(AllocationError::NoCompatibleMemoryTypeFound)?;
memory_type.committed_allocations.num_allocations += 1;
memory_type.committed_allocations.total_size += allocation_info.SizeInBytes;
Ok(Resource {
name: desc.name.into(),
allocation: None,
resource: Some(resource),
size: allocation_info.SizeInBytes,
memory_location: desc.memory_location,
memory_type_index: Some(memory_type.memory_type_index),
})
}
}
ResourceType::Placed => {
let allocation_desc = {
let allocation_info = unsafe {
self.device
.GetResourceAllocationInfo(0, &[*desc.resource_desc])
};
AllocationCreateDesc {
name: desc.name,
location: desc.memory_location,
size: allocation_info.SizeInBytes,
alignment: allocation_info.Alignment,
resource_category: desc.resource_category,
}
};
let allocation = self.allocate(&allocation_desc)?;
let mut result: Option<ID3D12Resource> = None;
if let Err(err) = unsafe {
self.device.CreatePlacedResource(
allocation.heap(),
allocation.offset(),
desc.resource_desc,
desc.initial_state,
None,
&mut result,
)
} {
Err(AllocationError::Internal(err.message().to_string()))
} else {
let resource =
result.expect("Allocation succeeded but no resource was returned?");
let size = allocation.size();
Ok(Resource {
name: desc.name.into(),
allocation: Some(allocation),
resource: Some(resource),
size,
memory_location: desc.memory_location,
memory_type_index: None,
})
}
}
}
}
/// Free a resource and its memory.
pub fn free_resource(&mut self, mut resource: Resource) -> Result<()> {
// Explicitly drop the resource (which is backed by a refcounted COM object)
// before freeing allocated memory. Windows-rs performs a Release() on drop().
let _ = resource
.resource
.take()
.expect("Resource was already freed.");
if let Some(allocation) = resource.allocation.take() {
self.free(allocation)
} else {
// Dx12 CommittedResources do not have an application managed allocation.
// We only have to update the tracked allocation count and memory usage.
if let Some(memory_type_index) = resource.memory_type_index {
let memory_type = &mut self.memory_types[memory_type_index];
memory_type.committed_allocations.num_allocations -= 1;
memory_type.committed_allocations.total_size -= resource.size;
}
Ok(())
}
}
}
impl fmt::Debug for Allocator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut allocation_report = vec![];
let mut total_reserved_size_in_bytes = 0;
for memory_type in &self.memory_types {
for block in memory_type.memory_blocks.iter().flatten() {
total_reserved_size_in_bytes += block.size;
allocation_report.extend(block.sub_allocator.report_allocations())
}
}
let total_used_size_in_bytes = allocation_report.iter().map(|report| report.size).sum();
allocation_report.sort_by_key(|alloc| std::cmp::Reverse(alloc.size));
writeln!(
f,
"================================================================",
)?;
writeln!(
f,
"ALLOCATION BREAKDOWN ({} / {})",
fmt_bytes(total_used_size_in_bytes),
fmt_bytes(total_reserved_size_in_bytes),
)?;
let max_num_allocations_to_print = f.precision().map_or(usize::MAX, |n| n);
for (idx, alloc) in allocation_report.iter().enumerate() {
if idx >= max_num_allocations_to_print {
break;
}
writeln!(
f,
"{:max_len$.max_len$}\t- {}",
alloc.name,
fmt_bytes(alloc.size),
max_len = allocator::VISUALIZER_TABLE_MAX_ENTRY_NAME_LEN,
)?;
}
Ok(())
}
}
impl Drop for Allocator {
fn drop(&mut self) {
if self.debug_settings.log_leaks_on_shutdown {
self.report_memory_leaks(Level::Warn);
}
// Because Rust drop rules drop members in source-code order (that would be the
// ID3D12Device before the ID3D12Heaps nested in these memory blocks), free
// all remaining memory blocks manually first by dropping.
for mem_type in self.memory_types.iter_mut() {
mem_type.memory_blocks.clear();
}
}
}

View file

@ -0,0 +1,400 @@
#![allow(clippy::new_without_default)]
use super::Allocator;
use crate::visualizer::ColorScheme;
use log::error;
use windows::Win32::Graphics::Direct3D12::*;
// Default value for block visualizer granularity.
#[allow(dead_code)]
const DEFAULT_BYTES_PER_UNIT: i32 = 1024;
#[allow(dead_code)]
struct AllocatorVisualizerBlockWindow {
memory_type_index: usize,
block_index: usize,
bytes_per_unit: i32,
show_backtraces: bool,
}
impl AllocatorVisualizerBlockWindow {
#[allow(dead_code)]
fn new(memory_type_index: usize, block_index: usize) -> Self {
Self {
memory_type_index,
block_index,
bytes_per_unit: DEFAULT_BYTES_PER_UNIT,
show_backtraces: false,
}
}
}
pub struct AllocatorVisualizer {
#[allow(dead_code)]
selected_blocks: Vec<AllocatorVisualizerBlockWindow>,
#[allow(dead_code)]
focus: Option<usize>,
color_scheme: ColorScheme,
allocation_breakdown_sorting: Option<(Option<imgui::TableSortDirection>, usize)>,
}
#[allow(dead_code)]
fn format_heap_type(heap_type: D3D12_HEAP_TYPE) -> &'static str {
let names = [
"D3D12_HEAP_TYPE_DEFAULT_INVALID",
"D3D12_HEAP_TYPE_DEFAULT",
"D3D12_HEAP_TYPE_UPLOAD",
"D3D12_HEAP_TYPE_READBACK",
"D3D12_HEAP_TYPE_CUSTOM",
];
names[heap_type.0 as usize]
}
#[allow(dead_code)]
fn format_cpu_page_property(prop: D3D12_CPU_PAGE_PROPERTY) -> &'static str {
let names = [
"D3D12_CPU_PAGE_PROPERTY_UNKNOWN",
"D3D12_CPU_PAGE_PROPERTY_NOT_AVAILABLE",
"D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE",
"D3D12_CPU_PAGE_PROPERTY_WRITE_BACK",
];
names[prop.0 as usize]
}
#[allow(dead_code)]
fn format_memory_pool(pool: D3D12_MEMORY_POOL) -> &'static str {
let names = [
"D3D12_MEMORY_POOL_UNKNOWN",
"D3D12_MEMORY_POOL_L0",
"D3D12_MEMORY_POOL_L1",
];
names[pool.0 as usize]
}
impl AllocatorVisualizer {
pub fn new() -> Self {
Self {
selected_blocks: Vec::default(),
focus: None,
color_scheme: ColorScheme::default(),
allocation_breakdown_sorting: None,
}
}
pub fn set_color_scheme(&mut self, color_scheme: ColorScheme) {
self.color_scheme = color_scheme;
}
pub fn render_main_window(
&mut self,
ui: &imgui::Ui,
opened: Option<&mut bool>,
alloc: &Allocator,
) {
let mut window = ui.window("Allocator visualization");
if let Some(opened) = opened {
window = window.opened(opened);
}
window
.size([512.0, 512.0], imgui::Condition::FirstUseEver)
.build(|| {
use imgui::*;
if CollapsingHeader::new(format!(
"Memory Types: ({} types)",
alloc.memory_types.len()
))
.flags(TreeNodeFlags::DEFAULT_OPEN)
.build(ui)
{
ui.indent();
for (mem_type_i, mem_type) in alloc.memory_types.iter().enumerate() {
if CollapsingHeader::new(format!("Type: {}", mem_type_i)).build(ui) {
let mut total_block_size = 0;
let mut total_allocated = 0;
for block in mem_type.memory_blocks.iter().flatten() {
total_block_size += block.sub_allocator.size();
total_allocated += block.sub_allocator.allocated();
}
ui.text(format!("heap category: {:?}", mem_type.heap_category));
ui.text(format!(
"Heap Type: {} ({})",
format_heap_type(mem_type.heap_properties.Type),
mem_type.heap_properties.Type.0
));
ui.text(format!(
"CpuPageProperty: {} ({})",
format_cpu_page_property(mem_type.heap_properties.CPUPageProperty),
mem_type.heap_properties.CPUPageProperty.0
));
ui.text(format!(
"MemoryPoolPreference: {} ({})",
format_memory_pool(mem_type.heap_properties.MemoryPoolPreference),
mem_type.heap_properties.MemoryPoolPreference.0
));
ui.text(format!("total block size: {} KiB", total_block_size / 1024));
ui.text(format!("total allocated: {} KiB", total_allocated / 1024));
ui.text(format!(
"committed resource allocations: {}",
mem_type.committed_allocations.num_allocations
));
ui.text(format!(
"total committed resource allocations: {} KiB",
mem_type.committed_allocations.total_size
));
let active_block_count = mem_type
.memory_blocks
.iter()
.filter(|block| block.is_some())
.count();
ui.text(format!("block count: {}", active_block_count));
for (block_i, block) in mem_type.memory_blocks.iter().enumerate() {
if let Some(block) = block {
if ui.tree_node(format!("Block: {}", block_i)).is_some() {
ui.indent();
ui.text(format!(
"size: {} KiB",
block.sub_allocator.size() / 1024
));
ui.text(format!(
"allocated: {} KiB",
block.sub_allocator.allocated() / 1024
));
ui.text(format!("D3D12 heap: {:?}", block.heap));
block.sub_allocator.draw_base_info(ui);
if block.sub_allocator.supports_visualization()
&& ui.small_button("visualize")
{
match self.selected_blocks.iter().enumerate().find(
|(_, x)| {
x.memory_type_index == mem_type_i
&& x.block_index == block_i
},
) {
Some(x) => self.focus = Some(x.0),
None => self.selected_blocks.push(
AllocatorVisualizerBlockWindow::new(
mem_type_i, block_i,
),
),
}
}
ui.unindent();
}
}
}
}
}
ui.unindent();
}
});
}
#[allow(dead_code)]
fn render_memory_block_windows(&mut self, ui: &imgui::Ui, alloc: &Allocator) {
// Copy here to workaround the borrow checker.
let focus_opt = self.focus;
// Keep track of a list of windows that are signaled by imgui to be closed.
let mut windows_to_close = Vec::default();
// Draw each window.
let color_scheme = &self.color_scheme;
for (window_i, window) in self.selected_blocks.iter_mut().enumerate() {
// Determine if this window needs focus.
let focus = focus_opt.map_or(false, |focus_i| window_i == focus_i);
let mut is_open = true;
ui.window(format!(
"Block Visualizer##memtype({})block({})",
window.memory_type_index, window.block_index
))
.size([1920.0 * 0.5, 1080.0 * 0.5], imgui::Condition::FirstUseEver)
.title_bar(true)
.scroll_bar(true)
.scrollable(true)
.focused(focus)
.opened(&mut is_open)
.build(|| {
use imgui::*;
let memblock = &alloc.memory_types[window.memory_type_index].memory_blocks
[window.block_index]
.as_ref();
if let Some(memblock) = memblock {
ui.text(format!(
"Memory type {}, Memory block {}, Block size: {} KiB",
window.memory_type_index,
window.block_index,
memblock.sub_allocator.size() / 1024
));
if alloc.debug_settings.store_stack_traces {
ui.checkbox("Show backtraces", &mut window.show_backtraces);
}
// Slider for changing the 'zoom' level of the visualizer.
#[allow(dead_code)]
const BYTES_PER_UNIT_MIN: i32 = 1;
#[allow(dead_code)]
const BYTES_PER_UNIT_MAX: i32 = 1024 * 1024;
Drag::new("Bytes per Pixel (zoom)")
.range(BYTES_PER_UNIT_MIN, BYTES_PER_UNIT_MAX)
.speed(10.0f32)
.build(ui, &mut window.bytes_per_unit);
// Imgui can actually modify this number to be out of bounds, so we will clamp manually.
window.bytes_per_unit = window
.bytes_per_unit
.clamp(BYTES_PER_UNIT_MIN, BYTES_PER_UNIT_MAX);
// Draw the visualization in a child window.
ui.child_window(format!(
"Visualization Sub-window##memtype({})block({})",
window.memory_type_index, window.block_index
))
.scrollable(true)
.scroll_bar(true)
.build(|| {
memblock.sub_allocator.draw_visualization(
color_scheme,
ui,
window.bytes_per_unit,
window.show_backtraces,
)
});
} else {
ui.text("Deallocated memory block");
}
});
// If imgui signalled to close the window, add it to the list of windows to close.
if !is_open {
windows_to_close.push(window_i);
}
}
//
// Clean-up
//
// Close windows.
let mut windows_removed = 0usize;
let mut i = 0usize;
if !windows_to_close.is_empty() && !self.selected_blocks.is_empty() {
loop {
if windows_to_close.iter().any(|j| i == (*j - windows_removed)) {
self.selected_blocks.remove(i);
windows_removed += 1;
} else {
i += 1;
}
if i == self.selected_blocks.len() {
break;
}
}
}
// Reset focus.
self.focus = None;
}
/// Renders imgui widgets.
///
/// The [`Option<&mut bool>`] can be used control and track changes to the opened/closed status of the widget.
/// Pass [`None`] if no control and readback information is required. This will always render the widget.
/// When passing `Some(&mut bool)`:
/// - If [`false`], the widget won't be drawn.
/// - If [`true`], the widget will be drawn and an (X) closing button will be added to the widget bar.
pub fn render(&mut self, allocator: &Allocator, ui: &imgui::Ui, opened: Option<&mut bool>) {
if opened != Some(&mut false) {
self.render_main_window(ui, opened, allocator);
self.render_memory_block_windows(ui, allocator);
}
}
pub fn render_breakdown(
&mut self,
allocator: &Allocator,
ui: &imgui::Ui,
opened: Option<&mut bool>,
) {
ui.window("Allocation Breakdown")
.position([20.0f32, 80.0f32], imgui::Condition::FirstUseEver)
.size([460.0f32, 420.0f32], imgui::Condition::FirstUseEver)
.opened(opened.unwrap_or(&mut false))
.build(|| {
let mut allocation_report = vec![];
for memory_type in &allocator.memory_types {
for block in memory_type.memory_blocks.iter().flatten() {
allocation_report
.extend_from_slice(&block.sub_allocator.report_allocations())
}
}
if let Some(_k) = ui.begin_table_header_with_flags(
"alloc_breakdown_table",
[
imgui::TableColumnSetup {
flags: imgui::TableColumnFlags::WIDTH_FIXED,
init_width_or_weight: 50.0,
..imgui::TableColumnSetup::new("Idx")
},
imgui::TableColumnSetup::new("Name"),
imgui::TableColumnSetup {
flags: imgui::TableColumnFlags::WIDTH_FIXED,
init_width_or_weight: 150.0,
..imgui::TableColumnSetup::new("Size")
},
],
imgui::TableFlags::SORTABLE | imgui::TableFlags::RESIZABLE,
) {
let mut allocation_report =
allocation_report.iter().enumerate().collect::<Vec<_>>();
if let Some(mut sort_data) = ui.table_sort_specs_mut() {
if sort_data.should_sort() {
let specs = sort_data.specs();
if let Some(spec) = specs.iter().next() {
self.allocation_breakdown_sorting =
Some((spec.sort_direction(), spec.column_idx()));
}
sort_data.set_sorted();
}
}
if let Some((Some(dir), column_idx)) = self.allocation_breakdown_sorting {
match dir {
imgui::TableSortDirection::Ascending => match column_idx {
0 => allocation_report.sort_by_key(|(idx, _)| *idx),
1 => allocation_report.sort_by_key(|(_, alloc)| &alloc.name),
2 => allocation_report.sort_by_key(|(_, alloc)| alloc.size),
_ => error!("Sorting invalid column index {}", column_idx),
},
imgui::TableSortDirection::Descending => match column_idx {
0 => allocation_report
.sort_by_key(|(idx, _)| std::cmp::Reverse(*idx)),
1 => allocation_report
.sort_by_key(|(_, alloc)| std::cmp::Reverse(&alloc.name)),
2 => allocation_report
.sort_by_key(|(_, alloc)| std::cmp::Reverse(alloc.size)),
_ => error!("Sorting invalid column index {}", column_idx),
},
}
}
for (idx, alloc) in &allocation_report {
ui.table_next_column();
ui.text(idx.to_string());
ui.table_next_column();
ui.text(&alloc.name);
ui.table_next_column();
ui.text(format!("{:.3?}", alloc.size));
}
}
});
}
}

View file

@ -0,0 +1,212 @@
//! This crate provides a fully written in Rust memory allocator for Vulkan and DirectX 12.
//!
//! ## [Windows-rs] and [winapi]
//!
//! `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.
//!
//! [Windows-rs]: https://github.com/microsoft/windows-rs
//! [winapi]: https://github.com/retep998/winapi-rs
//!
//! ## Setting up the Vulkan memory allocator
//!
//! ```no_run
//! # #[cfg(feature = "vulkan")]
//! # fn main() {
//! use gpu_allocator::vulkan::*;
//! # use ash::vk;
//! # let device = todo!();
//! # let instance = todo!();
//! # let physical_device = todo!();
//!
//! let mut allocator = Allocator::new(&AllocatorCreateDesc {
//! instance,
//! device,
//! physical_device,
//! debug_settings: Default::default(),
//! buffer_device_address: true, // Ideally, check the BufferDeviceAddressFeatures struct.
//! });
//! # }
//! # #[cfg(not(feature = "vulkan"))]
//! # fn main() {}
//! ```
//!
//! ## Simple Vulkan allocation example
//!
//! ```no_run
//! # #[cfg(feature = "vulkan")]
//! # fn main() {
//! use gpu_allocator::vulkan::*;
//! use gpu_allocator::MemoryLocation;
//! # use ash::vk;
//! # let device = todo!();
//! # let instance = todo!();
//! # let physical_device = todo!();
//!
//! # let mut allocator = Allocator::new(&AllocatorCreateDesc {
//! # instance,
//! # device,
//! # physical_device,
//! # debug_settings: Default::default(),
//! # buffer_device_address: true, // Ideally, check the BufferDeviceAddressFeatures struct.
//! # }).unwrap();
//!
//! // Setup vulkan info
//! let vk_info = vk::BufferCreateInfo::builder()
//! .size(512)
//! .usage(vk::BufferUsageFlags::STORAGE_BUFFER);
//!
//! let buffer = unsafe { device.create_buffer(&vk_info, None) }.unwrap();
//! let requirements = unsafe { device.get_buffer_memory_requirements(buffer) };
//!
//! let allocation = allocator
//! .allocate(&AllocationCreateDesc {
//! name: "Example allocation",
//! requirements,
//! location: MemoryLocation::CpuToGpu,
//! linear: true, // Buffers are always linear
//! allocation_scheme: AllocationScheme::GpuAllocatorManaged,
//! }).unwrap();
//!
//! // Bind memory to the buffer
//! unsafe { device.bind_buffer_memory(buffer, allocation.memory(), allocation.offset()).unwrap() };
//!
//! // Cleanup
//! allocator.free(allocation).unwrap();
//! unsafe { device.destroy_buffer(buffer, None) };
//! # }
//! # #[cfg(not(feature = "vulkan"))]
//! # fn main() {}
//! ```
//!
//! ## Setting up the D3D12 memory allocator
//!
//! ```no_run
//! # #[cfg(feature = "d3d12")]
//! # fn main() {
//! use gpu_allocator::d3d12::*;
//! # let device = todo!();
//!
//! let mut allocator = Allocator::new(&AllocatorCreateDesc {
//! device,
//! debug_settings: Default::default(),
//! });
//! # }
//! # #[cfg(not(feature = "d3d12"))]
//! # fn main() {}
//! ```
//!
//! ## Simple d3d12 allocation example
//!
//! ```no_run
//! # #[cfg(feature = "d3d12")]
//! # fn main() -> windows::core::Result<()> {
//! use gpu_allocator::d3d12::*;
//! use gpu_allocator::MemoryLocation;
//! # use windows::Win32::Graphics::{Dxgi, Direct3D12};
//! # let device = todo!();
//!
//! # let mut allocator = Allocator::new(&AllocatorCreateDesc {
//! # device: device,
//! # debug_settings: Default::default(),
//! # }).unwrap();
//!
//! let buffer_desc = Direct3D12::D3D12_RESOURCE_DESC {
//! Dimension: Direct3D12::D3D12_RESOURCE_DIMENSION_BUFFER,
//! Alignment: 0,
//! Width: 512,
//! Height: 1,
//! DepthOrArraySize: 1,
//! MipLevels: 1,
//! Format: Dxgi::Common::DXGI_FORMAT_UNKNOWN,
//! SampleDesc: Dxgi::Common::DXGI_SAMPLE_DESC {
//! Count: 1,
//! Quality: 0,
//! },
//! Layout: Direct3D12::D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
//! Flags: Direct3D12::D3D12_RESOURCE_FLAG_NONE,
//! };
//! let allocation_desc = AllocationCreateDesc::from_d3d12_resource_desc(
//! &allocator.device(),
//! &buffer_desc,
//! "Example allocation",
//! MemoryLocation::GpuOnly,
//! );
//! let allocation = allocator.allocate(&allocation_desc).unwrap();
//! let mut resource: Option<Direct3D12::ID3D12Resource> = None;
//! let hr = unsafe {
//! device.CreatePlacedResource(
//! allocation.heap(),
//! allocation.offset(),
//! &buffer_desc,
//! Direct3D12::D3D12_RESOURCE_STATE_COMMON,
//! None,
//! &mut resource,
//! )
//! }?;
//!
//! // Cleanup
//! drop(resource);
//! allocator.free(allocation).unwrap();
//! # Ok(())
//! # }
//! # #[cfg(not(feature = "d3d12"))]
//! # fn main() {}
//! ```
mod result;
pub use result::*;
pub(crate) mod allocator;
#[cfg(feature = "visualizer")]
pub mod visualizer;
#[cfg(feature = "vulkan")]
pub mod vulkan;
#[cfg(all(windows, feature = "d3d12"))]
pub mod d3d12;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum MemoryLocation {
/// The allocated resource is stored at an unknown memory location; let the driver decide what's the best location
Unknown,
/// Store the allocation in GPU only accessible memory - typically this is the faster GPU resource and this should be
/// where most of the allocations live.
GpuOnly,
/// Memory useful for uploading data to the GPU and potentially for constant buffers
CpuToGpu,
/// Memory useful for CPU readback of data
GpuToCpu,
}
#[derive(Copy, Clone, Debug)]
pub struct AllocatorDebugSettings {
/// Logs out debugging information about the various heaps the current device has on startup
pub log_memory_information: bool,
/// Logs out all memory leaks on shutdown with log level Warn
pub log_leaks_on_shutdown: bool,
/// Stores a copy of the full backtrace for every allocation made, this makes it easier to debug leaks
/// or other memory allocations, but storing stack traces has a RAM overhead so should be disabled
/// in shipping applications.
pub store_stack_traces: bool,
/// Log out every allocation as it's being made with log level Debug, rather spammy so off by default
pub log_allocations: bool,
/// Log out every free that is being called with log level Debug, rather spammy so off by default
pub log_frees: bool,
/// Log out stack traces when either `log_allocations` or `log_frees` is enabled.
pub log_stack_traces: bool,
}
impl Default for AllocatorDebugSettings {
fn default() -> Self {
Self {
log_memory_information: false,
log_leaks_on_shutdown: true,
store_stack_traces: false,
log_allocations: false,
log_frees: false,
log_stack_traces: false,
}
}
}

View file

@ -0,0 +1,19 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AllocationError {
#[error("Out of memory")]
OutOfMemory,
#[error("Failed to map memory: {0}")]
FailedToMap(String),
#[error("No compatible memory type available")]
NoCompatibleMemoryTypeFound,
#[error("Invalid AllocationCreateDesc")]
InvalidAllocationCreateDesc,
#[error("Invalid AllocatorCreateDesc {0}")]
InvalidAllocatorCreateDesc(String),
#[error("Internal error: {0}")]
Internal(String),
}
pub type Result<V, E = AllocationError> = ::std::result::Result<V, E>;

View file

@ -0,0 +1,34 @@
use imgui::*;
#[derive(Clone)]
pub struct ColorScheme {
pub free_color: ImColor32,
pub linear_color: ImColor32,
pub non_linear_color: ImColor32,
}
impl Default for ColorScheme {
fn default() -> Self {
Self {
free_color: 0xff9f_9f9f.into(), // gray
linear_color: 0xfffa_ce5b.into(), // blue
non_linear_color: 0xffb8_a9fa.into(), // pink
}
}
}
pub(crate) trait SubAllocatorVisualizer {
fn supports_visualization(&self) -> bool {
false
}
fn draw_base_info(&self, ui: &Ui) {
ui.text("No sub allocator information available");
}
fn draw_visualization(
&self,
_color_scheme: &ColorScheme,
_ui: &Ui,
_bytes_per_unit: i32,
_show_backtraces: bool,
) {
}
}

View file

@ -0,0 +1,808 @@
#![deny(clippy::unimplemented, clippy::unwrap_used, clippy::ok_expect)]
#[cfg(feature = "visualizer")]
mod visualizer;
#[cfg(feature = "visualizer")]
pub use visualizer::AllocatorVisualizer;
use super::allocator;
use super::allocator::AllocationType;
use ash::vk;
use log::{debug, Level};
use std::fmt;
use crate::{
allocator::fmt_bytes, AllocationError, AllocatorDebugSettings, MemoryLocation, Result,
};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum AllocationScheme {
/// Perform a dedicated, driver-managed allocation for the given buffer, allowing
/// it to perform optimizations on this type of allocation.
DedicatedBuffer(vk::Buffer),
/// Perform a dedicated, driver-managed allocation for the given image, allowing
/// it to perform optimizations on this type of allocation.
DedicatedImage(vk::Image),
/// The memory for this resource will be allocated and managed by gpu-allocator.
GpuAllocatorManaged,
}
#[derive(Clone, Debug)]
pub struct AllocationCreateDesc<'a> {
/// Name of the allocation, for tracking and debugging purposes
pub name: &'a str,
/// Vulkan memory requirements for an allocation
pub requirements: vk::MemoryRequirements,
/// Location where the memory allocation should be stored
pub location: MemoryLocation,
/// If the resource is linear (buffer / linear texture) or a regular (tiled) texture.
pub linear: bool,
/// Determines how this allocation should be managed.
pub allocation_scheme: AllocationScheme,
}
/// Wrapper type to only mark a raw pointer [`Send`] + [`Sync`] without having to
/// mark the entire [`Allocation`] as such, instead relying on the compiler to
/// auto-implement this or fail if fields are added that violate this constraint
#[derive(Clone, Copy, Debug)]
pub(crate) struct SendSyncPtr(std::ptr::NonNull<std::ffi::c_void>);
// Sending is fine because mapped_ptr does not change based on the thread we are in
unsafe impl Send for SendSyncPtr {}
// Sync is also okay because Sending &Allocation is safe: a mutable reference
// to the data in mapped_ptr is never exposed while `self` is immutably borrowed.
// In order to break safety guarantees, the user needs to `unsafe`ly dereference
// `mapped_ptr` themselves.
unsafe impl Sync for SendSyncPtr {}
pub struct AllocatorCreateDesc {
pub instance: ash::Instance,
pub device: ash::Device,
pub physical_device: ash::vk::PhysicalDevice,
pub debug_settings: AllocatorDebugSettings,
pub buffer_device_address: bool,
}
#[derive(Debug)]
pub struct Allocation {
chunk_id: Option<std::num::NonZeroU64>,
offset: u64,
size: u64,
memory_block_index: usize,
memory_type_index: usize,
device_memory: vk::DeviceMemory,
mapped_ptr: Option<SendSyncPtr>,
dedicated_allocation: bool,
name: Option<Box<str>>,
}
impl Allocation {
pub fn chunk_id(&self) -> Option<std::num::NonZeroU64> {
self.chunk_id
}
/// Returns the [`vk::DeviceMemory`] object that is backing this allocation.
/// This memory object can be shared with multiple other allocations and shouldn't be freed (or allocated from)
/// without this library, because that will lead to undefined behavior.
///
/// # Safety
/// The result of this function can safely be used to pass into [`ash::Device::bind_buffer_memory()`],
/// [`ash::Device::bind_image_memory()`] etc. It is exposed for this reason. Keep in mind to also
/// pass [`Self::offset()`] along to those.
pub unsafe fn memory(&self) -> vk::DeviceMemory {
self.device_memory
}
/// Returns [`true`] if this allocation is using a dedicated underlying allocation.
pub fn is_dedicated(&self) -> bool {
self.dedicated_allocation
}
/// Returns the offset of the allocation on the [`vk::DeviceMemory`].
/// When binding the memory to a buffer or image, this offset needs to be supplied as well.
pub fn offset(&self) -> u64 {
self.offset
}
/// Returns the size of the allocation
pub fn size(&self) -> u64 {
self.size
}
/// Returns a valid mapped pointer if the memory is host visible, otherwise it will return None.
/// The pointer already points to the exact memory region of the suballocation, so no offset needs to be applied.
pub fn mapped_ptr(&self) -> Option<std::ptr::NonNull<std::ffi::c_void>> {
self.mapped_ptr.map(|SendSyncPtr(p)| p)
}
/// Returns a valid mapped slice if the memory is host visible, otherwise it will return None.
/// The slice already references the exact memory region of the allocation, so no offset needs to be applied.
pub fn mapped_slice(&self) -> Option<&[u8]> {
self.mapped_ptr().map(|ptr| unsafe {
std::slice::from_raw_parts(ptr.cast().as_ptr(), self.size as usize)
})
}
/// Returns a valid mapped mutable slice if the memory is host visible, otherwise it will return None.
/// The slice already references the exact memory region of the allocation, so no offset needs to be applied.
pub fn mapped_slice_mut(&mut self) -> Option<&mut [u8]> {
self.mapped_ptr().map(|ptr| unsafe {
std::slice::from_raw_parts_mut(ptr.cast().as_ptr(), self.size as usize)
})
}
pub fn is_null(&self) -> bool {
self.chunk_id.is_none()
}
}
impl Default for Allocation {
fn default() -> Self {
Self {
chunk_id: None,
offset: 0,
size: 0,
memory_block_index: !0,
memory_type_index: !0,
device_memory: vk::DeviceMemory::null(),
mapped_ptr: None,
name: None,
dedicated_allocation: false,
}
}
}
#[derive(Debug)]
pub(crate) struct MemoryBlock {
pub(crate) device_memory: vk::DeviceMemory,
pub(crate) size: u64,
pub(crate) mapped_ptr: Option<SendSyncPtr>,
pub(crate) sub_allocator: Box<dyn allocator::SubAllocator>,
pub(crate) dedicated_allocation: bool,
}
impl MemoryBlock {
fn new(
device: &ash::Device,
size: u64,
mem_type_index: usize,
mapped: bool,
buffer_device_address: bool,
allocation_scheme: AllocationScheme,
requires_personal_block: bool,
) -> Result<Self> {
let dedicated_allocation = allocation_scheme != AllocationScheme::GpuAllocatorManaged;
let device_memory = {
let alloc_info = vk::MemoryAllocateInfo::builder()
.allocation_size(size)
.memory_type_index(mem_type_index as u32);
let allocation_flags = vk::MemoryAllocateFlags::DEVICE_ADDRESS;
let mut flags_info = vk::MemoryAllocateFlagsInfo::builder().flags(allocation_flags);
// TODO(manon): Test this based on if the device has this feature enabled or not
let alloc_info = if buffer_device_address {
alloc_info.push_next(&mut flags_info)
} else {
alloc_info
};
// Flag the memory as dedicated if required.
let mut dedicated_memory_info = vk::MemoryDedicatedAllocateInfo::builder();
let alloc_info = match allocation_scheme {
AllocationScheme::DedicatedBuffer(buffer) => {
dedicated_memory_info = dedicated_memory_info.buffer(buffer);
alloc_info.push_next(&mut dedicated_memory_info)
}
AllocationScheme::DedicatedImage(image) => {
dedicated_memory_info = dedicated_memory_info.image(image);
alloc_info.push_next(&mut dedicated_memory_info)
}
AllocationScheme::GpuAllocatorManaged => alloc_info,
};
unsafe { device.allocate_memory(&alloc_info, None) }.map_err(|e| match e {
vk::Result::ERROR_OUT_OF_DEVICE_MEMORY => AllocationError::OutOfMemory,
e => AllocationError::Internal(format!(
"Unexpected error in vkAllocateMemory: {:?}",
e
)),
})?
};
let mapped_ptr = mapped
.then(|| {
unsafe {
device.map_memory(
device_memory,
0,
vk::WHOLE_SIZE,
vk::MemoryMapFlags::empty(),
)
}
.map_err(|e| {
unsafe { device.free_memory(device_memory, None) };
AllocationError::FailedToMap(e.to_string())
})
.and_then(|p| {
std::ptr::NonNull::new(p).map(SendSyncPtr).ok_or_else(|| {
AllocationError::FailedToMap("Returned mapped pointer is null".to_owned())
})
})
})
.transpose()?;
let sub_allocator: Box<dyn allocator::SubAllocator> = if allocation_scheme
!= AllocationScheme::GpuAllocatorManaged
|| requires_personal_block
{
Box::new(allocator::DedicatedBlockAllocator::new(size))
} else {
Box::new(allocator::FreeListAllocator::new(size))
};
Ok(Self {
device_memory,
size,
mapped_ptr,
sub_allocator,
dedicated_allocation,
})
}
fn destroy(self, device: &ash::Device) {
if self.mapped_ptr.is_some() {
unsafe { device.unmap_memory(self.device_memory) };
}
unsafe { device.free_memory(self.device_memory, None) };
}
}
#[derive(Debug)]
pub(crate) struct MemoryType {
pub(crate) memory_blocks: Vec<Option<MemoryBlock>>,
pub(crate) memory_properties: vk::MemoryPropertyFlags,
pub(crate) memory_type_index: usize,
pub(crate) heap_index: usize,
pub(crate) mappable: bool,
pub(crate) active_general_blocks: usize,
pub(crate) buffer_device_address: bool,
}
const DEFAULT_DEVICE_MEMBLOCK_SIZE: u64 = 256 * 1024 * 1024;
const DEFAULT_HOST_MEMBLOCK_SIZE: u64 = 64 * 1024 * 1024;
impl MemoryType {
fn allocate(
&mut self,
device: &ash::Device,
desc: &AllocationCreateDesc<'_>,
granularity: u64,
backtrace: Option<backtrace::Backtrace>,
) -> Result<Allocation> {
let allocation_type = if desc.linear {
AllocationType::Linear
} else {
AllocationType::NonLinear
};
let memblock_size = if self
.memory_properties
.contains(vk::MemoryPropertyFlags::HOST_VISIBLE)
{
DEFAULT_HOST_MEMBLOCK_SIZE
} else {
DEFAULT_DEVICE_MEMBLOCK_SIZE
};
let size = desc.requirements.size;
let alignment = desc.requirements.alignment;
let dedicated_allocation = desc.allocation_scheme != AllocationScheme::GpuAllocatorManaged;
let requires_personal_block = size > memblock_size;
// Create a dedicated block for large memory allocations or allocations that require dedicated memory allocations.
if dedicated_allocation || requires_personal_block {
let mem_block = MemoryBlock::new(
device,
size,
self.memory_type_index,
self.mappable,
self.buffer_device_address,
desc.allocation_scheme,
requires_personal_block,
)?;
let mut block_index = None;
for (i, block) in self.memory_blocks.iter().enumerate() {
if block.is_none() {
block_index = Some(i);
break;
}
}
let block_index = match block_index {
Some(i) => {
self.memory_blocks[i].replace(mem_block);
i
}
None => {
self.memory_blocks.push(Some(mem_block));
self.memory_blocks.len() - 1
}
};
let mem_block = self.memory_blocks[block_index]
.as_mut()
.ok_or_else(|| AllocationError::Internal("Memory block must be Some".into()))?;
let (offset, chunk_id) = mem_block.sub_allocator.allocate(
size,
alignment,
allocation_type,
granularity,
desc.name,
backtrace,
)?;
return Ok(Allocation {
chunk_id: Some(chunk_id),
offset,
size,
memory_block_index: block_index,
memory_type_index: self.memory_type_index,
device_memory: mem_block.device_memory,
mapped_ptr: mem_block.mapped_ptr,
name: Some(desc.name.into()),
dedicated_allocation,
});
}
let mut empty_block_index = None;
for (mem_block_i, mem_block) in self.memory_blocks.iter_mut().enumerate().rev() {
if let Some(mem_block) = mem_block {
let allocation = mem_block.sub_allocator.allocate(
size,
alignment,
allocation_type,
granularity,
desc.name,
backtrace.clone(),
);
match allocation {
Ok((offset, chunk_id)) => {
let mapped_ptr = if let Some(SendSyncPtr(mapped_ptr)) = mem_block.mapped_ptr
{
let offset_ptr = unsafe { mapped_ptr.as_ptr().add(offset as usize) };
std::ptr::NonNull::new(offset_ptr).map(SendSyncPtr)
} else {
None
};
return Ok(Allocation {
chunk_id: Some(chunk_id),
offset,
size,
memory_block_index: mem_block_i,
memory_type_index: self.memory_type_index,
device_memory: mem_block.device_memory,
mapped_ptr,
dedicated_allocation: false,
name: Some(desc.name.into()),
});
}
Err(err) => match err {
AllocationError::OutOfMemory => {} // Block is full, continue search.
_ => return Err(err), // Unhandled error, return.
},
}
} else if empty_block_index.is_none() {
empty_block_index = Some(mem_block_i);
}
}
let new_memory_block = MemoryBlock::new(
device,
memblock_size,
self.memory_type_index,
self.mappable,
self.buffer_device_address,
desc.allocation_scheme,
false,
)?;
let new_block_index = if let Some(block_index) = empty_block_index {
self.memory_blocks[block_index] = Some(new_memory_block);
block_index
} else {
self.memory_blocks.push(Some(new_memory_block));
self.memory_blocks.len() - 1
};
self.active_general_blocks += 1;
let mem_block = self.memory_blocks[new_block_index]
.as_mut()
.ok_or_else(|| AllocationError::Internal("Memory block must be Some".into()))?;
let allocation = mem_block.sub_allocator.allocate(
size,
alignment,
allocation_type,
granularity,
desc.name,
backtrace,
);
let (offset, chunk_id) = match allocation {
Ok(value) => value,
Err(err) => match err {
AllocationError::OutOfMemory => {
return Err(AllocationError::Internal(
"Allocation that must succeed failed. This is a bug in the allocator."
.into(),
))
}
_ => return Err(err),
},
};
let mapped_ptr = if let Some(SendSyncPtr(mapped_ptr)) = mem_block.mapped_ptr {
let offset_ptr = unsafe { mapped_ptr.as_ptr().add(offset as usize) };
std::ptr::NonNull::new(offset_ptr).map(SendSyncPtr)
} else {
None
};
Ok(Allocation {
chunk_id: Some(chunk_id),
offset,
size,
memory_block_index: new_block_index,
memory_type_index: self.memory_type_index,
device_memory: mem_block.device_memory,
mapped_ptr,
name: Some(desc.name.into()),
dedicated_allocation: false,
})
}
#[allow(clippy::needless_pass_by_value)]
fn free(&mut self, allocation: Allocation, device: &ash::Device) -> Result<()> {
let block_idx = allocation.memory_block_index;
let mem_block = self.memory_blocks[block_idx]
.as_mut()
.ok_or_else(|| AllocationError::Internal("Memory block must be Some.".into()))?;
mem_block.sub_allocator.free(allocation.chunk_id)?;
if mem_block.sub_allocator.is_empty() {
if mem_block.sub_allocator.supports_general_allocations() {
if self.active_general_blocks > 1 {
let block = self.memory_blocks[block_idx].take();
let block = block.ok_or_else(|| {
AllocationError::Internal("Memory block must be Some.".into())
})?;
block.destroy(device);
self.active_general_blocks -= 1;
}
} else {
let block = self.memory_blocks[block_idx].take();
let block = block.ok_or_else(|| {
AllocationError::Internal("Memory block must be Some.".into())
})?;
block.destroy(device);
}
}
Ok(())
}
}
pub struct Allocator {
pub(crate) memory_types: Vec<MemoryType>,
pub(crate) memory_heaps: Vec<vk::MemoryHeap>,
device: ash::Device,
pub(crate) buffer_image_granularity: u64,
pub(crate) debug_settings: AllocatorDebugSettings,
}
impl fmt::Debug for Allocator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut allocation_report = vec![];
let mut total_reserved_size_in_bytes = 0;
for memory_type in &self.memory_types {
for block in memory_type.memory_blocks.iter().flatten() {
total_reserved_size_in_bytes += block.size;
allocation_report.extend(block.sub_allocator.report_allocations())
}
}
let total_used_size_in_bytes = allocation_report.iter().map(|report| report.size).sum();
allocation_report.sort_by_key(|alloc| std::cmp::Reverse(alloc.size));
writeln!(
f,
"================================================================",
)?;
writeln!(
f,
"ALLOCATION BREAKDOWN ({} / {})",
fmt_bytes(total_used_size_in_bytes),
fmt_bytes(total_reserved_size_in_bytes),
)?;
let max_num_allocations_to_print = f.precision().map_or(usize::MAX, |n| n);
for (idx, alloc) in allocation_report.iter().enumerate() {
if idx >= max_num_allocations_to_print {
break;
}
writeln!(
f,
"{:max_len$.max_len$}\t- {}",
alloc.name,
fmt_bytes(alloc.size),
max_len = allocator::VISUALIZER_TABLE_MAX_ENTRY_NAME_LEN,
)?;
}
Ok(())
}
}
impl Allocator {
pub fn new(desc: &AllocatorCreateDesc) -> Result<Self> {
if desc.physical_device == ash::vk::PhysicalDevice::null() {
return Err(AllocationError::InvalidAllocatorCreateDesc(
"AllocatorCreateDesc field `physical_device` is null.".into(),
));
}
let mem_props = unsafe {
desc.instance
.get_physical_device_memory_properties(desc.physical_device)
};
let memory_types = &mem_props.memory_types[..mem_props.memory_type_count as _];
let memory_heaps = mem_props.memory_heaps[..mem_props.memory_heap_count as _].to_vec();
if desc.debug_settings.log_memory_information {
debug!("memory type count: {}", mem_props.memory_type_count);
debug!("memory heap count: {}", mem_props.memory_heap_count);
for (i, mem_type) in memory_types.iter().enumerate() {
let flags = mem_type.property_flags;
debug!(
"memory type[{}]: prop flags: 0x{:x}, heap[{}]",
i,
flags.as_raw(),
mem_type.heap_index,
);
}
for (i, heap) in memory_heaps.iter().enumerate() {
debug!(
"heap[{}] flags: 0x{:x}, size: {} MiB",
i,
heap.flags.as_raw(),
heap.size / (1024 * 1024)
);
}
}
let memory_types = memory_types
.iter()
.enumerate()
.map(|(i, mem_type)| MemoryType {
memory_blocks: Vec::default(),
memory_properties: mem_type.property_flags,
memory_type_index: i,
heap_index: mem_type.heap_index as usize,
mappable: mem_type
.property_flags
.contains(vk::MemoryPropertyFlags::HOST_VISIBLE),
active_general_blocks: 0,
buffer_device_address: desc.buffer_device_address,
})
.collect::<Vec<_>>();
let physical_device_properties = unsafe {
desc.instance
.get_physical_device_properties(desc.physical_device)
};
let granularity = physical_device_properties.limits.buffer_image_granularity;
Ok(Self {
memory_types,
memory_heaps,
device: desc.device.clone(),
buffer_image_granularity: granularity,
debug_settings: desc.debug_settings,
})
}
pub fn allocate(&mut self, desc: &AllocationCreateDesc<'_>) -> Result<Allocation> {
let size = desc.requirements.size;
let alignment = desc.requirements.alignment;
let backtrace = if self.debug_settings.store_stack_traces {
Some(backtrace::Backtrace::new_unresolved())
} else {
None
};
if self.debug_settings.log_allocations {
debug!(
"Allocating `{}` of {} bytes with an alignment of {}.",
&desc.name, size, alignment
);
if self.debug_settings.log_stack_traces {
let backtrace = backtrace::Backtrace::new();
debug!("Allocation stack trace: {:?}", backtrace);
}
}
if size == 0 || !alignment.is_power_of_two() {
return Err(AllocationError::InvalidAllocationCreateDesc);
}
let mem_loc_preferred_bits = match desc.location {
MemoryLocation::GpuOnly => vk::MemoryPropertyFlags::DEVICE_LOCAL,
MemoryLocation::CpuToGpu => {
vk::MemoryPropertyFlags::HOST_VISIBLE
| vk::MemoryPropertyFlags::HOST_COHERENT
| vk::MemoryPropertyFlags::DEVICE_LOCAL
}
MemoryLocation::GpuToCpu => {
vk::MemoryPropertyFlags::HOST_VISIBLE
| vk::MemoryPropertyFlags::HOST_COHERENT
| vk::MemoryPropertyFlags::HOST_CACHED
}
MemoryLocation::Unknown => vk::MemoryPropertyFlags::empty(),
};
let mut memory_type_index_opt =
self.find_memorytype_index(&desc.requirements, mem_loc_preferred_bits);
if memory_type_index_opt.is_none() {
let mem_loc_required_bits = match desc.location {
MemoryLocation::GpuOnly => vk::MemoryPropertyFlags::DEVICE_LOCAL,
MemoryLocation::CpuToGpu | MemoryLocation::GpuToCpu => {
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT
}
MemoryLocation::Unknown => vk::MemoryPropertyFlags::empty(),
};
memory_type_index_opt =
self.find_memorytype_index(&desc.requirements, mem_loc_required_bits);
}
let memory_type_index = match memory_type_index_opt {
Some(x) => x as usize,
None => return Err(AllocationError::NoCompatibleMemoryTypeFound),
};
//Do not try to create a block if the heap is smaller than the required size (avoids validation warnings).
let memory_type = &mut self.memory_types[memory_type_index];
let allocation = if size > self.memory_heaps[memory_type.heap_index].size {
Err(AllocationError::OutOfMemory)
} else {
memory_type.allocate(
&self.device,
desc,
self.buffer_image_granularity,
backtrace.clone(),
)
};
if desc.location == MemoryLocation::CpuToGpu {
if allocation.is_err() {
let mem_loc_preferred_bits =
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT;
let memory_type_index_opt =
self.find_memorytype_index(&desc.requirements, mem_loc_preferred_bits);
let memory_type_index = match memory_type_index_opt {
Some(x) => x as usize,
None => return Err(AllocationError::NoCompatibleMemoryTypeFound),
};
self.memory_types[memory_type_index].allocate(
&self.device,
desc,
self.buffer_image_granularity,
backtrace,
)
} else {
allocation
}
} else {
allocation
}
}
pub fn free(&mut self, allocation: Allocation) -> Result<()> {
if self.debug_settings.log_frees {
let name = allocation.name.as_deref().unwrap_or("<null>");
debug!("Freeing `{}`.", name);
if self.debug_settings.log_stack_traces {
let backtrace = format!("{:?}", backtrace::Backtrace::new());
debug!("Free stack trace: {}", backtrace);
}
}
if allocation.is_null() {
return Ok(());
}
self.memory_types[allocation.memory_type_index].free(allocation, &self.device)?;
Ok(())
}
pub fn rename_allocation(&mut self, allocation: &mut Allocation, name: &str) -> Result<()> {
allocation.name = Some(name.into());
if allocation.is_null() {
return Ok(());
}
let mem_type = &mut self.memory_types[allocation.memory_type_index];
let mem_block = mem_type.memory_blocks[allocation.memory_block_index]
.as_mut()
.ok_or_else(|| AllocationError::Internal("Memory block must be Some.".into()))?;
mem_block
.sub_allocator
.rename_allocation(allocation.chunk_id, name)?;
Ok(())
}
pub fn report_memory_leaks(&self, log_level: Level) {
for (mem_type_i, mem_type) in self.memory_types.iter().enumerate() {
for (block_i, mem_block) in mem_type.memory_blocks.iter().enumerate() {
if let Some(mem_block) = mem_block {
mem_block
.sub_allocator
.report_memory_leaks(log_level, mem_type_i, block_i);
}
}
}
}
fn find_memorytype_index(
&self,
memory_req: &vk::MemoryRequirements,
flags: vk::MemoryPropertyFlags,
) -> Option<u32> {
self.memory_types
.iter()
.find(|memory_type| {
(1 << memory_type.memory_type_index) & memory_req.memory_type_bits != 0
&& memory_type.memory_properties.contains(flags)
})
.map(|memory_type| memory_type.memory_type_index as _)
}
}
impl Drop for Allocator {
fn drop(&mut self) {
if self.debug_settings.log_leaks_on_shutdown {
self.report_memory_leaks(Level::Warn);
}
// Free all remaining memory blocks
for mem_type in self.memory_types.iter_mut() {
for mem_block in mem_type.memory_blocks.iter_mut() {
let block = mem_block.take();
if let Some(block) = block {
block.destroy(&self.device);
}
}
}
}
}

View file

@ -0,0 +1,394 @@
#![allow(clippy::new_without_default)]
use super::Allocator;
use crate::allocator::{fmt_bytes, resolve_backtrace};
use crate::visualizer::ColorScheme;
use log::error;
// Default value for block visualizer granularity.
const DEFAULT_BYTES_PER_UNIT: i32 = 1024;
struct AllocatorVisualizerBlockWindow {
memory_type_index: usize,
block_index: usize,
bytes_per_unit: i32,
show_backtraces: bool,
}
impl AllocatorVisualizerBlockWindow {
fn new(memory_type_index: usize, block_index: usize) -> Self {
Self {
memory_type_index,
block_index,
bytes_per_unit: DEFAULT_BYTES_PER_UNIT,
show_backtraces: false,
}
}
}
pub struct AllocatorVisualizer {
selected_blocks: Vec<AllocatorVisualizerBlockWindow>,
focus: Option<usize>,
color_scheme: ColorScheme,
allocation_breakdown_sorting: Option<(Option<imgui::TableSortDirection>, usize)>,
breakdown_filter: String,
}
impl AllocatorVisualizer {
pub fn new() -> Self {
Self {
selected_blocks: Vec::default(),
focus: None,
color_scheme: ColorScheme::default(),
allocation_breakdown_sorting: None,
breakdown_filter: String::new(),
}
}
pub fn set_color_scheme(&mut self, color_scheme: ColorScheme) {
self.color_scheme = color_scheme;
}
fn render_main_window(&mut self, ui: &imgui::Ui, opened: Option<&mut bool>, alloc: &Allocator) {
let mut window = ui.window("Allocator visualization");
if let Some(opened) = opened {
window = window.opened(opened);
}
window
.size([512.0, 512.0], imgui::Condition::FirstUseEver)
.build(|| {
use imgui::*;
ui.text(format!(
"buffer image granularity: {:?}",
alloc.buffer_image_granularity
));
let heap_count = alloc.memory_heaps.len();
if CollapsingHeader::new(format!("Memory Heaps ({} heaps)", heap_count)).build(ui) {
for (i, heap) in alloc.memory_heaps.iter().enumerate() {
ui.indent();
if CollapsingHeader::new(format!("Heap: {}", i)).build(ui) {
ui.indent();
ui.text(format!("flags: {:?}", heap.flags));
ui.text(format!(
"size: {} MiB",
heap.size as f64 / (1024 * 1024) as f64
));
ui.unindent();
}
ui.unindent();
}
}
if CollapsingHeader::new(format!(
"Memory Types: ({} types)",
alloc.memory_types.len()
))
.flags(TreeNodeFlags::DEFAULT_OPEN)
.build(ui)
{
ui.indent();
for (mem_type_i, mem_type) in alloc.memory_types.iter().enumerate() {
if CollapsingHeader::new(format!(
"Type: {} ({} blocks)##Type{}",
mem_type_i,
mem_type.memory_blocks.len(),
mem_type_i,
))
.build(ui)
{
let mut total_block_size = 0;
let mut total_allocated = 0;
for block in mem_type.memory_blocks.iter().flatten() {
total_block_size += block.size;
total_allocated += block.sub_allocator.allocated();
}
ui.text(format!("properties: {:?}", mem_type.memory_properties));
ui.text(format!("heap index: {}", mem_type.heap_index));
ui.text(format!("total block size: {} KiB", total_block_size / 1024));
ui.text(format!("total allocated: {} KiB", total_allocated / 1024));
let active_block_count = mem_type
.memory_blocks
.iter()
.filter(|block| block.is_some())
.count();
ui.text(format!("block count: {}", active_block_count));
for (block_i, block) in mem_type.memory_blocks.iter().enumerate() {
if let Some(block) = block {
if ui.tree_node(format!("Block: {}", block_i)).is_some() {
use ash::vk::Handle;
ui.indent();
ui.text(format!("size: {} KiB", block.size / 1024));
ui.text(format!(
"allocated: {} KiB",
block.sub_allocator.allocated() / 1024
));
ui.text(format!(
"vk device memory: 0x{:x}",
block.device_memory.as_raw()
));
if let Some(mapped_ptr) = block.mapped_ptr {
ui.text(format!(
"mapped pointer: {:#p}",
mapped_ptr.0.as_ptr()
));
}
if block.dedicated_allocation {
ui.text("Dedicated Allocation");
}
block.sub_allocator.draw_base_info(ui);
if block.sub_allocator.supports_visualization()
&& ui.small_button("visualize")
{
match self.selected_blocks.iter().enumerate().find(
|(_, x)| {
x.memory_type_index == mem_type_i
&& x.block_index == block_i
},
) {
Some(x) => self.focus = Some(x.0),
None => self.selected_blocks.push(
AllocatorVisualizerBlockWindow::new(
mem_type_i, block_i,
),
),
}
}
ui.unindent();
}
}
}
}
}
ui.unindent();
}
});
}
fn render_memory_block_windows(&mut self, ui: &imgui::Ui, alloc: &Allocator) {
// Copy here to workaround the borrow checker.
let focus_opt = self.focus;
// Keep track of a list of windows that are signaled by imgui to be closed.
let mut windows_to_close = Vec::default();
// Draw each window.
let color_scheme = &self.color_scheme;
for (window_i, window) in self.selected_blocks.iter_mut().enumerate() {
// Determine if this window needs focus.
let focus = focus_opt.map_or(false, |focus_i| window_i == focus_i);
let mut is_open = true;
ui.window(format!(
"Block Visualizer##memtype({})block({})",
window.memory_type_index, window.block_index
))
.size([1920.0 * 0.5, 1080.0 * 0.5], imgui::Condition::FirstUseEver)
.title_bar(true)
.scroll_bar(true)
.scrollable(true)
.focused(focus)
.opened(&mut is_open)
.build(|| {
use imgui::*;
let memblock = &alloc.memory_types[window.memory_type_index].memory_blocks
[window.block_index]
.as_ref();
if let Some(memblock) = memblock {
ui.text(format!(
"Memory type {}, Memory block {}, Block size: {} KiB",
window.memory_type_index,
window.block_index,
memblock.size / 1024
));
if alloc.debug_settings.store_stack_traces {
ui.checkbox("Show backtraces", &mut window.show_backtraces);
}
// Slider for changing the 'zoom' level of the visualizer.
const BYTES_PER_UNIT_MIN: i32 = 1;
const BYTES_PER_UNIT_MAX: i32 = 1024 * 1024;
Drag::new("Bytes per Pixel (zoom)")
.range(BYTES_PER_UNIT_MIN, BYTES_PER_UNIT_MAX)
.speed(10.0f32)
.build(ui, &mut window.bytes_per_unit);
// Imgui can actually modify this number to be out of bounds, so we will clamp manually.
window.bytes_per_unit = window
.bytes_per_unit
.clamp(BYTES_PER_UNIT_MIN, BYTES_PER_UNIT_MAX);
// Draw the visualization in a child window.
ui.child_window(format!(
"Visualization Sub-window##memtype({})block({})",
window.memory_type_index, window.block_index
))
.scrollable(true)
.scroll_bar(true)
.build(|| {
memblock.sub_allocator.draw_visualization(
color_scheme,
ui,
window.bytes_per_unit,
window.show_backtraces,
)
});
} else {
ui.text("Deallocated memory block");
}
});
// If imgui signalled to close the window, add it to the list of windows to close.
if !is_open {
windows_to_close.push(window_i);
}
}
//
// Clean-up
//
// Close windows.
let mut windows_removed = 0usize;
let mut i = 0usize;
if !windows_to_close.is_empty() && !self.selected_blocks.is_empty() {
loop {
if windows_to_close.iter().any(|j| i == (*j - windows_removed)) {
self.selected_blocks.remove(i);
windows_removed += 1;
} else {
i += 1;
}
if i == self.selected_blocks.len() {
break;
}
}
}
// Reset focus.
self.focus = None;
}
/// Renders imgui widgets.
///
/// The [`Option<&mut bool>`] can be used control and track changes to the opened/closed status of the widget.
/// Pass [`None`] if no control and readback information is required. This will always render the widget.
/// When passing `Some(&mut bool)`:
/// - If [`false`], the widget won't be drawn.
/// - If [`true`], the widget will be drawn and an (X) closing button will be added to the widget bar.
pub fn render(&mut self, allocator: &Allocator, ui: &imgui::Ui, opened: Option<&mut bool>) {
if opened != Some(&mut false) {
self.render_main_window(ui, opened, allocator);
self.render_memory_block_windows(ui, allocator);
}
}
pub fn render_breakdown(
&mut self,
allocator: &Allocator,
ui: &imgui::Ui,
opened: Option<&mut bool>,
) {
let mut allocation_report = vec![];
let mut total_size_in_bytes = 0;
if let Some(true) = opened {
let lowercase_needle = &self.breakdown_filter.to_lowercase();
for memory_type in &allocator.memory_types {
for block in memory_type.memory_blocks.iter().flatten() {
for report in block.sub_allocator.report_allocations() {
if self.breakdown_filter.is_empty()
|| report.name.to_lowercase().contains(lowercase_needle)
{
allocation_report.push(report);
}
}
}
}
total_size_in_bytes = allocation_report.iter().map(|report| report.size).sum();
}
let mut window = ui
.window(format!(
"Allocation Breakdown ({})###allocation_breakdown_window",
fmt_bytes(total_size_in_bytes)
))
.position([20.0f32, 80.0f32], imgui::Condition::FirstUseEver)
.size([460.0f32, 420.0f32], imgui::Condition::FirstUseEver);
if let Some(opened) = opened {
window = window.opened(opened);
}
window.build(|| {
ui.input_text("Filter", &mut self.breakdown_filter).build();
if let Some(_k) = ui.begin_table_header_with_flags(
"alloc_breakdown_table",
[
imgui::TableColumnSetup {
flags: imgui::TableColumnFlags::WIDTH_FIXED,
init_width_or_weight: 50.0,
..imgui::TableColumnSetup::new("Idx")
},
imgui::TableColumnSetup::new("Name"),
imgui::TableColumnSetup {
flags: imgui::TableColumnFlags::WIDTH_FIXED,
init_width_or_weight: 150.0,
..imgui::TableColumnSetup::new("Size")
},
],
imgui::TableFlags::SORTABLE | imgui::TableFlags::RESIZABLE,
) {
let mut allocation_report =
allocation_report.iter().enumerate().collect::<Vec<_>>();
if let Some(mut sort_data) = ui.table_sort_specs_mut() {
if sort_data.should_sort() {
let specs = sort_data.specs();
if let Some(ref spec) = specs.iter().next() {
self.allocation_breakdown_sorting =
Some((spec.sort_direction(), spec.column_idx()));
}
sort_data.set_sorted();
}
}
if let Some((Some(dir), column_idx)) = self.allocation_breakdown_sorting {
match dir {
imgui::TableSortDirection::Ascending => match column_idx {
0 => allocation_report.sort_by_key(|(idx, _)| *idx),
1 => allocation_report.sort_by_key(|(_, alloc)| &alloc.name),
2 => allocation_report.sort_by_key(|(_, alloc)| alloc.size),
_ => error!("Sorting invalid column index {}", column_idx),
},
imgui::TableSortDirection::Descending => match column_idx {
0 => allocation_report.sort_by_key(|(idx, _)| std::cmp::Reverse(*idx)),
1 => allocation_report
.sort_by_key(|(_, alloc)| std::cmp::Reverse(&alloc.name)),
2 => allocation_report
.sort_by_key(|(_, alloc)| std::cmp::Reverse(alloc.size)),
_ => error!("Sorting invalid column index {}", column_idx),
},
}
}
for (idx, alloc) in &allocation_report {
ui.table_next_column();
ui.text(idx.to_string());
ui.table_next_column();
ui.text(&alloc.name);
if ui.is_item_hovered() && alloc.backtrace.is_some() {
ui.tooltip(|| {
ui.text(resolve_backtrace(&alloc.backtrace));
});
}
ui.table_next_column();
ui.text(fmt_bytes(alloc.size));
}
}
});
}
}