Compare commits
No commits in common. "f9c18bcd3c2a94e013fdb1d7ff1e1e4d23cb2c99" and "00c67d5bdf2a203916c1ed82fc213f83524f146a" have entirely different histories.
f9c18bcd3c
...
00c67d5bdf
41
AGENTS.md
41
AGENTS.md
|
|
@ -1,41 +0,0 @@
|
|||
# Workspace guide
|
||||
|
||||
## Workspace
|
||||
- Rust workspace uses the nightly toolchain (`rust-toolchain`), edition 2024, resolver 2.
|
||||
- Members: `crates/renderer`, `crates/renderer/renderer_macros`, `crates/renderer_device`, `crates/renderer_resources`, `crates/window`, `crates/game`, `crates/text`, and `crates/bevy_vulkan_render`.
|
||||
- Dependency direction for Vulkan rendering: `bevy_vulkan_render` → `renderer` → `renderer_resources` → `renderer_device`. `renderer_macros` supplies `DeviceRef` derives to `renderer` and is a dev-dependency for `renderer_device` tests.
|
||||
- `renderer` re-exports device, instance, queue, and sync modules and many root device types. Prefer its paths for downstream rendering code; its resource-module aliases remain crate-private.
|
||||
- `crates/renderer/renderer_macros` is the proc-macro crate used by `renderer` for `DeviceRef` derives.
|
||||
- `crates/renderer_device` owns Vulkan instance/device setup, queues, sync primitives, and handle lifetimes.
|
||||
- `crates/renderer_resources` owns public buffer, image, view, descriptor, sampler, shader-module, and pipeline resource APIs on `renderer_device`.
|
||||
- `crates/renderer` owns rendering orchestration, commands, swapchains, and render graph APIs; resource module aliases are internal, not `renderer` public modules.
|
||||
- `crates/bevy_vulkan_render` integrates the renderer with Bevy. `crates/window`, `crates/game`, and `crates/text` are separate workspace crates.
|
||||
- The workspace root package is `vidya`; it also contains examples and depends on `bevy_vulkan_render`.
|
||||
- `renderer_resources` depends on `renderer_device`, not `renderer`; do not introduce a dependency cycle.
|
||||
- `renderer` uses `renderer_device` for device/instance ownership and accesses resource modules through crate-private aliases.
|
||||
- `renderer_macros` is nested under the renderer package directory but is a distinct workspace package.
|
||||
|
||||
## Layout
|
||||
- Workspace and shared dependency versions: root `Cargo.toml`.
|
||||
- Per-crate manifests live beside crate source under `crates/`.
|
||||
- Rendering shaders and assets are under `crates/renderer/shaders` and `assets/`.
|
||||
- Root-level demos and examples are under `examples/`; add shared dependencies under `[workspace.dependencies]` where appropriate.
|
||||
- The workspace uses Rust 2024 syntax; `renderer` and `renderer_device` use nightly features. `renderer_resources` has no crate-level nightly feature attributes.
|
||||
|
||||
## Conventions
|
||||
- Keep changes local to the owning crate; preserve the device/rendering layer boundary.
|
||||
- Follow existing Rust style and use the workspace dependency declarations in the root manifest when applicable.
|
||||
- Keep the device/resource/rendering dependency boundaries described above; use `renderer`'s re-exported API in Bevy integration where possible.
|
||||
- Do not commit changes; the user owns commits.
|
||||
- Read the crate-level instructions before changing rendering/device/resource boundaries:
|
||||
- [`crates/renderer_device/AGENTS.md`](crates/renderer_device/AGENTS.md)
|
||||
- [`crates/renderer_resources/AGENTS.md`](crates/renderer_resources/AGENTS.md)
|
||||
- [`crates/renderer/AGENTS.md`](crates/renderer/AGENTS.md)
|
||||
|
||||
## Verification
|
||||
- `cargo check --workspace` checks all workspace crates.
|
||||
- `cargo test -p renderer_device` runs the device-layer tests.
|
||||
- Rendering code uses nightly features; use the pinned workspace toolchain for all Cargo commands.
|
||||
- Run Cargo commands from the workspace root to use the pinned toolchain and shared workspace configuration.
|
||||
- Use crate-scoped `cargo check -p <crate>` or `cargo test -p <crate>` for focused checks, then workspace check for cross-crate API changes.
|
||||
- Review `git status` and `git diff` before reporting changes; preserve unrelated user modifications.
|
||||
52
Cargo.toml
52
Cargo.toml
|
|
@ -3,16 +3,18 @@ name = "vidya"
|
|||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[patch.crates-io]
|
||||
bevy = {path = "../bevy"}
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
|
||||
members = [
|
||||
"crates/renderer",
|
||||
"crates/renderer/renderer_macros",
|
||||
"crates/window",
|
||||
"crates/game",
|
||||
"crates/text",
|
||||
"crates/bevy_vulkan_render", "crates/renderer_device", "crates/renderer_resources", "crates/renderer_rendergraph",
|
||||
"crates/bevy_vulkan_render",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
|
|
@ -34,35 +36,34 @@ derive_more = { version = "1.0.0", features = ["deref", "debug", "deref_mut"] }
|
|||
tracing = "0.1"
|
||||
tracing-subscriber = {version ="0.3", features = ["env-filter"]}
|
||||
|
||||
glam = {version = "0.33", features = ["bytemuck"]}
|
||||
glam = {version = "0.29.0", features = ["bytemuck"]}
|
||||
rand = "0.9"
|
||||
bitflags = "2.6"
|
||||
thread_local = "1.1.8"
|
||||
|
||||
ash = "0.38.0"
|
||||
ash-window = "0.13.0"
|
||||
# vk-mem = "0.5.0"
|
||||
vk-mem = "0.5.0"
|
||||
gpu-allocator = { git = "https://github.com/janis-bhm/gpu-allocator", branch = "main" }
|
||||
rectangle-pack = "0.4.2"
|
||||
# vk-sync = "0.1.6"
|
||||
vk-sync = "0.1.6"
|
||||
|
||||
arrayvec = "0.7.6"
|
||||
tinyvec = "1.8"
|
||||
indexmap = "2"
|
||||
petgraph = "0.7"
|
||||
# itertools = "0.14.0"
|
||||
itertools = "0.14.0"
|
||||
ahash = "0.8"
|
||||
|
||||
# for non-cryptographic hashing of resources like pipelines, e.g. for caching
|
||||
md-5 = "0.11.0"
|
||||
|
||||
parking_lot = "0.12.3"
|
||||
bytemuck = { version = "1.21.0", features = ["derive"] }
|
||||
|
||||
# tokio = "1.42"
|
||||
# futures = "0.3"
|
||||
tokio = "1.42"
|
||||
futures = "0.3"
|
||||
smol = "2.0"
|
||||
# rayon = "1.10"
|
||||
rayon = "1.10"
|
||||
|
||||
winit = {version = "0.30.5", features = ["rwh_06"]}
|
||||
raw-window-handle = "0.6"
|
||||
|
|
@ -70,21 +71,22 @@ raw-window-handle = "0.6"
|
|||
egui = "0.32"
|
||||
egui_winit_platform = "0.27"
|
||||
|
||||
bevy = { path = "/var/code/bevy-engine/bevy" }
|
||||
bevy_app = { path = "/var/code/bevy-engine/bevy/crates/bevy_app" }
|
||||
bevy_asset = { path = "/var/code/bevy-engine/bevy/crates/bevy_asset" }
|
||||
bevy_derive = { path = "/var/code/bevy-engine/bevy/crates/bevy_derive" }
|
||||
bevy_ecs = { path = "/var/code/bevy-engine/bevy/crates/bevy_ecs" }
|
||||
bevy_log = { path = "/var/code/bevy-engine/bevy/crates/bevy_log" }
|
||||
# bevy_math = { path = "/var/code/bevy-engine/bevy/crates/bevy_math" }
|
||||
bevy_platform = { path = "/var/code/bevy-engine/bevy/crates/bevy_platform" }
|
||||
bevy_reflect = { path = "/var/code/bevy-engine/bevy/crates/bevy_reflect" }
|
||||
bevy_tasks = { path = "/var/code/bevy-engine/bevy/crates/bevy_tasks" }
|
||||
bevy_time = { path = "/var/code/bevy-engine/bevy/crates/bevy_time" }
|
||||
# bevy_transform = { path = "/var/code/bevy-engine/bevy/crates/bevy_transform" }
|
||||
bevy_utils = { path = "/var/code/bevy-engine/bevy/crates/bevy_utils" }
|
||||
bevy_window = { path = "/var/code/bevy-engine/bevy/crates/bevy_window" }
|
||||
# bevy_winit = { path = "/var/code/bevy-engine/bevy/crates/bevy_winit" }
|
||||
bevy = { version = "0.20.0-dev" }
|
||||
bevy_app = { version = "0.20.0-dev" }
|
||||
bevy_asset = { version = "0.20.0-dev" }
|
||||
bevy_derive = { version = "0.20.0-dev" }
|
||||
bevy_ecs = { version = "0.20.0-dev" }
|
||||
bevy_hierarchy = { version = "0.20.0-dev" }
|
||||
bevy_log = { version = "0.20.0-dev" }
|
||||
bevy_math = { version = "0.20.0-dev" }
|
||||
bevy_platform = { version = "0.20.0-dev" }
|
||||
bevy_reflect = { version = "0.20.0-dev" }
|
||||
bevy_tasks = { version = "0.20.0-dev" }
|
||||
bevy_time = { version = "0.20.0-dev" }
|
||||
bevy_transform = { version = "0.20.0-dev" }
|
||||
bevy_utils = { version = "0.20.0-dev" }
|
||||
bevy_window = { version = "0.20.0-dev" }
|
||||
bevy_winit = { version = "0.20.0-dev" }
|
||||
|
||||
[[example]]
|
||||
name = "bevy_integration"
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ use crate::extract_plugin::MainWorld;
|
|||
use bevy_ecs::{
|
||||
change_detection::Tick,
|
||||
prelude::*,
|
||||
query::FilteredAccessSet,
|
||||
system::{
|
||||
ReadOnlySystemParam, SystemAccess, SystemMeta, SystemParam, SystemParamItem,
|
||||
SystemParamValidationError, SystemState,
|
||||
ReadOnlySystemParam, SystemMeta, SystemParam, SystemParamItem, SystemParamValidationError,
|
||||
SystemState,
|
||||
},
|
||||
world::unsafe_world_cell::UnsafeWorldCell,
|
||||
};
|
||||
|
|
@ -82,9 +83,15 @@ where
|
|||
fn init_access(
|
||||
state: &Self::State,
|
||||
system_meta: &mut SystemMeta,
|
||||
system_access: &mut SystemAccess,
|
||||
) -> Result<(), bevy_ecs::system::SystemParamAccessConflict> {
|
||||
Res::<MainWorld>::init_access(&state.main_world_state, system_meta, system_access)
|
||||
component_access_set: &mut FilteredAccessSet,
|
||||
world: &mut World,
|
||||
) {
|
||||
Res::<MainWorld>::init_access(
|
||||
&state.main_world_state,
|
||||
system_meta,
|
||||
component_access_set,
|
||||
world,
|
||||
);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
|||
|
|
@ -89,7 +89,6 @@ fn mock_render_system() {
|
|||
impl Plugin for RenderPlugin {
|
||||
fn build(&self, app: &mut bevy_app::App) {
|
||||
app.add_plugins(ExtractPlugin::default());
|
||||
app.insert_non_send(window::Surfaces::default());
|
||||
|
||||
let asset_server = app.world().resource::<AssetServer>().clone();
|
||||
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
|
|
@ -101,6 +100,18 @@ impl Plugin for RenderPlugin {
|
|||
}
|
||||
}
|
||||
|
||||
fn init_vulkan(app: &mut App) -> Option<()> {
|
||||
let device = renderer::device::Device::new_from_default_desc(None, &[]).ok()?;
|
||||
|
||||
let device = RenderDevice { device };
|
||||
app.insert_resource(device.clone());
|
||||
|
||||
let render_app = app.sub_app_mut(RenderApp);
|
||||
render_app.insert_resource(device.clone());
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// A "scratch" world used to avoid allocating new worlds every frame when
|
||||
/// swapping out the [`MainWorld`] for [`ExtractSchedule`].
|
||||
#[derive(Resource, Default)]
|
||||
|
|
|
|||
|
|
@ -93,12 +93,12 @@ impl Plugin for SyncWorldPlugin {
|
|||
fn build(&self, app: &mut bevy_app::App) {
|
||||
app.init_resource::<PendingSyncEntity>();
|
||||
app.add_observer(
|
||||
|add: On<Add<SyncToRenderWorld>>, mut pending: ResMut<PendingSyncEntity>| {
|
||||
|add: On<Add, SyncToRenderWorld>, mut pending: ResMut<PendingSyncEntity>| {
|
||||
pending.push(EntityRecord::Added(add.entity));
|
||||
},
|
||||
);
|
||||
app.add_observer(
|
||||
|remove: On<Remove<SyncToRenderWorld>>,
|
||||
|remove: On<Remove, SyncToRenderWorld>,
|
||||
mut pending: ResMut<PendingSyncEntity>,
|
||||
query: Query<&RenderEntity>| {
|
||||
if let Ok(e) = query.get(remove.entity) {
|
||||
|
|
@ -344,13 +344,6 @@ mod render_entities_world_query_impls {
|
|||
<&RenderEntity as WorldQuery>::update_component_access(&component_id, access);
|
||||
}
|
||||
|
||||
fn init_nested_access(
|
||||
state: &Self::State,
|
||||
component_access_set: &mut bevy_ecs::query::FilteredAccessSet,
|
||||
) -> Result<(), bevy_ecs::query::FilteredAccessSet> {
|
||||
<&RenderEntity as WorldQuery>::init_nested_access(state, component_access_set)
|
||||
}
|
||||
|
||||
fn init_state(world: &mut World) -> ComponentId {
|
||||
<&RenderEntity as WorldQuery>::init_state(world)
|
||||
}
|
||||
|
|
@ -365,10 +358,6 @@ mod render_entities_world_query_impls {
|
|||
) -> bool {
|
||||
<&RenderEntity as WorldQuery>::matches_component_set(&state, set_contains_id)
|
||||
}
|
||||
|
||||
fn update_archetypes(state: &mut Self::State, world: UnsafeWorldCell) {
|
||||
<&RenderEntity as WorldQuery>::update_archetypes(state, world);
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: Component access of Self::ReadOnly is a subset of Self.
|
||||
|
|
@ -475,13 +464,6 @@ mod render_entities_world_query_impls {
|
|||
<&MainEntity as WorldQuery>::update_component_access(&component_id, access);
|
||||
}
|
||||
|
||||
fn init_nested_access(
|
||||
state: &Self::State,
|
||||
component_access_set: &mut bevy_ecs::query::FilteredAccessSet,
|
||||
) -> Result<(), bevy_ecs::query::FilteredAccessSet> {
|
||||
<&MainEntity as WorldQuery>::init_nested_access(state, component_access_set)
|
||||
}
|
||||
|
||||
fn init_state(world: &mut World) -> ComponentId {
|
||||
<&MainEntity as WorldQuery>::init_state(world)
|
||||
}
|
||||
|
|
@ -496,10 +478,6 @@ mod render_entities_world_query_impls {
|
|||
) -> bool {
|
||||
<&MainEntity as WorldQuery>::matches_component_set(&state, set_contains_id)
|
||||
}
|
||||
|
||||
fn update_archetypes(state: &mut Self::State, world: UnsafeWorldCell) {
|
||||
<&MainEntity as WorldQuery>::update_archetypes(state, world);
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: Component access of Self::ReadOnly is a subset of Self.
|
||||
|
|
@ -580,12 +558,12 @@ mod tests {
|
|||
main_world.init_resource::<PendingSyncEntity>();
|
||||
|
||||
main_world.add_observer(
|
||||
|add: On<Add<SyncToRenderWorld>>, mut pending: ResMut<PendingSyncEntity>| {
|
||||
|add: On<Add, SyncToRenderWorld>, mut pending: ResMut<PendingSyncEntity>| {
|
||||
pending.push(EntityRecord::Added(add.entity));
|
||||
},
|
||||
);
|
||||
main_world.add_observer(
|
||||
|remove: On<Remove<SyncToRenderWorld>>,
|
||||
|remove: On<Remove, SyncToRenderWorld>,
|
||||
mut pending: ResMut<PendingSyncEntity>,
|
||||
query: Query<&RenderEntity>| {
|
||||
if let Ok(e) = query.get(remove.entity) {
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ use std::{
|
|||
|
||||
use bevy_ecs::{entity::EntityHashMap, prelude::*};
|
||||
use bevy_window::{PrimaryWindow, RawHandleWrapper, Window, WindowClosing};
|
||||
use renderer::{ash::vk, swapchain::Surface};
|
||||
use renderer::{ash::vk, swapchain::WindowSurface};
|
||||
|
||||
use crate::extract_param::Extract;
|
||||
use crate::{RenderDevice, extract_param::Extract};
|
||||
|
||||
pub struct ExtractedWindow {
|
||||
pub entity: Entity,
|
||||
|
|
@ -39,7 +39,7 @@ impl DerefMut for ExtractedWindows {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Resource)]
|
||||
pub struct Surfaces {
|
||||
surfaces: EntityHashMap<SurfaceData>,
|
||||
configured: HashSet<Entity>,
|
||||
|
|
@ -57,12 +57,11 @@ impl Surfaces {
|
|||
}
|
||||
|
||||
pub struct SurfaceData {
|
||||
renderer: renderer::Renderer2,
|
||||
pub surface: Surface,
|
||||
pub surface: WindowSurface,
|
||||
}
|
||||
|
||||
impl Deref for SurfaceData {
|
||||
type Target = Surface;
|
||||
type Target = WindowSurface;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.surface
|
||||
|
|
@ -75,7 +74,7 @@ pub(crate) fn extract_windows(
|
|||
mut closing: Extract<MessageReader<WindowClosing>>,
|
||||
windows: Extract<Query<(Entity, &RawHandleWrapper, &Window, Option<&PrimaryWindow>)>>,
|
||||
mut removed: Extract<RemovedComponents<RawHandleWrapper>>,
|
||||
mut surfaces: NonSendMut<Surfaces>,
|
||||
mut surfaces: ResMut<Surfaces>,
|
||||
) {
|
||||
for (entity, handle, window, primary) in windows.iter() {
|
||||
if primary.is_some() {
|
||||
|
|
@ -117,7 +116,7 @@ pub(crate) fn extract_windows(
|
|||
|
||||
pub(crate) fn need_surface_configuration(
|
||||
windows: Res<ExtractedWindows>,
|
||||
surfaces: NonSend<Surfaces>,
|
||||
surfaces: Res<Surfaces>,
|
||||
) -> bool {
|
||||
windows
|
||||
.windows
|
||||
|
|
@ -127,43 +126,37 @@ pub(crate) fn need_surface_configuration(
|
|||
|
||||
pub(crate) fn configure_surfaces(
|
||||
windows: Res<ExtractedWindows>,
|
||||
mut surfaces: NonSendMut<Surfaces>,
|
||||
mut surfaces: ResMut<Surfaces>,
|
||||
device: Res<RenderDevice>,
|
||||
) {
|
||||
for window in windows.windows.values() {
|
||||
let surface_data = surfaces.surfaces.entry(window.entity).or_insert_with(|| {
|
||||
let surface = surfaces.surfaces.entry(window.entity).or_insert_with(|| {
|
||||
let (display_handle, window_handle) = (
|
||||
window.handle.get_display_handle(),
|
||||
window.handle.get_window_handle(),
|
||||
);
|
||||
|
||||
let renderer =
|
||||
renderer::Renderer2::new(display_handle).expect("Failed to create Vulkan renderer");
|
||||
let surface = renderer
|
||||
.create_surface(
|
||||
window_handle,
|
||||
vk::Extent2D {
|
||||
width: window.physical_width,
|
||||
height: window.physical_height,
|
||||
},
|
||||
)
|
||||
.expect("Failed to create window surface");
|
||||
let surface = WindowSurface::new(
|
||||
(**device).clone(),
|
||||
vk::Extent2D {
|
||||
width: window.physical_width,
|
||||
height: window.physical_height,
|
||||
},
|
||||
window_handle,
|
||||
display_handle,
|
||||
)
|
||||
.expect("Failed to create window surface");
|
||||
|
||||
SurfaceData { renderer, surface }
|
||||
SurfaceData { surface }
|
||||
});
|
||||
|
||||
if window.size_changed {
|
||||
surface_data
|
||||
surface
|
||||
.surface
|
||||
.configure(
|
||||
surface_data.renderer.device(),
|
||||
renderer::swapchain::SwapchainConfiguration {
|
||||
extent: vk::Extent2D {
|
||||
width: window.physical_width,
|
||||
height: window.physical_height,
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.recreate_with(Some(vk::Extent2D {
|
||||
width: window.physical_width,
|
||||
height: window.physical_height,
|
||||
}))
|
||||
.expect("Failed to recreate surface");
|
||||
}
|
||||
surfaces.configured.insert(window.entity);
|
||||
|
|
|
|||
|
|
@ -1,130 +0,0 @@
|
|||
# renderer
|
||||
|
||||
## Purpose
|
||||
`renderer` builds rendering orchestration, command submission, swapchains, and render-graph APIs on `renderer_resources` and the lower-level `renderer_device` layer. It re-exports selected `renderer_device` APIs and uses crate-private aliases to the resource crate. Follow [`../renderer_device/AGENTS.md`](../renderer_device/AGENTS.md) for device ownership internals and [`../renderer_resources/AGENTS.md`](../renderer_resources/AGENTS.md) for resource construction and ownership.
|
||||
|
||||
## Module map
|
||||
- `lib.rs`: public root API, `Renderer2`, sampler cache, egui state, texture manager, and `renderer_device` re-exports.
|
||||
- `memory.rs` (not declared by `lib.rs`): empty; not part of the compiled crate module tree.
|
||||
- `renderer_resources::{buffers, images, pipeline}`: public resource-crate modules aliased through `pub(crate) use` in this crate root. These aliases preserve internal `crate::buffers::X`, `crate::images::X`, and `crate::pipeline::X` paths without exposing public renderer modules.
|
||||
- `commands.rs` (public): command pools/buffers, command recording helpers, queue submission, and command futures.
|
||||
- `swapchain.rs` (public): `Surface`, swapchain configuration, acquired images/frames, and presentation.
|
||||
- `render_graph/mod.rs` (public): re-exports the legacy graph API from `legacy.rs`; sibling `commands.rs`, `graph_builder.rs`, `recorder.rs`, and `resources.rs` are private modules.
|
||||
- `render_graph/legacy.rs`: public resource/pass descriptions, graph construction/resolution, resource barriers, and `RenderContext`.
|
||||
- `render_graph/commands.rs`: private typed render-graph commands and resource-access declarations; not wired into the legacy graph.
|
||||
- `render_graph/graph_builder.rs`: private newer graph-builder scaffolding and API example.
|
||||
- `render_graph/recorder.rs`: private typed command-list and side-effect tracking scaffolding.
|
||||
- `render_graph/resources.rs`: graph resource IDs, buffer/texture ranges and access scopes; private.
|
||||
- `rendering/mod.rs` (public): `Wireframe` helper and re-exports `egui_pass` / `egui_pre_pass`.
|
||||
- `egui.rs` is included as private `egui_pass`: egui texture upload/pre-pass and drawing integration.
|
||||
- `egui_pass` is a private module, but `rendering` publicly re-exports its `egui_pass` and `egui_pre_pass` functions.
|
||||
- `text.rs` (private): font storage, glyph rasterization, and CPU-side glyph-atlas data; largely internal/dead-code-allowed.
|
||||
- `util.rs` (public): renderer helpers and stable re-exports of `eq_f32`, `ord_f32`, `hash_f32`, and `F32` from `renderer_resources::util`; `weak_vec` moved there and is not re-exported from renderer.
|
||||
- `texture` (private inline module in `lib.rs`): `TextureManager` and `TextureId` around shared images.
|
||||
|
||||
## Key types and API surface
|
||||
- Public modules are `commands`, `render_graph`, `rendering`, `swapchain`, and `util`; `text`, `egui_pass`, and `texture` are private. `buffers`, `images`, and `pipeline` are crate-private aliases to public `renderer_resources` modules, not public paths through `renderer`.
|
||||
- `lib.rs` re-exports `ash` and the principal `renderer_device` APIs at crate root, including `device`, `instance`, `queue`, `sync`, `Device`, `DeviceOwned`, `DeviceRef`, `Instance`, `Error`, and `Result`.
|
||||
- Root entry point: `Renderer2` owns a `Device`, `SamplerCache`, `TextureManager`, and display handle. Its API creates surfaces and builds/submits a `RenderGraph` in `draw_graph`.
|
||||
- Root helper types include `EguiState`, `EguiTextureInfo`, and `SamplerCache`. `TextureManager` and `TextureId` are used by these internals but are not re-exported at crate root.
|
||||
- `SamplerCache` caches `renderer_resources::pipeline::Sampler` objects by `SamplerDesc`; `EguiState` keeps texture mappings and descriptor/pipeline resources for egui.
|
||||
- `commands::CommandPools`, `CommandPool`, `CommandBuffer`, and `CommandBufferFuture` provide per-thread command-pool allocation and submission. `SingleUseCommandPool`/`SingleUseCommand` support one-shot operations.
|
||||
- `commands::CommandBufferExt` adds recording helpers for barriers, copies, rendering, and pipeline state. `CommandList<T: HasQueue>` submits command buffers that share a queue.
|
||||
- `swapchain::Surface` owns the surface and current configured swapchain; `SwapchainConfiguration` describes image format, extent, present mode, usage, and image count. Acquired image values must be presented.
|
||||
- Public `render_graph` items come from `legacy.rs`: `RenderGraph`, `GraphResourceId`, `GraphResourceDesc`, `PassDesc`, `RenderContext`, `Access`, `LoadOp`, and `StoreOp` provide pass/resource declaration and resolution.
|
||||
- `RenderGraph::add_resource` / `import_image` / `import_buffer` / `import_framebuffer` register graph resources; `add_pass` records read/write declarations and a callback; `resolve` creates internal resources and returns a command list with graph transitions.
|
||||
- `rendering::Wireframe` is an example higher-level renderer; egui pass functions are re-exported from `rendering` rather than the private `egui_pass` module.
|
||||
- Resource types are defined in `renderer_resources`: `Buffer`/`BufferDesc`; `Image`/`ImageDesc`, `ImageView`/`ImageViewDesc`, `MipRange`, `Extent`, and `Offset`; and pipeline, descriptor, shader, and sampler types.
|
||||
- Buffer/image descriptors carry Vulkan usage, memory location, queue-sharing flags, and allocation strategy; `Buffer::map`/`map_mut` expose mapped memory only when the allocator returns a mapped allocation.
|
||||
- The egui integration uploads texture deltas to GPU images and records passes through the legacy `RenderGraph`; shader binaries remain under `crates/renderer/shaders`, and renderer's hardcoded shader paths are unchanged.
|
||||
- `ImageDesc` includes image shape, format, layout, usage, queue family selection, and allocation options. The resource crate's `Image::new_with_allocation` checks allocation size and memory-type compatibility before binding.
|
||||
- `ImageViewDesc` selects view type, format, aspects, mip range, and array layers; the resource crate's `Image::create_view` normalizes `REMAINING_MIP_LEVELS` / `REMAINING_ARRAY_LAYERS` and checks image/view compatibility.
|
||||
- `BufferDesc` and `ImageDesc` use `renderer_device::device::QueueFlags` to select concurrent sharing families. Empty/single-family selections use exclusive sharing.
|
||||
- `Pipeline` retains its `PipelineLayout` via `Arc`; `DescriptorPool` serializes descriptor allocation/reset using a mutex. Pipeline creation uses the device pipeline cache.
|
||||
- `Surface::configure` skips recreating an equivalent swapchain for the same device; configuration validation falls back for unsupported present mode/format and rejects zero or oversized extents.
|
||||
- `SwapchainImage` dereferences to an `ImageView`; `SwapchainFrame` exposes the swapchain, image, format, view, and acquire/release semaphores used by frame submission.
|
||||
- `render_graph::legacy::RenderGraph` stores resource descriptions until `resolve`; framebuffer imports are marked as present outputs.
|
||||
- `render_graph::legacy::PassDesc` lists per-pass `reads`, `writes`, and an optional `RecordFn`; the graph resolver builds dependencies and transitions between `Access` states.
|
||||
- `render_graph::commands::Command` distinguishes side-effect declaration from GPU command application, with commands marking resource ranges/stages/accesses for barriers.
|
||||
- `render_graph::recorder::CommandList` type-erases recorded commands into bytes and tracks per-resource side effects.
|
||||
- Internal text support defines `FontId` and a private font atlas store; it rasterizes glyphs with `cosmic-text` into CPU image data but is not a public renderer API.
|
||||
- `util::DropGuard`, `MutexExt`, `FormatExt`, `Rgba`/`Rgba8`, and `def_monotonic_id!` support lifetime callbacks, lock closures, format conversion, color values, and IDs.
|
||||
- `render_graph::legacy::RenderContext` exposes the selected device, one-shot command buffer, graph resources, and optional framebuffer to each pass record callback.
|
||||
- Graph `Access` includes stage, access mask, and image layout. Imported resources require callers to describe their initial access; framebuffer import marks presentation as an output.
|
||||
- `DeviceObject<vk::Buffer>`, `DeviceObject<vk::Image>`, `DeviceObject<vk::ImageView>`, and `DeviceObject<vk::SwapchainKHR>` wrap device-owned Vulkan handles. Resource wrappers retain allocations/devices as needed; see the device-layer guide for lifecycle rules.
|
||||
- `renderer::device::Allocation` (from `renderer_device`) distinguishes owned, shared, and unmanaged GPU allocations; the resource image implementation uses it when constructing allocated images.
|
||||
- `GraphResource` can hold imported resources, owned buffers/images, framebuffers, or resource descriptions awaiting resolution. `RenderGraph::resolve` creates described resources and records `SingleUseCommand`s for its passes.
|
||||
- Descriptor, pipeline, shader, and sampler abstractions are implemented in `renderer_resources::pipeline`. Root `SamplerCache` uses them through the private alias, not a public renderer pipeline module.
|
||||
|
||||
## Invariants and gotchas
|
||||
- Device ownership is delegated to `renderer_device`: wrappers here strongly retain `Device` through its `DeviceObject` or explicit fields. Do not place a device-owning wrapper back inside `DeviceInner`.
|
||||
- `Image::from_swapchain_raw(image, device, format, extent, usage)` wraps a swapchain-owned raw image without taking image ownership. `Swapchain::acquire_image` passes the swapchain device, configured format/usage, and configured extent with depth 1. Its descriptor remains TYPE_2D, one mip/layer, TYPE_1 samples, OPTIMAL tiling, PRESENT queue flags, UNDEFINED layout, and GpuOnly memory. Do not destroy that image as an independently allocated image.
|
||||
- `ImageView` retains an `Arc<Image>` so its source image outlives the Vulkan view. `Image::create_view` in `renderer_resources` returns `ManuallyDrop<ImageView>`; renderer call sites transfer it deliberately (the swapchain path uses `ManuallyDrop::into_inner`).
|
||||
- For allocated images, `Allocation::Owned` frees allocation memory with its `DeviceObject`; `Allocation::Shared` permits shared ownership and only exposes mutable access when uniquely held; `Unmanaged` carries no allocator allocation.
|
||||
- `Swapchain` owns raw swapchain images and manually created acquire/release semaphores. Its drop path waits idle and destroys those semaphores; the `DeviceObject` destroys the swapchain handle.
|
||||
- `SwapchainImage` / `SwapchainFrame` are `#[must_use]` acquired-frame values. Presentation consumes the frame; preserve its `Arc<Swapchain>` so image ownership stays valid until presentation.
|
||||
- `Surface` uses a `DropGuard` for surface destruction and keeps its `Instance`; field order drops the swapchain lock first, then the guard, then the instance.
|
||||
- `SingleUseCommand` and `CommandBuffer` use `DeviceObject<vk::CommandBuffer>`, whose device-layer destroy implementation is intentionally a no-op. Their own drops free command buffers through their pools. `SingleUseCommandPool` owns a raw mutex-protected pool and destroys it itself.
|
||||
- `CommandList::submit` requires all commands to come from the same queue; `CommandBuffer::submit` checks queue-family compatibility.
|
||||
- `CommandPool` is explicitly `!Sync`; `CommandBuffer` is explicitly `!Send` and `!Sync`. `SingleUseCommand` is explicitly `!Sync`. Preserve these thread-affinity constraints when changing allocation/submission wrappers.
|
||||
- `CommandPool::alloc` currently calls `free_command_buffers()` before allocating, so its free-list is not an enduring reusable buffer cache. `CommandBufferFuture` frees the submitted buffer when the future is dropped.
|
||||
- `SingleUseCommand` begins recording with `ONE_TIME_SUBMIT`; callers must end/submit according to its state and must not access it concurrently (it is `!Sync`).
|
||||
- `CommandPools` is a thread-local registry sharded by thread and queue family; its unsafe `Send` implementation documents an assumption that shards are unused during drop/movement.
|
||||
- `Surface::new_from_raw_window_handle` is unsafe: the caller keeps display/window handles valid while surface use continues.
|
||||
- `Swapchain::release_resources` is an unsafe once-only pre-destruction operation; `Drop` invokes it and currently ignores the result of its internal idle wait.
|
||||
- `Swapchain` uses separate synchronization semaphore lifetimes: acquire semaphores are sized for frames in flight, release semaphores are per swapchain image. Preserve their indexing, explicit destruction, and uniqueness; they are manually managed, not entries in `DevicePools`.
|
||||
- Resource implementation details are owned by `renderer_resources`; `crate::buffers`, `crate::images`, and `crate::pipeline` resolve through crate-private aliases. Do not expose these aliases as renderer public modules.
|
||||
- `Allocation::Shared` only permits `allocation_mut()` through `Arc::get_mut`; callers must not assume shared allocation contents can always be mutated.
|
||||
- `ImageDesc` and `BufferDesc` omit `alloc_scheme` from both `Hash` and `Eq`; `Hash` omits `name`, and `Eq` also omits `name`. Check descriptor-cache assumptions before relying on either as a complete semantic identity.
|
||||
- `Image::new_with_allocation` in `renderer_resources` validates memory size/type but still creates the raw image before validation; failure paths should account for cleaning up handles if this function is revised.
|
||||
- `Surface::configure` replaces the current `Arc<Swapchain>` after creating the new one; in-flight tokens retain old swapchains, so preserve that overlap behavior on reconfiguration.
|
||||
- `Swapchain::acquire_image` moves the potentially blocking acquire call to `smol::unblock`, waits on a pooled fence, and constructs the acquired view before returning the frame.
|
||||
- `Renderer2::draw_graph` acquires a frame, imports its image, resolves/submits the graph with acquire/release semaphores, awaits completion, and then presents.
|
||||
- The active public graph API is `render_graph/legacy.rs`. Other graph siblings are private experimental/incomplete implementation, not a replacement API; avoid relying on TODO scaffolding.
|
||||
- `render_graph/legacy.rs` has partial/unfinished behavior in some paths; inspect the exact pass/resource path being changed.
|
||||
- `GraphResource::simple_hash` is used as an import deduplication key, not a collision-proof identity or equality check. `resolve` appends an output-read pass.
|
||||
- `RenderGraph::resolve` creates a `SingleUseCommandPool` on the device main queue and records pass callbacks into command buffers. The graph's task scheduling remains single-queue in this path.
|
||||
- Some legacy graph commands (`clear_pass`, `present_pass`) are convenience helpers; `present_pass` is deprecated in favor of marking outputs with `Access::present()`.
|
||||
- The newer `render_graph/commands.rs` and `recorder.rs` use additional `ResourceId`/typed resource wrappers, distinct from legacy `GraphResourceId` and `GraphResource`.
|
||||
- The crate root also exposes `Extent2D` and `utils` alongside renderer modules and device re-exports.
|
||||
- `SamplerDesc` implements float-aware equality and hashing with utility helpers; keep those implementations consistent if adding sampler fields.
|
||||
- `Pipeline::new_graphics` supports dynamic rendering state through `RenderingState`; its render-pass and rendering choices are supplied in `GraphicsPipelineDesc`.
|
||||
- `Buffer` and `Image` creation in `renderer_resources` allocates/binds via `DeviceInner.alloc2`; device ownership and final allocation release are handled by the device-layer wrappers.
|
||||
- Resource types live in `renderer_resources`; the `renderer` aliases are crate-private and are not externally reachable as `renderer::buffers::...` etc.
|
||||
- `Renderer2::new` selects an adapter with `None` for the surface, then creates the logical device using its retained display handle. `choose_adapter_default` does not evaluate raw surface compatibility.
|
||||
- `render_graph/legacy.rs` implements the active public graph resolver. Its resources are ordered by `GraphResourceId`, and passes declare reads/writes through `PassDesc` so the resolver can derive dependencies and transitions.
|
||||
|
||||
## Conventions
|
||||
- Nightly crate features in `lib.rs`: `negative_impls`, `map_try_insert`, `debug_closure_helpers`.
|
||||
- Use `renderer`'s re-exported `Device`, `DeviceOwned`, and `DeviceRef` APIs rather than adding a parallel device ownership model.
|
||||
- Do not commit changes. Verify with `cargo check --workspace`; device-layer tests run with `cargo test -p renderer_device`.
|
||||
- Run focused crate checks with `cargo check -p renderer`; use `cargo check --workspace` for changes crossing crate boundaries.
|
||||
- Resource APIs live in the public `renderer_resources` crate. Renderer aliases are deliberately crate-private; keep the renderer-facing API intentional when adding re-exports.
|
||||
|
||||
## Change guidance
|
||||
- Add buffer/image/pipeline resource constructors in `renderer_resources`, using its `renderer_device` re-exports and retaining the correct device/handle owner. Do not add a dependency from `renderer_resources` back to `renderer`.
|
||||
- For every resource, identify whether it owns, shares, or merely borrows Vulkan handles and memory before implementing `Drop` or changing field order.
|
||||
- Keep image-view format, aspect, mip, layer, and view-kind validation in `renderer_resources`; Vulkan view compatibility depends on the backing image description and create flags.
|
||||
- A swapchain image is not an independently destroyable `Image`; retain its swapchain and do not attach a GPU allocation to it.
|
||||
- Keep allocation freeing and Vulkan handle destruction in `renderer_resources` in a safe drop order: image/buffer before bound allocation memory.
|
||||
- Use the correct queue family set in buffer/image descriptions when resources are accessed from more than one queue; `QueueFlags` here is the device-layer selection type.
|
||||
- Command buffer recording and queue submission have separate synchronization requirements. Preserve queue locks and ensure an in-flight command buffer/pool is not freed before completion.
|
||||
- `CommandBufferFuture` owns the submitted command buffer until the future is dropped; callers that discard it early change its lifetime and must ensure GPU work is complete.
|
||||
- Surface/window raw-handle validity is the caller's obligation; retain the originating instance until surface teardown.
|
||||
- `SwapchainImage` and `SwapchainFrame` are must-use ownership tokens; keep presentation consuming the token to prevent duplicate presentation.
|
||||
- Graph callbacks in `PassDesc` capture resources across graph resolution. Preserve their lifetimes and correctly declare resource reads/writes for dependency/barrier generation.
|
||||
- When adding a public graph API, check whether it belongs to the legacy `render_graph` re-export or an unexposed experimental sibling; do not accidentally promise incomplete internals.
|
||||
- The newer typed graph modules have stubs and `todo!()` paths; add tests and complete the actual resolve/record flow before routing runtime rendering through them.
|
||||
- Egui texture IDs map separately between `egui::TextureId` and internal `TextureId`; updates and frees must maintain both maps and texture manager state.
|
||||
- Shader paths are currently relative to the workspace working directory; preserve or intentionally redesign that runtime asset lookup contract.
|
||||
- `renderer_resources::util` owns the float helpers and `F32`; renderer re-exports those names through `renderer::util`. Keep `SamplerDesc` float equality/hash helpers in sync for every field so `SamplerCache` map keys remain valid.
|
||||
- For pipeline changes, account for both legacy render-pass and dynamic-rendering descriptions and the shared `device.pools.pipeline_cache.raw` handle.
|
||||
- `util::DropGuard` invokes its closure at drop; do not allow a closure to outlive raw Vulkan dependencies it captures.
|
||||
- The `egui` pre-pass currently packs texture updates into a staging image/buffer and registers graph work; texture-free deltas and upload ordering deserve explicit coverage when changing the integration.
|
||||
- The private font-atlas image is CPU memory, not a Vulkan `images::Image`; do not conflate the similarly named `Image` types when editing `text.rs`.
|
||||
- `weak_vec` moved to `renderer_resources::util` and is not re-exported by `renderer`; it prunes dead weak references on capacity pressure. Image-side view tracking should remain weak to avoid ownership cycles.
|
||||
- Render graph IDs are distinct: legacy `GraphResourceId` wraps `u32`; the newer internal `resources::ResourceId` wraps `u64`.
|
||||
- Public `commands::traits::CommandBufferExt` contains raw recording convenience methods; those methods assume valid command-buffer state and do not all enforce it at runtime.
|
||||
- `SwapchainConfiguration.image_count` is treated as the additional desired in-flight count during validation, while creation asks for minimum surface image count plus that configured value. The fallback-configuration helper assigns a total count.
|
||||
- The supported surface capability extension query can filter present modes based on the selected mode; its behavior depends on optional `InstanceExtensions` support.
|
||||
- `rendering::Wireframe` allocates GPU buffers and submits a one-shot upload before returning, then records its rendering pass through the legacy graph.
|
||||
|
|
@ -4,42 +4,44 @@ version = "0.1.0"
|
|||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
renderer_device = { path = "../renderer_device" }
|
||||
renderer_resources = { path = "../renderer_resources" }
|
||||
renderer_macros = { path = "renderer_macros" }
|
||||
tinyvec = { workspace = true }
|
||||
arrayvec = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
petgraph = { workspace = true }
|
||||
# itertools = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
ahash = { workspace = true }
|
||||
# rand = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
glam = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
thread_local = {workspace = true}
|
||||
|
||||
thiserror = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
ash = { workspace = true }
|
||||
ash-window = { workspace = true }
|
||||
# vk-mem = { workspace = true }
|
||||
vk-mem = { workspace = true }
|
||||
gpu-allocator = { workspace = true }
|
||||
rectangle-pack = { workspace = true }
|
||||
|
||||
md-5 = { workspace = true }
|
||||
|
||||
raw-window-handle = { workspace = true }
|
||||
egui = { workspace = true , features = ["bytemuck"]}
|
||||
# egui_winit_platform = { workspace = true }
|
||||
egui_winit_platform = { workspace = true }
|
||||
|
||||
# futures = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
smol = { workspace = true }
|
||||
# tokio = {workspace = true, features = ["rt", "sync"]}
|
||||
|
||||
# bevy_tasks = { workspace = true }
|
||||
bevy_tasks = { workspace = true }
|
||||
|
||||
derive_more = { workspace = true , features = ["debug"] }
|
||||
|
||||
cfg-if = "1.0.0"
|
||||
dyn-clone = "1"
|
||||
crossbeam = "0.8.4"
|
||||
bytemuck = { version = "1.21.0", features = ["derive"] }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
[package]
|
||||
name = "renderer_macros"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = { version = "2", features = ["full"] }
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, Data, DeriveInput, Fields};
|
||||
|
||||
#[proc_macro_derive(DeviceRef, attributes(device))]
|
||||
pub fn derive_device_ref(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
let field = match &input.data {
|
||||
Data::Struct(data) => match &data.fields {
|
||||
Fields::Named(fields) => {
|
||||
let marked_fields = fields
|
||||
.named.iter()
|
||||
.filter(|field| {
|
||||
field
|
||||
.attrs
|
||||
.iter()
|
||||
.any(|attr| attr.path().is_ident("device"))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let field = match marked_fields.as_slice() {
|
||||
[] => {
|
||||
return syn::Error::new_spanned(
|
||||
&input.ident,
|
||||
"no field marked with #[device]",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
[field] => *field,
|
||||
duplicates => {
|
||||
let mut errors = duplicates.iter().skip(1).map(|field| {
|
||||
let name = field.ident.as_ref().expect("named fields have identifiers");
|
||||
syn::Error::new_spanned(
|
||||
name,
|
||||
format!("duplicate #[device] field `{name}`"),
|
||||
)
|
||||
});
|
||||
let mut error = errors.next().expect("at least two marked fields");
|
||||
for duplicate in errors {
|
||||
error.combine(duplicate);
|
||||
}
|
||||
return error.to_compile_error().into();
|
||||
}
|
||||
};
|
||||
|
||||
field
|
||||
}
|
||||
Fields::Unnamed(fields) => {
|
||||
let marked_fields = fields
|
||||
.unnamed.iter()
|
||||
.filter(|field| {
|
||||
field
|
||||
.attrs
|
||||
.iter()
|
||||
.any(|attr| attr.path().is_ident("device"))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let field = match marked_fields.as_slice() {
|
||||
[] => {
|
||||
return syn::Error::new_spanned(
|
||||
&input.ident,
|
||||
"no field marked with #[device]",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
[field] => *field,
|
||||
duplicates => {
|
||||
let mut errors = duplicates.iter().skip(1).map(|field| {
|
||||
let name = field.ident.as_ref().expect("named fields have identifiers");
|
||||
syn::Error::new_spanned(
|
||||
name,
|
||||
format!("duplicate #[device] field `{name}`"),
|
||||
)
|
||||
});
|
||||
let mut error = errors.next().expect("at least two marked fields");
|
||||
for duplicate in errors {
|
||||
error.combine(duplicate);
|
||||
}
|
||||
return error.to_compile_error().into();
|
||||
}
|
||||
};
|
||||
|
||||
field
|
||||
}
|
||||
_ => {
|
||||
return syn::Error::new_spanned(
|
||||
&input.ident,
|
||||
"DeviceRef can only be derived for structs with named fields",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
},
|
||||
Data::Enum(_) | Data::Union(_) => {
|
||||
return syn::Error::new_spanned(
|
||||
&input.ident,
|
||||
"DeviceRef can only be derived for structs with named fields",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
};
|
||||
|
||||
let field_ident = field.ident.as_ref().expect("named fields have identifiers");
|
||||
let ident = &input.ident;
|
||||
let generics = input.generics;
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
|
||||
quote! {
|
||||
impl #impl_generics ::renderer_device::device::DeviceRef for #ident #ty_generics #where_clause {
|
||||
fn device(&self) -> &::renderer_device::device::Device {
|
||||
::renderer_device::device::DeviceRef::device(&self.#field_ident)
|
||||
}
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ use std::borrow::Cow;
|
|||
|
||||
use ash::vk;
|
||||
use gpu_allocator::vulkan::AllocationScheme;
|
||||
use renderer_device::queue::QueueFamilyIndices;
|
||||
|
||||
use crate::{
|
||||
Device,
|
||||
|
|
@ -50,6 +49,7 @@ impl PartialEq for BufferDesc {
|
|||
fn eq(&self, other: &Self) -> bool {
|
||||
self.flags == other.flags
|
||||
// for hashmaps, `Eq` may be more strict than `Hash`
|
||||
&& self.name == other.name
|
||||
&& self.size == other.size
|
||||
&& self.usage == other.usage
|
||||
&& self.queue_families == other.queue_families
|
||||
|
|
@ -133,7 +133,7 @@ impl Buffer {
|
|||
let create_info = vk::BufferCreateInfo::default()
|
||||
.size(desc.size)
|
||||
.usage(desc.usage)
|
||||
.queue_family_indices(queue_families.as_u32_slice())
|
||||
.queue_family_indices(&queue_families)
|
||||
.sharing_mode(sharing_mode);
|
||||
|
||||
let buffer = unsafe { device.dev().create_buffer(&create_info, None)? };
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
use std::sync::{Arc, atomic::AtomicU8};
|
||||
|
||||
use crate::{
|
||||
device::{DeviceObject, DeviceOwned},
|
||||
define_device_owned_handle,
|
||||
device::DeviceOwned,
|
||||
images::{Image, QueueOwnership},
|
||||
pipeline::{Pipeline, PipelineLayout},
|
||||
sync::{self, FenceFuture},
|
||||
|
|
@ -32,7 +33,7 @@ impl Drop for SingleUseCommandPool {
|
|||
impl SingleUseCommandPool {
|
||||
pub fn new(device: Device, queue: Queue) -> VkResult<Arc<Self>> {
|
||||
let pool_info = vk::CommandPoolCreateInfo::default()
|
||||
.queue_family_index(queue.family_index().index())
|
||||
.queue_family_index(queue.family_index())
|
||||
.flags(vk::CommandPoolCreateFlags::TRANSIENT);
|
||||
|
||||
let pool = unsafe { device.dev().create_command_pool(&pool_info, None)? };
|
||||
|
|
@ -173,32 +174,15 @@ impl CommandBufferState2 {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SingleUseCommand {
|
||||
inner: DeviceObject<vk::CommandBuffer>,
|
||||
pool: Arc<SingleUseCommandPool>,
|
||||
state: CommandBufferState2,
|
||||
}
|
||||
|
||||
impl DeviceOwned<vk::CommandBuffer> for SingleUseCommand {
|
||||
fn device(&self) -> &Device {
|
||||
self.inner.device()
|
||||
}
|
||||
|
||||
fn handle(&self) -> vk::CommandBuffer {
|
||||
*self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SingleUseCommand {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
self.pool.pool.with_locked(|&pool| {
|
||||
self.device()
|
||||
.dev()
|
||||
.free_command_buffers(pool, &[self.handle()])
|
||||
})
|
||||
}
|
||||
define_device_owned_handle! {
|
||||
#[derive(Debug)]
|
||||
pub SingleUseCommand(vk::CommandBuffer) {
|
||||
pool: Arc<SingleUseCommandPool>,
|
||||
state: CommandBufferState2,
|
||||
} => |this| unsafe {
|
||||
this.pool
|
||||
.pool
|
||||
.with_locked(|&pool| this.device().dev().free_command_buffers(pool, &[this.handle()]))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -224,15 +208,7 @@ impl SingleUseCommand {
|
|||
buffer
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
inner: DeviceObject::new_debug_named(
|
||||
device,
|
||||
buffer,
|
||||
None::<std::borrow::Cow<'static, str>>,
|
||||
),
|
||||
pool,
|
||||
state: CommandBufferState2::recording(),
|
||||
})
|
||||
Self::construct(device, buffer, None, pool, CommandBufferState2::recording())
|
||||
}
|
||||
|
||||
pub fn state(&self) -> CommandBufferState {
|
||||
|
|
@ -247,7 +223,7 @@ impl SingleUseCommand {
|
|||
pub fn end(&self) -> VkResult<()> {
|
||||
assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
unsafe {
|
||||
self.device().dev().end_command_buffer(self.handle())?;
|
||||
self.inner.dev().dev().end_command_buffer(self.handle())?;
|
||||
}
|
||||
self.state.set_executable();
|
||||
Ok(())
|
||||
|
|
@ -312,7 +288,7 @@ impl SingleUseCommand {
|
|||
wait: Option<(vk::Semaphore, vk::PipelineStageFlags)>,
|
||||
signal: Option<vk::Semaphore>,
|
||||
) -> crate::Result<()> {
|
||||
let fence = Arc::new(sync::Fence::from_pool(self.device(), None)?);
|
||||
let fence = Arc::new(sync::Fence::from_pool(&self.device().pools.fences, None)?);
|
||||
let future = self.submit_async(wait, signal, fence)?;
|
||||
future.block()?;
|
||||
Ok(())
|
||||
|
|
@ -662,12 +638,12 @@ mod command_pools {
|
|||
use arrayvec::ArrayVec;
|
||||
use ash::{prelude::VkResult, vk};
|
||||
use parking_lot::Mutex;
|
||||
use renderer_device::queue::QueueFamilyIndex;
|
||||
use smol::future::FutureExt;
|
||||
use thread_local::ThreadLocal;
|
||||
|
||||
use crate::{
|
||||
device::{Device, DeviceObject, DeviceOwned},
|
||||
define_device_owned_handle,
|
||||
device::{Device, DeviceOwned},
|
||||
queue::Queue,
|
||||
sync,
|
||||
util::MutexExt,
|
||||
|
|
@ -683,19 +659,13 @@ mod command_pools {
|
|||
InvalidQueueSubmission,
|
||||
}
|
||||
|
||||
pub struct CommandPool {
|
||||
inner: DeviceObject<vk::CommandPool>,
|
||||
family: QueueFamilyIndex,
|
||||
free_list: Mutex<Cell<(Vec<vk::CommandBufferLevel>, Vec<vk::CommandBuffer>)>>,
|
||||
}
|
||||
|
||||
impl DeviceOwned<vk::CommandPool> for CommandPool {
|
||||
fn device(&self) -> &Device {
|
||||
self.inner.device()
|
||||
}
|
||||
|
||||
fn handle(&self) -> vk::CommandPool {
|
||||
*self.inner
|
||||
define_device_owned_handle! {
|
||||
#[derive()]
|
||||
pub CommandPool(vk::CommandPool) {
|
||||
family: u32,
|
||||
free_list: Mutex<Cell<(Vec<vk::CommandBufferLevel>, Vec<vk::CommandBuffer>)>>,
|
||||
} => |this| unsafe {
|
||||
this.device().dev().destroy_command_pool(this.handle(), None);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -704,21 +674,17 @@ mod command_pools {
|
|||
impl CommandPool {
|
||||
pub fn new(
|
||||
dev: Device,
|
||||
family: QueueFamilyIndex,
|
||||
family: u32,
|
||||
flags: vk::CommandPoolCreateFlags,
|
||||
name: Option<Cow<'static, str>>,
|
||||
) -> Result<CommandPool, vk::Result> {
|
||||
let pool_info = vk::CommandPoolCreateInfo::default()
|
||||
.queue_family_index(family.index())
|
||||
.queue_family_index(family)
|
||||
.flags(flags);
|
||||
|
||||
let pool = unsafe { dev.dev().create_command_pool(&pool_info, None)? };
|
||||
|
||||
Ok(Self {
|
||||
inner: DeviceObject::new_debug_named(dev, pool, name),
|
||||
family,
|
||||
free_list: Mutex::new(Default::default()),
|
||||
})
|
||||
Self::construct(dev, pool, name, family, Mutex::new(Default::default()))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
|
@ -795,11 +761,13 @@ mod command_pools {
|
|||
.begin_command_buffer(handle, &begin_info)?;
|
||||
}
|
||||
|
||||
Ok(CommandBuffer {
|
||||
inner: DeviceObject::new_debug_named(self.device().clone(), handle, name),
|
||||
pool: self.clone(),
|
||||
Ok(CommandBuffer::construct(
|
||||
self.device().clone(),
|
||||
handle,
|
||||
name,
|
||||
self.clone(),
|
||||
level,
|
||||
})
|
||||
)?)
|
||||
}
|
||||
|
||||
pub fn alloc_primary(
|
||||
|
|
@ -827,7 +795,7 @@ mod command_pools {
|
|||
}
|
||||
}
|
||||
|
||||
fn find_or_default(&mut self, dev: &Device, family: QueueFamilyIndex) -> Arc<CommandPool> {
|
||||
fn find_or_default(&mut self, dev: &Device, family: u32) -> Arc<CommandPool> {
|
||||
match self
|
||||
.per_family_pools
|
||||
.binary_search_by_key(&family, |entry| entry.0)
|
||||
|
|
@ -861,7 +829,7 @@ mod command_pools {
|
|||
|
||||
#[derive(Debug)]
|
||||
struct CommandPoolsShard {
|
||||
per_family_pools: ArrayVec<(QueueFamilyIndex, Arc<CommandPool>), 8>,
|
||||
per_family_pools: ArrayVec<(u32, Arc<CommandPool>), 8>,
|
||||
}
|
||||
|
||||
pub struct CommandPools {
|
||||
|
|
@ -870,7 +838,7 @@ mod command_pools {
|
|||
}
|
||||
|
||||
impl CommandPools {
|
||||
pub fn get_for_family(&self, family: QueueFamilyIndex) -> Arc<CommandPool> {
|
||||
pub fn get_for_family(&self, family: u32) -> Arc<CommandPool> {
|
||||
let shard = self
|
||||
.shards
|
||||
.get_or(|| core::cell::Cell::new(CommandPoolsShard::new()).into());
|
||||
|
|
@ -884,7 +852,7 @@ mod command_pools {
|
|||
|
||||
pub fn allocate_buffer(
|
||||
&self,
|
||||
family: QueueFamilyIndex,
|
||||
family: u32,
|
||||
name: Option<Cow<'static, str>>,
|
||||
level: vk::CommandBufferLevel,
|
||||
flags: vk::CommandBufferUsageFlags,
|
||||
|
|
@ -893,31 +861,14 @@ mod command_pools {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CommandBuffer {
|
||||
inner: DeviceObject<vk::CommandBuffer>,
|
||||
pool: Arc<CommandPool>,
|
||||
#[allow(unused)]
|
||||
level: vk::CommandBufferLevel,
|
||||
}
|
||||
|
||||
impl DeviceOwned<vk::CommandBuffer> for CommandBuffer {
|
||||
fn device(&self) -> &Device {
|
||||
self.inner.device()
|
||||
}
|
||||
|
||||
fn handle(&self) -> vk::CommandBuffer {
|
||||
*self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CommandBuffer {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
self.device()
|
||||
.dev()
|
||||
.free_command_buffers(self.pool.handle(), &[self.handle()]);
|
||||
}
|
||||
define_device_owned_handle! {
|
||||
#[derive(Debug)]
|
||||
pub CommandBuffer(vk::CommandBuffer) {
|
||||
pool: Arc<CommandPool>,
|
||||
#[allow(unused)]
|
||||
level: vk::CommandBufferLevel,
|
||||
} => |this| unsafe {
|
||||
this.device().dev().free_command_buffers(this.pool.handle(), &[this.handle()]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,78 +3,21 @@ use std::{
|
|||
collections::{BTreeSet, HashMap, HashSet},
|
||||
ffi::CStr,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::{Arc, Weak},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use ash::{
|
||||
ext, khr,
|
||||
prelude::VkResult,
|
||||
vk,
|
||||
vk::{self, Handle},
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use raw_window_handle::RawDisplayHandle;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct DebugName {
|
||||
#[cfg(debug_assertions)]
|
||||
name: Cow<'static, str>,
|
||||
}
|
||||
|
||||
impl core::fmt::Display for DebugName {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &**self)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for DebugName {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &**self)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Deref for DebugName {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
#[cfg(debug_assertions)]
|
||||
return &self.name;
|
||||
#[cfg(not(debug_assertions))]
|
||||
return "FEATURE_DISABLED";
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Cow<'static, str>> for DebugName {
|
||||
fn from(_value: Cow<'static, str>) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
name: _value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for DebugName {
|
||||
fn from(_value: &'static str) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
name: Cow::Borrowed(_value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for DebugName {
|
||||
fn from(_value: String) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
name: Cow::Owned(_value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
use crate::{
|
||||
Instance, PhysicalDeviceFeatures, PhysicalDeviceInfo, Result,
|
||||
device::asdf::traits::ExternallyManagedObject,
|
||||
pipeline_cache::PipelineCache,
|
||||
pipeline::pipeline_cache::PipelineCache,
|
||||
queue::{DeviceQueueInfos, DeviceQueues, Queue},
|
||||
sync::{self, BinarySemaphore, TimelineSemaphore},
|
||||
};
|
||||
|
|
@ -93,25 +36,21 @@ bitflags::bitflags! {
|
|||
}
|
||||
}
|
||||
|
||||
struct DeviceDrop {
|
||||
device: ash::Device,
|
||||
pipeline_cache: PipelineCache,
|
||||
}
|
||||
struct DeviceDrop(ash::Device);
|
||||
impl Drop for DeviceDrop {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
_ = self.device.device_wait_idle();
|
||||
self.pipeline_cache.destroy(&self.device);
|
||||
self.device.destroy_device(None);
|
||||
_ = self.0.device_wait_idle();
|
||||
self.0.destroy_device(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DeviceExtensions {
|
||||
pub debug_utils: ext::debug_utils::Device,
|
||||
pub swapchain: Option<khr::swapchain::Device>,
|
||||
pub(crate) struct DeviceExtensions {
|
||||
pub(crate) debug_utils: ext::debug_utils::Device,
|
||||
pub(crate) swapchain: Option<khr::swapchain::Device>,
|
||||
#[allow(dead_code)]
|
||||
pub mesh_shader: Option<ext::mesh_shader::Device>,
|
||||
pub(crate) mesh_shader: Option<ext::mesh_shader::Device>,
|
||||
}
|
||||
|
||||
type GpuAllocation = gpu_allocator::vulkan::Allocation;
|
||||
|
|
@ -136,7 +75,7 @@ pub enum Allocation {
|
|||
}
|
||||
|
||||
impl Allocation {
|
||||
pub fn allocation(&self) -> Option<&GpuAllocation> {
|
||||
pub(crate) fn allocation(&self) -> Option<&GpuAllocation> {
|
||||
match self {
|
||||
Allocation::Owned(obj) => Some(obj),
|
||||
Allocation::Shared(arc) => Some(arc.as_ref()),
|
||||
|
|
@ -144,7 +83,7 @@ impl Allocation {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn allocation_mut(&mut self) -> Option<&mut GpuAllocation> {
|
||||
pub(crate) fn allocation_mut(&mut self) -> Option<&mut GpuAllocation> {
|
||||
match self {
|
||||
Allocation::Owned(obj) => Some(obj),
|
||||
Allocation::Shared(arc) => Arc::get_mut(arc).map(DerefMut::deref_mut),
|
||||
|
|
@ -154,16 +93,15 @@ impl Allocation {
|
|||
}
|
||||
|
||||
pub struct DeviceInner {
|
||||
pub alloc2: Mutex<gpu_allocator::vulkan::Allocator>,
|
||||
pub raw: ash::Device,
|
||||
pub adapter: PhysicalDeviceInfo,
|
||||
pub instance: Instance,
|
||||
pub queues: DeviceQueues,
|
||||
pub sync_threadpool: sync::SyncThreadpool,
|
||||
pub device_extensions: DeviceExtensions,
|
||||
pub pools: DevicePools,
|
||||
pub(crate) alloc2: Mutex<gpu_allocator::vulkan::Allocator>,
|
||||
pub(crate) raw: ash::Device,
|
||||
pub(crate) adapter: PhysicalDeviceInfo,
|
||||
pub(crate) instance: Instance,
|
||||
pub(crate) queues: DeviceQueues,
|
||||
pub(crate) sync_threadpool: sync::SyncThreadpool,
|
||||
pub(crate) device_extensions: DeviceExtensions,
|
||||
#[allow(dead_code)]
|
||||
pub enabled_extensions: Vec<&'static CStr>,
|
||||
pub(crate) enabled_extensions: Vec<&'static CStr>,
|
||||
|
||||
_drop: DeviceDrop,
|
||||
}
|
||||
|
|
@ -212,7 +150,7 @@ impl<'a> std::hash::Hash for Extension<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_available_extensions(
|
||||
pub(crate) fn get_available_extensions(
|
||||
entry: &ash::Entry,
|
||||
layers: &[&CStr],
|
||||
) -> Result<Vec<ash::vk::ExtensionProperties>> {
|
||||
|
|
@ -232,7 +170,7 @@ pub fn get_available_extensions(
|
|||
}
|
||||
|
||||
/// returns a tuple of supported-or-enabled extensions and unsupported-and-requested extensions
|
||||
pub fn get_extensions<'a>(
|
||||
pub(crate) fn get_extensions<'a>(
|
||||
entry: &ash::Entry,
|
||||
layers: &[&'a CStr],
|
||||
mut extensions: Vec<Extension<'a>>,
|
||||
|
|
@ -350,7 +288,7 @@ pub fn get_extensions<'a>(
|
|||
}
|
||||
|
||||
/// returns a list of enabled, or a tuple of enabled and unsupported but requested layers.
|
||||
pub fn get_layers<'a>(
|
||||
pub(crate) fn get_layers<'a>(
|
||||
entry: &ash::Entry,
|
||||
wants_layers: Vec<&'a CStr>,
|
||||
) -> core::result::Result<Vec<&'a CStr>, (Vec<&'a CStr>, Vec<&'a CStr>)> {
|
||||
|
|
@ -455,8 +393,7 @@ impl PhysicalDeviceInfo {
|
|||
},
|
||||
})?;
|
||||
|
||||
let pipeline_cache = PipelineCache::new(&device, &self).unwrap();
|
||||
let shared = Arc::new_cyclic(|inner: &Weak<DeviceInner>| DeviceInner {
|
||||
let inner = DeviceInner {
|
||||
raw: device.clone(),
|
||||
alloc2: Mutex::new(alloc2),
|
||||
instance: instance.clone(),
|
||||
|
|
@ -465,13 +402,14 @@ impl PhysicalDeviceInfo {
|
|||
device_extensions,
|
||||
enabled_extensions,
|
||||
sync_threadpool: sync::SyncThreadpool::new(),
|
||||
pools: DevicePools::new(inner.clone(), pipeline_cache),
|
||||
_drop: DeviceDrop {
|
||||
device,
|
||||
pipeline_cache,
|
||||
},
|
||||
});
|
||||
Ok(Device(shared))
|
||||
_drop: DeviceDrop(device),
|
||||
};
|
||||
|
||||
let shared = Arc::new(inner);
|
||||
Ok(Device {
|
||||
pools: Arc::new(DevicePools::new(shared.clone())),
|
||||
shared,
|
||||
})
|
||||
}
|
||||
|
||||
fn required_extensions(&self, requested_extensions: &[Extension<'static>]) -> Vec<*const i8> {
|
||||
|
|
@ -500,58 +438,42 @@ impl PhysicalDeviceInfo {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DevicePools {
|
||||
pub pipeline_cache: PipelineCache,
|
||||
pub fences: Pool<vk::Fence>,
|
||||
pub binary_semaphores: Pool<BinarySemaphore>,
|
||||
pub timeline_semaphores: Pool<TimelineSemaphore>,
|
||||
pub(crate) struct DevicePools {
|
||||
pub(crate) pipeline_cache: asdf::DeviceObject<PipelineCache, Arc<DeviceInner>>,
|
||||
pub(crate) fences: Arc<Pool<vk::Fence>>,
|
||||
pub(crate) binary_semaphores: Pool<BinarySemaphore>,
|
||||
pub(crate) timeline_semaphores: Pool<TimelineSemaphore>,
|
||||
}
|
||||
|
||||
impl AsRef<DevicePools> for DevicePools {
|
||||
fn as_ref(&self) -> &DevicePools {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl DevicePools {
|
||||
fn new(device: Weak<DeviceInner>, pipeline_cache: PipelineCache) -> Self {
|
||||
pub fn new(device: Arc<DeviceInner>) -> Self {
|
||||
Self {
|
||||
fences: Pool::new(device.clone()),
|
||||
fences: Arc::new(Pool::new(device.clone())),
|
||||
binary_semaphores: Pool::new(device.clone()),
|
||||
timeline_semaphores: Pool::new(device.clone()),
|
||||
pipeline_cache,
|
||||
pipeline_cache: asdf::DeviceObject::new(
|
||||
device.clone(),
|
||||
PipelineCache::new(&device.raw, &device.adapter).unwrap(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DeviceRef {
|
||||
fn device(&self) -> &Device;
|
||||
}
|
||||
|
||||
impl DeviceRef for Device {
|
||||
fn device(&self) -> &Device {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceRef for Arc<Device> {
|
||||
fn device(&self) -> &Device {
|
||||
self.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceRef for &Device {
|
||||
fn device(&self) -> &Device {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceRef for &Arc<Device> {
|
||||
fn device(&self) -> &Device {
|
||||
self.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Device(Arc<DeviceInner>);
|
||||
pub struct Device {
|
||||
pub(crate) shared: Arc<DeviceInner>,
|
||||
pub(crate) pools: Arc<DevicePools>,
|
||||
}
|
||||
|
||||
impl PartialEq for Device {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Arc::ptr_eq(&self.0, &other.0)
|
||||
Arc::ptr_eq(&self.shared, &other.shared)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -561,7 +483,7 @@ impl core::ops::Deref for Device {
|
|||
type Target = DeviceInner;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
&self.shared
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -594,7 +516,7 @@ impl DeviceInner {
|
|||
pub fn features(&self) -> &crate::PhysicalDeviceFeatures {
|
||||
&self.adapter.features
|
||||
}
|
||||
pub fn properties(&self) -> &crate::PhysicalDeviceProperties {
|
||||
pub(crate) fn properties(&self) -> &crate::PhysicalDeviceProperties {
|
||||
&self.adapter.properties
|
||||
}
|
||||
pub fn physical_device(&self) -> &PhysicalDeviceInfo {
|
||||
|
|
@ -703,7 +625,75 @@ impl DeviceInner {
|
|||
}
|
||||
}
|
||||
|
||||
pub use asdf::DeviceObject;
|
||||
#[derive(Clone)]
|
||||
pub struct DeviceOwnedDebugObject<T> {
|
||||
pub(crate) device: Device,
|
||||
pub(crate) object: T,
|
||||
#[cfg(debug_assertions)]
|
||||
name: Option<Cow<'static, str>>,
|
||||
}
|
||||
|
||||
impl<T: Eq> Eq for DeviceOwnedDebugObject<T> {}
|
||||
impl<T: PartialEq> PartialEq for DeviceOwnedDebugObject<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
std::sync::Arc::ptr_eq(&self.device.shared, &other.device.shared)
|
||||
&& self.object == other.object
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: std::fmt::Debug + vk::Handle + Copy> std::fmt::Debug for DeviceOwnedDebugObject<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut fmt = f.debug_struct(core::any::type_name::<T>());
|
||||
|
||||
fmt.field_with("device", |f| {
|
||||
write!(f, "0x{:x}", self.device.raw.handle().as_raw())
|
||||
})
|
||||
.field_with("handle", |f| write!(f, "0x{:x}", &self.object.as_raw()));
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
fmt.field("name", &self.name);
|
||||
}
|
||||
|
||||
fmt.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DeviceOwnedDebugObject<T> {
|
||||
pub fn new(
|
||||
device: crate::Device,
|
||||
object: T,
|
||||
name: Option<Cow<'static, str>>,
|
||||
) -> ash::prelude::VkResult<Self>
|
||||
where
|
||||
T: vk::Handle + Copy,
|
||||
{
|
||||
if let Some(name) = name.as_ref() {
|
||||
unsafe {
|
||||
device.debug_name_object(object, name);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
device,
|
||||
object,
|
||||
#[cfg(debug_assertions)]
|
||||
name,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dev(&self) -> &crate::Device {
|
||||
&self.device
|
||||
}
|
||||
pub fn handle(&self) -> T
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
self.object
|
||||
}
|
||||
}
|
||||
|
||||
pub use asdf::{DeviceObject, InnerDeviceObject};
|
||||
|
||||
pub trait DeviceHandle {
|
||||
/// # Safety
|
||||
|
|
@ -718,68 +708,18 @@ impl<O, T: ExternallyManagedObject<O>> ExternallyManagedObject<O> for Mutex<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl ExternallyManagedObject<Device> for vk::Buffer {
|
||||
unsafe fn destroy(self, device: &Device) {
|
||||
unsafe { device.raw.destroy_buffer(self, None) };
|
||||
}
|
||||
}
|
||||
impl ExternallyManagedObject<Device> for vk::Image {
|
||||
unsafe fn destroy(self, device: &Device) {
|
||||
unsafe { device.raw.destroy_image(self, None) };
|
||||
}
|
||||
}
|
||||
impl ExternallyManagedObject<Device> for vk::ImageView {
|
||||
unsafe fn destroy(self, device: &Device) {
|
||||
unsafe { device.raw.destroy_image_view(self, None) };
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceHandle for vk::DescriptorPool {
|
||||
unsafe fn destroy(&mut self, device: &Device) {
|
||||
unsafe { device.dev().destroy_descriptor_pool(*self, None) };
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternallyManagedObject<Device> for vk::DescriptorPool {
|
||||
unsafe fn destroy(self, device: &Device) {
|
||||
unsafe { device.raw.destroy_descriptor_pool(self, None) };
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternallyManagedObject<Device> for vk::DescriptorSetLayout {
|
||||
unsafe fn destroy(self, device: &Device) {
|
||||
unsafe { device.raw.destroy_descriptor_set_layout(self, None) };
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternallyManagedObject<Device> for vk::PipelineLayout {
|
||||
unsafe fn destroy(self, device: &Device) {
|
||||
unsafe { device.raw.destroy_pipeline_layout(self, None) };
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternallyManagedObject<Device> for vk::Sampler {
|
||||
unsafe fn destroy(self, device: &Device) {
|
||||
unsafe { device.raw.destroy_sampler(self, None) };
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternallyManagedObject<Device> for vk::ShaderModule {
|
||||
unsafe fn destroy(self, device: &Device) {
|
||||
unsafe { device.raw.destroy_shader_module(self, None) };
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternallyManagedObject<Device> for vk::Pipeline {
|
||||
unsafe fn destroy(self, device: &Device) {
|
||||
unsafe { device.raw.destroy_pipeline(self, None) };
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternallyManagedObject<Device> for vk::SwapchainKHR {
|
||||
unsafe fn destroy(self, device: &Device) {
|
||||
impl<T: AsRef<DeviceInner>> ExternallyManagedObject<T> for vk::Buffer {
|
||||
unsafe fn destroy(self, device: &T) {
|
||||
unsafe {
|
||||
if let Some(swapchain) = device.device_extensions.swapchain.as_ref() {
|
||||
device.as_ref().raw.destroy_buffer(self, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<DeviceInner>> ExternallyManagedObject<T> for vk::SwapchainKHR {
|
||||
unsafe fn destroy(self, device: &T) {
|
||||
unsafe {
|
||||
if let Some(swapchain) = device.as_ref().device_extensions.swapchain.as_ref() {
|
||||
swapchain.destroy_swapchain(self, None)
|
||||
}
|
||||
}
|
||||
|
|
@ -795,22 +735,17 @@ pub trait Pooled: Sized {
|
|||
fn create_from_pool(pool: &Pool<Self>) -> Result<Self>;
|
||||
}
|
||||
|
||||
/// A pooled handle which knows how to return itself to its pool.
|
||||
pub trait Recycle: Sized {
|
||||
fn recycle(self, device: &Device);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Pool<T> {
|
||||
pub pool: Mutex<Vec<T>>,
|
||||
device: Weak<DeviceInner>,
|
||||
pub(crate) pool: Mutex<Vec<T>>,
|
||||
pub(crate) device: Arc<DeviceInner>,
|
||||
}
|
||||
|
||||
impl<T> Pool<T> {
|
||||
pub fn push(&self, item: T) {
|
||||
self.pool.lock().push(item);
|
||||
}
|
||||
pub fn new(device: Weak<DeviceInner>) -> Self {
|
||||
pub fn new(device: Arc<DeviceInner>) -> Self {
|
||||
Self {
|
||||
pool: Mutex::new(Vec::new()),
|
||||
device,
|
||||
|
|
@ -819,10 +754,6 @@ impl<T> Pool<T> {
|
|||
pub fn pop(&self) -> Option<T> {
|
||||
self.pool.lock().pop()
|
||||
}
|
||||
|
||||
pub fn device(&self) -> Arc<DeviceInner> {
|
||||
self.device.upgrade().expect("pool outlived its device")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<Pool<T>> for Pool<T> {
|
||||
|
|
@ -831,43 +762,7 @@ impl<T> AsRef<Pool<T>> for Pool<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct PoolObject<T: Recycle> {
|
||||
inner: std::mem::ManuallyDrop<T>,
|
||||
device: Device,
|
||||
}
|
||||
|
||||
impl<T: Recycle> PoolObject<T> {
|
||||
pub fn new(inner: T, device: Device) -> Self {
|
||||
Self {
|
||||
inner: std::mem::ManuallyDrop::new(inner),
|
||||
device,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn device(&self) -> &Device {
|
||||
&self.device
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Recycle> Deref for PoolObject<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Recycle> DerefMut for PoolObject<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Recycle> Drop for PoolObject<T> {
|
||||
fn drop(&mut self) {
|
||||
let inner = unsafe { std::mem::ManuallyDrop::take(&mut self.inner) };
|
||||
inner.recycle(&self.device);
|
||||
}
|
||||
}
|
||||
pub type PoolObject<T, U = Arc<Pool<T>>> = asdf::ExternallyManagedObject<T, U>;
|
||||
|
||||
impl<T: Pooled> Pool<T> {
|
||||
pub fn get(&self) -> Result<T> {
|
||||
|
|
@ -889,33 +784,90 @@ impl<T: Pooled> Pool<T> {
|
|||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let name = name.map(Into::into).unwrap_or_default();
|
||||
let device = self.device();
|
||||
<T as asdf::traits::DebugNameable>::debug_name(&obj, &device, &name);
|
||||
<T as asdf::traits::DebugNameable>::debug_name(&obj, &self.device, &name);
|
||||
}
|
||||
|
||||
Ok(obj)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod asdf {
|
||||
// Macro for helping create and destroy Vulkan objects which are owned by a device.
|
||||
#[macro_export]
|
||||
macro_rules! define_device_owned_handle {
|
||||
($(#[$attr:meta])*
|
||||
$ty_vis:vis $ty:ident($handle:ty) {
|
||||
$($(#[$field_attr:meta])* $field_vis:vis $field_name:ident : $field_ty:ty),*
|
||||
$(,)?
|
||||
} $(=> |$this:ident| $dtor:stmt)?) => {
|
||||
$(#[$attr])*
|
||||
$ty_vis struct $ty {
|
||||
inner: $crate::device::DeviceOwnedDebugObject<$handle>,
|
||||
$(
|
||||
$(#[$field_attr])*
|
||||
$field_vis $field_name: $field_ty,
|
||||
)*
|
||||
}
|
||||
|
||||
impl $crate::device::DeviceOwned<$handle> for $ty {
|
||||
fn device(&self) -> &$crate::device::Device {
|
||||
self.inner.dev()
|
||||
}
|
||||
fn handle(&self) -> $handle {
|
||||
self.inner.handle()
|
||||
}
|
||||
}
|
||||
|
||||
impl $ty {
|
||||
#[allow(clippy::too_many_arguments, reason = "This function is generated by a macro")]
|
||||
fn construct(
|
||||
device: $crate::device::Device,
|
||||
handle: $handle,
|
||||
name: Option<::std::borrow::Cow<'static, str>>,
|
||||
$($field_name: $field_ty,)*
|
||||
) -> ::ash::prelude::VkResult<Self> {
|
||||
Ok(Self {
|
||||
inner: $crate::device::DeviceOwnedDebugObject::new(
|
||||
device,
|
||||
handle,
|
||||
name,
|
||||
)?,
|
||||
$($field_name,)*
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
$(
|
||||
impl Drop for $ty {
|
||||
fn drop(&mut self) {
|
||||
#[allow(unused_mut)]
|
||||
let mut $this = self;
|
||||
$dtor
|
||||
}
|
||||
}
|
||||
)?
|
||||
};
|
||||
}
|
||||
|
||||
// This module is an experiment in a more generic way to manage device-owned resources.
|
||||
// #[cfg(false)]
|
||||
pub(crate) mod asdf {
|
||||
use std::{
|
||||
mem::ManuallyDrop,
|
||||
mem::{ManuallyDrop, MaybeUninit},
|
||||
ops::{Deref, DerefMut},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use ash::vk;
|
||||
use ash::vk::Handle;
|
||||
|
||||
use crate::device::{DebugName, DeviceInner};
|
||||
use crate::Device;
|
||||
use crate::{device::DeviceInner, util::DebugName};
|
||||
|
||||
pub mod traits {
|
||||
/// A trait describing an object owned by some manager-type, which is
|
||||
/// responsible for destroying it.
|
||||
pub trait ExternallyManagedObject<Owner> {
|
||||
pub trait ExternallyManagedObject<T> {
|
||||
/// # Safety
|
||||
/// The caller must ensure this function is only called once for a given object.
|
||||
unsafe fn destroy(self, owner: &Owner);
|
||||
unsafe fn destroy(self, owner: &T);
|
||||
}
|
||||
|
||||
/// A trait describing an object which can have a debug name assigned to it.
|
||||
|
|
@ -952,6 +904,45 @@ pub mod asdf {
|
|||
owner,
|
||||
}
|
||||
}
|
||||
pub fn owner(&self) -> &O {
|
||||
&self.owner
|
||||
}
|
||||
|
||||
pub fn map_inner<U>(self, f: impl FnOnce(T) -> U) -> ExternallyManagedObject<U, O>
|
||||
where
|
||||
U: traits::ExternallyManagedObject<O>,
|
||||
{
|
||||
unsafe {
|
||||
let mut this = MaybeUninit::new(self);
|
||||
let inner = ManuallyDrop::take(&mut this.assume_init_mut().inner);
|
||||
let owner = core::ptr::read(&raw const this.assume_init_mut().owner);
|
||||
|
||||
let new_inner = f(inner);
|
||||
|
||||
ExternallyManagedObject {
|
||||
inner: ManuallyDrop::new(new_inner),
|
||||
owner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_owner<U>(self, f: impl FnOnce(O) -> U) -> ExternallyManagedObject<T, U>
|
||||
where
|
||||
T: traits::ExternallyManagedObject<U>,
|
||||
{
|
||||
unsafe {
|
||||
let mut this = MaybeUninit::new(self);
|
||||
let inner = ManuallyDrop::take(&mut this.assume_init_mut().inner);
|
||||
|
||||
// get the old owner without calling `Self::drop`
|
||||
let owner = core::ptr::read(&raw const this.assume_init_mut().owner);
|
||||
|
||||
ExternallyManagedObject {
|
||||
inner: ManuallyDrop::new(inner),
|
||||
owner: f(owner),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, O> Drop for ExternallyManagedObject<T, O>
|
||||
|
|
@ -966,131 +957,138 @@ pub mod asdf {
|
|||
}
|
||||
}
|
||||
|
||||
pub type InnerDeviceObject<T> = DeviceObject<T, Arc<DeviceInner>>;
|
||||
|
||||
/// A wrapper for vulkan types which are owned by the device, taking care of destruction.
|
||||
pub struct DeviceObject<T: traits::ExternallyManagedObject<Device>> {
|
||||
inner: ExternallyManagedObject<T, Device>,
|
||||
#[derive(Debug)]
|
||||
pub struct DeviceObject<
|
||||
T: traits::ExternallyManagedObject<O>,
|
||||
O: AsRef<super::DeviceInner> = super::Device,
|
||||
> {
|
||||
inner: ExternallyManagedObject<T, O>,
|
||||
#[allow(dead_code)]
|
||||
name: Option<DebugName>,
|
||||
}
|
||||
|
||||
impl<T: traits::ExternallyManagedObject<Device> + traits::DebugNameable> DeviceObject<T> {
|
||||
pub fn new_debug_named(
|
||||
device: Device,
|
||||
inner: T,
|
||||
name: Option<impl Into<DebugName>>,
|
||||
) -> Self {
|
||||
impl<
|
||||
T: traits::ExternallyManagedObject<O> + traits::DebugNameable,
|
||||
O: AsRef<super::DeviceInner>,
|
||||
> DeviceObject<T, O>
|
||||
{
|
||||
pub fn new_debug_named(owner: O, inner: T, name: Option<impl Into<DebugName>>) -> Self {
|
||||
let name = name.map(Into::into);
|
||||
if let Some(ref name) = name {
|
||||
traits::DebugNameable::debug_name(&inner, &device, name);
|
||||
traits::DebugNameable::debug_name(&inner, owner.as_ref(), name);
|
||||
}
|
||||
|
||||
let obj = ExternallyManagedObject::new(inner, device);
|
||||
let obj = ExternallyManagedObject::new(inner, owner);
|
||||
|
||||
Self { inner: obj, name }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: traits::ExternallyManagedObject<Device>> DeviceObject<T> {
|
||||
pub fn new(device: Device, inner: T) -> Self {
|
||||
let inner = ExternallyManagedObject::new(inner, device);
|
||||
impl<T: traits::ExternallyManagedObject<O>, O: AsRef<super::DeviceInner>> DeviceObject<T, O> {
|
||||
pub fn new(owner: O, inner: T) -> Self {
|
||||
let inner = ExternallyManagedObject::new(inner, owner);
|
||||
|
||||
Self { inner, name: None }
|
||||
}
|
||||
|
||||
pub fn device(&self) -> &Device {
|
||||
&self.inner.owner
|
||||
pub fn new_debug_named_with<D>(
|
||||
owner: O,
|
||||
inner: T,
|
||||
name: Option<impl Into<DebugName>>,
|
||||
debug_namable: impl FnOnce(&T) -> D,
|
||||
) -> Self
|
||||
where
|
||||
D: traits::DebugNameable,
|
||||
{
|
||||
let name = name.map(Into::into);
|
||||
if let Some(ref name) = name {
|
||||
traits::DebugNameable::debug_name(&debug_namable(&inner), owner.as_ref(), name);
|
||||
}
|
||||
|
||||
let obj = ExternallyManagedObject::new(inner, owner);
|
||||
|
||||
Self { inner: obj, name }
|
||||
}
|
||||
|
||||
pub fn handle(&self) -> T
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
*Deref::deref(self)
|
||||
pub fn device(&self) -> &O {
|
||||
self.inner.owner()
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Option<&str> {
|
||||
self.name.as_deref()
|
||||
self.name.as_ref().map(|n| &**n)
|
||||
}
|
||||
|
||||
pub fn map_inner<U>(self, f: impl FnOnce(T) -> U) -> DeviceObject<U, O>
|
||||
where
|
||||
U: traits::ExternallyManagedObject<O>,
|
||||
{
|
||||
DeviceObject {
|
||||
inner: self.inner.map_inner(f),
|
||||
name: self.name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_owner<U>(self, f: impl FnOnce(O) -> U) -> DeviceObject<T, U>
|
||||
where
|
||||
T: traits::ExternallyManagedObject<U>,
|
||||
U: AsRef<super::DeviceInner>,
|
||||
{
|
||||
DeviceObject {
|
||||
inner: self.inner.map_owner(f),
|
||||
name: self.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::fmt::Debug for DeviceObject<T>
|
||||
impl<T, O> Deref for DeviceObject<T, O>
|
||||
where
|
||||
T: std::fmt::Debug + traits::ExternallyManagedObject<Device>,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut fmt = f.debug_struct(core::any::type_name::<T>());
|
||||
let inner: &T = &self.inner;
|
||||
|
||||
fmt.field_with("device", |f| {
|
||||
write!(
|
||||
f,
|
||||
"0x{:x}", self.inner.owner.raw.handle().as_raw()
|
||||
)
|
||||
})
|
||||
.field_with("handle", |f| write!(f, "{inner:?}"))
|
||||
.field("name", &self.name);
|
||||
|
||||
fmt.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for DeviceObject<T>
|
||||
where
|
||||
T: traits::ExternallyManagedObject<Device>,
|
||||
T: traits::ExternallyManagedObject<O>,
|
||||
O: AsRef<super::DeviceInner>,
|
||||
{
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
&*self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for DeviceObject<T>
|
||||
impl<T, O> DerefMut for DeviceObject<T, O>
|
||||
where
|
||||
T: traits::ExternallyManagedObject<Device>,
|
||||
T: traits::ExternallyManagedObject<O>,
|
||||
O: AsRef<super::DeviceInner>,
|
||||
{
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
&mut *self.inner
|
||||
}
|
||||
}
|
||||
|
||||
mod impls {
|
||||
use crate::device::GpuAllocation;
|
||||
use crate::device::{DeviceInner, DevicePools, GpuAllocation};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl traits::ExternallyManagedObject<Device> for vk::Semaphore {
|
||||
unsafe fn destroy(self, device: &Device) {
|
||||
unsafe { device.raw.destroy_semaphore(self, None) };
|
||||
impl<T: AsRef<DeviceInner>> traits::ExternallyManagedObject<T> for vk::Semaphore {
|
||||
unsafe fn destroy(self, device: &T) {
|
||||
unsafe {
|
||||
device.as_ref().raw.destroy_semaphore(self, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl traits::ExternallyManagedObject<Device> for GpuAllocation {
|
||||
unsafe fn destroy(self, device: &Device) {
|
||||
_ = device.alloc2.lock().free(self);
|
||||
impl<T: AsRef<DeviceInner>> traits::ExternallyManagedObject<T> for GpuAllocation {
|
||||
unsafe fn destroy(self, device: &T) {
|
||||
_ = device.as_ref().alloc2.lock().free(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl traits::ExternallyManagedObject<Device> for vk::CommandBuffer {
|
||||
// Command buffers cannot be destroyed individually; their outer wrappers free them in Drop.
|
||||
unsafe fn destroy(self, _: &Device) {}
|
||||
}
|
||||
|
||||
impl traits::ExternallyManagedObject<Device> for vk::CommandPool {
|
||||
unsafe fn destroy(self, device: &Device) {
|
||||
unsafe { device.raw.destroy_command_pool(self, None) };
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> crate::device::DeviceOwned<T> for DeviceObject<T>
|
||||
where
|
||||
T: vk::Handle + Copy + traits::ExternallyManagedObject<Device>,
|
||||
{
|
||||
fn device(&self) -> &Device {
|
||||
self.device()
|
||||
}
|
||||
fn handle(&self) -> T {
|
||||
self.handle()
|
||||
impl traits::ExternallyManagedObject<DevicePools> for vk::Semaphore {
|
||||
unsafe fn destroy(self, owner: &DevicePools) {
|
||||
owner
|
||||
.binary_semaphores
|
||||
.push(crate::sync::BinarySemaphore(self));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1109,27 +1107,30 @@ pub mod asdf {
|
|||
#[allow(dead_code)]
|
||||
#[cfg(test)]
|
||||
fn asdf() {
|
||||
use crate::device::GpuAllocation;
|
||||
use crate::device::{DevicePools, GpuAllocation};
|
||||
fn summon<T>() -> T {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
let _inner_ref: DeviceObject<vk::Semaphore> = DeviceObject::new_debug_named(
|
||||
summon::<Device>(),
|
||||
let _inner_ref: DeviceObject<vk::Semaphore, &DeviceInner> = DeviceObject::new_debug_named(
|
||||
summon::<&DeviceInner>(),
|
||||
summon::<vk::Semaphore>(),
|
||||
Some("my semaphore"),
|
||||
);
|
||||
|
||||
let _device_owned: DeviceObject<vk::Semaphore> = DeviceObject::new_debug_named(
|
||||
summon::<super::Device>(),
|
||||
summon::<vk::Semaphore>(),
|
||||
Some("my other semaphore"),
|
||||
let _device_owned: DeviceObject<vk::Semaphore, super::Device> =
|
||||
DeviceObject::new_debug_named(
|
||||
summon::<super::Device>(),
|
||||
summon::<vk::Semaphore>(),
|
||||
Some("my other semaphore"),
|
||||
);
|
||||
|
||||
let _allocation: DeviceObject<GpuAllocation, Arc<super::DeviceInner>> = DeviceObject::new(
|
||||
summon::<Arc<super::DeviceInner>>(),
|
||||
summon::<GpuAllocation>(),
|
||||
);
|
||||
|
||||
let _allocation: DeviceObject<GpuAllocation> =
|
||||
DeviceObject::new(summon::<Device>(), summon::<GpuAllocation>());
|
||||
|
||||
let _pool_owned: DeviceObject<vk::Semaphore> =
|
||||
DeviceObject::new(summon::<Device>(), summon::<vk::Semaphore>());
|
||||
let _pool_owned: ExternallyManagedObject<vk::Semaphore, DevicePools> =
|
||||
ExternallyManagedObject::new(summon::<vk::Semaphore>(), summon::<DevicePools>());
|
||||
}
|
||||
}
|
||||
|
|
@ -6,8 +6,12 @@ use std::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
device::{Allocation, AllocationStrategy, DeviceObject, QueueFlags},
|
||||
util::WeakVec,
|
||||
device::{
|
||||
Allocation, AllocationStrategy, DeviceInner, DeviceObject, QueueFlags,
|
||||
asdf::traits::ExternallyManagedObject,
|
||||
},
|
||||
swapchain::Swapchain,
|
||||
util::weak_vec::WeakVec,
|
||||
};
|
||||
|
||||
use super::Device;
|
||||
|
|
@ -15,7 +19,6 @@ use ash::vk::{self, Handle};
|
|||
use ash_formats::FormatExt;
|
||||
use gpu_allocator::vulkan::{AllocationCreateDesc, AllocationScheme};
|
||||
use parking_lot::Mutex;
|
||||
use renderer_device::queue::QueueFamilyIndices;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ImageDesc {
|
||||
|
|
@ -73,6 +76,7 @@ impl Eq for ImageDesc {}
|
|||
impl PartialEq for ImageDesc {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.flags == other.flags
|
||||
&& self.name == other.name
|
||||
&& self.format == other.format
|
||||
&& self.kind == other.kind
|
||||
&& self.mip_levels == other.mip_levels
|
||||
|
|
@ -87,7 +91,7 @@ impl PartialEq for ImageDesc {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ImageDesc {
|
||||
impl<'a> std::fmt::Debug for ImageDesc {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ImageDesc")
|
||||
.field("flags", &self.flags)
|
||||
|
|
@ -135,6 +139,14 @@ enum ImageInner {
|
|||
Allocated(DeviceObject<vk::Image>, Allocation),
|
||||
}
|
||||
|
||||
impl<T: AsRef<DeviceInner>> ExternallyManagedObject<T> for vk::Image {
|
||||
unsafe fn destroy(self, device: &T) {
|
||||
unsafe {
|
||||
device.as_ref().raw.destroy_image(self, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ImageInner {
|
||||
fn image(&self) -> vk::Image {
|
||||
match self {
|
||||
|
|
@ -252,24 +264,22 @@ impl Image {
|
|||
Self::new_with_allocation_unchecked(device, image, allocation, desc)
|
||||
}
|
||||
|
||||
pub fn from_swapchain_raw(
|
||||
image: vk::Image,
|
||||
device: Device,
|
||||
format: vk::Format,
|
||||
extent: vk::Extent3D,
|
||||
usage: vk::ImageUsageFlags,
|
||||
) -> Self {
|
||||
pub fn from_swapchain_image(image: vk::Image, swapchain: &Swapchain) -> Self {
|
||||
Self {
|
||||
image: ImageInner::Swapchain(image, device),
|
||||
image: ImageInner::Swapchain(image, swapchain.swapchain.device().clone()),
|
||||
desc: ImageDesc {
|
||||
format,
|
||||
format: swapchain.config.format,
|
||||
kind: vk::ImageType::TYPE_2D,
|
||||
mip_levels: 1,
|
||||
array_layers: 1,
|
||||
samples: vk::SampleCountFlags::TYPE_1,
|
||||
extent,
|
||||
extent: vk::Extent3D {
|
||||
width: swapchain.config.extent.width,
|
||||
height: swapchain.config.extent.height,
|
||||
depth: 1,
|
||||
},
|
||||
tiling: vk::ImageTiling::OPTIMAL,
|
||||
usage,
|
||||
usage: swapchain.config.usage,
|
||||
queue_families: QueueFlags::PRESENT,
|
||||
layout: vk::ImageLayout::UNDEFINED,
|
||||
mem_location: gpu_allocator::MemoryLocation::GpuOnly,
|
||||
|
|
@ -317,7 +327,7 @@ impl Image {
|
|||
.tiling(*tiling)
|
||||
.usage(*usage)
|
||||
.sharing_mode(sharing_mode)
|
||||
.queue_family_indices(queue_families.as_u32_slice())
|
||||
.queue_family_indices(&queue_families)
|
||||
.array_layers(*array_layers)
|
||||
.mip_levels(*mip_levels);
|
||||
|
||||
|
|
@ -1007,6 +1017,14 @@ impl ImageView {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<DeviceInner>> ExternallyManagedObject<T> for vk::ImageView {
|
||||
unsafe fn destroy(self, device: &T) {
|
||||
unsafe {
|
||||
device.as_ref().raw.destroy_image_view(self, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QueueOwnership {
|
||||
pub src: u32,
|
||||
pub dst: u32,
|
||||
|
|
@ -13,6 +13,7 @@ use crate::{
|
|||
Error, PhysicalDeviceFeatures, PhysicalDeviceInfo, SurfaceCapabilities,
|
||||
device::{Extension, get_extensions, get_layers},
|
||||
get_physical_device_features, get_physical_device_properties, make_extension,
|
||||
swapchain::Surface,
|
||||
};
|
||||
|
||||
pub struct DebugUtilsCreateInfo {
|
||||
|
|
@ -20,21 +21,21 @@ pub struct DebugUtilsCreateInfo {
|
|||
pub message_type: vk::DebugUtilsMessageTypeFlagsEXT,
|
||||
}
|
||||
|
||||
pub struct InstanceExtensions {
|
||||
pub get_surface_capabilities2: Option<khr::get_surface_capabilities2::Instance>,
|
||||
pub surface_maintenance1: Option<()>,
|
||||
pub(crate) struct InstanceExtensions {
|
||||
pub(crate) get_surface_capabilities2: Option<khr::get_surface_capabilities2::Instance>,
|
||||
pub(crate) surface_maintenance1: Option<()>,
|
||||
}
|
||||
|
||||
pub struct InstanceInner {
|
||||
pub raw: ash::Instance,
|
||||
pub extensions: InstanceExtensions,
|
||||
pub(crate) extensions: InstanceExtensions,
|
||||
pub entry: Entry,
|
||||
pub _debug_utils: DebugUtils,
|
||||
pub(crate) _debug_utils: DebugUtils,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Instance {
|
||||
pub inner: Arc<InstanceInner>,
|
||||
pub(crate) inner: Arc<InstanceInner>,
|
||||
}
|
||||
|
||||
impl Deref for Instance {
|
||||
|
|
@ -254,23 +255,28 @@ impl Instance {
|
|||
/// extensions are available, it will also query present mode compatibility
|
||||
/// information to filter the present modes based on the provided initial
|
||||
/// mode.
|
||||
pub fn get_adapter_surface_capabilities(
|
||||
pub(crate) fn get_adapter_surface_capabilities(
|
||||
&self,
|
||||
pdev: vk::PhysicalDevice,
|
||||
functor: &khr::surface::Instance,
|
||||
raw: vk::SurfaceKHR,
|
||||
surface: &Surface,
|
||||
present_mode: Option<vk::PresentModeKHR>,
|
||||
) -> crate::Result<SurfaceCapabilities> {
|
||||
let formats = unsafe {
|
||||
functor.get_physical_device_surface_formats(pdev, raw)?
|
||||
surface
|
||||
.functor
|
||||
.get_physical_device_surface_formats(pdev, surface.raw)?
|
||||
};
|
||||
|
||||
let present_modes = unsafe {
|
||||
functor.get_physical_device_surface_present_modes(pdev, raw)?
|
||||
surface
|
||||
.functor
|
||||
.get_physical_device_surface_present_modes(pdev, surface.raw)?
|
||||
};
|
||||
|
||||
let generic_caps = unsafe {
|
||||
functor.get_physical_device_surface_capabilities(pdev, raw)?
|
||||
surface
|
||||
.functor
|
||||
.get_physical_device_surface_capabilities(pdev, surface.raw)?
|
||||
};
|
||||
|
||||
let (capabilities, present_modes) = if let Some(ref functor) =
|
||||
|
|
@ -279,7 +285,7 @@ impl Instance {
|
|||
{
|
||||
let mut capabilities = vk::SurfaceCapabilities2KHR::default();
|
||||
let mut surface_info =
|
||||
vk::PhysicalDeviceSurfaceInfo2KHR::default().surface(raw);
|
||||
vk::PhysicalDeviceSurfaceInfo2KHR::default().surface(surface.raw());
|
||||
|
||||
let present_mode = present_mode.unwrap_or(present_modes[0]);
|
||||
let mut surface_present_mode =
|
||||
|
|
@ -318,7 +324,7 @@ impl Instance {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn expose_adapter(
|
||||
pub(crate) fn expose_adapter(
|
||||
&self,
|
||||
pdev: vk::PhysicalDevice,
|
||||
) -> crate::Result<PhysicalDeviceInfo> {
|
||||
|
|
@ -332,9 +338,9 @@ impl Instance {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn choose_adapter_default(
|
||||
pub(crate) fn choose_adapter_default(
|
||||
&self,
|
||||
surface: Option<vk::SurfaceKHR>,
|
||||
surface: Option<&Surface>,
|
||||
required_extensions: &[Extension],
|
||||
required_features: Option<&PhysicalDeviceFeatures>,
|
||||
) -> crate::Result<PhysicalDeviceInfo> {
|
||||
|
|
@ -447,7 +453,7 @@ impl Instance {
|
|||
}
|
||||
|
||||
/// So... basically this probably doesn't matter because the graphics queue will always be a present queue as well...
|
||||
pub fn query_presentation_support(
|
||||
pub(crate) fn query_presentation_support(
|
||||
&self,
|
||||
pdev: vk::PhysicalDevice,
|
||||
queue_family: u32,
|
||||
|
|
@ -491,7 +497,7 @@ impl Instance {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct DebugUtils {
|
||||
pub(crate) struct DebugUtils {
|
||||
pub instance: ext::debug_utils::Instance,
|
||||
pub messenger: vk::DebugUtilsMessengerEXT,
|
||||
}
|
||||
|
|
@ -2,36 +2,40 @@
|
|||
negative_impls,
|
||||
map_try_insert,
|
||||
debug_closure_helpers,
|
||||
core_io_borrowed_buf,
|
||||
slice_partition_dedup
|
||||
)]
|
||||
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use std::{collections::HashMap, fmt::Debug, path::PathBuf, sync::Arc};
|
||||
|
||||
use ash::{prelude::VkResult, vk};
|
||||
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
|
||||
|
||||
use ash::{
|
||||
ext,
|
||||
prelude::VkResult,
|
||||
vk::{self, SurfaceCapabilitiesKHR},
|
||||
};
|
||||
use dyn_clone::DynClone;
|
||||
|
||||
pub use ash;
|
||||
|
||||
mod buffers;
|
||||
pub mod commands;
|
||||
mod debug;
|
||||
pub mod device;
|
||||
#[path = "egui.rs"]
|
||||
mod egui_pass;
|
||||
mod images;
|
||||
pub mod instance;
|
||||
mod pipeline;
|
||||
pub mod render_graph;
|
||||
pub mod rendering;
|
||||
pub mod swapchain;
|
||||
pub mod sync;
|
||||
mod text;
|
||||
pub mod util;
|
||||
|
||||
pub(crate) use renderer_resources::{buffers, images, pipeline};
|
||||
|
||||
pub use renderer_device::{device, instance, queue, sync};
|
||||
pub use renderer_device::{
|
||||
compatible_extension_properties, extension_intersection,
|
||||
get_physical_device_features, get_physical_device_properties, make_extension,
|
||||
DebugUtils, DebugUtilsCreateInfo, Device, DeviceOwned, DeviceRef, Error,
|
||||
ExtendsDeviceFeatures2Debug, ExtendsDeviceProperties2Debug, Extension, Instance,
|
||||
PhysicalDeviceFeatures, PhysicalDeviceInfo, PhysicalDeviceProperties, Result,
|
||||
SurfaceCapabilities,
|
||||
};
|
||||
pub use renderer_macros::DeviceRef;
|
||||
use device::{Device, DeviceOwned};
|
||||
|
||||
mod texture {
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
|
|
@ -75,7 +79,581 @@ mod texture {
|
|||
|
||||
use util::Rgba;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("No Physical Device found for this instance.")]
|
||||
NoAdapter,
|
||||
#[error("Swapchain suboptimal.")]
|
||||
SuboptimalSwapchain,
|
||||
#[error(transparent)]
|
||||
LoadingError(#[from] ash::LoadingError),
|
||||
#[error(transparent)]
|
||||
Result(#[from] ash::vk::Result),
|
||||
#[error(transparent)]
|
||||
CStrError(#[from] core::ffi::c_str::FromBytesUntilNulError),
|
||||
#[error(transparent)]
|
||||
Utf8Error(#[from] core::str::Utf8Error),
|
||||
#[error(transparent)]
|
||||
NulError(#[from] std::ffi::NulError),
|
||||
#[error(transparent)]
|
||||
GpuAllocatorError(#[from] gpu_allocator::AllocationError),
|
||||
#[error("No Physical Device found.")]
|
||||
NoPhysicalDevice,
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error(
|
||||
"Image dimensions ({width}x{height}) exceed the maximum allowed size of {max_size}x{max_size}."
|
||||
)]
|
||||
ImageTooLarge {
|
||||
width: u32,
|
||||
height: u32,
|
||||
max_size: u32,
|
||||
},
|
||||
#[error("Image dimensions cannot be zero.")]
|
||||
ImageZeroSized,
|
||||
#[error("Incompatible image view kind {view_kind:?} for image kind {image_kind:?}.")]
|
||||
IncompatibleImageViewKind {
|
||||
view_kind: vk::ImageViewType,
|
||||
image_kind: vk::ImageType,
|
||||
},
|
||||
#[error("Asset missing at path {0:?}")]
|
||||
AssetMissing(PathBuf),
|
||||
#[error("Unspecified Error")]
|
||||
Unspecified,
|
||||
#[error("BEEP BOOP create an error variant for {0} BEEP BOOP")]
|
||||
Todo(&'static str),
|
||||
}
|
||||
|
||||
pub type Result<T> = core::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
struct DeviceExtension<'a> {
|
||||
name: &'a core::ffi::CStr,
|
||||
version: u32,
|
||||
}
|
||||
|
||||
/// returns true if lhs and rhs have the same name and lhs spec_version is less
|
||||
/// than or equal to rhs spec_version
|
||||
#[allow(dead_code)]
|
||||
fn compatible_extension_properties(
|
||||
lhs: &vk::ExtensionProperties,
|
||||
rhs: &vk::ExtensionProperties,
|
||||
) -> bool {
|
||||
let Some(lhs_name) = lhs.extension_name_as_c_str().ok() else {
|
||||
return false;
|
||||
};
|
||||
let Some(rhs_name) = rhs.extension_name_as_c_str().ok() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if lhs_name == rhs_name {
|
||||
lhs.spec_version <= rhs.spec_version
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub mod queue;
|
||||
|
||||
pub trait ExtendsDeviceFeatures2Debug:
|
||||
vk::ExtendsPhysicalDeviceFeatures2 + Debug + Send + Sync
|
||||
{
|
||||
}
|
||||
pub trait ExtendsDeviceProperties2Debug:
|
||||
vk::ExtendsPhysicalDeviceProperties2 + Debug + DynClone + Send + Sync
|
||||
{
|
||||
}
|
||||
|
||||
impl<T: vk::ExtendsPhysicalDeviceFeatures2 + Debug + Send + Sync> ExtendsDeviceFeatures2Debug
|
||||
for T
|
||||
{
|
||||
}
|
||||
impl<T: vk::ExtendsPhysicalDeviceProperties2 + Debug + DynClone + Send + Sync>
|
||||
ExtendsDeviceProperties2Debug for T
|
||||
{
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PhysicalDeviceInfo {
|
||||
pub pdev: vk::PhysicalDevice,
|
||||
pub properties: PhysicalDeviceProperties,
|
||||
pub features: PhysicalDeviceFeatures,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SurfaceCapabilities {
|
||||
pub capabilities: SurfaceCapabilitiesKHR,
|
||||
/// The minimum number of images that must be present in the swapchain for
|
||||
/// the surface for any present mode supported by the surface.
|
||||
pub min_image_count: u32,
|
||||
pub formats: Vec<vk::SurfaceFormatKHR>,
|
||||
pub present_modes: Vec<vk::PresentModeKHR>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct PhysicalDeviceFeatures {
|
||||
pub core: vk::PhysicalDeviceFeatures,
|
||||
pub core11: vk::PhysicalDeviceVulkan11Features<'static>,
|
||||
pub core12: vk::PhysicalDeviceVulkan12Features<'static>,
|
||||
pub core13: vk::PhysicalDeviceVulkan13Features<'static>,
|
||||
pub mesh_shader: Option<vk::PhysicalDeviceMeshShaderFeaturesEXT<'static>>,
|
||||
}
|
||||
|
||||
impl PhysicalDeviceFeatures {
|
||||
pub fn push_to_device_create_info<'a>(
|
||||
&'a mut self,
|
||||
mut create_info: vk::DeviceCreateInfo<'a>,
|
||||
) -> vk::DeviceCreateInfo<'a> {
|
||||
create_info = create_info
|
||||
.enabled_features(&self.core)
|
||||
.push_next(&mut self.core11)
|
||||
.push_next(&mut self.core12)
|
||||
.push_next(&mut self.core13);
|
||||
|
||||
if let Some(mesh_shader) = &mut self.mesh_shader {
|
||||
create_info = create_info.push_next(mesh_shader);
|
||||
}
|
||||
create_info
|
||||
}
|
||||
pub fn superset_of(&self, other: &PhysicalDeviceFeatures) -> bool {
|
||||
fn core_superset_of(
|
||||
a: &vk::PhysicalDeviceFeatures,
|
||||
b: &vk::PhysicalDeviceFeatures,
|
||||
) -> bool {
|
||||
(b.robust_buffer_access == vk::FALSE
|
||||
|| a.robust_buffer_access != b.robust_buffer_access)
|
||||
&& (b.full_draw_index_uint32 == vk::FALSE
|
||||
|| a.full_draw_index_uint32 != b.full_draw_index_uint32)
|
||||
&& (b.image_cube_array == vk::FALSE || a.image_cube_array != b.image_cube_array)
|
||||
&& (b.independent_blend == vk::FALSE || a.independent_blend != b.independent_blend)
|
||||
&& (b.geometry_shader == vk::FALSE || a.geometry_shader != b.geometry_shader)
|
||||
&& (b.tessellation_shader == vk::FALSE
|
||||
|| a.tessellation_shader != b.tessellation_shader)
|
||||
&& (b.sample_rate_shading == vk::FALSE
|
||||
|| a.sample_rate_shading != b.sample_rate_shading)
|
||||
&& (b.dual_src_blend == vk::FALSE || a.dual_src_blend != b.dual_src_blend)
|
||||
&& (b.logic_op == vk::FALSE || a.logic_op != b.logic_op)
|
||||
&& (b.multi_draw_indirect == vk::FALSE
|
||||
|| a.multi_draw_indirect != b.multi_draw_indirect)
|
||||
&& (b.draw_indirect_first_instance == vk::FALSE
|
||||
|| a.draw_indirect_first_instance != b.draw_indirect_first_instance)
|
||||
&& (b.depth_clamp == vk::FALSE || a.depth_clamp != b.depth_clamp)
|
||||
&& (b.depth_bias_clamp == vk::FALSE || a.depth_bias_clamp != b.depth_bias_clamp)
|
||||
&& (b.fill_mode_non_solid == vk::FALSE
|
||||
|| a.fill_mode_non_solid != b.fill_mode_non_solid)
|
||||
&& (b.depth_bounds == vk::FALSE || a.depth_bounds != b.depth_bounds)
|
||||
&& (b.wide_lines == vk::FALSE || a.wide_lines != b.wide_lines)
|
||||
&& (b.large_points == vk::FALSE || a.large_points != b.large_points)
|
||||
&& (b.alpha_to_one == vk::FALSE || a.alpha_to_one != b.alpha_to_one)
|
||||
&& (b.multi_viewport == vk::FALSE || a.multi_viewport != b.multi_viewport)
|
||||
&& (b.sampler_anisotropy == vk::FALSE
|
||||
|| a.sampler_anisotropy != b.sampler_anisotropy)
|
||||
&& (b.texture_compression_etc2 == vk::FALSE
|
||||
|| a.texture_compression_etc2 != b.texture_compression_etc2)
|
||||
&& (b.texture_compression_astc_ldr == vk::FALSE
|
||||
|| a.texture_compression_astc_ldr != b.texture_compression_astc_ldr)
|
||||
&& (b.texture_compression_bc == vk::FALSE
|
||||
|| a.texture_compression_bc != b.texture_compression_bc)
|
||||
&& (b.occlusion_query_precise == vk::FALSE
|
||||
|| a.occlusion_query_precise != b.occlusion_query_precise)
|
||||
&& (b.pipeline_statistics_query == vk::FALSE
|
||||
|| a.pipeline_statistics_query != b.pipeline_statistics_query)
|
||||
&& (b.vertex_pipeline_stores_and_atomics == vk::FALSE
|
||||
|| a.vertex_pipeline_stores_and_atomics != b.vertex_pipeline_stores_and_atomics)
|
||||
&& (b.fragment_stores_and_atomics == vk::FALSE
|
||||
|| a.fragment_stores_and_atomics != b.fragment_stores_and_atomics)
|
||||
&& (b.shader_tessellation_and_geometry_point_size == vk::FALSE
|
||||
|| a.shader_tessellation_and_geometry_point_size
|
||||
!= b.shader_tessellation_and_geometry_point_size)
|
||||
&& (b.shader_image_gather_extended == vk::FALSE
|
||||
|| a.shader_image_gather_extended != b.shader_image_gather_extended)
|
||||
&& (b.shader_storage_image_extended_formats == vk::FALSE
|
||||
|| a.shader_storage_image_extended_formats
|
||||
!= b.shader_storage_image_extended_formats)
|
||||
&& (b.shader_storage_image_multisample == vk::FALSE
|
||||
|| a.shader_storage_image_multisample != b.shader_storage_image_multisample)
|
||||
&& (b.shader_storage_image_read_without_format == vk::FALSE
|
||||
|| a.shader_storage_image_read_without_format
|
||||
!= b.shader_storage_image_read_without_format)
|
||||
&& (b.shader_storage_image_write_without_format == vk::FALSE
|
||||
|| a.shader_storage_image_write_without_format
|
||||
!= b.shader_storage_image_write_without_format)
|
||||
&& (b.shader_uniform_buffer_array_dynamic_indexing == vk::FALSE
|
||||
|| a.shader_uniform_buffer_array_dynamic_indexing
|
||||
!= b.shader_uniform_buffer_array_dynamic_indexing)
|
||||
&& (b.shader_sampled_image_array_dynamic_indexing == vk::FALSE
|
||||
|| a.shader_sampled_image_array_dynamic_indexing
|
||||
!= b.shader_sampled_image_array_dynamic_indexing)
|
||||
&& (b.shader_storage_buffer_array_dynamic_indexing == vk::FALSE
|
||||
|| a.shader_storage_buffer_array_dynamic_indexing
|
||||
!= b.shader_storage_buffer_array_dynamic_indexing)
|
||||
&& (b.shader_storage_image_array_dynamic_indexing == vk::FALSE
|
||||
|| a.shader_storage_image_array_dynamic_indexing
|
||||
!= b.shader_storage_image_array_dynamic_indexing)
|
||||
&& (b.shader_clip_distance == vk::FALSE
|
||||
|| a.shader_clip_distance != b.shader_clip_distance)
|
||||
&& (b.shader_cull_distance == vk::FALSE
|
||||
|| a.shader_cull_distance != b.shader_cull_distance)
|
||||
&& (b.shader_float64 == vk::FALSE || a.shader_float64 != b.shader_float64)
|
||||
&& (b.shader_int64 == vk::FALSE || a.shader_int64 != b.shader_int64)
|
||||
&& (b.shader_int16 == vk::FALSE || a.shader_int16 != b.shader_int16)
|
||||
&& (b.shader_resource_residency == vk::FALSE
|
||||
|| a.shader_resource_residency != b.shader_resource_residency)
|
||||
&& (b.shader_resource_min_lod == vk::FALSE
|
||||
|| a.shader_resource_min_lod != b.shader_resource_min_lod)
|
||||
&& (b.sparse_binding == vk::FALSE || a.sparse_binding != b.sparse_binding)
|
||||
&& (b.sparse_residency_buffer == vk::FALSE
|
||||
|| a.sparse_residency_buffer != b.sparse_residency_buffer)
|
||||
&& (b.sparse_residency_image2_d == vk::FALSE
|
||||
|| a.sparse_residency_image2_d != b.sparse_residency_image2_d)
|
||||
&& (b.sparse_residency_image3_d == vk::FALSE
|
||||
|| a.sparse_residency_image3_d != b.sparse_residency_image3_d)
|
||||
&& (b.sparse_residency2_samples == vk::FALSE
|
||||
|| a.sparse_residency2_samples != b.sparse_residency2_samples)
|
||||
&& (b.sparse_residency4_samples == vk::FALSE
|
||||
|| a.sparse_residency4_samples != b.sparse_residency4_samples)
|
||||
&& (b.sparse_residency8_samples == vk::FALSE
|
||||
|| a.sparse_residency8_samples != b.sparse_residency8_samples)
|
||||
&& (b.sparse_residency16_samples == vk::FALSE
|
||||
|| a.sparse_residency16_samples != b.sparse_residency16_samples)
|
||||
&& (b.sparse_residency_aliased == vk::FALSE
|
||||
|| a.sparse_residency_aliased != b.sparse_residency_aliased)
|
||||
&& (b.variable_multisample_rate == vk::FALSE
|
||||
|| a.variable_multisample_rate != b.variable_multisample_rate)
|
||||
&& (b.inherited_queries == vk::FALSE || a.inherited_queries != b.inherited_queries)
|
||||
}
|
||||
|
||||
fn core11_superset_of(
|
||||
a: &vk::PhysicalDeviceVulkan11Features,
|
||||
b: &vk::PhysicalDeviceVulkan11Features,
|
||||
) -> bool {
|
||||
(b.storage_buffer16_bit_access == vk::FALSE
|
||||
|| a.storage_buffer16_bit_access != b.storage_buffer16_bit_access)
|
||||
&& (b.uniform_and_storage_buffer16_bit_access == vk::FALSE
|
||||
|| a.uniform_and_storage_buffer16_bit_access
|
||||
!= b.uniform_and_storage_buffer16_bit_access)
|
||||
&& (b.storage_push_constant16 == vk::FALSE
|
||||
|| a.storage_push_constant16 != b.storage_push_constant16)
|
||||
&& (b.storage_input_output16 == vk::FALSE
|
||||
|| a.storage_input_output16 != b.storage_input_output16)
|
||||
&& (b.multiview == vk::FALSE || a.multiview != b.multiview)
|
||||
&& (b.multiview_geometry_shader == vk::FALSE
|
||||
|| a.multiview_geometry_shader != b.multiview_geometry_shader)
|
||||
&& (b.multiview_tessellation_shader == vk::FALSE
|
||||
|| a.multiview_tessellation_shader != b.multiview_tessellation_shader)
|
||||
&& (b.variable_pointers_storage_buffer == vk::FALSE
|
||||
|| a.variable_pointers_storage_buffer != b.variable_pointers_storage_buffer)
|
||||
&& (b.variable_pointers == vk::FALSE || a.variable_pointers != b.variable_pointers)
|
||||
&& (b.protected_memory == vk::FALSE || a.protected_memory != b.protected_memory)
|
||||
&& (b.sampler_ycbcr_conversion == vk::FALSE
|
||||
|| a.sampler_ycbcr_conversion != b.sampler_ycbcr_conversion)
|
||||
&& (b.shader_draw_parameters == vk::FALSE
|
||||
|| a.shader_draw_parameters != b.shader_draw_parameters)
|
||||
}
|
||||
|
||||
fn core12_superset_of(
|
||||
a: &vk::PhysicalDeviceVulkan12Features,
|
||||
b: &vk::PhysicalDeviceVulkan12Features,
|
||||
) -> bool {
|
||||
(b.sampler_mirror_clamp_to_edge == vk::FALSE
|
||||
|| a.sampler_mirror_clamp_to_edge != b.sampler_mirror_clamp_to_edge)
|
||||
&& (b.draw_indirect_count == vk::FALSE
|
||||
|| a.draw_indirect_count != b.draw_indirect_count)
|
||||
&& (b.storage_buffer8_bit_access == vk::FALSE
|
||||
|| a.storage_buffer8_bit_access != b.storage_buffer8_bit_access)
|
||||
&& (b.uniform_and_storage_buffer8_bit_access == vk::FALSE
|
||||
|| a.uniform_and_storage_buffer8_bit_access
|
||||
!= b.uniform_and_storage_buffer8_bit_access)
|
||||
&& (b.storage_push_constant8 == vk::FALSE
|
||||
|| a.storage_push_constant8 != b.storage_push_constant8)
|
||||
&& (b.shader_buffer_int64_atomics == vk::FALSE
|
||||
|| a.shader_buffer_int64_atomics != b.shader_buffer_int64_atomics)
|
||||
&& (b.shader_shared_int64_atomics == vk::FALSE
|
||||
|| a.shader_shared_int64_atomics != b.shader_shared_int64_atomics)
|
||||
&& (b.shader_float16 == vk::FALSE || a.shader_float16 != b.shader_float16)
|
||||
&& (b.shader_int8 == vk::FALSE || a.shader_int8 != b.shader_int8)
|
||||
&& (b.descriptor_indexing == vk::FALSE
|
||||
|| a.descriptor_indexing != b.descriptor_indexing)
|
||||
&& (b.shader_input_attachment_array_dynamic_indexing == vk::FALSE
|
||||
|| a.shader_input_attachment_array_dynamic_indexing
|
||||
!= b.shader_input_attachment_array_dynamic_indexing)
|
||||
&& (b.shader_uniform_texel_buffer_array_dynamic_indexing == vk::FALSE
|
||||
|| a.shader_uniform_texel_buffer_array_dynamic_indexing
|
||||
!= b.shader_uniform_texel_buffer_array_dynamic_indexing)
|
||||
&& (b.shader_storage_texel_buffer_array_dynamic_indexing == vk::FALSE
|
||||
|| a.shader_storage_texel_buffer_array_dynamic_indexing
|
||||
!= b.shader_storage_texel_buffer_array_dynamic_indexing)
|
||||
&& (b.shader_uniform_buffer_array_non_uniform_indexing == vk::FALSE
|
||||
|| a.shader_uniform_buffer_array_non_uniform_indexing
|
||||
!= b.shader_uniform_buffer_array_non_uniform_indexing)
|
||||
&& (b.shader_sampled_image_array_non_uniform_indexing == vk::FALSE
|
||||
|| a.shader_sampled_image_array_non_uniform_indexing
|
||||
!= b.shader_sampled_image_array_non_uniform_indexing)
|
||||
&& (b.shader_storage_buffer_array_non_uniform_indexing == vk::FALSE
|
||||
|| a.shader_storage_buffer_array_non_uniform_indexing
|
||||
!= b.shader_storage_buffer_array_non_uniform_indexing)
|
||||
&& (b.shader_storage_image_array_non_uniform_indexing == vk::FALSE
|
||||
|| a.shader_storage_image_array_non_uniform_indexing
|
||||
!= b.shader_storage_image_array_non_uniform_indexing)
|
||||
&& (b.shader_input_attachment_array_non_uniform_indexing == vk::FALSE
|
||||
|| a.shader_input_attachment_array_non_uniform_indexing
|
||||
!= b.shader_input_attachment_array_non_uniform_indexing)
|
||||
&& (b.shader_uniform_texel_buffer_array_non_uniform_indexing == vk::FALSE
|
||||
|| a.shader_uniform_texel_buffer_array_non_uniform_indexing
|
||||
!= b.shader_uniform_texel_buffer_array_non_uniform_indexing)
|
||||
&& (b.shader_storage_texel_buffer_array_non_uniform_indexing == vk::FALSE
|
||||
|| a.shader_storage_texel_buffer_array_non_uniform_indexing
|
||||
!= b.shader_storage_texel_buffer_array_non_uniform_indexing)
|
||||
&& (b.descriptor_binding_uniform_buffer_update_after_bind == vk::FALSE
|
||||
|| a.descriptor_binding_uniform_buffer_update_after_bind
|
||||
!= b.descriptor_binding_uniform_buffer_update_after_bind)
|
||||
&& (b.descriptor_binding_sampled_image_update_after_bind == vk::FALSE
|
||||
|| a.descriptor_binding_sampled_image_update_after_bind
|
||||
!= b.descriptor_binding_sampled_image_update_after_bind)
|
||||
&& (b.descriptor_binding_storage_image_update_after_bind == vk::FALSE
|
||||
|| a.descriptor_binding_storage_image_update_after_bind
|
||||
!= b.descriptor_binding_storage_image_update_after_bind)
|
||||
&& (b.descriptor_binding_storage_buffer_update_after_bind == vk::FALSE
|
||||
|| a.descriptor_binding_storage_buffer_update_after_bind
|
||||
!= b.descriptor_binding_storage_buffer_update_after_bind)
|
||||
&& (b.descriptor_binding_uniform_texel_buffer_update_after_bind == vk::FALSE
|
||||
|| a.descriptor_binding_uniform_texel_buffer_update_after_bind
|
||||
!= b.descriptor_binding_uniform_texel_buffer_update_after_bind)
|
||||
&& (b.descriptor_binding_storage_texel_buffer_update_after_bind == vk::FALSE
|
||||
|| a.descriptor_binding_storage_texel_buffer_update_after_bind
|
||||
!= b.descriptor_binding_storage_texel_buffer_update_after_bind)
|
||||
&& (b.descriptor_binding_update_unused_while_pending == vk::FALSE
|
||||
|| a.descriptor_binding_update_unused_while_pending
|
||||
!= b.descriptor_binding_update_unused_while_pending)
|
||||
&& (b.descriptor_binding_partially_bound == vk::FALSE
|
||||
|| a.descriptor_binding_partially_bound != b.descriptor_binding_partially_bound)
|
||||
&& (b.descriptor_binding_variable_descriptor_count == vk::FALSE
|
||||
|| a.descriptor_binding_variable_descriptor_count
|
||||
!= b.descriptor_binding_variable_descriptor_count)
|
||||
&& (b.runtime_descriptor_array == vk::FALSE
|
||||
|| a.runtime_descriptor_array != b.runtime_descriptor_array)
|
||||
&& (b.sampler_filter_minmax == vk::FALSE
|
||||
|| a.sampler_filter_minmax != b.sampler_filter_minmax)
|
||||
&& (b.scalar_block_layout == vk::FALSE
|
||||
|| a.scalar_block_layout != b.scalar_block_layout)
|
||||
&& (b.imageless_framebuffer == vk::FALSE
|
||||
|| a.imageless_framebuffer != b.imageless_framebuffer)
|
||||
&& (b.uniform_buffer_standard_layout == vk::FALSE
|
||||
|| a.uniform_buffer_standard_layout != b.uniform_buffer_standard_layout)
|
||||
&& (b.shader_subgroup_extended_types == vk::FALSE
|
||||
|| a.shader_subgroup_extended_types != b.shader_subgroup_extended_types)
|
||||
&& (b.separate_depth_stencil_layouts == vk::FALSE
|
||||
|| a.separate_depth_stencil_layouts != b.separate_depth_stencil_layouts)
|
||||
&& (b.host_query_reset == vk::FALSE || a.host_query_reset != b.host_query_reset)
|
||||
&& (b.timeline_semaphore == vk::FALSE
|
||||
|| a.timeline_semaphore != b.timeline_semaphore)
|
||||
&& (b.buffer_device_address == vk::FALSE
|
||||
|| a.buffer_device_address != b.buffer_device_address)
|
||||
&& (b.buffer_device_address_capture_replay == vk::FALSE
|
||||
|| a.buffer_device_address_capture_replay
|
||||
!= b.buffer_device_address_capture_replay)
|
||||
&& (b.buffer_device_address_multi_device == vk::FALSE
|
||||
|| a.buffer_device_address_multi_device != b.buffer_device_address_multi_device)
|
||||
&& (b.vulkan_memory_model == vk::FALSE
|
||||
|| a.vulkan_memory_model != b.vulkan_memory_model)
|
||||
&& (b.vulkan_memory_model_device_scope == vk::FALSE
|
||||
|| a.vulkan_memory_model_device_scope != b.vulkan_memory_model_device_scope)
|
||||
&& (b.vulkan_memory_model_availability_visibility_chains == vk::FALSE
|
||||
|| a.vulkan_memory_model_availability_visibility_chains
|
||||
!= b.vulkan_memory_model_availability_visibility_chains)
|
||||
&& (b.shader_output_viewport_index == vk::FALSE
|
||||
|| a.shader_output_viewport_index != b.shader_output_viewport_index)
|
||||
&& (b.shader_output_layer == vk::FALSE
|
||||
|| a.shader_output_layer != b.shader_output_layer)
|
||||
&& (b.subgroup_broadcast_dynamic_id == vk::FALSE
|
||||
|| a.subgroup_broadcast_dynamic_id != b.subgroup_broadcast_dynamic_id)
|
||||
}
|
||||
|
||||
fn core13_superset_of(
|
||||
a: &vk::PhysicalDeviceVulkan13Features,
|
||||
b: &vk::PhysicalDeviceVulkan13Features,
|
||||
) -> bool {
|
||||
(b.robust_image_access == vk::FALSE || a.robust_image_access != b.robust_image_access)
|
||||
&& (b.inline_uniform_block == vk::FALSE
|
||||
|| a.inline_uniform_block != b.inline_uniform_block)
|
||||
&& (b.descriptor_binding_inline_uniform_block_update_after_bind == vk::FALSE
|
||||
|| a.descriptor_binding_inline_uniform_block_update_after_bind
|
||||
!= b.descriptor_binding_inline_uniform_block_update_after_bind)
|
||||
&& (b.pipeline_creation_cache_control == vk::FALSE
|
||||
|| a.pipeline_creation_cache_control != b.pipeline_creation_cache_control)
|
||||
&& (b.private_data == vk::FALSE || a.private_data != b.private_data)
|
||||
&& (b.shader_demote_to_helper_invocation == vk::FALSE
|
||||
|| a.shader_demote_to_helper_invocation != b.shader_demote_to_helper_invocation)
|
||||
&& (b.shader_terminate_invocation == vk::FALSE
|
||||
|| a.shader_terminate_invocation != b.shader_terminate_invocation)
|
||||
&& (b.subgroup_size_control == vk::FALSE
|
||||
|| a.subgroup_size_control != b.subgroup_size_control)
|
||||
&& (b.compute_full_subgroups == vk::FALSE
|
||||
|| a.compute_full_subgroups != b.compute_full_subgroups)
|
||||
&& (b.synchronization2 == vk::FALSE || a.synchronization2 != b.synchronization2)
|
||||
&& (b.texture_compression_astc_hdr == vk::FALSE
|
||||
|| a.texture_compression_astc_hdr != b.texture_compression_astc_hdr)
|
||||
&& (b.shader_zero_initialize_workgroup_memory == vk::FALSE
|
||||
|| a.shader_zero_initialize_workgroup_memory
|
||||
!= b.shader_zero_initialize_workgroup_memory)
|
||||
&& (b.dynamic_rendering == vk::FALSE || a.dynamic_rendering != b.dynamic_rendering)
|
||||
&& (b.shader_integer_dot_product == vk::FALSE
|
||||
|| a.shader_integer_dot_product != b.shader_integer_dot_product)
|
||||
&& (b.maintenance4 == vk::FALSE || a.maintenance4 != b.maintenance4)
|
||||
}
|
||||
|
||||
fn mesh_shader_superset_of(
|
||||
a: &vk::PhysicalDeviceMeshShaderFeaturesEXT,
|
||||
b: &vk::PhysicalDeviceMeshShaderFeaturesEXT,
|
||||
) -> bool {
|
||||
(b.task_shader == vk::FALSE || a.task_shader != b.task_shader)
|
||||
&& (b.mesh_shader == vk::FALSE || a.mesh_shader != b.mesh_shader)
|
||||
&& (b.multiview_mesh_shader == vk::FALSE
|
||||
|| a.multiview_mesh_shader != b.multiview_mesh_shader)
|
||||
&& (b.primitive_fragment_shading_rate_mesh_shader == vk::FALSE
|
||||
|| a.primitive_fragment_shading_rate_mesh_shader
|
||||
!= b.primitive_fragment_shading_rate_mesh_shader)
|
||||
&& (b.mesh_shader_queries == vk::FALSE
|
||||
|| a.mesh_shader_queries != b.mesh_shader_queries)
|
||||
}
|
||||
|
||||
core_superset_of(&self.core, &other.core)
|
||||
&& core11_superset_of(&self.core11, &other.core11)
|
||||
&& core12_superset_of(&self.core12, &other.core12)
|
||||
&& core13_superset_of(&self.core13, &other.core13)
|
||||
&& match (&self.mesh_shader, &other.mesh_shader) {
|
||||
(Some(a), Some(b)) => mesh_shader_superset_of(a, b),
|
||||
(None, Some(_)) => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PhysicalDeviceProperties {
|
||||
/// Extensions supported by the device, as reported by `enumerate_device_extension_properties`.
|
||||
supported_extensions: Vec<vk::ExtensionProperties>,
|
||||
/// Vulkan 1.0 properties.
|
||||
core: vk::PhysicalDeviceProperties,
|
||||
/// Vulkan 1.1 properties.
|
||||
core11: vk::PhysicalDeviceVulkan11Properties<'static>,
|
||||
/// Vulkan 1.2 properties.
|
||||
core12: vk::PhysicalDeviceVulkan12Properties<'static>,
|
||||
/// Vulkan 1.3 properties.
|
||||
core13: vk::PhysicalDeviceVulkan13Properties<'static>,
|
||||
/// Mesh shader properties from the `VK_EXT_mesh_shader` extension, if supported.
|
||||
mesh_shader: Option<vk::PhysicalDeviceMeshShaderPropertiesEXT<'static>>,
|
||||
/// Memory properties of the device, as reported by `get_physical_device_memory_properties`.
|
||||
memory_properties: vk::PhysicalDeviceMemoryProperties,
|
||||
/// The API version supported by the device.
|
||||
device_api_version: u32,
|
||||
}
|
||||
|
||||
impl PhysicalDeviceProperties {
|
||||
pub(crate) fn get_device_local_memory_count(&self) -> u64 {
|
||||
self.memory_properties
|
||||
.memory_heaps
|
||||
.iter()
|
||||
.filter(|heap| heap.flags.contains(vk::MemoryHeapFlags::DEVICE_LOCAL))
|
||||
.map(|heap| heap.size)
|
||||
.sum::<u64>()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_physical_device_features(
|
||||
instance: &InstanceInner,
|
||||
pdev: vk::PhysicalDevice,
|
||||
properties: &PhysicalDeviceProperties,
|
||||
) -> PhysicalDeviceFeatures {
|
||||
let mut features = PhysicalDeviceFeatures::default();
|
||||
let mut features2 = vk::PhysicalDeviceFeatures2::default();
|
||||
|
||||
features2 = features2
|
||||
.push_next(&mut features.core11)
|
||||
.push_next(&mut features.core12)
|
||||
.push_next(&mut features.core13);
|
||||
|
||||
if properties.supports_extension(make_extension!(ext::mesh_shader)) {
|
||||
features2 = features2.push_next(
|
||||
features
|
||||
.mesh_shader
|
||||
.insert(vk::PhysicalDeviceMeshShaderFeaturesEXT::default()),
|
||||
);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
instance
|
||||
.raw
|
||||
.get_physical_device_features2(pdev, &mut features2);
|
||||
}
|
||||
|
||||
features.core = features2.features;
|
||||
|
||||
features
|
||||
}
|
||||
|
||||
fn get_physical_device_properties(
|
||||
instance: &InstanceInner,
|
||||
pdev: vk::PhysicalDevice,
|
||||
) -> Result<PhysicalDeviceProperties> {
|
||||
let mut props = PhysicalDeviceProperties::default();
|
||||
props.core = unsafe { instance.raw.get_physical_device_properties(pdev) };
|
||||
props.device_api_version = props.core.api_version;
|
||||
props.supported_extensions =
|
||||
unsafe { instance.raw.enumerate_device_extension_properties(pdev)? };
|
||||
props.memory_properties = unsafe { instance.raw.get_physical_device_memory_properties(pdev) };
|
||||
|
||||
let supports_mesh_shader = props.supports_extension(make_extension!(ext::mesh_shader));
|
||||
|
||||
// TODO: fallibly check anything beyond 1.0
|
||||
let mut props2 = vk::PhysicalDeviceProperties2::default()
|
||||
.push_next(&mut props.core11)
|
||||
.push_next(&mut props.core12)
|
||||
.push_next(&mut props.core13);
|
||||
|
||||
if supports_mesh_shader {
|
||||
let next = props
|
||||
.mesh_shader
|
||||
.insert(vk::PhysicalDeviceMeshShaderPropertiesEXT::default());
|
||||
props2 = props2.push_next(next);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
instance
|
||||
.raw
|
||||
.get_physical_device_properties2(pdev, &mut props2);
|
||||
}
|
||||
|
||||
Ok(props)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn extension_intersection<'a>(
|
||||
supported: &'a [vk::ExtensionProperties],
|
||||
required: &[Extension<'a>],
|
||||
) -> Vec<vk::ExtensionProperties> {
|
||||
supported
|
||||
.iter()
|
||||
.filter(|ext| {
|
||||
required.iter().any(|req| {
|
||||
ext.extension_name_as_c_str() == Ok(req.name) && ext.spec_version >= req.version
|
||||
})
|
||||
})
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl PhysicalDeviceProperties {
|
||||
pub(crate) fn supports_extension(&self, e: Extension) -> bool {
|
||||
self.supported_extensions
|
||||
.iter()
|
||||
.any(|ext| ext.extension_name_as_c_str() == Ok(e.name) && ext.spec_version >= e.version)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use instance::DebugUtils;
|
||||
pub use instance::{DebugUtilsCreateInfo, Instance};
|
||||
|
||||
pub struct SamplerCache {
|
||||
device: Device,
|
||||
|
|
@ -344,9 +922,7 @@ impl EguiState {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(renderer_macros::DeviceRef)]
|
||||
pub struct Renderer2 {
|
||||
#[device]
|
||||
pub device: Device,
|
||||
pub samplers: SamplerCache,
|
||||
pub texture_manager: texture::TextureManager,
|
||||
|
|
@ -459,7 +1035,7 @@ impl Renderer2 {
|
|||
let future = cmds.submit(
|
||||
Some((frame.acquire, vk::PipelineStageFlags::TRANSFER)),
|
||||
Some(frame.release),
|
||||
Arc::new(sync::Fence::from_pool(&self.device, None)?),
|
||||
Arc::new(sync::Fence::from_pool(&self.device.pools.fences, None)?),
|
||||
)?;
|
||||
|
||||
future.await;
|
||||
|
|
@ -475,7 +1051,8 @@ impl Renderer2 {
|
|||
pub use vk::Extent2D;
|
||||
|
||||
use crate::{
|
||||
instance::InstanceDesc,
|
||||
device::Extension,
|
||||
instance::{InstanceDesc, InstanceInner},
|
||||
};
|
||||
|
||||
pub mod utils {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use parking_lot::Mutex;
|
|||
|
||||
use crate::{
|
||||
device::{
|
||||
Device, DeviceObject,
|
||||
Device, DeviceHandle, DeviceInner, DeviceObject, asdf::traits::ExternallyManagedObject,
|
||||
},
|
||||
make_extension,
|
||||
};
|
||||
|
|
@ -229,6 +229,20 @@ pub struct DescriptorSetAllocDesc<'a> {
|
|||
pub layout: &'a DescriptorSetLayout,
|
||||
}
|
||||
|
||||
impl DeviceHandle for vk::DescriptorPool {
|
||||
unsafe fn destroy(&mut self, device: &Device) {
|
||||
unsafe { device.dev().destroy_descriptor_pool(*self, None) };
|
||||
}
|
||||
}
|
||||
impl<T: AsRef<DeviceInner>> ExternallyManagedObject<T> for vk::DescriptorPool {
|
||||
unsafe fn destroy(self, device: &T) {
|
||||
// SAFETY: We have exclusive ownership of the descriptor pool, so it's safe to destroy it.
|
||||
unsafe {
|
||||
device.as_ref().raw.destroy_descriptor_pool(self, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DescriptorPool {
|
||||
pool: DeviceObject<vk::DescriptorPool>,
|
||||
|
|
@ -286,6 +300,17 @@ impl DescriptorPool {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<DeviceInner>> ExternallyManagedObject<T> for vk::DescriptorSetLayout {
|
||||
unsafe fn destroy(self, device: &T) {
|
||||
unsafe {
|
||||
device
|
||||
.as_ref()
|
||||
.raw
|
||||
.destroy_descriptor_set_layout(self, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DescriptorSetLayout {
|
||||
layout: DeviceObject<vk::DescriptorSetLayout>,
|
||||
|
|
@ -335,6 +360,14 @@ impl DescriptorSetLayout {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<DeviceInner>> ExternallyManagedObject<T> for vk::PipelineLayout {
|
||||
unsafe fn destroy(self, device: &T) {
|
||||
unsafe {
|
||||
device.as_ref().raw.destroy_pipeline_layout(self, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PipelineLayout {
|
||||
layout: DeviceObject<vk::PipelineLayout>,
|
||||
|
|
@ -424,6 +457,17 @@ impl std::hash::Hash for SamplerDesc {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> ExternallyManagedObject<T> for vk::Sampler
|
||||
where
|
||||
T: AsRef<DeviceInner>,
|
||||
{
|
||||
unsafe fn destroy(self, device: &T) {
|
||||
unsafe {
|
||||
device.as_ref().raw.destroy_sampler(self, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Sampler {
|
||||
sampler: DeviceObject<vk::Sampler>,
|
||||
|
|
@ -461,6 +505,14 @@ impl Sampler {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<DeviceInner>> ExternallyManagedObject<T> for vk::ShaderModule {
|
||||
unsafe fn destroy(self, device: &T) {
|
||||
unsafe {
|
||||
device.as_ref().raw.destroy_shader_module(self, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ShaderModule {
|
||||
module: DeviceObject<vk::ShaderModule>,
|
||||
|
|
@ -505,6 +557,14 @@ pub struct Pipeline {
|
|||
layout: Arc<PipelineLayout>,
|
||||
}
|
||||
|
||||
impl<T: AsRef<DeviceInner>> ExternallyManagedObject<T> for vk::Pipeline {
|
||||
unsafe fn destroy(self, device: &T) {
|
||||
unsafe {
|
||||
device.as_ref().raw.destroy_pipeline(self, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new_compute(device: Device, desc: ComputePipelineDesc) -> crate::Result<Self> {
|
||||
let info = &vk::ComputePipelineCreateInfo::default()
|
||||
|
|
@ -717,3 +777,122 @@ impl Pipeline {
|
|||
&self.layout
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod pipeline_cache {
|
||||
use std::sync::Arc;
|
||||
|
||||
use ash::vk;
|
||||
|
||||
use ash::Device;
|
||||
|
||||
use crate::PhysicalDeviceInfo;
|
||||
use crate::device::DeviceInner;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PipelineCache {
|
||||
#[allow(dead_code)]
|
||||
key: u128,
|
||||
pub(crate) raw: vk::PipelineCache,
|
||||
}
|
||||
|
||||
impl crate::device::asdf::traits::ExternallyManagedObject<Arc<DeviceInner>> for PipelineCache {
|
||||
unsafe fn destroy(self, owner: &Arc<DeviceInner>) {
|
||||
tracing::info!("destroying pipeline cache with key {:x}", self.key);
|
||||
if let Ok(data) = self.export(&owner.raw) {
|
||||
tracing::info!(
|
||||
"exported pipeline cache with key {:x} and size {} bytes",
|
||||
self.key,
|
||||
data.len()
|
||||
);
|
||||
_ = Self::write_to_disk(self.key, &data).inspect_err(|err| {
|
||||
tracing::error!("failed to write pipeline cache to disk: {err}");
|
||||
});
|
||||
}
|
||||
unsafe { owner.raw.destroy_pipeline_cache(self.raw, None) };
|
||||
}
|
||||
}
|
||||
|
||||
impl PipelineCache {
|
||||
const MAGIC: [u8; 4] = *b"VYPC";
|
||||
const KEY_VERSION: u32 = 1;
|
||||
const PATH: &'static str = "pipeline_cache.bin";
|
||||
fn calculate_key(adapter: &PhysicalDeviceInfo) -> u128 {
|
||||
use md5::Digest;
|
||||
let mut hasher = md5::Md5::new();
|
||||
let props = &adapter.properties;
|
||||
hasher.update(bytemuck::bytes_of(&[
|
||||
props.core.vendor_id,
|
||||
props.core.api_version,
|
||||
props.core.device_id,
|
||||
props.core.driver_version,
|
||||
]));
|
||||
u128::from_le_bytes(hasher.finalize().into())
|
||||
}
|
||||
|
||||
fn load_from_disk(key: u128) -> Option<(u128, Vec<u8>)> {
|
||||
use std::io::Read;
|
||||
|
||||
let file = std::fs::File::open(Self::PATH).ok()?;
|
||||
let mut reader = std::io::BufReader::new(file);
|
||||
let mut magic = [0; 4];
|
||||
reader.read_exact(&mut magic).ok()?;
|
||||
if magic != Self::MAGIC {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut version = 0;
|
||||
reader
|
||||
.read_exact(bytemuck::bytes_of_mut(&mut version))
|
||||
.ok()?;
|
||||
if version != Self::KEY_VERSION {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut disk_key = 0;
|
||||
reader
|
||||
.read_exact(bytemuck::bytes_of_mut(&mut disk_key))
|
||||
.ok()?;
|
||||
if disk_key != key {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut data = Vec::new();
|
||||
reader.read_to_end(&mut data).ok()?;
|
||||
|
||||
Some((key, data))
|
||||
}
|
||||
|
||||
fn write_to_disk(key: u128, data: &[u8]) -> std::io::Result<()> {
|
||||
use std::io::Write;
|
||||
|
||||
let file = std::fs::File::create(Self::PATH)?;
|
||||
let mut writer = std::io::BufWriter::new(file);
|
||||
writer.write_all(&Self::MAGIC)?;
|
||||
writer.write_all(bytemuck::bytes_of(&Self::KEY_VERSION))?;
|
||||
writer.write_all(bytemuck::bytes_of(&key))?;
|
||||
writer.write_all(data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new(device: &Device, adapter: &PhysicalDeviceInfo) -> crate::Result<Self> {
|
||||
let key = Self::calculate_key(adapter);
|
||||
let data = Self::load_from_disk(key).map(|(key, data)| {
|
||||
tracing::info!("loaded pipeline cache from disk with key {key:x}");
|
||||
data
|
||||
});
|
||||
|
||||
let info = vk::PipelineCacheCreateInfo::default()
|
||||
// .flags(vk::PipelineCacheCreateFlags::EXTERNALLY_SYNCHRONIZED)
|
||||
.initial_data(data.as_deref().unwrap_or_default());
|
||||
|
||||
let cache = unsafe { device.create_pipeline_cache(&info, None)? };
|
||||
|
||||
Ok(Self { key, raw: cache })
|
||||
}
|
||||
|
||||
pub fn export(&self, device: &ash::Device) -> crate::Result<Vec<u8>> {
|
||||
let data = unsafe { device.get_pipeline_cache_data(self.raw)? };
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
use bitflags::bitflags;
|
||||
use core::fmt;
|
||||
use parking_lot::Mutex;
|
||||
use raw_window_handle::RawDisplayHandle;
|
||||
use std::{collections::HashMap, ops::Deref, sync::Arc};
|
||||
|
|
@ -37,73 +36,35 @@ impl Queue {
|
|||
F: FnOnce(&Queue) -> R,
|
||||
{
|
||||
let _lock = self.inner.lock.lock();
|
||||
f(self)
|
||||
f(&self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct QueueInner {
|
||||
pub raw: vk::Queue,
|
||||
pub family: QueueFamily,
|
||||
pub lock: Mutex<()>,
|
||||
pub(crate) raw: vk::Queue,
|
||||
pub(crate) family: QueueFamily,
|
||||
pub(crate) lock: Mutex<()>,
|
||||
}
|
||||
|
||||
impl QueueInner {
|
||||
pub fn raw(&self) -> vk::Queue {
|
||||
self.raw
|
||||
}
|
||||
pub fn family_index(&self) -> QueueFamilyIndex {
|
||||
pub fn family_index(&self) -> u32 {
|
||||
self.family.index
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct QueueFamily {
|
||||
pub index: QueueFamilyIndex,
|
||||
pub index: u32,
|
||||
pub count: u32,
|
||||
pub flags: QueueFlags,
|
||||
pub granularity: TransferGranuality,
|
||||
pub timestamp_valid_bits: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct QueueFamilyIndex(u32);
|
||||
|
||||
pub trait QueueFamilyIndices {
|
||||
fn as_u32_slice(&self) -> &[u32];
|
||||
}
|
||||
|
||||
impl QueueFamilyIndices for [QueueFamilyIndex] {
|
||||
fn as_u32_slice(&self) -> &[u32] {
|
||||
// SAFETY: QueueFamilyIndex is repr(transparent) over u32
|
||||
unsafe { std::slice::from_raw_parts(self.as_ptr() as *const u32, self.len()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for QueueFamilyIndex {
|
||||
fn default() -> Self {
|
||||
Self::INVALID
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for QueueFamilyIndex {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "q{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl QueueFamilyIndex {
|
||||
const INVALID: QueueFamilyIndex = QueueFamilyIndex(u32::MAX);
|
||||
pub fn new(index: u32) -> Self {
|
||||
Self(index)
|
||||
}
|
||||
|
||||
pub fn index(&self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct QueueFlags: u16 {
|
||||
|
|
@ -182,11 +143,8 @@ impl DeviceQueues {
|
|||
&self.transfer
|
||||
}
|
||||
|
||||
pub fn family_indices(
|
||||
&self,
|
||||
flags: crate::device::QueueFlags,
|
||||
) -> ArrayVec<[QueueFamilyIndex; 4]> {
|
||||
let mut indices = array_vec!([QueueFamilyIndex; 4]);
|
||||
pub fn family_indices(&self, flags: crate::device::QueueFlags) -> ArrayVec<[u32; 4]> {
|
||||
let mut indices = array_vec!([u32; 4]);
|
||||
use crate::device::QueueFlags as QF;
|
||||
if flags.intersects(QF::GRAPHICS | QF::PRESENT) {
|
||||
indices.push(self.graphics.family.index);
|
||||
|
|
@ -202,7 +160,7 @@ impl DeviceQueues {
|
|||
indices
|
||||
}
|
||||
|
||||
pub fn swapchain_family_indices(&self) -> &[QueueFamilyIndex] {
|
||||
pub fn swapchain_family_indices(&self) -> &[u32] {
|
||||
core::slice::from_ref(&self.graphics.family.index)
|
||||
}
|
||||
|
||||
|
|
@ -245,13 +203,13 @@ impl DeviceQueueInfos {
|
|||
.into_iter()
|
||||
.map(|(index, count)| {
|
||||
vk::DeviceQueueCreateInfo::default()
|
||||
.queue_family_index(index.index())
|
||||
.queue_family_index(index)
|
||||
.queue_priorities(&Self::PRIORITIES[..count as usize])
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn queue_family_indices(&self) -> HashMap<QueueFamilyIndex, u32> {
|
||||
fn queue_family_indices(&self) -> HashMap<u32, u32> {
|
||||
let mut families = HashMap::new();
|
||||
|
||||
families.insert(self.graphics.index, 1);
|
||||
|
|
@ -273,12 +231,12 @@ impl DeviceQueueInfos {
|
|||
.map(|(queue_family_index, count)| {
|
||||
let queues = (0..count)
|
||||
.map(|queue_index| unsafe {
|
||||
dev.get_device_queue(queue_family_index.index(), queue_index)
|
||||
dev.get_device_queue(queue_family_index, queue_index)
|
||||
})
|
||||
.collect();
|
||||
(queue_family_index, queues)
|
||||
})
|
||||
.collect::<HashMap<QueueFamilyIndex, Vec<vk::Queue>>>();
|
||||
.collect::<HashMap<u32, Vec<vk::Queue>>>();
|
||||
|
||||
let graphics = Queue::from_vk_queue(
|
||||
queues.get_mut(&self.graphics.index).unwrap().pop().unwrap(),
|
||||
|
|
@ -327,14 +285,15 @@ impl DeviceQueueInfos {
|
|||
.enumerate()
|
||||
.map(|(index, props)| {
|
||||
let mut flags = QueueFlags::from_vk_flags(props.queue_flags);
|
||||
if let Some(display_handle) = display_handle
|
||||
&& instance.query_presentation_support(pdev.pdev, index as u32, display_handle)
|
||||
{
|
||||
flags |= QueueFlags::PRESENT;
|
||||
if let Some(display_handle) = display_handle {
|
||||
if instance.query_presentation_support(pdev.pdev, index as u32, display_handle)
|
||||
{
|
||||
flags |= QueueFlags::PRESENT;
|
||||
}
|
||||
}
|
||||
|
||||
QueueFamily {
|
||||
index: QueueFamilyIndex(index as u32),
|
||||
index: index as u32,
|
||||
count: props.queue_count,
|
||||
flags,
|
||||
granularity: TransferGranuality::from_vk_granularity(
|
||||
|
|
@ -471,6 +471,52 @@ pub struct RenderAttachment {
|
|||
pub store_op: vk::AttachmentStoreOp,
|
||||
}
|
||||
|
||||
impl RenderAttachment {
|
||||
pub fn side_effect(&self, mut map: SideEffectMap, load: bool) {
|
||||
for attachment in &self.color_attachments {
|
||||
// The dependencies declared here need to be barrier'd before and
|
||||
// after the render pass: the spec says pipelineBarrier inside of a
|
||||
// render pass instance referencing framebuffer-local stages must
|
||||
// contain only framebuffer-local stages.
|
||||
|
||||
map.with(
|
||||
vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
|
||||
if attachment.load_op == vk::AttachmentLoadOp::LOAD {
|
||||
vk::AccessFlags2::COLOR_ATTACHMENT_READ
|
||||
} else {
|
||||
vk::AccessFlags2::empty()
|
||||
},
|
||||
Some(attachment.layout),
|
||||
)
|
||||
.insert_range(attachment.texture.id(), attachment.region);
|
||||
}
|
||||
if let Some(depth) = &self.depth_attachment {
|
||||
map.with(
|
||||
vk::PipelineStageFlags2::EARLY_FRAGMENT_TESTS,
|
||||
if depth.load_op == vk::AttachmentLoadOp::LOAD {
|
||||
vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_READ
|
||||
} else {
|
||||
vk::AccessFlags2::empty()
|
||||
},
|
||||
Some(depth.layout),
|
||||
)
|
||||
.insert_range(depth.texture.id(), depth.region);
|
||||
}
|
||||
if let Some(stencil) = &self.stencil_attachment {
|
||||
map.with(
|
||||
vk::PipelineStageFlags2::EARLY_FRAGMENT_TESTS,
|
||||
if stencil.load_op == vk::AttachmentLoadOp::LOAD {
|
||||
vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_READ
|
||||
} else {
|
||||
vk::AccessFlags2::empty()
|
||||
},
|
||||
Some(stencil.layout),
|
||||
)
|
||||
.insert_range(stencil.texture.id(), stencil.region);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BeginRendering {
|
||||
pub color_attachments: Vec<RenderAttachment>,
|
||||
pub depth_attachment: Option<RenderAttachment>,
|
||||
|
|
@ -479,7 +525,21 @@ pub struct BeginRendering {
|
|||
pub layers: u32,
|
||||
}
|
||||
|
||||
pub struct EndRendering;
|
||||
pub struct EndRendering {
|
||||
pub color_attachments: Vec<RenderAttachment>,
|
||||
pub depth_attachment: Option<RenderAttachment>,
|
||||
pub stencil_attachment: Option<RenderAttachment>,
|
||||
}
|
||||
|
||||
impl BeginRendering {
|
||||
fn end_rendering(&self) -> EndRendering {
|
||||
EndRendering {
|
||||
color_attachments: self.color_attachments.clone(),
|
||||
depth_attachment: self.depth_attachment.clone(),
|
||||
stencil_attachment: self.stencil_attachment.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Command for BeginRendering {
|
||||
fn side_effects(&self, mut map: SideEffectMap) {
|
||||
|
|
|
|||
|
|
@ -80,7 +80,11 @@ impl<'r> GraphBuilder<'r> {
|
|||
|
||||
mod api_demo {
|
||||
|
||||
use crate::{EguiState, pipeline::Pipeline, render_graph::commands};
|
||||
use crate::{
|
||||
EguiState,
|
||||
pipeline::Pipeline,
|
||||
render_graph::{commands, resources::TextureRegion},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use ash::vk::{self, BufferUsageFlags};
|
||||
|
|
@ -150,17 +154,13 @@ mod api_demo {
|
|||
let staging_size = vertex_size + index_size + draw_calls_size + textures_indices_size;
|
||||
let mut staging = builder.make_staging_buffer(staging_size as u64);
|
||||
|
||||
let staging_map = staging.map_mut().unwrap();
|
||||
let mut offset = 0;
|
||||
for data in [
|
||||
bytemuck::cast_slice(&vertices),
|
||||
bytemuck::cast_slice(&indices),
|
||||
bytemuck::cast_slice(&draw_calls),
|
||||
bytemuck::cast_slice(&textures_indices),
|
||||
] {
|
||||
staging_map[offset..offset + data.len()].copy_from_slice(data);
|
||||
offset += data.len();
|
||||
}
|
||||
use core::io::BorrowedBuf;
|
||||
let mut buf = BorrowedBuf::from(staging.map_mut().unwrap());
|
||||
let mut cursor = buf.unfilled();
|
||||
cursor.append(bytemuck::cast_slice(&vertices));
|
||||
cursor.append(bytemuck::cast_slice(&indices));
|
||||
cursor.append(bytemuck::cast_slice(&draw_calls));
|
||||
cursor.append(bytemuck::cast_slice(&textures_indices));
|
||||
|
||||
let vertex_buffer = builder.make_buffer(
|
||||
vertex_size as u64,
|
||||
|
|
@ -177,7 +177,7 @@ mod api_demo {
|
|||
BufferUsageFlags::TRANSFER_DST | BufferUsageFlags::INDIRECT_BUFFER,
|
||||
MemoryLocation::GpuOnly,
|
||||
);
|
||||
let _texture_indices = builder.make_buffer(
|
||||
let texture_indices = builder.make_buffer(
|
||||
textures_indices_size as u64,
|
||||
BufferUsageFlags::TRANSFER_DST | BufferUsageFlags::STORAGE_BUFFER,
|
||||
MemoryLocation::GpuOnly,
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ pub struct RenderPass<'cmd, 'a> {
|
|||
|
||||
impl<'cmd, 'a> RenderPass<'cmd, 'a> {
|
||||
pub fn finalise(self) {
|
||||
self.cmd_list.push_inner(super::commands::EndRendering {});
|
||||
self.cmd_list.push_inner(super::commands::EndRendering);
|
||||
}
|
||||
|
||||
pub fn push<C: Command + InsideRenderPass + 'cmd>(&mut self, command: C) {
|
||||
|
|
@ -396,7 +396,7 @@ impl<'cmd> CommandList<'cmd> {
|
|||
|
||||
// construct barriers
|
||||
// TODO: need to know each images aspects at this point
|
||||
for _barrier in barriers {
|
||||
for barrier in barriers {
|
||||
// stuff
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::ops::Deref;
|
|||
|
||||
use ash::vk;
|
||||
|
||||
use crate::images::MipRange;
|
||||
use crate::{images::MipRange, render_graph::recorder::SideEffectMap2};
|
||||
|
||||
pub trait Resource: Sized {
|
||||
fn id(&self) -> ResourceId;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use ash::vk;
|
||||
use glam::{
|
||||
camera::rh::{proj, view},
|
||||
vec3,
|
||||
};
|
||||
use glam::{f32::Mat4, vec3};
|
||||
use gpu_allocator::MemoryLocation;
|
||||
|
||||
pub use crate::egui_pass::{egui_pass, egui_pre_pass};
|
||||
|
|
@ -164,7 +161,11 @@ impl Wireframe {
|
|||
);
|
||||
}
|
||||
|
||||
let future = cmd.submit_async(None, None, Arc::new(sync::Fence::from_pool(&dev, None)?))?;
|
||||
let future = cmd.submit_async(
|
||||
None,
|
||||
None,
|
||||
Arc::new(sync::Fence::from_pool(&dev.pools.fences, None)?),
|
||||
)?;
|
||||
|
||||
let pipeline = Self::create_pipeline(dev.clone())?;
|
||||
|
||||
|
|
@ -318,14 +319,14 @@ impl Wireframe {
|
|||
let cmd = &ctx.cmd;
|
||||
|
||||
//let model = Mat4::from_scale(vec3(0.01, 0.01, 0.01));
|
||||
let proj = proj::vulkan::perspective(
|
||||
let proj = Mat4::perspective_lh(
|
||||
core::f32::consts::FRAC_PI_4,
|
||||
target.width() as f32 / target.height() as f32,
|
||||
0.1,
|
||||
100.0,
|
||||
);
|
||||
let pos = vec3(0.0, 0.0, -5.0);
|
||||
let view = view::look_at_mat4(pos, pos + vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 0.0));
|
||||
let view = Mat4::look_at_rh(pos, pos + vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 0.0));
|
||||
let to_clip = proj * view; // * model;
|
||||
|
||||
let color_attachment = &vk::RenderingAttachmentInfo::default()
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ use ash::{
|
|||
};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
|
||||
use renderer_device::queue::QueueFamilyIndices;
|
||||
|
||||
use crate::{
|
||||
Instance, PhysicalDeviceInfo, Result, SurfaceCapabilities,
|
||||
|
|
@ -148,12 +147,7 @@ impl Surface {
|
|||
adapter: &PhysicalDeviceInfo,
|
||||
config: &mut SwapchainConfiguration,
|
||||
) -> Result<SurfaceCapabilities> {
|
||||
let surface_caps = instance.get_adapter_surface_capabilities(
|
||||
adapter.pdev,
|
||||
&self.functor,
|
||||
self.raw,
|
||||
None,
|
||||
)?;
|
||||
let surface_caps = instance.get_adapter_surface_capabilities(adapter.pdev, self, None)?;
|
||||
|
||||
let max_image_dim = adapter.properties.core.limits.max_image_dimension2_d;
|
||||
if config.extent.width > max_image_dim || config.extent.height > max_image_dim {
|
||||
|
|
@ -209,8 +203,7 @@ impl Surface {
|
|||
// capabilities to get the correct image counts.
|
||||
let surface_caps = instance.get_adapter_surface_capabilities(
|
||||
adapter.pdev,
|
||||
&self.functor,
|
||||
self.raw,
|
||||
self,
|
||||
Some(config.present_mode),
|
||||
)?;
|
||||
|
||||
|
|
@ -247,12 +240,7 @@ impl Surface {
|
|||
instance: &Instance,
|
||||
adapter: &PhysicalDeviceInfo,
|
||||
) -> Result<SwapchainConfiguration> {
|
||||
let surface_caps = instance.get_adapter_surface_capabilities(
|
||||
adapter.pdev,
|
||||
&self.functor,
|
||||
self.raw,
|
||||
None,
|
||||
)?;
|
||||
let surface_caps = instance.get_adapter_surface_capabilities(adapter.pdev, self, None)?;
|
||||
|
||||
let present_mode = surface_caps
|
||||
.present_modes
|
||||
|
|
@ -434,7 +422,7 @@ impl Swapchain {
|
|||
} else {
|
||||
vk::SharingMode::CONCURRENT
|
||||
})
|
||||
.queue_family_indices(queue_families.as_u32_slice())
|
||||
.queue_family_indices(queue_families)
|
||||
.pre_transform(vk::SurfaceTransformFlagsKHR::IDENTITY)
|
||||
.composite_alpha(config.composite_alpha_mode)
|
||||
.old_swapchain(old_swapchain)
|
||||
|
|
@ -543,7 +531,7 @@ impl Swapchain {
|
|||
tracing::trace!(frame, "acquiring image for frame {frame}");
|
||||
|
||||
async move {
|
||||
let fence = Fence::from_pool(self.swapchain.device(), None)?;
|
||||
let fence = Fence::from_pool(&self.swapchain.device().pools.fences, None)?;
|
||||
let acquire = self.acquire_semaphores[frame];
|
||||
|
||||
// spawn on threadpool because it might block.
|
||||
|
|
@ -563,17 +551,7 @@ impl Swapchain {
|
|||
|
||||
let idx = idx as usize;
|
||||
let image = self.images[idx];
|
||||
let image = Arc::new(images::Image::from_swapchain_raw(
|
||||
image,
|
||||
self.swapchain.device().clone(),
|
||||
self.config.format,
|
||||
vk::Extent3D {
|
||||
width: self.config.extent.width,
|
||||
height: self.config.extent.height,
|
||||
depth: 1,
|
||||
},
|
||||
self.config.usage,
|
||||
));
|
||||
let image = Arc::new(images::Image::from_swapchain_image(image, &self));
|
||||
let view = image.create_view(ImageViewDesc {
|
||||
name: Some(format!("swapchain-{:x}-image-view-{idx}", self.raw().as_raw()).into()),
|
||||
kind: vk::ImageViewType::TYPE_2D,
|
||||
|
|
@ -854,7 +832,7 @@ mod tests {
|
|||
|
||||
let surface = Arc::new(Surface::headless(&instance)?);
|
||||
|
||||
let adapter = instance.choose_adapter_default(Some(surface.raw), &[], Some(&features))?;
|
||||
let adapter = instance.choose_adapter_default(Some(&surface), &[], Some(&features))?;
|
||||
let device = adapter.create_logical_device(&instance, &[], features, None)?;
|
||||
|
||||
surface.configure(
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ use std::{
|
|||
};
|
||||
|
||||
use crate::device::{
|
||||
Pool, PoolObject, Pooled, Recycle,
|
||||
DevicePools, Pool, PoolObject, Pooled,
|
||||
asdf::{
|
||||
DeviceObject,
|
||||
DeviceObject, InnerDeviceObject,
|
||||
traits::ExternallyManagedObject as ExternallyManagedObjectTrait,
|
||||
},
|
||||
};
|
||||
|
|
@ -62,7 +62,7 @@ impl SyncThreadpool {
|
|||
use std::sync::atomic::Ordering;
|
||||
match self
|
||||
.num_threads
|
||||
.try_update(Ordering::Release, Ordering::Acquire, |i| {
|
||||
.fetch_update(Ordering::Release, Ordering::Acquire, |i| {
|
||||
if i < self.max_threads {
|
||||
Some(i + 1)
|
||||
} else {
|
||||
|
|
@ -160,17 +160,17 @@ impl SyncThreadpool {
|
|||
|
||||
pub enum Fence {
|
||||
Dedicated {
|
||||
fence: DeviceObject<vk::Fence>,
|
||||
fence: InnerDeviceObject<vk::Fence>,
|
||||
},
|
||||
Pooled {
|
||||
fence: PoolObject<vk::Fence>,
|
||||
fence: PoolObject<vk::Fence, Arc<Pool<vk::Fence>>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Pooled for vk::Fence {
|
||||
fn create_from_pool(pool: &Pool<Self>) -> Result<Self> {
|
||||
let fence = unsafe {
|
||||
pool.device()
|
||||
pool.device
|
||||
.raw
|
||||
.create_fence(&vk::FenceCreateInfo::default(), None)?
|
||||
};
|
||||
|
|
@ -179,15 +179,17 @@ impl Pooled for vk::Fence {
|
|||
}
|
||||
}
|
||||
|
||||
impl Recycle for vk::Fence {
|
||||
fn recycle(self, device: &Device) {
|
||||
device.pools.fences.push(self);
|
||||
impl ExternallyManagedObjectTrait<Arc<Pool<vk::Fence>>> for vk::Fence {
|
||||
unsafe fn destroy(self, pool: &Arc<Pool<vk::Fence>>) {
|
||||
pool.push(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternallyManagedObjectTrait<Device> for vk::Fence {
|
||||
unsafe fn destroy(self, device: &Device) {
|
||||
unsafe { device.raw.destroy_fence(self, None) };
|
||||
impl<T: AsRef<DeviceInner>> ExternallyManagedObjectTrait<T> for vk::Fence {
|
||||
unsafe fn destroy(self, device: &T) {
|
||||
unsafe {
|
||||
device.as_ref().raw.destroy_fence(self, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -205,14 +207,14 @@ impl Fence {
|
|||
.create_fence(&vk::FenceCreateInfo::default(), None)?
|
||||
};
|
||||
Ok(Self::Dedicated {
|
||||
fence: DeviceObject::new_debug_named(device, fence, name),
|
||||
fence: DeviceObject::new_debug_named(device.shared.clone(), fence, name),
|
||||
})
|
||||
}
|
||||
pub fn from_pool(device: &Device, name: Option<&'static str>) -> Result<Fence> {
|
||||
let fence = device.pools.fences.get_debug_named(name)?;
|
||||
pub fn from_pool(pool: &Arc<Pool<vk::Fence>>, name: Option<&'static str>) -> Result<Fence> {
|
||||
let fence = pool.get_debug_named(name)?;
|
||||
|
||||
Ok(Self::Pooled {
|
||||
fence: PoolObject::new(fence, device.clone()),
|
||||
fence: PoolObject::new(fence, pool.clone()),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -223,10 +225,10 @@ impl Fence {
|
|||
}
|
||||
}
|
||||
|
||||
fn device(&self) -> &Device {
|
||||
fn device(&self) -> &Arc<DeviceInner> {
|
||||
match self {
|
||||
Fence::Dedicated { fence } => fence.device(),
|
||||
Fence::Pooled { fence } => fence.device(),
|
||||
Fence::Dedicated { fence } => &fence.device(),
|
||||
Fence::Pooled { fence } => &fence.owner().device,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -276,21 +278,21 @@ pub enum SemaphoreInner {
|
|||
Timeline(vk::Semaphore),
|
||||
}
|
||||
|
||||
impl Recycle for SemaphoreInner {
|
||||
fn recycle(self, owner: &Device) {
|
||||
impl ExternallyManagedObjectTrait<Arc<DevicePools>> for SemaphoreInner {
|
||||
unsafe fn destroy(self, owner: &Arc<DevicePools>) {
|
||||
match self {
|
||||
SemaphoreInner::Binary(semaphore) => {
|
||||
owner.pools.binary_semaphores.push(BinarySemaphore(semaphore))
|
||||
owner.binary_semaphores.push(BinarySemaphore(semaphore))
|
||||
}
|
||||
SemaphoreInner::Timeline(semaphore) => {
|
||||
owner.pools.timeline_semaphores.push(TimelineSemaphore(semaphore))
|
||||
owner.timeline_semaphores.push(TimelineSemaphore(semaphore))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternallyManagedObjectTrait<Device> for SemaphoreInner {
|
||||
unsafe fn destroy(self, owner: &Device) {
|
||||
impl ExternallyManagedObjectTrait<Arc<DeviceInner>> for SemaphoreInner {
|
||||
unsafe fn destroy(self, owner: &Arc<DeviceInner>) {
|
||||
match self {
|
||||
SemaphoreInner::Binary(semaphore) | SemaphoreInner::Timeline(semaphore) => {
|
||||
unsafe { owner.raw.destroy_semaphore(semaphore, None) };
|
||||
|
|
@ -334,18 +336,18 @@ impl From<TimelineSemaphore> for SemaphoreInner {
|
|||
|
||||
pub enum Semaphore {
|
||||
Dedicated {
|
||||
semaphore: DeviceObject<SemaphoreInner>,
|
||||
semaphore: InnerDeviceObject<SemaphoreInner>,
|
||||
},
|
||||
Pooled {
|
||||
#[allow(private_interfaces)]
|
||||
semaphore: PoolObject<SemaphoreInner>,
|
||||
semaphore: PoolObject<SemaphoreInner, Arc<DevicePools>>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BinarySemaphore(pub vk::Semaphore);
|
||||
pub(crate) struct BinarySemaphore(pub(crate) vk::Semaphore);
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct TimelineSemaphore(pub vk::Semaphore);
|
||||
pub(crate) struct TimelineSemaphore(pub(crate) vk::Semaphore);
|
||||
|
||||
// This is just so that ash can name these semaphore newtypes
|
||||
impl vk::Handle for BinarySemaphore {
|
||||
|
|
@ -377,7 +379,7 @@ impl Pooled for BinarySemaphore {
|
|||
let mut type_info =
|
||||
vk::SemaphoreTypeCreateInfo::default().semaphore_type(vk::SemaphoreType::BINARY);
|
||||
let create_info = vk::SemaphoreCreateInfo::default().push_next(&mut type_info);
|
||||
let inner = unsafe { pool.device().raw.create_semaphore(&create_info, None)? };
|
||||
let inner = unsafe { pool.device.raw.create_semaphore(&create_info, None)? };
|
||||
Ok(Self(inner))
|
||||
}
|
||||
}
|
||||
|
|
@ -388,7 +390,7 @@ impl Pooled for TimelineSemaphore {
|
|||
.semaphore_type(vk::SemaphoreType::TIMELINE)
|
||||
.initial_value(0);
|
||||
let create_info = vk::SemaphoreCreateInfo::default().push_next(&mut type_info);
|
||||
let inner = unsafe { pool.device().raw.create_semaphore(&create_info, None)? };
|
||||
let inner = unsafe { pool.device.raw.create_semaphore(&create_info, None)? };
|
||||
Ok(Self(inner))
|
||||
}
|
||||
}
|
||||
|
|
@ -419,7 +421,7 @@ impl Semaphore {
|
|||
};
|
||||
|
||||
Ok(Self::Dedicated {
|
||||
semaphore: DeviceObject::new_debug_named(device, inner, name),
|
||||
semaphore: DeviceObject::new_debug_named(device.shared.clone(), inner, name),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -432,7 +434,7 @@ impl Semaphore {
|
|||
SemaphoreType::Binary => {
|
||||
let semaphore: SemaphoreInner =
|
||||
device.pools.binary_semaphores.get_debug_named(name)?.into();
|
||||
PoolObject::new(semaphore, device)
|
||||
PoolObject::new(semaphore, device.pools.clone())
|
||||
}
|
||||
SemaphoreType::Timeline(value) => {
|
||||
let semaphore: SemaphoreInner = device
|
||||
|
|
@ -448,7 +450,7 @@ impl Semaphore {
|
|||
device.raw.signal_semaphore(&info)?;
|
||||
}
|
||||
|
||||
PoolObject::new(semaphore, device)
|
||||
PoolObject::new(semaphore, device.pools.clone())
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
use std::{
|
||||
borrow::Cow,
|
||||
mem::ManuallyDrop,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
|
@ -6,7 +7,75 @@ use std::{
|
|||
use ash::vk;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
pub use renderer_resources::util::{F32, eq_f32, hash_f32, ord_f32};
|
||||
pub(crate) mod weak_vec {
|
||||
#![allow(dead_code)]
|
||||
//! Module containing the [`WeakVec`] API.
|
||||
|
||||
use std::{sync::Weak, vec::Vec};
|
||||
|
||||
/// An optimized container for `Weak` references of `T` that minimizes reallocations by
|
||||
/// dropping older elements that no longer have strong references to them.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct WeakVec<T> {
|
||||
inner: Vec<Weak<T>>,
|
||||
}
|
||||
|
||||
impl<T> Default for WeakVec<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inner: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> WeakVec<T> {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self { inner: Vec::new() }
|
||||
}
|
||||
|
||||
/// Pushes a new element to this collection.
|
||||
///
|
||||
/// If the inner Vec needs to be reallocated, we will first drop older elements that
|
||||
/// no longer have strong references to them.
|
||||
pub(crate) fn push(&mut self, value: Weak<T>) {
|
||||
if self.inner.len() == self.inner.capacity() {
|
||||
// Iterating backwards has the advantage that we don't do more work than we have to.
|
||||
for i in (0..self.inner.len()).rev() {
|
||||
if self.inner[i].strong_count() == 0 {
|
||||
self.inner.swap_remove(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure our capacity is twice the number of live elements.
|
||||
// Leaving some spare capacity ensures that we won't re-scan immediately.
|
||||
self.inner.reserve_exact(self.inner.len());
|
||||
}
|
||||
|
||||
self.inner.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct WeakVecIter<T> {
|
||||
inner: std::vec::IntoIter<Weak<T>>,
|
||||
}
|
||||
|
||||
impl<T> Iterator for WeakVecIter<T> {
|
||||
type Item = Weak<T>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoIterator for WeakVec<T> {
|
||||
type Item = Weak<T>;
|
||||
type IntoIter = WeakVecIter<T>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
WeakVecIter {
|
||||
inner: self.inner.into_iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(derive_more::Debug)]
|
||||
pub(crate) struct DropGuard(#[debug(skip)] ManuallyDrop<Box<dyn FnOnce()>>);
|
||||
|
|
@ -25,6 +94,62 @@ impl Drop for DropGuard {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct DebugName {
|
||||
#[cfg(debug_assertions)]
|
||||
name: Cow<'static, str>,
|
||||
}
|
||||
|
||||
impl core::fmt::Display for DebugName {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &**self)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for DebugName {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &**self)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Deref for DebugName {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
#[cfg(debug_assertions)]
|
||||
return &self.name;
|
||||
#[cfg(not(debug_assertions))]
|
||||
return "FEATURE_DISABLED";
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Cow<'static, str>> for DebugName {
|
||||
fn from(_value: Cow<'static, str>) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
name: _value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for DebugName {
|
||||
fn from(_value: &'static str) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
name: Cow::Borrowed(_value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for DebugName {
|
||||
fn from(_value: String) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
name: Cow::Owned(_value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! def_monotonic_id {
|
||||
($vis:vis $ty:ident) => {
|
||||
|
|
@ -351,6 +476,104 @@ impl Rect2D {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn eq_f32(lhs: f32, rhs: f32) -> bool {
|
||||
use core::num::FpCategory::*;
|
||||
match (lhs.classify(), rhs.classify()) {
|
||||
(Zero, Zero) | (Nan, Nan) => true,
|
||||
(Infinite, Infinite) => lhs.signum() == rhs.signum(),
|
||||
_ => lhs == rhs,
|
||||
}
|
||||
}
|
||||
pub fn ord_f32(lhs: f32, rhs: f32) -> core::cmp::Ordering {
|
||||
use core::cmp::Ordering;
|
||||
use core::num::FpCategory::*;
|
||||
match (lhs.classify(), rhs.classify()) {
|
||||
(Zero, Zero) | (Nan, Nan) => Ordering::Equal,
|
||||
(Infinite, Infinite) => {
|
||||
core::cmp::PartialOrd::partial_cmp(&lhs.signum(), &rhs.signum()).unwrap()
|
||||
}
|
||||
_ => core::cmp::PartialOrd::partial_cmp(&lhs, &rhs).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hash_f32<H: std::hash::Hasher>(state: &mut H, f: f32) {
|
||||
use std::hash::Hash;
|
||||
let classify = f.classify();
|
||||
match classify {
|
||||
std::num::FpCategory::Nan => (classify as u8).hash(state),
|
||||
std::num::FpCategory::Infinite | std::num::FpCategory::Zero => {
|
||||
(classify as u8, f.signum() as i8).hash(state)
|
||||
}
|
||||
std::num::FpCategory::Subnormal | std::num::FpCategory::Normal => f.to_bits().hash(state),
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct F32(f32);
|
||||
|
||||
impl F32 {
|
||||
#[inline]
|
||||
pub fn from_bits(bits: u32) -> Self {
|
||||
Self(f32::from_bits(bits))
|
||||
}
|
||||
#[inline]
|
||||
pub fn into_bits(self) -> u32 {
|
||||
self.0.to_bits()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for F32 {
|
||||
fn from(value: f32) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl From<F32> for f32 {
|
||||
fn from(value: F32) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for F32 {
|
||||
type Target = f32;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for F32 {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for F32 {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
eq_f32(self.0, other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for F32 {}
|
||||
|
||||
impl PartialOrd for F32 {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(ord_f32(self.0, other.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for F32 {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
ord_f32(self.0, other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::hash::Hash for F32 {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
hash_f32(state, self.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn timed<T, F: FnOnce() -> T>(label: &str, f: F) -> T {
|
||||
let now = std::time::Instant::now();
|
||||
let out = f();
|
||||
|
|
|
|||
|
|
@ -1,119 +0,0 @@
|
|||
# renderer_device
|
||||
|
||||
## Purpose
|
||||
`renderer_device` is the Vulkan instance, logical-device, queue, synchronization, and device-owned-object layer extracted from `renderer`. It owns Vulkan device lifecycle and shared device services; higher-level resource and rendering abstractions belong in `renderer`.
|
||||
|
||||
## Module map
|
||||
- `lib.rs`: crate error/result types, physical-device feature/property descriptions and queries, surface capabilities, extension helpers, and public module exports. `extern crate self as renderer_device` supports generated derive paths.
|
||||
- `device.rs`: `Device`/`DeviceInner`, logical-device creation, pools, ownership traits/wrappers, extension and allocation helpers.
|
||||
- `instance.rs`: `Instance`/`InstanceInner`, instance setup, adapter discovery/selection, surface capability queries, debug messenger lifecycle.
|
||||
- `queue.rs`: queue-family selection, `Queue`, queue synchronization, and `DeviceQueues`.
|
||||
- `sync.rs`: fence/semaphore wrappers and pools, `FenceFuture`, and background fence waiters.
|
||||
- `pipeline_cache.rs`: keyed Vulkan pipeline cache with `pipeline_cache.bin` load/export.
|
||||
- `debug.rs`: Vulkan debug callback forwarding messages to tracing.
|
||||
- `renderer_macros/src/lib.rs`: separate workspace proc-macro source; derives `DeviceRef` for a struct field marked `#[device]`.
|
||||
|
||||
## Key types and ownership
|
||||
- `Device(Arc<DeviceInner>)` is the cloneable owning handle. It dereferences to `DeviceInner`; equality is `Arc::ptr_eq`.
|
||||
- `DeviceInner` keeps the raw `ash::Device`, allocator mutex, adapter and instance, queues, synchronization threadpool, extension loaders, and inline `DevicePools`.
|
||||
- `PhysicalDeviceInfo::create_logical_device` selects queues, enables requested/supported extensions and features, initializes the allocator/cache, and constructs `DeviceInner` with `Arc::new_cyclic`.
|
||||
- `DeviceInner::dev`, `instance`, `queues`, `physical_device`, `main_queue`, `compute_queue`, and `transfer_queue` are the main convenience accessors for downstream code.
|
||||
- `DevicePools` contains a `PipelineCache` wrapper plus pools for raw fences and binary/timeline semaphore newtypes. A `Pool<T>` stores bare handles in `Mutex<Vec<T>>` and a `Weak<DeviceInner>`; `get()` reuses a handle or creates one through `Pooled`.
|
||||
- `PoolObject<T: Recycle>` strongly owns a `Device`; dropping it returns its handle to the relevant pool. The pool's weak reference prevents a `DeviceInner` ownership cycle.
|
||||
- `asdf::DeviceObject<T>` strongly owns its `Device`, dereferences to its handle, and destroys that handle through `asdf::traits::ExternallyManagedObject<Device>` on drop. `DeviceOwned<T>` exposes the owning device and raw handle.
|
||||
- `DeviceRef` supplies `device()` for `Device`, Arc/reference forms, and structs supported by the `DeviceRef` derive (`#[device]` identifies the field). `Pooled` creates pooled handles; `Recycle` returns them.
|
||||
- The `DeviceRef` proc macro is implemented in the separate `renderer_macros` package and emits paths rooted at `::renderer_device::`.
|
||||
- `sync::Fence` and `sync::Semaphore` distinguish `Dedicated` (`DeviceObject`) from `Pooled` (`PoolObject`) ownership. `SemaphoreInner`, `BinarySemaphore`, and `TimelineSemaphore` represent semaphore-kind handles.
|
||||
- `PhysicalDeviceInfo`, `PhysicalDeviceFeatures`, `PhysicalDeviceProperties`, and `SurfaceCapabilities` carry adapter/capability data. `Instance::choose_adapter_default` accepts `Option<vk::SurfaceKHR>` (a raw handle), not a swapchain/surface wrapper.
|
||||
- `Error` and `Result<T>` are the crate-wide error types; `Error` covers Vulkan, loading, allocator, I/O, and renderer-facing resource/capability failures.
|
||||
- `InstanceDesc` configures borrowed instance extensions/layers, optional app metadata, and a raw display handle; its default requests surface and validation layers, with validation settings in debug builds. `Instance::new` creates a shared `Instance` and requests debug-utils plus optional capability-query extensions alongside requested/platform extensions.
|
||||
- `Instance::expose_adapter` queries one adapter. `choose_adapter_default` ranks enumerated adapters against requested extensions/features and prefers discrete devices, then newer APIs, device-local memory, and texture size.
|
||||
- `PhysicalDeviceFeatures` stores core, Vulkan 1.1/1.2/1.3, and optional mesh-shader feature structures; `push_to_device_create_info` chains enabled feature structs.
|
||||
- `PhysicalDeviceProperties` stores supported extensions, core/versioned properties, optional mesh-shader properties, memory properties, and API version; `supports_extension` checks name and minimum version.
|
||||
- `get_physical_device_features` and `get_physical_device_properties` query the feature/property chains. Extension query results are consumed by the instance and logical-device setup helpers.
|
||||
- `DeviceExtensions` holds the debug-utils device loader and optional swapchain/mesh-shader loaders. `DeviceInner.enabled_extensions` records enabled extension names.
|
||||
- `AllocationStrategy` selects allocator-managed blocks or a dedicated allocation. `Allocation` represents `Owned`, `Shared`, or `Unmanaged` GPU allocations.
|
||||
- `get_extensions` returns supported/enabled and unsupported requested extension sets; `get_layers` returns available requested layers or the supported/unsupported split.
|
||||
- `Queue` shares a `QueueInner` through `Arc`; `with_locked` serializes operations on the Vulkan queue. `QueueFamily` records index, count, flags, granularity, and timestamp bits.
|
||||
- `DeviceQueues` provides graphics, compute, and transfer queues; absent compute/transfer queues alias the graphics queue. Queue selection requires the selected graphics family to support presentation.
|
||||
- `DeviceQueueInfos` chooses queue families and produces `vk::DeviceQueueCreateInfo` values before `retrieve_queues` obtains the raw Vulkan queues.
|
||||
- `QueueFlags` in `device.rs` describes requested graphics/async-compute/transfer/present families; `queue::QueueFlags` describes discovered Vulkan family capabilities. They are distinct types.
|
||||
- `FenceFuture` waits and resets a fence, and `SyncThreadpool` schedules blocking fence waits for async wakers. `Fence::from_pool` and `Semaphore::from_pool` wrap recycled pool entries.
|
||||
- `SemaphoreType` selects binary or timeline construction. `SemaphoreInner` wraps the common Vulkan handle while `BinarySemaphore` and `TimelineSemaphore` tag their respective pools.
|
||||
- `Fence::wait_on`, `is_signaled`, and `reset` operate on either dedicated or pooled fence handles through their owning device.
|
||||
- Pooled timeline semaphores are created at value zero; `Semaphore::from_pool` signals the requested initial value when it obtains one.
|
||||
- `PipelineCache::new` computes its key from adapter vendor/API/device/driver properties and tries to load compatible data; cache persistence uses a magic and format version.
|
||||
- `DebugName` is the wrapper for Vulkan object debug names.
|
||||
- `Extension` is the name/version pair used by extension helpers and adapter creation. `make_extension!`, `get_extensions`, `get_layers`, and the support/intersection functions centralize Vulkan extension discovery.
|
||||
- `SurfaceCapabilities` returns raw `SurfaceCapabilitiesKHR` plus the effective minimum image count, surface formats, and present modes queried for the raw surface.
|
||||
- `DebugUtilsCreateInfo` is the small severity/message-type configuration record used with debug utilities; `DebugUtils` itself owns the instance loader and messenger handle.
|
||||
- `InstanceExtensions` stores optional surface-capability-query extension loaders and an optional surface-maintenance marker.
|
||||
- `DeviceHandle` is a separate unsafe mutable-handle destruction trait used by `vk::DescriptorPool`; the normal `DeviceObject` destruction path uses `ExternallyManagedObject` instead.
|
||||
- `asdf::ExternallyManagedObject<T, O>` is the generic owner/inner wrapper: it uses `ManuallyDrop`, invokes `destroy` exactly once, and retains the owner until that call.
|
||||
- `DeviceObject<T>` exposes `device()`, `handle()` for `Copy` handles, and optional `name()`. It only implements `DeviceOwned<T>` when `T` is a Vulkan handle.
|
||||
- `Queue::with_locked` serializes calls on a queue; `DeviceQueues::family_indices` deduplicates family IDs for resource-sharing modes.
|
||||
- `Fence::new_dedicated` and `Semaphore::new_dedicated` create per-object device-owned primitives; `from_pool` constructors create pooled sync objects.
|
||||
- `FenceFuture::block` waits then resets. Polling an unsignaled future schedules a blocking waiter through `SyncThreadpool`; the pool's worker count is bounded by its configured maximum.
|
||||
|
||||
## Invariants and gotchas
|
||||
- `asdf` is the literal legacy module name for `DeviceObject` and its traits. Do not silently rename paths; generated/client code refers to these APIs.
|
||||
- Vulkan child handles left bare in pools at device teardown are implicitly released by `vkDestroyDevice`; pool entries must not independently retain device-owning wrappers.
|
||||
- `_drop: DeviceDrop` is the private shutdown path: it waits for the device to idle, exports and destroys the pipeline cache to `pipeline_cache.bin`, then destroys the Vulkan device. The cache is also exposed as a copyable wrapper through `DevicePools` for pipeline creation.
|
||||
- `Device` cloning is cheap and prolongs the full device lifetime; any live `DeviceObject`, `PoolObject`, sync wrapper, or other owner retaining a `Device` can delay shutdown.
|
||||
- Device-owned wrapper structs live outside `DeviceInner` and its pools. Pools hold bare handles, never `DeviceObject` or `PoolObject` wrappers.
|
||||
- `Pool::device()` upgrades its `Weak` and panics if the pool outlives its device; don't retain pools independently.
|
||||
- `PoolObject` uses `ManuallyDrop` to transfer its inner handle to `Recycle` exactly once. New `Recycle` implementations must route to the matching pool and handle type.
|
||||
- The derive parser requires a named-field struct with exactly one `#[device]` field and does not add generic bounds itself; generic field types must satisfy `DeviceRef` through the input declaration. Its generated paths require the crate self-alias in `lib.rs`.
|
||||
- `vk::Semaphore` and `SemaphoreInner` have destruction implementations for dedicated ownership; pooled semaphore ownership instead recycles `SemaphoreInner` into a kind-specific pool.
|
||||
- `vk::Buffer`, `vk::Image`, `vk::ImageView`, descriptor/pipeline objects, swapchains, semaphores, and GPU allocations have concrete destruction paths; inspect `device.rs` and `sync.rs` before introducing another handle wrapper.
|
||||
- Queue `lock()`/`unlock()` and `DeviceInner::lock_queues`/`unlock_queues` are unsafe paired operations. Prefer `Queue::with_locked` or `wait_idle` unless the caller can uphold their contracts.
|
||||
- `Instance` shares its raw Vulkan instance through `Arc`; `DebugUtils` owns and destroys its messenger. Device references retain their `Instance`.
|
||||
- `superset_of` compares requested-true flags with `a != b`, which looks inverted for a superset test — verify before relying on adapter feature selection.
|
||||
- Physical-device feature/property queries chain Vulkan 1.1/1.2/1.3 structures and optional mesh-shader data without checking each version's availability; property querying marks beyond-1.0 probing as not yet fallible.
|
||||
- `choose_adapter_default` ignores its optional raw surface argument (`TODO`); it does not test surface compatibility.
|
||||
- Instance/queue selection accepts `RawDisplayHandle`; surface capability lookup uses a raw `vk::SurfaceKHR`. This crate has no `renderer::swapchain::Surface` dependency.
|
||||
- `queue::QueueFlags` and `device::QueueFlags` intentionally serve different purposes. Keep imports qualified when adding queue-family selection logic.
|
||||
- `PipelineCache` persistence uses a single working-directory path and one cache record. Changes to the key or file header should account for compatibility with existing cache files.
|
||||
- Logical-device setup always includes the swapchain extension in its required extension list, regardless of the caller's extra requested extensions.
|
||||
- Queue-family selection uses the optional display handle to query presentation support and currently asserts that its chosen graphics family is present-capable.
|
||||
- Extension selectors and feature checks drive selection; do not infer that every optional extension is enabled just because a loader type exists.
|
||||
- `Instance::query_presentation_support` handles platform-specific display variants and has an unimplemented XCB branch.
|
||||
- `DebugUtils` is stored inside `InstanceInner`; its destructor destroys the messenger, so retain the loader/messenger for the instance lifetime.
|
||||
- `DeviceObject`'s concrete destruction impls are split between `device.rs` and `sync.rs`; search both before adding a duplicate or assuming a type has an implementation.
|
||||
- `PhysicalDeviceInfo` construction can discard adapters that fail property queries; `Instance::choose_adapter` filters failed exposures before sorting.
|
||||
- `DeviceQueueInfos::retrieve_queues` reuses the graphics queue handle for absent compute or transfer roles. These aliases share the same `QueueInner` lock.
|
||||
- `QueueFamily::granularity` uses the source spelling `TransferGranuality`; avoid renaming it as unrelated cleanup.
|
||||
- `sync::Semaphore::from_pool` signals pooled timeline semaphores to the provided value but does not reset them to zero first; ensure requested timeline value semantics remain valid on reuse.
|
||||
- Fence waiters wake on successful waits, log and stop on non-timeout errors, and can time out of the worker pool after its idle duration.
|
||||
- `PipelineCache` uses MD5 only to derive a local adapter key; this is a cache identity, not a security mechanism.
|
||||
- Device-level extension loaders are constructed only for extensions recorded as enabled; callers should check optional loaders such as `device_extensions.swapchain` before use.
|
||||
- `Device::PartialEq` compares `Arc` identity, not raw Vulkan handles or physical-device identity.
|
||||
- `Instance::get_adapter_surface_capabilities` receives the surface loader separately from the raw handle and returns format/mode data alongside generic capabilities.
|
||||
- In debug builds `DebugName` stores its string; in non-debug builds its dereference returns a placeholder and does not retain the supplied name. `debug_name_object` assigns names through debug utils.
|
||||
- GPU allocation free is routed through `DeviceObject<GpuAllocation>` into the allocator mutex. Do not call allocator free on an allocation after ownership has moved or dropped.
|
||||
- `ExternallyManagedObject<Device>` must destroy each owned handle once. The `vk::CommandBuffer` implementation is intentionally a no-op: its outer command-buffer wrapper frees it through its command pool. `vk::CommandPool` itself is really destroyed.
|
||||
- Device handle types are hand-written around `DeviceObject` with `DeviceOwned` implementations where needed; the old `define_device_owned_handle!` macro no longer exists.
|
||||
|
||||
## Conventions
|
||||
- Nightly crate features: `debug_closure_helpers`, `slice_partition_dedup`.
|
||||
- Keep unsafe Vulkan destruction and queue locking contracts explicit; inspect each trait's safety requirements when adding implementations.
|
||||
- Do not commit changes. Verify with `cargo check --workspace` and `cargo test -p renderer_device` from the workspace root.
|
||||
- Keep `renderer_device` independent of higher-level render resource/swapchain wrapper types; public raw Vulkan handles and capability records form this crate's boundary.
|
||||
|
||||
## Change guidance
|
||||
- A new device-owned Vulkan handle needs a concrete `ExternallyManagedObject<Device>` implementation before it can be wrapped in `DeviceObject<T>`.
|
||||
- Keep `DeviceOwned<T>` aligned with its wrapper: `device()` returns the strong owner used for destruction, and `handle()` returns the wrapped raw handle.
|
||||
- A pooled type needs both `Pooled::create_from_pool` and `Recycle::recycle`, a matching inline entry in `DevicePools`, and initialization from its `Weak<DeviceInner>`.
|
||||
- Keep dedicated and pooled sync paths semantically separate. Reused fences may require reset; timeline semaphore reuse must honor counter values.
|
||||
- `DeviceObject::new_debug_named` requires `DebugNameable`; use `DeviceObject::new` when no valid debug naming route exists.
|
||||
- Never put `Device` or `Arc<Device>` inside a value stored inline in `DeviceInner` unless the ownership cycle is deliberately redesigned.
|
||||
- Raw Vulkan calls must use the instance/device associated with their handle. Document synchronization and lifetime obligations when exposing raw handles.
|
||||
- New queue operations must use `Queue::with_locked` or explicitly document their external-synchronization safety contract.
|
||||
- When adding required features/extensions, update adapter support checks, physical-device queries, and logical-device enablement together.
|
||||
- `required_extensions` warns when an additional requested device extension is unsupported and always includes swapchain; warnings are not a hard-failure policy.
|
||||
- Keep platform surface creation out of adapter APIs; selection/capability methods use raw Vulkan surface and display handles.
|
||||
- Pipeline cache data is an optimization: load failures fall back to an empty cache; export/write failures are logged during teardown.
|
||||
- Preserve `extern crate self as renderer_device`: proc-macro expansion uses this absolute path inside crate tests as well as downstream code.
|
||||
- The derive parser's diagnostics require named-field structs and exactly one `#[device]` field. Do not rely on the incomplete tuple-field branch.
|
||||
- `get_available_extensions` ignores failed per-layer extension enumeration results; extension compatibility otherwise compares names and minimum spec versions.
|
||||
- On `Instance`/`Device` teardown changes, inspect explicit `Drop` implementations and field drop order, not only local wrapper drops.
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
[package]
|
||||
name = "renderer_device"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
ash = { workspace = true }
|
||||
ash-window = { workspace = true }
|
||||
gpu-allocator = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
raw-window-handle = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
tinyvec = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
md-5 = { workspace = true }
|
||||
crossbeam = "0.8.4"
|
||||
dyn-clone = "1"
|
||||
bytemuck = { version = "1.21.0", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
renderer_macros = { path = "../renderer/renderer_macros" }
|
||||
|
|
@ -1,626 +0,0 @@
|
|||
#![feature(debug_closure_helpers, slice_partition_dedup)]
|
||||
|
||||
extern crate self as renderer_device;
|
||||
|
||||
use std::{fmt::Debug, path::PathBuf};
|
||||
use ash::{ext, vk::{self, SurfaceCapabilitiesKHR}};
|
||||
use dyn_clone::DynClone;
|
||||
use crate::instance::InstanceInner;
|
||||
|
||||
pub mod device;
|
||||
pub mod instance;
|
||||
pub mod pipeline_cache;
|
||||
pub mod queue;
|
||||
pub mod sync;
|
||||
mod debug;
|
||||
|
||||
pub use device::{Device, DeviceOwned, DeviceRef, Extension};
|
||||
pub use instance::{DebugUtils, DebugUtilsCreateInfo, Instance};
|
||||
|
||||
#[cfg(test)]
|
||||
mod device_ref_derive_tests {
|
||||
#![allow(dead_code)]
|
||||
|
||||
use crate::device::{Device, DeviceRef};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(renderer_macros::DeviceRef)]
|
||||
struct Wrapper {
|
||||
#[device]
|
||||
device: Device,
|
||||
count: u32,
|
||||
}
|
||||
|
||||
#[derive(renderer_macros::DeviceRef)]
|
||||
struct ArcWrapper {
|
||||
#[device]
|
||||
device: Arc<Device>,
|
||||
}
|
||||
|
||||
#[derive(renderer_macros::DeviceRef)]
|
||||
struct GenericWrapper<D: DeviceRef> {
|
||||
#[device]
|
||||
device: D,
|
||||
tag: &'static str,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derives_impl_device_ref() {
|
||||
fn assert_impl<T: DeviceRef>() {}
|
||||
assert_impl::<Wrapper>();
|
||||
assert_impl::<ArcWrapper>();
|
||||
assert_impl::<GenericWrapper<Device>>();
|
||||
assert_impl::<GenericWrapper<Arc<Device>>>();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("No Physical Device found for this instance.")]
|
||||
NoAdapter,
|
||||
#[error("Swapchain suboptimal.")]
|
||||
SuboptimalSwapchain,
|
||||
#[error(transparent)]
|
||||
LoadingError(#[from] ash::LoadingError),
|
||||
#[error(transparent)]
|
||||
Result(#[from] ash::vk::Result),
|
||||
#[error(transparent)]
|
||||
CStrError(#[from] core::ffi::c_str::FromBytesUntilNulError),
|
||||
#[error(transparent)]
|
||||
Utf8Error(#[from] core::str::Utf8Error),
|
||||
#[error(transparent)]
|
||||
NulError(#[from] std::ffi::NulError),
|
||||
#[error(transparent)]
|
||||
GpuAllocatorError(#[from] gpu_allocator::AllocationError),
|
||||
#[error("No Physical Device found.")]
|
||||
NoPhysicalDevice,
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error(
|
||||
"Image dimensions ({width}x{height}) exceed the maximum allowed size of {max_size}x{max_size}."
|
||||
)]
|
||||
ImageTooLarge {
|
||||
width: u32,
|
||||
height: u32,
|
||||
max_size: u32,
|
||||
},
|
||||
#[error("Image dimensions cannot be zero.")]
|
||||
ImageZeroSized,
|
||||
#[error("Incompatible image view kind {view_kind:?} for image kind {image_kind:?}.")]
|
||||
IncompatibleImageViewKind {
|
||||
view_kind: vk::ImageViewType,
|
||||
image_kind: vk::ImageType,
|
||||
},
|
||||
#[error("Asset missing at path {0:?}")]
|
||||
AssetMissing(PathBuf),
|
||||
#[error("Unspecified Error")]
|
||||
Unspecified,
|
||||
#[error("BEEP BOOP create an error variant for {0} BEEP BOOP")]
|
||||
Todo(&'static str),
|
||||
}
|
||||
|
||||
pub type Result<T> = core::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub struct DeviceExtension<'a> {
|
||||
name: &'a core::ffi::CStr,
|
||||
version: u32,
|
||||
}
|
||||
|
||||
/// returns true if lhs and rhs have the same name and lhs spec_version is less
|
||||
/// than or equal to rhs spec_version
|
||||
#[allow(dead_code)]
|
||||
pub fn compatible_extension_properties(
|
||||
lhs: &vk::ExtensionProperties,
|
||||
rhs: &vk::ExtensionProperties,
|
||||
) -> bool {
|
||||
let Some(lhs_name) = lhs.extension_name_as_c_str().ok() else {
|
||||
return false;
|
||||
};
|
||||
let Some(rhs_name) = rhs.extension_name_as_c_str().ok() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if lhs_name == rhs_name {
|
||||
lhs.spec_version <= rhs.spec_version
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ExtendsDeviceFeatures2Debug:
|
||||
vk::ExtendsPhysicalDeviceFeatures2 + Debug + Send + Sync
|
||||
{
|
||||
}
|
||||
pub trait ExtendsDeviceProperties2Debug:
|
||||
vk::ExtendsPhysicalDeviceProperties2 + Debug + DynClone + Send + Sync
|
||||
{
|
||||
}
|
||||
|
||||
impl<T: vk::ExtendsPhysicalDeviceFeatures2 + Debug + Send + Sync> ExtendsDeviceFeatures2Debug
|
||||
for T
|
||||
{
|
||||
}
|
||||
impl<T: vk::ExtendsPhysicalDeviceProperties2 + Debug + DynClone + Send + Sync>
|
||||
ExtendsDeviceProperties2Debug for T
|
||||
{
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PhysicalDeviceInfo {
|
||||
pub pdev: vk::PhysicalDevice,
|
||||
pub properties: PhysicalDeviceProperties,
|
||||
pub features: PhysicalDeviceFeatures,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SurfaceCapabilities {
|
||||
pub capabilities: SurfaceCapabilitiesKHR,
|
||||
/// The minimum number of images that must be present in the swapchain for
|
||||
/// the surface for any present mode supported by the surface.
|
||||
pub min_image_count: u32,
|
||||
pub formats: Vec<vk::SurfaceFormatKHR>,
|
||||
pub present_modes: Vec<vk::PresentModeKHR>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct PhysicalDeviceFeatures {
|
||||
pub core: vk::PhysicalDeviceFeatures,
|
||||
pub core11: vk::PhysicalDeviceVulkan11Features<'static>,
|
||||
pub core12: vk::PhysicalDeviceVulkan12Features<'static>,
|
||||
pub core13: vk::PhysicalDeviceVulkan13Features<'static>,
|
||||
pub mesh_shader: Option<vk::PhysicalDeviceMeshShaderFeaturesEXT<'static>>,
|
||||
}
|
||||
|
||||
impl PhysicalDeviceFeatures {
|
||||
pub fn push_to_device_create_info<'a>(
|
||||
&'a mut self,
|
||||
mut create_info: vk::DeviceCreateInfo<'a>,
|
||||
) -> vk::DeviceCreateInfo<'a> {
|
||||
create_info = create_info
|
||||
.enabled_features(&self.core)
|
||||
.push_next(&mut self.core11)
|
||||
.push_next(&mut self.core12)
|
||||
.push_next(&mut self.core13);
|
||||
|
||||
if let Some(mesh_shader) = &mut self.mesh_shader {
|
||||
create_info = create_info.push_next(mesh_shader);
|
||||
}
|
||||
create_info
|
||||
}
|
||||
pub fn superset_of(&self, other: &PhysicalDeviceFeatures) -> bool {
|
||||
fn core_superset_of(
|
||||
a: &vk::PhysicalDeviceFeatures,
|
||||
b: &vk::PhysicalDeviceFeatures,
|
||||
) -> bool {
|
||||
(b.robust_buffer_access == vk::FALSE
|
||||
|| a.robust_buffer_access != b.robust_buffer_access)
|
||||
&& (b.full_draw_index_uint32 == vk::FALSE
|
||||
|| a.full_draw_index_uint32 != b.full_draw_index_uint32)
|
||||
&& (b.image_cube_array == vk::FALSE || a.image_cube_array != b.image_cube_array)
|
||||
&& (b.independent_blend == vk::FALSE || a.independent_blend != b.independent_blend)
|
||||
&& (b.geometry_shader == vk::FALSE || a.geometry_shader != b.geometry_shader)
|
||||
&& (b.tessellation_shader == vk::FALSE
|
||||
|| a.tessellation_shader != b.tessellation_shader)
|
||||
&& (b.sample_rate_shading == vk::FALSE
|
||||
|| a.sample_rate_shading != b.sample_rate_shading)
|
||||
&& (b.dual_src_blend == vk::FALSE || a.dual_src_blend != b.dual_src_blend)
|
||||
&& (b.logic_op == vk::FALSE || a.logic_op != b.logic_op)
|
||||
&& (b.multi_draw_indirect == vk::FALSE
|
||||
|| a.multi_draw_indirect != b.multi_draw_indirect)
|
||||
&& (b.draw_indirect_first_instance == vk::FALSE
|
||||
|| a.draw_indirect_first_instance != b.draw_indirect_first_instance)
|
||||
&& (b.depth_clamp == vk::FALSE || a.depth_clamp != b.depth_clamp)
|
||||
&& (b.depth_bias_clamp == vk::FALSE || a.depth_bias_clamp != b.depth_bias_clamp)
|
||||
&& (b.fill_mode_non_solid == vk::FALSE
|
||||
|| a.fill_mode_non_solid != b.fill_mode_non_solid)
|
||||
&& (b.depth_bounds == vk::FALSE || a.depth_bounds != b.depth_bounds)
|
||||
&& (b.wide_lines == vk::FALSE || a.wide_lines != b.wide_lines)
|
||||
&& (b.large_points == vk::FALSE || a.large_points != b.large_points)
|
||||
&& (b.alpha_to_one == vk::FALSE || a.alpha_to_one != b.alpha_to_one)
|
||||
&& (b.multi_viewport == vk::FALSE || a.multi_viewport != b.multi_viewport)
|
||||
&& (b.sampler_anisotropy == vk::FALSE
|
||||
|| a.sampler_anisotropy != b.sampler_anisotropy)
|
||||
&& (b.texture_compression_etc2 == vk::FALSE
|
||||
|| a.texture_compression_etc2 != b.texture_compression_etc2)
|
||||
&& (b.texture_compression_astc_ldr == vk::FALSE
|
||||
|| a.texture_compression_astc_ldr != b.texture_compression_astc_ldr)
|
||||
&& (b.texture_compression_bc == vk::FALSE
|
||||
|| a.texture_compression_bc != b.texture_compression_bc)
|
||||
&& (b.occlusion_query_precise == vk::FALSE
|
||||
|| a.occlusion_query_precise != b.occlusion_query_precise)
|
||||
&& (b.pipeline_statistics_query == vk::FALSE
|
||||
|| a.pipeline_statistics_query != b.pipeline_statistics_query)
|
||||
&& (b.vertex_pipeline_stores_and_atomics == vk::FALSE
|
||||
|| a.vertex_pipeline_stores_and_atomics != b.vertex_pipeline_stores_and_atomics)
|
||||
&& (b.fragment_stores_and_atomics == vk::FALSE
|
||||
|| a.fragment_stores_and_atomics != b.fragment_stores_and_atomics)
|
||||
&& (b.shader_tessellation_and_geometry_point_size == vk::FALSE
|
||||
|| a.shader_tessellation_and_geometry_point_size
|
||||
!= b.shader_tessellation_and_geometry_point_size)
|
||||
&& (b.shader_image_gather_extended == vk::FALSE
|
||||
|| a.shader_image_gather_extended != b.shader_image_gather_extended)
|
||||
&& (b.shader_storage_image_extended_formats == vk::FALSE
|
||||
|| a.shader_storage_image_extended_formats
|
||||
!= b.shader_storage_image_extended_formats)
|
||||
&& (b.shader_storage_image_multisample == vk::FALSE
|
||||
|| a.shader_storage_image_multisample != b.shader_storage_image_multisample)
|
||||
&& (b.shader_storage_image_read_without_format == vk::FALSE
|
||||
|| a.shader_storage_image_read_without_format
|
||||
!= b.shader_storage_image_read_without_format)
|
||||
&& (b.shader_storage_image_write_without_format == vk::FALSE
|
||||
|| a.shader_storage_image_write_without_format
|
||||
!= b.shader_storage_image_write_without_format)
|
||||
&& (b.shader_uniform_buffer_array_dynamic_indexing == vk::FALSE
|
||||
|| a.shader_uniform_buffer_array_dynamic_indexing
|
||||
!= b.shader_uniform_buffer_array_dynamic_indexing)
|
||||
&& (b.shader_sampled_image_array_dynamic_indexing == vk::FALSE
|
||||
|| a.shader_sampled_image_array_dynamic_indexing
|
||||
!= b.shader_sampled_image_array_dynamic_indexing)
|
||||
&& (b.shader_storage_buffer_array_dynamic_indexing == vk::FALSE
|
||||
|| a.shader_storage_buffer_array_dynamic_indexing
|
||||
!= b.shader_storage_buffer_array_dynamic_indexing)
|
||||
&& (b.shader_storage_image_array_dynamic_indexing == vk::FALSE
|
||||
|| a.shader_storage_image_array_dynamic_indexing
|
||||
!= b.shader_storage_image_array_dynamic_indexing)
|
||||
&& (b.shader_clip_distance == vk::FALSE
|
||||
|| a.shader_clip_distance != b.shader_clip_distance)
|
||||
&& (b.shader_cull_distance == vk::FALSE
|
||||
|| a.shader_cull_distance != b.shader_cull_distance)
|
||||
&& (b.shader_float64 == vk::FALSE || a.shader_float64 != b.shader_float64)
|
||||
&& (b.shader_int64 == vk::FALSE || a.shader_int64 != b.shader_int64)
|
||||
&& (b.shader_int16 == vk::FALSE || a.shader_int16 != b.shader_int16)
|
||||
&& (b.shader_resource_residency == vk::FALSE
|
||||
|| a.shader_resource_residency != b.shader_resource_residency)
|
||||
&& (b.shader_resource_min_lod == vk::FALSE
|
||||
|| a.shader_resource_min_lod != b.shader_resource_min_lod)
|
||||
&& (b.sparse_binding == vk::FALSE || a.sparse_binding != b.sparse_binding)
|
||||
&& (b.sparse_residency_buffer == vk::FALSE
|
||||
|| a.sparse_residency_buffer != b.sparse_residency_buffer)
|
||||
&& (b.sparse_residency_image2_d == vk::FALSE
|
||||
|| a.sparse_residency_image2_d != b.sparse_residency_image2_d)
|
||||
&& (b.sparse_residency_image3_d == vk::FALSE
|
||||
|| a.sparse_residency_image3_d != b.sparse_residency_image3_d)
|
||||
&& (b.sparse_residency2_samples == vk::FALSE
|
||||
|| a.sparse_residency2_samples != b.sparse_residency2_samples)
|
||||
&& (b.sparse_residency4_samples == vk::FALSE
|
||||
|| a.sparse_residency4_samples != b.sparse_residency4_samples)
|
||||
&& (b.sparse_residency8_samples == vk::FALSE
|
||||
|| a.sparse_residency8_samples != b.sparse_residency8_samples)
|
||||
&& (b.sparse_residency16_samples == vk::FALSE
|
||||
|| a.sparse_residency16_samples != b.sparse_residency16_samples)
|
||||
&& (b.sparse_residency_aliased == vk::FALSE
|
||||
|| a.sparse_residency_aliased != b.sparse_residency_aliased)
|
||||
&& (b.variable_multisample_rate == vk::FALSE
|
||||
|| a.variable_multisample_rate != b.variable_multisample_rate)
|
||||
&& (b.inherited_queries == vk::FALSE || a.inherited_queries != b.inherited_queries)
|
||||
}
|
||||
|
||||
fn core11_superset_of(
|
||||
a: &vk::PhysicalDeviceVulkan11Features,
|
||||
b: &vk::PhysicalDeviceVulkan11Features,
|
||||
) -> bool {
|
||||
(b.storage_buffer16_bit_access == vk::FALSE
|
||||
|| a.storage_buffer16_bit_access != b.storage_buffer16_bit_access)
|
||||
&& (b.uniform_and_storage_buffer16_bit_access == vk::FALSE
|
||||
|| a.uniform_and_storage_buffer16_bit_access
|
||||
!= b.uniform_and_storage_buffer16_bit_access)
|
||||
&& (b.storage_push_constant16 == vk::FALSE
|
||||
|| a.storage_push_constant16 != b.storage_push_constant16)
|
||||
&& (b.storage_input_output16 == vk::FALSE
|
||||
|| a.storage_input_output16 != b.storage_input_output16)
|
||||
&& (b.multiview == vk::FALSE || a.multiview != b.multiview)
|
||||
&& (b.multiview_geometry_shader == vk::FALSE
|
||||
|| a.multiview_geometry_shader != b.multiview_geometry_shader)
|
||||
&& (b.multiview_tessellation_shader == vk::FALSE
|
||||
|| a.multiview_tessellation_shader != b.multiview_tessellation_shader)
|
||||
&& (b.variable_pointers_storage_buffer == vk::FALSE
|
||||
|| a.variable_pointers_storage_buffer != b.variable_pointers_storage_buffer)
|
||||
&& (b.variable_pointers == vk::FALSE || a.variable_pointers != b.variable_pointers)
|
||||
&& (b.protected_memory == vk::FALSE || a.protected_memory != b.protected_memory)
|
||||
&& (b.sampler_ycbcr_conversion == vk::FALSE
|
||||
|| a.sampler_ycbcr_conversion != b.sampler_ycbcr_conversion)
|
||||
&& (b.shader_draw_parameters == vk::FALSE
|
||||
|| a.shader_draw_parameters != b.shader_draw_parameters)
|
||||
}
|
||||
|
||||
fn core12_superset_of(
|
||||
a: &vk::PhysicalDeviceVulkan12Features,
|
||||
b: &vk::PhysicalDeviceVulkan12Features,
|
||||
) -> bool {
|
||||
(b.sampler_mirror_clamp_to_edge == vk::FALSE
|
||||
|| a.sampler_mirror_clamp_to_edge != b.sampler_mirror_clamp_to_edge)
|
||||
&& (b.draw_indirect_count == vk::FALSE
|
||||
|| a.draw_indirect_count != b.draw_indirect_count)
|
||||
&& (b.storage_buffer8_bit_access == vk::FALSE
|
||||
|| a.storage_buffer8_bit_access != b.storage_buffer8_bit_access)
|
||||
&& (b.uniform_and_storage_buffer8_bit_access == vk::FALSE
|
||||
|| a.uniform_and_storage_buffer8_bit_access
|
||||
!= b.uniform_and_storage_buffer8_bit_access)
|
||||
&& (b.storage_push_constant8 == vk::FALSE
|
||||
|| a.storage_push_constant8 != b.storage_push_constant8)
|
||||
&& (b.shader_buffer_int64_atomics == vk::FALSE
|
||||
|| a.shader_buffer_int64_atomics != b.shader_buffer_int64_atomics)
|
||||
&& (b.shader_shared_int64_atomics == vk::FALSE
|
||||
|| a.shader_shared_int64_atomics != b.shader_shared_int64_atomics)
|
||||
&& (b.shader_float16 == vk::FALSE || a.shader_float16 != b.shader_float16)
|
||||
&& (b.shader_int8 == vk::FALSE || a.shader_int8 != b.shader_int8)
|
||||
&& (b.descriptor_indexing == vk::FALSE
|
||||
|| a.descriptor_indexing != b.descriptor_indexing)
|
||||
&& (b.shader_input_attachment_array_dynamic_indexing == vk::FALSE
|
||||
|| a.shader_input_attachment_array_dynamic_indexing
|
||||
!= b.shader_input_attachment_array_dynamic_indexing)
|
||||
&& (b.shader_uniform_texel_buffer_array_dynamic_indexing == vk::FALSE
|
||||
|| a.shader_uniform_texel_buffer_array_dynamic_indexing
|
||||
!= b.shader_uniform_texel_buffer_array_dynamic_indexing)
|
||||
&& (b.shader_storage_texel_buffer_array_dynamic_indexing == vk::FALSE
|
||||
|| a.shader_storage_texel_buffer_array_dynamic_indexing
|
||||
!= b.shader_storage_texel_buffer_array_dynamic_indexing)
|
||||
&& (b.shader_uniform_buffer_array_non_uniform_indexing == vk::FALSE
|
||||
|| a.shader_uniform_buffer_array_non_uniform_indexing
|
||||
!= b.shader_uniform_buffer_array_non_uniform_indexing)
|
||||
&& (b.shader_sampled_image_array_non_uniform_indexing == vk::FALSE
|
||||
|| a.shader_sampled_image_array_non_uniform_indexing
|
||||
!= b.shader_sampled_image_array_non_uniform_indexing)
|
||||
&& (b.shader_storage_buffer_array_non_uniform_indexing == vk::FALSE
|
||||
|| a.shader_storage_buffer_array_non_uniform_indexing
|
||||
!= b.shader_storage_buffer_array_non_uniform_indexing)
|
||||
&& (b.shader_storage_image_array_non_uniform_indexing == vk::FALSE
|
||||
|| a.shader_storage_image_array_non_uniform_indexing
|
||||
!= b.shader_storage_image_array_non_uniform_indexing)
|
||||
&& (b.shader_input_attachment_array_non_uniform_indexing == vk::FALSE
|
||||
|| a.shader_input_attachment_array_non_uniform_indexing
|
||||
!= b.shader_input_attachment_array_non_uniform_indexing)
|
||||
&& (b.shader_uniform_texel_buffer_array_non_uniform_indexing == vk::FALSE
|
||||
|| a.shader_uniform_texel_buffer_array_non_uniform_indexing
|
||||
!= b.shader_uniform_texel_buffer_array_non_uniform_indexing)
|
||||
&& (b.shader_storage_texel_buffer_array_non_uniform_indexing == vk::FALSE
|
||||
|| a.shader_storage_texel_buffer_array_non_uniform_indexing
|
||||
!= b.shader_storage_texel_buffer_array_non_uniform_indexing)
|
||||
&& (b.descriptor_binding_uniform_buffer_update_after_bind == vk::FALSE
|
||||
|| a.descriptor_binding_uniform_buffer_update_after_bind
|
||||
!= b.descriptor_binding_uniform_buffer_update_after_bind)
|
||||
&& (b.descriptor_binding_sampled_image_update_after_bind == vk::FALSE
|
||||
|| a.descriptor_binding_sampled_image_update_after_bind
|
||||
!= b.descriptor_binding_sampled_image_update_after_bind)
|
||||
&& (b.descriptor_binding_storage_image_update_after_bind == vk::FALSE
|
||||
|| a.descriptor_binding_storage_image_update_after_bind
|
||||
!= b.descriptor_binding_storage_image_update_after_bind)
|
||||
&& (b.descriptor_binding_storage_buffer_update_after_bind == vk::FALSE
|
||||
|| a.descriptor_binding_storage_buffer_update_after_bind
|
||||
!= b.descriptor_binding_storage_buffer_update_after_bind)
|
||||
&& (b.descriptor_binding_uniform_texel_buffer_update_after_bind == vk::FALSE
|
||||
|| a.descriptor_binding_uniform_texel_buffer_update_after_bind
|
||||
!= b.descriptor_binding_uniform_texel_buffer_update_after_bind)
|
||||
&& (b.descriptor_binding_storage_texel_buffer_update_after_bind == vk::FALSE
|
||||
|| a.descriptor_binding_storage_texel_buffer_update_after_bind
|
||||
!= b.descriptor_binding_storage_texel_buffer_update_after_bind)
|
||||
&& (b.descriptor_binding_update_unused_while_pending == vk::FALSE
|
||||
|| a.descriptor_binding_update_unused_while_pending
|
||||
!= b.descriptor_binding_update_unused_while_pending)
|
||||
&& (b.descriptor_binding_partially_bound == vk::FALSE
|
||||
|| a.descriptor_binding_partially_bound != b.descriptor_binding_partially_bound)
|
||||
&& (b.descriptor_binding_variable_descriptor_count == vk::FALSE
|
||||
|| a.descriptor_binding_variable_descriptor_count
|
||||
!= b.descriptor_binding_variable_descriptor_count)
|
||||
&& (b.runtime_descriptor_array == vk::FALSE
|
||||
|| a.runtime_descriptor_array != b.runtime_descriptor_array)
|
||||
&& (b.sampler_filter_minmax == vk::FALSE
|
||||
|| a.sampler_filter_minmax != b.sampler_filter_minmax)
|
||||
&& (b.scalar_block_layout == vk::FALSE
|
||||
|| a.scalar_block_layout != b.scalar_block_layout)
|
||||
&& (b.imageless_framebuffer == vk::FALSE
|
||||
|| a.imageless_framebuffer != b.imageless_framebuffer)
|
||||
&& (b.uniform_buffer_standard_layout == vk::FALSE
|
||||
|| a.uniform_buffer_standard_layout != b.uniform_buffer_standard_layout)
|
||||
&& (b.shader_subgroup_extended_types == vk::FALSE
|
||||
|| a.shader_subgroup_extended_types != b.shader_subgroup_extended_types)
|
||||
&& (b.separate_depth_stencil_layouts == vk::FALSE
|
||||
|| a.separate_depth_stencil_layouts != b.separate_depth_stencil_layouts)
|
||||
&& (b.host_query_reset == vk::FALSE || a.host_query_reset != b.host_query_reset)
|
||||
&& (b.timeline_semaphore == vk::FALSE
|
||||
|| a.timeline_semaphore != b.timeline_semaphore)
|
||||
&& (b.buffer_device_address == vk::FALSE
|
||||
|| a.buffer_device_address != b.buffer_device_address)
|
||||
&& (b.buffer_device_address_capture_replay == vk::FALSE
|
||||
|| a.buffer_device_address_capture_replay
|
||||
!= b.buffer_device_address_capture_replay)
|
||||
&& (b.buffer_device_address_multi_device == vk::FALSE
|
||||
|| a.buffer_device_address_multi_device != b.buffer_device_address_multi_device)
|
||||
&& (b.vulkan_memory_model == vk::FALSE
|
||||
|| a.vulkan_memory_model != b.vulkan_memory_model)
|
||||
&& (b.vulkan_memory_model_device_scope == vk::FALSE
|
||||
|| a.vulkan_memory_model_device_scope != b.vulkan_memory_model_device_scope)
|
||||
&& (b.vulkan_memory_model_availability_visibility_chains == vk::FALSE
|
||||
|| a.vulkan_memory_model_availability_visibility_chains
|
||||
!= b.vulkan_memory_model_availability_visibility_chains)
|
||||
&& (b.shader_output_viewport_index == vk::FALSE
|
||||
|| a.shader_output_viewport_index != b.shader_output_viewport_index)
|
||||
&& (b.shader_output_layer == vk::FALSE
|
||||
|| a.shader_output_layer != b.shader_output_layer)
|
||||
&& (b.subgroup_broadcast_dynamic_id == vk::FALSE
|
||||
|| a.subgroup_broadcast_dynamic_id != b.subgroup_broadcast_dynamic_id)
|
||||
}
|
||||
|
||||
fn core13_superset_of(
|
||||
a: &vk::PhysicalDeviceVulkan13Features,
|
||||
b: &vk::PhysicalDeviceVulkan13Features,
|
||||
) -> bool {
|
||||
(b.robust_image_access == vk::FALSE || a.robust_image_access != b.robust_image_access)
|
||||
&& (b.inline_uniform_block == vk::FALSE
|
||||
|| a.inline_uniform_block != b.inline_uniform_block)
|
||||
&& (b.descriptor_binding_inline_uniform_block_update_after_bind == vk::FALSE
|
||||
|| a.descriptor_binding_inline_uniform_block_update_after_bind
|
||||
!= b.descriptor_binding_inline_uniform_block_update_after_bind)
|
||||
&& (b.pipeline_creation_cache_control == vk::FALSE
|
||||
|| a.pipeline_creation_cache_control != b.pipeline_creation_cache_control)
|
||||
&& (b.private_data == vk::FALSE || a.private_data != b.private_data)
|
||||
&& (b.shader_demote_to_helper_invocation == vk::FALSE
|
||||
|| a.shader_demote_to_helper_invocation != b.shader_demote_to_helper_invocation)
|
||||
&& (b.shader_terminate_invocation == vk::FALSE
|
||||
|| a.shader_terminate_invocation != b.shader_terminate_invocation)
|
||||
&& (b.subgroup_size_control == vk::FALSE
|
||||
|| a.subgroup_size_control != b.subgroup_size_control)
|
||||
&& (b.compute_full_subgroups == vk::FALSE
|
||||
|| a.compute_full_subgroups != b.compute_full_subgroups)
|
||||
&& (b.synchronization2 == vk::FALSE || a.synchronization2 != b.synchronization2)
|
||||
&& (b.texture_compression_astc_hdr == vk::FALSE
|
||||
|| a.texture_compression_astc_hdr != b.texture_compression_astc_hdr)
|
||||
&& (b.shader_zero_initialize_workgroup_memory == vk::FALSE
|
||||
|| a.shader_zero_initialize_workgroup_memory
|
||||
!= b.shader_zero_initialize_workgroup_memory)
|
||||
&& (b.dynamic_rendering == vk::FALSE || a.dynamic_rendering != b.dynamic_rendering)
|
||||
&& (b.shader_integer_dot_product == vk::FALSE
|
||||
|| a.shader_integer_dot_product != b.shader_integer_dot_product)
|
||||
&& (b.maintenance4 == vk::FALSE || a.maintenance4 != b.maintenance4)
|
||||
}
|
||||
|
||||
fn mesh_shader_superset_of(
|
||||
a: &vk::PhysicalDeviceMeshShaderFeaturesEXT,
|
||||
b: &vk::PhysicalDeviceMeshShaderFeaturesEXT,
|
||||
) -> bool {
|
||||
(b.task_shader == vk::FALSE || a.task_shader != b.task_shader)
|
||||
&& (b.mesh_shader == vk::FALSE || a.mesh_shader != b.mesh_shader)
|
||||
&& (b.multiview_mesh_shader == vk::FALSE
|
||||
|| a.multiview_mesh_shader != b.multiview_mesh_shader)
|
||||
&& (b.primitive_fragment_shading_rate_mesh_shader == vk::FALSE
|
||||
|| a.primitive_fragment_shading_rate_mesh_shader
|
||||
!= b.primitive_fragment_shading_rate_mesh_shader)
|
||||
&& (b.mesh_shader_queries == vk::FALSE
|
||||
|| a.mesh_shader_queries != b.mesh_shader_queries)
|
||||
}
|
||||
|
||||
core_superset_of(&self.core, &other.core)
|
||||
&& core11_superset_of(&self.core11, &other.core11)
|
||||
&& core12_superset_of(&self.core12, &other.core12)
|
||||
&& core13_superset_of(&self.core13, &other.core13)
|
||||
&& match (&self.mesh_shader, &other.mesh_shader) {
|
||||
(Some(a), Some(b)) => mesh_shader_superset_of(a, b),
|
||||
(None, Some(_)) => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PhysicalDeviceProperties {
|
||||
/// Extensions supported by the device, as reported by `enumerate_device_extension_properties`.
|
||||
pub supported_extensions: Vec<vk::ExtensionProperties>,
|
||||
/// Vulkan 1.0 properties.
|
||||
pub core: vk::PhysicalDeviceProperties,
|
||||
/// Vulkan 1.1 properties.
|
||||
pub core11: vk::PhysicalDeviceVulkan11Properties<'static>,
|
||||
/// Vulkan 1.2 properties.
|
||||
pub core12: vk::PhysicalDeviceVulkan12Properties<'static>,
|
||||
/// Vulkan 1.3 properties.
|
||||
pub core13: vk::PhysicalDeviceVulkan13Properties<'static>,
|
||||
/// Mesh shader properties from the `VK_EXT_mesh_shader` extension, if supported.
|
||||
pub mesh_shader: Option<vk::PhysicalDeviceMeshShaderPropertiesEXT<'static>>,
|
||||
/// Memory properties of the device, as reported by `get_physical_device_memory_properties`.
|
||||
pub memory_properties: vk::PhysicalDeviceMemoryProperties,
|
||||
/// The API version supported by the device.
|
||||
pub device_api_version: u32,
|
||||
}
|
||||
|
||||
impl PhysicalDeviceProperties {
|
||||
pub fn get_device_local_memory_count(&self) -> u64 {
|
||||
self.memory_properties
|
||||
.memory_heaps
|
||||
.iter()
|
||||
.filter(|heap| heap.flags.contains(vk::MemoryHeapFlags::DEVICE_LOCAL))
|
||||
.map(|heap| heap.size)
|
||||
.sum::<u64>()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_physical_device_features(
|
||||
instance: &InstanceInner,
|
||||
pdev: vk::PhysicalDevice,
|
||||
properties: &PhysicalDeviceProperties,
|
||||
) -> PhysicalDeviceFeatures {
|
||||
let mut features = PhysicalDeviceFeatures::default();
|
||||
let mut features2 = vk::PhysicalDeviceFeatures2::default();
|
||||
|
||||
features2 = features2
|
||||
.push_next(&mut features.core11)
|
||||
.push_next(&mut features.core12)
|
||||
.push_next(&mut features.core13);
|
||||
|
||||
if properties.supports_extension(make_extension!(ext::mesh_shader)) {
|
||||
features2 = features2.push_next(
|
||||
features
|
||||
.mesh_shader
|
||||
.insert(vk::PhysicalDeviceMeshShaderFeaturesEXT::default()),
|
||||
);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
instance
|
||||
.raw
|
||||
.get_physical_device_features2(pdev, &mut features2);
|
||||
}
|
||||
|
||||
features.core = features2.features;
|
||||
|
||||
features
|
||||
}
|
||||
|
||||
pub fn get_physical_device_properties(
|
||||
instance: &InstanceInner,
|
||||
pdev: vk::PhysicalDevice,
|
||||
) -> Result<PhysicalDeviceProperties> {
|
||||
let mut props = PhysicalDeviceProperties::default();
|
||||
props.core = unsafe { instance.raw.get_physical_device_properties(pdev) };
|
||||
props.device_api_version = props.core.api_version;
|
||||
props.supported_extensions =
|
||||
unsafe { instance.raw.enumerate_device_extension_properties(pdev)? };
|
||||
props.memory_properties = unsafe { instance.raw.get_physical_device_memory_properties(pdev) };
|
||||
|
||||
let supports_mesh_shader = props.supports_extension(make_extension!(ext::mesh_shader));
|
||||
|
||||
// TODO: fallibly check anything beyond 1.0
|
||||
let mut props2 = vk::PhysicalDeviceProperties2::default()
|
||||
.push_next(&mut props.core11)
|
||||
.push_next(&mut props.core12)
|
||||
.push_next(&mut props.core13);
|
||||
|
||||
if supports_mesh_shader {
|
||||
let next = props
|
||||
.mesh_shader
|
||||
.insert(vk::PhysicalDeviceMeshShaderPropertiesEXT::default());
|
||||
props2 = props2.push_next(next);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
instance
|
||||
.raw
|
||||
.get_physical_device_properties2(pdev, &mut props2);
|
||||
}
|
||||
|
||||
Ok(props)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn extension_intersection<'a>(
|
||||
supported: &'a [vk::ExtensionProperties],
|
||||
required: &[Extension<'a>],
|
||||
) -> Vec<vk::ExtensionProperties> {
|
||||
supported
|
||||
.iter()
|
||||
.filter(|ext| {
|
||||
required.iter().any(|req| {
|
||||
ext.extension_name_as_c_str() == Ok(req.name) && ext.spec_version >= req.version
|
||||
})
|
||||
})
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl PhysicalDeviceProperties {
|
||||
pub fn supports_extension(&self, e: Extension) -> bool {
|
||||
self.supported_extensions
|
||||
.iter()
|
||||
.any(|ext| ext.extension_name_as_c_str() == Ok(e.name) && ext.spec_version >= e.version)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
use ash::vk;
|
||||
|
||||
use ash::Device;
|
||||
|
||||
use crate::PhysicalDeviceInfo;
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct PipelineCache {
|
||||
#[allow(dead_code)]
|
||||
key: u128,
|
||||
pub raw: vk::PipelineCache,
|
||||
}
|
||||
|
||||
impl PipelineCache {
|
||||
pub(crate) unsafe fn destroy(self, device: &ash::Device) {
|
||||
tracing::info!("destroying pipeline cache with key {:x}", self.key);
|
||||
if let Ok(data) = self.export(device) {
|
||||
tracing::info!(
|
||||
"exported pipeline cache with key {:x} and size {} bytes",
|
||||
self.key,
|
||||
data.len()
|
||||
);
|
||||
_ = Self::write_to_disk(self.key, &data).inspect_err(|err| {
|
||||
tracing::error!("failed to write pipeline cache to disk: {err}");
|
||||
});
|
||||
}
|
||||
unsafe { device.destroy_pipeline_cache(self.raw, None) };
|
||||
}
|
||||
|
||||
const MAGIC: [u8; 4] = *b"VYPC";
|
||||
const KEY_VERSION: u32 = 1;
|
||||
const PATH: &'static str = "pipeline_cache.bin";
|
||||
fn calculate_key(adapter: &PhysicalDeviceInfo) -> u128 {
|
||||
use md5::Digest;
|
||||
let mut hasher = md5::Md5::new();
|
||||
let props = &adapter.properties;
|
||||
hasher.update(bytemuck::bytes_of(&[
|
||||
props.core.vendor_id,
|
||||
props.core.api_version,
|
||||
props.core.device_id,
|
||||
props.core.driver_version,
|
||||
]));
|
||||
u128::from_le_bytes(hasher.finalize().into())
|
||||
}
|
||||
|
||||
fn load_from_disk(key: u128) -> Option<(u128, Vec<u8>)> {
|
||||
use std::io::Read;
|
||||
|
||||
let file = std::fs::File::open(Self::PATH).ok()?;
|
||||
let mut reader = std::io::BufReader::new(file);
|
||||
let mut magic = [0; 4];
|
||||
reader.read_exact(&mut magic).ok()?;
|
||||
if magic != Self::MAGIC {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut version = 0;
|
||||
reader
|
||||
.read_exact(bytemuck::bytes_of_mut(&mut version))
|
||||
.ok()?;
|
||||
if version != Self::KEY_VERSION {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut disk_key = 0;
|
||||
reader
|
||||
.read_exact(bytemuck::bytes_of_mut(&mut disk_key))
|
||||
.ok()?;
|
||||
if disk_key != key {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut data = Vec::new();
|
||||
reader.read_to_end(&mut data).ok()?;
|
||||
|
||||
Some((key, data))
|
||||
}
|
||||
|
||||
fn write_to_disk(key: u128, data: &[u8]) -> std::io::Result<()> {
|
||||
use std::io::Write;
|
||||
|
||||
let file = std::fs::File::create(Self::PATH)?;
|
||||
let mut writer = std::io::BufWriter::new(file);
|
||||
writer.write_all(&Self::MAGIC)?;
|
||||
writer.write_all(bytemuck::bytes_of(&Self::KEY_VERSION))?;
|
||||
writer.write_all(bytemuck::bytes_of(&key))?;
|
||||
writer.write_all(data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new(device: &Device, adapter: &PhysicalDeviceInfo) -> crate::Result<Self> {
|
||||
let key = Self::calculate_key(adapter);
|
||||
let data = Self::load_from_disk(key).map(|(key, data)| {
|
||||
tracing::info!("loaded pipeline cache from disk with key {key:x}");
|
||||
data
|
||||
});
|
||||
|
||||
let info = vk::PipelineCacheCreateInfo::default()
|
||||
// .flags(vk::PipelineCacheCreateFlags::EXTERNALLY_SYNCHRONIZED)
|
||||
.initial_data(data.as_deref().unwrap_or_default());
|
||||
|
||||
let cache = unsafe { device.create_pipeline_cache(&info, None)? };
|
||||
|
||||
Ok(Self { key, raw: cache })
|
||||
}
|
||||
|
||||
pub fn export(&self, device: &ash::Device) -> crate::Result<Vec<u8>> {
|
||||
let data = unsafe { device.get_pipeline_cache_data(self.raw)? };
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
[package]
|
||||
name = "renderer_rendergraph"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
# renderer_resources
|
||||
|
||||
## Purpose
|
||||
`renderer_resources` owns Vulkan buffer, image, view, descriptor, sampler, shader-module, and pipeline construction on the `renderer_device` layer. It handles resource memory and handle lifetimes, but contains no window, swapchain, or presentation orchestration; those remain in `renderer`. It must not depend on `renderer`.
|
||||
|
||||
## Module map
|
||||
- `lib.rs`: re-exports `Device`, `Error`, `Result`, `device`, and `make_extension` from `renderer_device`; declares four public resource modules.
|
||||
- `buffers.rs`: `BufferDesc` and `Buffer`; Vulkan buffer creation, GPU allocation/binding, raw access, and host mapping.
|
||||
- `images.rs`: `ImageDesc`, `Image`, `ImageViewDesc`, `ImageView`, subresource ranges, extents, offsets, and queue ownership. `ImageInner` distinguishes allocated images from borrowed swapchain images.
|
||||
- `pipeline.rs`: shader-stage and graphics/compute pipeline descriptions; `DescriptorPool`, `DescriptorSetLayout`, `PipelineLayout`, `Sampler`, `ShaderModule`, and `Pipeline`.
|
||||
- `util.rs`: `eq_f32`, `ord_f32`, `hash_f32`, `F32`, and the crate-private `weak_vec` module / `WeakVec` used by image view tracking.
|
||||
|
||||
## Key types and API
|
||||
- These modules are public from `renderer_resources`; the crate root re-exports the core device types and `device` module from `renderer_device` for resource construction.
|
||||
- `BufferDesc` configures Vulkan buffer flags, size, usage, queue families, memory location, allocation strategy, and optional debug name. `Buffer::new` allocates/binds memory; `map` and `map_mut` return mapped slices when available.
|
||||
- `Buffer::raw` returns the Vulkan handle and `len` returns the configured size. Mapping is bounded to the descriptor size, not the allocator block size.
|
||||
- `ImageDesc` configures image type, format, extent, layers/mips, samples, tiling, usage, initial layout, queue sharing, and allocation.
|
||||
- `Image::new` creates an allocated image. `Image::new_with_allocation` accepts an explicit `renderer_device::device::Allocation` and validates size and memory-type compatibility.
|
||||
- `Image::from_swapchain_raw(image, device, format, extent, usage)` builds an image wrapper for an image owned by a swapchain; renderer calls it from `Swapchain::acquire_image`.
|
||||
- `ImageViewDesc` describes a view's type, format, component mapping, aspect, mip range, and layer range. `Image::create_view` validates compatibility and returns `ManuallyDrop<ImageView>`.
|
||||
- `Image::create_view` resolves remaining mip/layer sentinels against the parent image, defaults an undefined view format to the image format, and checks image/view kind and format compatibility.
|
||||
- `Extent`, `Offset`, and `MipRange` represent image dimensions, offsets, and mip/layer ranges. `QueueOwnership` carries source and destination family indices.
|
||||
- `Image` exposes its raw handle, format, dimensions, and optional allocation; swapchain-backed images have no allocation.
|
||||
- Pipeline configuration records include `ShaderStageDesc`, `GraphicsPipelineDesc`, `ComputePipelineDesc`, `PipelineLayoutDesc`, `DescriptorSetLayoutDesc`, `DescriptorSetLayoutBindingDesc`, `DescriptorPoolDesc`, `DescriptorSetAllocDesc`, `SamplerDesc`, and fixed-function state descriptions.
|
||||
- Fixed-function state records are `VertexInputState`, `TessellationState`, `InputAssemblyState`, `ViewportState`, `DepthBiasState`, `RasterizationState`, `MultisampleState`, `DepthBounds`, `DepthState`, `StencilState`, `DepthStencilState`, `ColorBlendState`, `RenderingState`, and `DynamicState`.
|
||||
- `Pipeline` wraps graphics or compute Vulkan pipelines and retains its `PipelineLayout` in an `Arc`. Creation uses `device.pools.pipeline_cache.raw`.
|
||||
- `DescriptorPool` serializes allocation/reset with a mutex; descriptor set layouts and pipeline layouts own their Vulkan handles.
|
||||
- `DescriptorPool::allocate` returns raw descriptor-set handles from the provided `DescriptorSetLayout` values and optionally assigns names from `DescriptorSetAllocDesc`.
|
||||
- `DescriptorSetLayout::new` applies per-binding descriptor flags when the device supports Vulkan 1.2 or `VK_EXT_descriptor_indexing`.
|
||||
- `PipelineLayoutDesc` references descriptor-set layouts and push-constant ranges; `PipelineLayout` owns the created Vulkan layout.
|
||||
- `SamplerDesc` describes filtering, addressing, anisotropy, compare, LOD, and border settings; `Sampler::new` creates the Vulkan sampler.
|
||||
- `ShaderModule::new_from_path` reads a shader file relative to the process working directory; file-open failures map to `Error::AssetMissing`, while read/Vulkan failures use their corresponding `Error` conversions. `new_from_memory` accepts SPIR-V words.
|
||||
- `GraphicsPipelineDesc` supports render-pass/subpass inputs or dynamic `RenderingState`; optional fixed-function states are converted during pipeline creation.
|
||||
- `SamplerDesc` uses `eq_f32` / `hash_f32` for float-valued fields. `F32` provides `Eq`/`Ord`/`Hash` semantics using the same helpers.
|
||||
- `WeakVec<T>` stores `Weak<T>` references and prunes entries whose strong count is zero when growing; it is crate-private, not public API.
|
||||
|
||||
## Invariants and gotchas
|
||||
- This crate consumes the `renderer_device` ownership types and re-exports; do not add a parallel device model or a dependency on `renderer`.
|
||||
- `Image::from_swapchain_raw` stores the raw image in the `ImageInner::Swapchain` variant with its device. The swapchain owns that Vulkan image; never destroy it as an independently allocated image.
|
||||
- `from_swapchain_raw` records a 2D, one-mip, one-layer, single-sample, optimal-tiled image with PRESENT queue sharing and UNDEFINED initial layout; the caller supplies format, extent, and usage.
|
||||
- `from_swapchain_raw` retains a `Device` for views and device queries, but not ownership of the image handle or swapchain lifetime; the renderer's frame/swapchain token must keep the swapchain alive.
|
||||
- Allocated images and buffers use `DeviceObject` for Vulkan handles and allocation ownership. Keep resource destruction ordered before freeing bound memory.
|
||||
- `Buffer` equality compares the raw buffer handle; `Image` equality compares the raw image handle. Descriptor equality is separate from resource object identity.
|
||||
- `Allocation::Owned` owns a device-managed allocation; `Shared` shares it through `Arc` and permits mutable access only when uniquely held; `Unmanaged` carries no allocation.
|
||||
- `ImageView` retains `Arc<Image>` so the source image outlives the view. `Image::create_view` returns `ManuallyDrop<ImageView>`; callers must transfer/drop it deliberately.
|
||||
- `Image` tracks views through `WeakVec<ImageView>` to avoid a strong-reference cycle. The weak list does not keep views alive.
|
||||
- `ImageView`'s `DeviceObject` owns and destroys the Vulkan view; its parent `Arc<Image>` must remain alive through view destruction.
|
||||
- `ImageDesc` and `BufferDesc` omit `alloc_scheme` from both `Hash` and `Eq`; their `Hash` implementations omit `name`, and `Eq` also omits `name`. Do not treat these descriptors as complete resource identities without accounting for these fields.
|
||||
- Descriptor hashing/equality covers resource shape, usage, sharing, and memory location but not every construction input. Update both implementations together if adding descriptor-keyed resource deduplication.
|
||||
- `Pipeline` retains its layout with `Arc`; descriptor-pool operations are mutex-serialized. Keep these lifetime and synchronization guarantees when changing constructors or wrappers.
|
||||
- `DescriptorPool::allocate` returns raw `vk::DescriptorSet` handles; keep the owning pool alive while those sets are in use. Descriptor sets are not wrapped in owning resource objects here.
|
||||
- Descriptor-set layout binding flags are chained into layout creation when the device API version or descriptor-indexing extension supports them.
|
||||
- Pipeline cache lifetime and teardown belong to `renderer_device`; use the device's existing cache rather than adding a resource-crate cache.
|
||||
- Renderer shader assets remain under `crates/renderer/shaders`; this crate does not own those assets.
|
||||
- `renderer` accesses these modules through crate-private aliases. Making a resource type available in `renderer`'s public API requires an explicit API/re-export decision there.
|
||||
|
||||
## Conventions
|
||||
- `renderer_resources` has no crate-level nightly feature attributes; use APIs available under the workspace's pinned nightly toolchain and edition 2024.
|
||||
- Depend on `renderer_device` paths re-exported at this crate root (`crate::Device`, `crate::device::...`, and `crate::make_extension`) rather than introducing a dependency on `renderer`.
|
||||
- Do not commit changes. Verify with `cargo check -p renderer_resources` and `cargo check --workspace` from the workspace root.
|
||||
|
||||
## Change guidance
|
||||
- Add buffer, image, view, descriptor, sampler, shader, and pipeline constructors in this crate, not in `renderer`'s orchestration layer.
|
||||
- For each Vulkan handle, preserve the distinction between resource-owned handles and externally owned handles such as raw swapchain images.
|
||||
- Keep image-view range/format/kind validation close to image/view construction. Cover `REMAINING_MIP_LEVELS` and `REMAINING_ARRAY_LAYERS` normalization when changing range handling.
|
||||
- When modifying descriptor equality/hash behavior, update both together and account for `name` and `alloc_scheme` deliberately.
|
||||
- Preserve `renderer_device::device::Allocation` ownership semantics when accepting shared or unmanaged memory; ensure bound resources outlive their allocation use.
|
||||
- Keep `SamplerDesc` float equality and hashing consistent across every float field.
|
||||
- `renderer_resources::util::weak_vec` is internal support for images; do not expose or move it back into `renderer` without reviewing view ownership cycles.
|
||||
- Keep shader loading errors compatible with `renderer_device::Error`, and test path-based loading relative to the workspace runtime working directory.
|
||||
- Keep this crate independent of swapchain/window wrappers. Integrate swapchain images through the raw image plus explicit device, format, extent, and usage, not renderer types.
|
||||
- Keep pipeline descriptors, Vulkan state conversion, and handle ownership in this crate rather than moving resource ownership back into renderer orchestration.
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
[package]
|
||||
name = "renderer_resources"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
renderer_device = { path = "../renderer_device" }
|
||||
ash = { workspace = true }
|
||||
gpu-allocator = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
bytemuck = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
ash-formats = { git = "https://github.com/janis-bhm/ash-formats", branch = "main" }
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
pub use renderer_device::{Device, Error, Result, device, make_extension};
|
||||
|
||||
pub mod buffers;
|
||||
pub mod images;
|
||||
pub mod pipeline;
|
||||
pub mod util;
|
||||
|
|
@ -1,170 +0,0 @@
|
|||
pub(crate) mod weak_vec {
|
||||
#![allow(dead_code)]
|
||||
//! Module containing the [`WeakVec`] API.
|
||||
|
||||
use std::{sync::Weak, vec::Vec};
|
||||
|
||||
/// An optimized container for `Weak` references of `T` that minimizes reallocations by
|
||||
/// dropping older elements that no longer have strong references to them.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct WeakVec<T> {
|
||||
inner: Vec<Weak<T>>,
|
||||
}
|
||||
|
||||
impl<T> Default for WeakVec<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inner: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> WeakVec<T> {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self { inner: Vec::new() }
|
||||
}
|
||||
|
||||
/// Pushes a new element to this collection.
|
||||
///
|
||||
/// If the inner Vec needs to be reallocated, we will first drop older elements that
|
||||
/// no longer have strong references to them.
|
||||
pub(crate) fn push(&mut self, value: Weak<T>) {
|
||||
if self.inner.len() == self.inner.capacity() {
|
||||
// Iterating backwards has the advantage that we don't do more work than we have to.
|
||||
for i in (0..self.inner.len()).rev() {
|
||||
if self.inner[i].strong_count() == 0 {
|
||||
self.inner.swap_remove(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure our capacity is twice the number of live elements.
|
||||
// Leaving some spare capacity ensures that we won't re-scan immediately.
|
||||
self.inner.reserve_exact(self.inner.len());
|
||||
}
|
||||
|
||||
self.inner.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct WeakVecIter<T> {
|
||||
inner: std::vec::IntoIter<Weak<T>>,
|
||||
}
|
||||
|
||||
impl<T> Iterator for WeakVecIter<T> {
|
||||
type Item = Weak<T>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoIterator for WeakVec<T> {
|
||||
type Item = Weak<T>;
|
||||
type IntoIter = WeakVecIter<T>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
WeakVecIter {
|
||||
inner: self.inner.into_iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use weak_vec::WeakVec;
|
||||
|
||||
pub fn eq_f32(lhs: f32, rhs: f32) -> bool {
|
||||
use core::num::FpCategory::*;
|
||||
match (lhs.classify(), rhs.classify()) {
|
||||
(Zero, Zero) | (Nan, Nan) => true,
|
||||
(Infinite, Infinite) => lhs.signum() == rhs.signum(),
|
||||
_ => lhs == rhs,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ord_f32(lhs: f32, rhs: f32) -> core::cmp::Ordering {
|
||||
use core::cmp::Ordering;
|
||||
use core::num::FpCategory::*;
|
||||
match (lhs.classify(), rhs.classify()) {
|
||||
(Zero, Zero) | (Nan, Nan) => Ordering::Equal,
|
||||
(Infinite, Infinite) => {
|
||||
core::cmp::PartialOrd::partial_cmp(&lhs.signum(), &rhs.signum()).unwrap()
|
||||
}
|
||||
_ => core::cmp::PartialOrd::partial_cmp(&lhs, &rhs).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hash_f32<H: std::hash::Hasher>(state: &mut H, f: f32) {
|
||||
use std::hash::Hash;
|
||||
let classify = f.classify();
|
||||
match classify {
|
||||
std::num::FpCategory::Nan => (classify as u8).hash(state),
|
||||
std::num::FpCategory::Infinite | std::num::FpCategory::Zero => {
|
||||
(classify as u8, f.signum() as i8).hash(state)
|
||||
}
|
||||
std::num::FpCategory::Subnormal | std::num::FpCategory::Normal => f.to_bits().hash(state),
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct F32(f32);
|
||||
|
||||
impl F32 {
|
||||
#[inline]
|
||||
pub fn from_bits(bits: u32) -> Self {
|
||||
Self(f32::from_bits(bits))
|
||||
}
|
||||
#[inline]
|
||||
pub fn into_bits(self) -> u32 {
|
||||
self.0.to_bits()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for F32 {
|
||||
fn from(value: f32) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl From<F32> for f32 {
|
||||
fn from(value: F32) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for F32 {
|
||||
type Target = f32;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for F32 {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for F32 {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
eq_f32(self.0, other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for F32 {}
|
||||
|
||||
impl PartialOrd for F32 {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(ord_f32(self.0, other.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for F32 {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
ord_f32(self.0, other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::hash::Hash for F32 {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
hash_f32(state, self.0)
|
||||
}
|
||||
}
|
||||
|
|
@ -24,8 +24,10 @@ mod components {
|
|||
pub struct Text(pub String);
|
||||
}
|
||||
|
||||
const ROBOTO_BYTES: &[u8] = include_bytes!("../../../assets/fonts/Roboto.ttf");
|
||||
const NOTO_SANS_HAN_BYTES: &[u8] = include_bytes!("../../../assets/fonts/NotoSansSC.ttf");
|
||||
const ROBOTO_BYTES: &[u8] =
|
||||
include_bytes!("../../../assets/fonts/Roboto/Roboto-VariableFont_wdth,wght.ttf");
|
||||
const NOTO_SANS_HAN_BYTES: &[u8] =
|
||||
include_bytes!("../../../assets/fonts/Noto_Sans_SC/NotoSansSC-VariableFont_wght.ttf");
|
||||
|
||||
def_monotonic_id!(pub FontId);
|
||||
|
||||
|
|
@ -40,7 +42,7 @@ impl std::fmt::Debug for FontData {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, TypePath)]
|
||||
#[derive(Debug, Default)]
|
||||
struct FontLoader;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
|
|
|
|||
Loading…
Reference in a new issue