molgfx_gpu/residency/upload_types.rs
1//! Public upload lifecycle values and telemetry.
2
3use thiserror::Error;
4
5/// Monotonic submission completion value supplied by a backend.
6#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
7pub struct FenceValue(pub u64);
8
9/// Stable identifier for one ring reservation.
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
11pub struct UploadTicket(pub(super) u64);
12
13impl UploadTicket {
14 /// Monotonic ticket sequence, useful for tracing.
15 #[must_use]
16 pub const fn sequence(self) -> u64 {
17 self.0
18 }
19}
20
21/// Current lifecycle of an upload reservation.
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub enum UploadState {
24 /// Reserved bytes may be filled by the caller.
25 Reserved,
26 /// Staging bytes are complete and may be submitted.
27 Ready,
28 /// A backend submission may still read the bytes.
29 InFlight(FenceValue),
30 /// The reservation was cancelled before submission.
31 Cancelled,
32}
33
34/// Immutable description returned when bytes are reserved.
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub struct UploadReservation {
37 pub(super) ticket: UploadTicket,
38 pub(super) offset: usize,
39 pub(super) len: usize,
40}
41
42impl UploadReservation {
43 /// Ticket used for commit, submission and cancellation.
44 #[must_use]
45 pub const fn ticket(self) -> UploadTicket {
46 self.ticket
47 }
48
49 /// Offset into the ring's staging buffer.
50 #[must_use]
51 pub const fn offset(self) -> usize {
52 self.offset
53 }
54
55 /// Payload byte length.
56 #[must_use]
57 pub const fn len(self) -> usize {
58 self.len
59 }
60
61 /// Whether the payload is empty.
62 #[must_use]
63 pub const fn is_empty(self) -> bool {
64 self.len == 0
65 }
66}
67
68/// Fixed resource and upload throughput budgets.
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70pub struct UploadRingConfig {
71 /// Staging bytes retained for the lifetime of the ring.
72 pub capacity_bytes: usize,
73 /// Maximum simultaneous reservations.
74 pub ticket_capacity: usize,
75 /// Maximum payload bytes accepted between calls to `begin_epoch`.
76 pub epoch_budget_bytes: usize,
77 /// Maximum submitted payload bytes awaiting fence completion.
78 pub in_flight_budget_bytes: usize,
79 /// Required payload offset alignment. Must be a power of two.
80 pub alignment: usize,
81}
82
83impl UploadRingConfig {
84 /// Validates capacities and budgets without allocating staging storage.
85 ///
86 /// # Errors
87 ///
88 /// Rejects empty capacities or budgets, an in-flight limit larger than
89 /// staging capacity, and alignment that is not a power of two.
90 pub const fn validate(self) -> Result<(), UploadBackpressure> {
91 if self.capacity_bytes == 0
92 || self.ticket_capacity == 0
93 || self.epoch_budget_bytes == 0
94 || self.in_flight_budget_bytes == 0
95 || self.in_flight_budget_bytes > self.capacity_bytes
96 || !self.alignment.is_power_of_two()
97 {
98 return Err(UploadBackpressure::InvalidConfiguration);
99 }
100 Ok(())
101 }
102}
103
104/// Observable ring counters. Values are cumulative except active gauges.
105#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
106pub struct UploadMetrics {
107 /// Payload bytes committed into staging.
108 pub bytes_staged: u64,
109 /// Payload bytes handed to backend submissions.
110 pub bytes_submitted: u64,
111 /// Payload bytes made reusable after fence completion.
112 pub bytes_retired: u64,
113 /// Payload bytes cancelled before submission.
114 pub bytes_cancelled: u64,
115 /// Payload bytes accepted in the current epoch.
116 pub epoch_bytes: u64,
117 /// Submitted payload bytes awaiting fence completion.
118 pub in_flight_bytes: u64,
119 /// Largest in-flight payload footprint observed.
120 pub peak_in_flight_bytes: u64,
121 /// Ring span occupied by live reservations, including padding.
122 pub occupied_bytes: u64,
123 /// Largest occupied ring span observed.
124 pub peak_occupied_bytes: u64,
125 /// Number of live reservations.
126 pub active_tickets: u64,
127 /// Requests rejected by any budget or capacity bound.
128 pub stall_events: u64,
129 /// Payload bytes represented by rejected requests.
130 pub stalled_bytes: u64,
131 /// Host storage allocations performed by this primitive.
132 pub host_allocation_events: u64,
133}
134
135/// Why an upload reservation or lifecycle transition could not proceed.
136#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
137pub enum UploadBackpressure {
138 /// Configuration must provide non-zero capacities and power-of-two alignment.
139 #[error("upload ring configuration is invalid")]
140 InvalidConfiguration,
141 /// Empty uploads are rejected.
142 #[error("an upload reservation must contain at least one byte")]
143 EmptyUpload,
144 /// The configured per-epoch payload budget was exhausted.
145 #[error("upload epoch budget exhausted")]
146 EpochBudget,
147 /// Submitted work reached its fence-protected byte budget.
148 #[error("upload in-flight budget exhausted")]
149 InFlightBudget,
150 /// Staging bytes remain occupied by older work.
151 #[error("upload staging ring is full")]
152 RingFull,
153 /// All ticket records are in use.
154 #[error("upload ticket capacity is exhausted")]
155 TicketCapacity,
156 /// Arithmetic could not represent the reservation.
157 #[error("upload reservation size overflow")]
158 SizeOverflow,
159 /// The ticket is stale or in the wrong lifecycle state.
160 #[error("upload ticket is stale or has an invalid state")]
161 InvalidTicket,
162}
163
164/// Work made reusable by one retirement call.
165#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
166pub struct Retirement {
167 /// Number of retired tickets.
168 pub tickets: usize,
169 /// Payload bytes released after completed submissions.
170 pub bytes: u64,
171}