|
| ShaderSourceKind | shaderSourceKind () const override |
| | Native shader representation accepted by this device.
|
|
bool | usesUnifiedMemoryForTest () const |
| | Whether this device's resources are built for unified memory. Test accessor.
|
| uint64_t | hostWritePublishCountForTest () const |
| | How many buffer writes have published their range to the device copy. Test accessor.
|
|
uint64_t | deviceWritePublishCountForTest () const |
| | How many submissions have published the device's changes back to the host copy. Test accessor, for the same reason as above but in the other direction.
|
|
WriteStats | writeStatsForTest () const |
| | Current queue-write counters. Does not wait or change device state.
|
|
Status | pauseSubmissionsForTest () |
| | Pauses submitted GPU work at a shared event until resumeSubmissionsForTest is called. A deterministic test seam for writes issued while an earlier submission is in flight. Each command buffer encoded while paused waits for its own turn, numbered from one in encoding order, so releasePausedCommandBuffersForTest can let them run a few at a time.
|
| void | releasePausedCommandBuffersForTest (uint64_t count) |
| | Lets the first count command buffers encoded since pauseSubmissionsForTest run and keeps the rest paused. It only signals a shared event, so another thread may call it while the device's own thread waits, provided that thread does not pause or resume meanwhile. Does nothing when no pause is active.
|
|
void | resumeSubmissionsForTest () |
| | Releases the event installed by pauseSubmissionsForTest. Safe when no pause is active.
|
| void | failNextSubmissionForTest (std::optional< size_t > commandBufferIndex=std::nullopt) |
| | Makes one command buffer of the next submission report an execution error when it completes, as a command buffer the GPU faulted on does, through the same completion path.
|
|
void | holdNextCompletionForTest () |
| | Parks the completion of the next submission when its handler runs, as a GPU that has not finished that work yet looks: the handler records its outcome but publishes nothing until releaseHeldCompletionForTest. Later submissions complete normally meanwhile.
|
|
void | releaseHeldCompletionForTest () |
| | Publishes the completion holdNextCompletionForTest parked, from the calling thread, or lets it publish normally when its handler has not run yet. Safe when nothing is held, and callable from any thread while the device's own thread waits.
|
| void | setPresentCompletionTimeoutForTest (std::chrono::milliseconds timeout) |
| | Bounds how long a present's wait for its frame's work may go without progress, in place of the five seconds it otherwise allows, so a case reaches the bound without spending it, and before the system's own timeout ends a command buffer that makes no progress.
|
| void | setSubmissionStallTimeoutForTest (std::chrono::milliseconds timeout) |
| | Bounds how long a submission waits for room among the command buffers in flight while neither this device nor a device its waiting work depends on completes any, in place of the five seconds it otherwise allows.
|
| void | setSubmissionWaitCapForTest (std::chrono::milliseconds timeout) |
| | Bounds how long one submission waits for room in all, in place of the sixty seconds it otherwise allows, however much progress it sees meanwhile.
|
|
uint64_t | commandBufferRoomWaitsForTest () const |
| | How many submissions found too many command buffers in flight and waited for room, whether or not they were then committed. Test accessor, readable from any thread.
|
|
uint64_t | commandBuffersInFlightForTest () const |
| | Command buffers committed and not yet completed, as the backstop counts them. Test accessor, readable from any thread.
|
|
uint64_t | serialWaitLooksForTest () const |
| | How many times serial waits on this device have looked at its completion state, so a case can tell a wait that looks only when that state changes from one that keeps checking. Test accessor, readable from any thread.
|
| bool | waitForCompletionHandlersForTest (uint64_t count, double timeoutSeconds) const |
| | Waits until count submissions have had all their completion handlers run on this device, parked ones included. Test seam for ordering completions deterministically.
|
|
| ~MetalDevice () override |
| | Destructor; releases all Metal objects still alive.
|
| uint64_t | completedSerial () const override |
| | Serial of the most recent submission whose Metal command buffer has completed on the GPU (0 if none). Updated by completion handlers, which may run on another thread.
|
| Result< std::vector< uint8_t > > | readBackBuffer (const Buffer &buffer) |
| | Copies the full contents of buffer back to the host and returns the bytes.
|
| Result< NativeTextureUsage > | textureUsageForTest (const Texture &texture) const |
| | Reads native usage after validating the texture's device, liveness, and generation.
|
|
std::string | lastErrorForTest () const |
| | Message of the most recent asynchronous command-buffer execution error captured by a completion handler, or an empty string if none occurred. Test/diagnostic accessor.
|
|
virtual | ~Device () |
| | Destructor; expires the device-alive token (so handles that outlive the device release nothing) and frees all remaining resources. Backends that submit asynchronously must wait for in-flight submissions in their own destructor before backend state is torn down.
|
|
| Device (const Device &)=delete |
|
Device & | operator= (const Device &)=delete |
|
| Device (Device &&)=delete |
|
Device & | operator= (Device &&)=delete |
|
uint64_t | deviceId () const |
| | Process-unique identity of this device (starts at 1, never reused). Baked into every handle for cross-device validation.
|
| bool | isLost () const |
| | True once the backend root this device drives has been declared lost, either by the backend's device-lost callback or by a bounded wait giving up on it.
|
|
DeviceLossSnapshot | deviceLossSnapshot () const |
| | First-winner loss attribution, once its diagnostic fields have settled.
|
| virtual RcString | deviceLostReason () const |
| | Backend-provided loss reason, when the backend retains one. Empty otherwise.
|
| void | markLostAfterWaitTimeout (DeviceLostWaitSite site, std::chrono::milliseconds elapsed, const char *reason) const |
| | Declares this device's backend root lost because a bounded wait gave up, recording which wait it was and how long it actually ran.
|
| virtual bool | supportsFullIndexRange (IndexFormat format) const |
| | Whether indexed draws through this device honor every value of format. Metal and WebGPU always do; a Vulkan device without fullDrawIndexUint32 caps 32-bit indices below the full range, and RenderPassEncoder::setIndexBuffer refuses that format on it rather than let a driver truncate index values.
|
| Result< Buffer > | createBuffer (const BufferDescriptor &descriptor) |
| | Creates a buffer.
|
| Result< Texture > | createTexture (const TextureDescriptor &descriptor) |
| | Creates a 2D texture.
|
| Result< TextureDescriptor > | textureDescriptor (const Texture &texture) const |
| | Returns the descriptor texture was created with, by value.
|
| Result< TextureView > | createTextureView (const Texture &texture, const TextureViewDescriptor &descriptor) |
| | Creates a view of texture covering the whole texture.
|
| Result< Sampler > | createSampler (const SamplerDescriptor &descriptor) |
| | Creates a sampler.
|
| Result< BindGroupLayout > | createBindGroupLayout (const BindGroupLayoutDescriptor &descriptor) |
| | Creates a bind group layout.
|
| Result< BindGroup > | createBindGroup (const BindGroupDescriptor &descriptor) |
| | Creates a bind group.
|
| Result< PipelineLayout > | createPipelineLayout (const PipelineLayoutDescriptor &descriptor) |
| | Creates a pipeline layout.
|
| Result< ShaderModule > | createShaderModule (const ShaderModuleDescriptor &descriptor) |
| | Creates a shader module from trusted generated source.
|
| Result< RenderPipeline > | createRenderPipeline (const RenderPipelineDescriptor &descriptor) |
| | Creates a render pipeline.
|
| Result< ComputePipeline > | createComputePipeline (const ComputePipelineDescriptor &descriptor) |
| | Creates a compute pipeline.
|
| Status | destroyBuffer (Buffer &&buffer) |
| | Destroys a buffer (see the destroy contract above).
|
| Status | destroyTexture (Texture &&texture) |
| | Destroys a texture (see the destroy contract above).
|
| Status | destroyTextureView (TextureView &&textureView) |
| | Destroys a texture view (see the destroy contract above).
|
| Status | destroySampler (Sampler &&sampler) |
| | Destroys a sampler (see the destroy contract above).
|
| Status | destroyBindGroupLayout (BindGroupLayout &&bindGroupLayout) |
| | Destroys a bind group layout (see the destroy contract above).
|
| Status | destroyBindGroup (BindGroup &&bindGroup) |
| | Destroys a bind group (see the destroy contract above).
|
| Status | destroyPipelineLayout (PipelineLayout &&pipelineLayout) |
| | Destroys a pipeline layout (see the destroy contract above).
|
| Status | destroyShaderModule (ShaderModule &&shaderModule) |
| | Destroys a shader module (see the destroy contract above).
|
| Status | destroyRenderPipeline (RenderPipeline &&renderPipeline) |
| | Destroys a render pipeline (see the destroy contract above).
|
| Status | destroyComputePipeline (ComputePipeline &&computePipeline) |
| | Destroys a compute pipeline (see the destroy contract above).
|
| Status | destroyBufferBacking (Buffer &&buffer) |
| | Destroys buffer and releases its backend allocation at once, rather than leaving the allocation to the backend's own collector.
|
| Status | destroyTextureBacking (Texture &&texture) |
| | Destroys texture and releases its backend allocation at once, the texture counterpart of destroyBufferBacking; a succession of resized render targets is where the deferred release shows up as retained memory.
|
| bool | ownsTextureBacking (const Texture &texture) const |
| | Whether texture names backing this device allocated, rather than memory some other owner holds and this device only named.
|
| Result< TextureExport > | exportTexture (const Texture &texture) |
| | Exports texture so another runtime device over the same backend device can register it.
|
| Result< Texture > | registerTexture (const TextureExport &source) |
| | Registers a texture another runtime device exported, as a read-only texture of this one.
|
| bool | waitForTextureSource (const Texture ®istration, double timeoutSeconds) |
| | Waits until work naming registration may be submitted, the budget runs out, or either device is lost, and reports whether it may.
|
| SerialWaitResult | waitForTextureSourceUnlessStalled (const Texture ®istration, std::chrono::milliseconds stallBound) |
| | waitForTextureSource for a caller whose bound exists to detect a hung producer: the wait gives up only once the producer has made no progress for stallBound, measured from that progress, rather than giving the producer's whole backlog a fixed budget.
|
| uint64_t | sharedTextureTailBytes () const |
| | Bytes of this device's exported textures that are still held by an export token or a registration after this device released its own handle.
|
|
Result< std::unique_ptr< CommandEncoder > > | createCommandEncoder () |
| | Creates a command encoder recording against this device. The encoder must not outlive the device.
|
| Result< Surface > | createSurface (const SurfaceDescriptor &descriptor) |
| | Creates a surface presenting to a platform object.
|
| Result< SurfaceCapabilities > | surfaceCapabilities (const Surface &surface) const |
| | What surface supports, for choosing a configuration.
|
| Status | configureSurface (const Surface &surface, const SurfaceConfiguration &configuration) |
| | Configures how surface presents, replacing any previous configuration.
|
| Result< SurfaceTexture > | acquireCurrentTexture (const Surface &surface) |
| | Acquires the texture for the next frame of surface.
|
| Result< SurfaceStatus > | presentSurface (const Surface &surface) |
| | Presents the texture acquired from surface and invalidates it.
|
| Status | abandonCurrentTexture (const Surface &surface) |
| | Releases the acquired texture of surface without presenting it, for a frame the caller decided not to show.
|
| Status | destroySurface (Surface &&surface) |
| | Destroys a surface (see the destroy contract above).
|
| Result< BufferMapping > | mapBufferAsync (const Buffer &buffer, MapMode mode, uint64_t offsetBytes, uint64_t byteCount) |
| | Begins mapping a range of buffer for host access, and returns the handle that names the mapping.
|
| Result< MapSliceReport > | pollMapping (const BufferMapping &mapping) |
| | Polls a mapping once without blocking or yielding; ready mappings become readable.
|
| Result< MapWaitReport > | waitForMapping (const BufferMapping &mapping, const MapWaitParams ¶ms, const std::function< bool()> &shouldCancel, const MapWaitTestHooks &testHooks={}) |
| | Waits for a pending mapping in slices, until it completes, the caller stops it, the budget runs out, or the device is lost.
|
| Result< std::span< const uint8_t > > | mappedBytes (const BufferMapping &mapping) const |
| | Returns the mapped bytes of a completed mapping.
|
| Status | unmapBuffer (BufferMapping &&mapping) |
| | Releases a mapping, invalidating the handle and every copy of it.
|
| Status | writeBuffer (const Buffer &buffer, uint64_t offsetBytes, std::span< const uint8_t > data) |
| | Writes data into buffer at offsetBytes.
|
| Status | writeTexture (const Texture &texture, std::span< const uint8_t > data, const TexelCopyBufferLayout &dataLayout, const Extent2d &writeSize, const Origin2d &destinationOrigin={}) |
| | Writes texel rows from data into the writeSize rectangle of texture whose top-left texel is destinationOrigin, leaving every texel outside that rectangle unchanged.
|
| Result< uint64_t > | submit (CommandBuffer commandBuffer) |
| | Submits a finished command buffer, consuming it, and returns the assigned submission serial.
|
| Result< uint64_t > | submit (std::span< CommandBuffer > commandBuffers) |
| | Submits finished command buffers as one submission, consuming them, and returns the single assigned submission serial.
|
| void | poll () |
| | Processes deferred destructions: releases the backend object of every destroyed resource whose last referencing submission has completed (completedSerial), and recycles its slot.
|
|
void | pollCompletionOnce () |
| | Drive one nonblocking backend completion iteration even when no resource destroy is pending.
|
|
bool | hasPendingDestroys () const |
| | Whether a completed submission can still release a backend object. Owner-thread only.
|
|
uint64_t | lastSubmittedSerial () const |
| | Serial assigned to the most recent submission (0 if none yet).
|
| bool | waitForSerial (uint64_t serial, double timeoutSeconds) |
| | Blocks until completedSerial reaches serial, timeoutSeconds elapses, or the backend gives up on the work, and reports whether the submission completed.
|
| SerialWaitResult | waitForSerialUnlessStalled (uint64_t serial, std::chrono::milliseconds stallBound) |
| | Blocks until completedSerial reaches serial, giving up only when the work the serial waits behind stops making progress.
|
| std::optional< std::chrono::steady_clock::time_point > | lastProgress () |
| | When the work this device's submissions wait behind last made progress, or nothing when the backend has no notion of progress.
|
| Status | installObserver (DeviceObserver &observer) |
| | Installs observer to be notified of the work this device accepts (see DeviceObserver).
|
| void | removeObserver (const DeviceObserver &observer) |
| | Removes observer when it is the installed one and otherwise changes nothing, so an owner can only ever remove its own.
|
|
const DeviceObserver * | observer () const |
| | The installed observer, or null when there is none.
|
|
| BackendDeviceIdentity | backendDeviceIdentity () const override |
| | Identifies the MTLDevice this device records against, so a sibling device over the same MTLDevice can register its textures.
|
| Result< BackendTextureExport > | onExportTexture (uint32_t slotIndex) override |
| | Exports the texture in slotIndex.
|
| Status | onRegisterTexture (uint32_t slotIndex, const ExportedTextureBacking &backing) override |
| | Names another device's exported texture in slotIndex, refusing one created by a different MTLDevice object, which Metal would not accept in this device's command buffers.
|
| bool | onTextureWritePending (uint32_t slotIndex) const override |
| | Whether the write just accepted for slotIndex was queued for the next submission because earlier work still used the texture.
|
| Status | onCreateBuffer (uint32_t slotIndex, const BufferDescriptor &descriptor) override |
| | Backend hook: a buffer passed validation and occupies slotIndex.
|
| Status | onCreateTexture (uint32_t slotIndex, const TextureDescriptor &descriptor) override |
| | Backend hook: a texture passed validation and occupies slotIndex.
|
| Status | onCreateTextureView (uint32_t slotIndex, uint32_t textureSlotIndex, const TextureViewDescriptor &descriptor) override |
| | Backend hook: a texture view passed validation and occupies slotIndex.
|
| Status | onCreateSampler (uint32_t slotIndex, const SamplerDescriptor &descriptor) override |
| | Backend hook: a sampler passed validation and occupies slotIndex.
|
| Status | onCreateBindGroupLayout (uint32_t slotIndex, const BindGroupLayoutDescriptor &descriptor) override |
| | Backend hook: a bind group layout passed validation and occupies slotIndex.
|
| Status | onCreateBindGroup (uint32_t slotIndex, const BindGroupDescriptor &descriptor) override |
| | Backend hook: a bind group passed validation and occupies slotIndex.
|
| Status | onCreatePipelineLayout (uint32_t slotIndex, const PipelineLayoutDescriptor &descriptor) override |
| | Backend hook: a pipeline layout passed validation and occupies slotIndex.
|
| Status | onCreateShaderModule (uint32_t slotIndex, const ShaderModuleDescriptor &descriptor) override |
| | Backend hook: a shader module passed validation and occupies slotIndex.
|
| Status | onCreateRenderPipeline (uint32_t slotIndex, const RenderPipelineDescriptor &descriptor) override |
| | Backend hook: a render pipeline passed validation and occupies slotIndex.
|
| Status | onCreateComputePipeline (uint32_t slotIndex, const ComputePipelineDescriptor &descriptor) override |
| | Backend hook: a compute pipeline passed validation and occupies slotIndex.
|
| void | onRetireBuffer (uint32_t slotIndex) override |
| | Backend hook: a buffer handle was retired. Discard unsubmitted work, but keep native resources alive until onDestroyResource.
|
| void | onRetireTexture (uint32_t slotIndex) override |
| | Backend hook: a texture handle was retired. Discard unsubmitted work, but keep native resources alive until onDestroyResource.
|
| void | onDestroyResource (std::string_view resourceName, uint32_t slotIndex) override |
| | Backend hook: a retired resource is no longer used by submitted work and can be released.
|
| Status | onWriteBuffer (uint32_t slotIndex, uint64_t offsetBytes, std::span< const uint8_t > data) override |
| | Backend hook: a validated buffer write.
|
| Status | onWriteTexture (uint32_t slotIndex, std::span< const uint8_t > data, const TexelCopyBufferLayout &dataLayout, const Extent2d &writeSize, const Origin2d &destinationOrigin) override |
| | Backend hook: a validated texture write. The written rectangle is writeSize texels wide and tall with its top-left texel at destinationOrigin; texels outside it keep their contents.
|
| Status | onSubmit (uint64_t submissionSerial, std::span< const SubmittedCommandBuffer > commandBuffers) override |
| | Backend hook: validated command buffers were submitted as one submission.
|
| Status | onSubmitAfterSources (uint64_t submissionSerial, std::span< const SubmittedCommandBuffer > commandBuffers, std::span< const SourceWait > waits) override |
| | Places a GPU wait on each producer's completion event at the start of the submission's first command buffer, one per producer at the latest serial it needs, then submits.
|
| Status | onCreateSurface (uint32_t slotIndex, const SurfaceDescriptor &descriptor) override |
| | Backend hook: create a surface for a platform object.
|
| Result< SurfaceCapabilities > | onSurfaceCapabilities (uint32_t slotIndex) const override |
| | Backend hook: what a surface supports.
|
| Status | onConfigureSurface (uint32_t slotIndex, const SurfaceConfiguration &configuration) override |
| | Backend hook: apply a configuration.
|
| Result< SurfaceStatus > | onAcquireCurrentTexture (uint32_t slotIndex, uint32_t textureSlotIndex) override |
| | Backend hook: acquire the next frame's texture and report the surface's state.
|
| Result< SurfaceStatus > | onPresentSurface (uint32_t slotIndex) override |
| | Backend hook: present the acquired texture.
|
| void | onAbandonCurrentTexture (uint32_t slotIndex) override |
| | Backend hook: drop the acquired texture without presenting.
|
| void | onDestroySurface (uint32_t slotIndex) override |
| | Backend hook: release the platform state of a surface that is going away.
|
| Status | onMapBufferAsync (uint32_t mappingSlotIndex, uint32_t bufferSlotIndex, MapMode mode, uint64_t offsetBytes, uint64_t byteCount) override |
| | Backend hook: begin mapping a buffer range.
|
| MapSliceReport | onWaitMappingSlice (uint32_t mappingSlotIndex, double sliceSeconds) override |
| | Backend hook: wait up to sliceSeconds for a pending mapping, and report what it found and how it spent the slice.
|
| bool | onWaitForSerial (uint64_t serial, double timeoutSeconds) override |
| | Sleeps until the completion counter reaches serial, the budget runs out, a completed command buffer reports an execution error (see lastErrorForTest), or the root is declared lost.
|
| SerialWaitResult | onWaitForSerialUnlessStalled (uint64_t serial, std::chrono::milliseconds stallBound) override |
| | Waits for serial on the progress clock the backstop on work in flight uses: the wait gives up once neither this device nor a producer its uncompleted work waits on has made progress for stallBound, measured from that progress.
|
| std::optional< std::chrono::steady_clock::time_point > | onLastProgress () override |
| | The latest progress of this device and of the producers its uncompleted submissions wait on, as the backstop on work in flight measures it.
|
| Result< std::span< const uint8_t > > | onMappedBytes (uint32_t mappingSlotIndex) const override |
| | Backend hook: bytes of a completed mapping.
|
| void | onUnmapBuffer (uint32_t mappingSlotIndex) override |
| | Backend hook: release a mapping.
|
|
| Device () |
| | Constructor for backends; assigns the process-unique device identity.
|
| virtual void | onPollBackend () |
| | One nonblocking backend event iteration before a pending-destroy scan. Native devices report completion directly; callback-driven adapters override this hook.
|
| void | adoptLostState (std::shared_ptr< DeviceLostState > state) |
| | Adopts the sticky loss condition of a backend root this device shares with another device.
|
| uint64_t | bufferLastUseSerial (uint32_t slotIndex) const |
| | Last accepted submission referencing a buffer slot already validated by the caller.
|
| uint64_t | textureLastUseSerial (uint32_t slotIndex) const |
| | Last accepted submission referencing a texture slot already validated by the caller.
|
| virtual void | onDestroyBufferBacking (uint32_t slotIndex) |
| | Backend hook: destroyBufferBacking validated slotIndex and is about to destroy its handle; release the allocation now rather than when the slot is recycled.
|
| virtual void | onDestroyTextureBacking (uint32_t slotIndex) |
| | Backend hook: the texture counterpart of onDestroyBufferBacking, with the same default. A slot whose backing this device does not own keeps its allocation.
|
| virtual bool | onOwnsTextureBacking (uint32_t slotIndex) const |
| | Backend hook: whether slotIndex holds backing this device allocated.
|
| Status | validateBufferHandleForBackend (const Buffer &buffer) const |
| | Validates a buffer handle for backend-provided auxiliary entry points (test readback helpers and similar), running the same null/device-identity/generation checks the template-method public API performs before its hooks.
|
| Status | validateTextureHandleForBackend (const Texture &texture) const |
| | Validates a texture handle for backend-provided auxiliary entry points, running the same null/device-identity/generation checks the template-method public API performs.
|
| Status | validateTextureViewHandleForBackend (const TextureView &textureView) const |
| | Validates a texture view handle for backend-provided auxiliary entry points: the view itself plus a re-resolution of its viewed texture, so a view of a destroyed (or slot-recycled) texture fails closed exactly like it does on the normal Device paths.
|
| Status | validateBufferMappingHandleForBackend (const BufferMapping &mapping) const |
| | Validates a buffer-mapping handle for backend-provided auxiliary entry points, running the same null/device-identity/generation checks the template-method public API performs, so a stale handle cannot read state belonging to the slot's new occupant.
|
| uint64_t | lastTextureUseSerial (uint32_t textureSlotIndex) const |
| | Serial of the last submission that referenced the texture at textureSlotIndex, or 0 when nothing has submitted work naming it.
|
| virtual uint64_t | onTextureWriteByteCount (std::span< const uint8_t > data) const |
| | Backend hook: the bytes the write onWriteTexture just accepted handed the backend's queue, which DeviceObserver::onTextureWritten reports.
|
|
void | notifyObserverOfBackendSubmission () const |
| | Reports a queue submission the backend made on its own, outside submit, to the installed observer as a submission of no command buffers and no draws.
|
Metal backend of the Donner GPU runtime.
Inherits every fail-closed validation check from donner::gpu::Device; the on* hooks receive only validated input and translate it to Metal objects. Any Metal-side failure (nil object, compile error, encoder failure) fails closed with a donner::gpu::GpuError; the backend never crashes on such failures.
Scope: host-visible buffers and textures, MSL shader modules, render pipelines with up to kMaxVertexBuffers vertex buffer layouts stepped per vertex or per instance, render passes with color attachments, scissors and viewports, non-indexed and indexed (16- and 32-bit) draws with instancing and base vertex, compute pipelines and passes over sampled and storage textures, texture-to-buffer and texture-to-texture copies, queue writes, host buffer mapping, and surface presentation. Resources bind through bind group 0 only; a submission that sets any other group is refused. Bindings follow the deterministic argument-table mapping in donner/gpu/shader/MslBindingMap.h: buffer binding b maps to Metal buffer index 1 + b, and texture and sampler bindings map directly. Vertex buffer layouts take the highest free vertex-stage argument indices at or below 30, in slot order, skipping the length table and every vertex-visible resource buffer; a pipeline whose vertex buffers do not fit is refused at creation.
Generated runtime-array reads use the exact declared binding sizes in a fixed length table. Metal buffer index 0 is reserved for that table, so raw MSL this backend accepts must leave index 0 free. Each immutable bind group caches the table; when the bound group changes it is uploaded to every stage the active encoder has, deliberately without consulting the layout, because the generated code's need for the table follows from the shader IR rather than from a layout's binding types or visibility. Repeated draws and dispatches reuse that binding, and each new pass binds it again. MSL modules must supply buffer interface metadata before native compilation, including an explicitly empty list when no entry point uses buffers. Pipeline creation checks the supplied facts against the layout's binding types and stage visibility.
Presentation goes through a Core Animation Metal layer the embedder supplies and continues to own: configuring sets the layer's pixel format, drawable extent, pacing and alpha compositing, and each acquired frame is one of that layer's drawables, handed out only while it matches the configured extent. Presenting waits for the frame's own submission to complete and then hands the drawable over, because a drawable presented from a command buffer is shown when that buffer is scheduled rather than when it completes, which would show a frame the GPU is still drawing. A frame whose work cannot finish because the root is lost is abandoned, and the present reports SurfaceStatus::DeviceLost; so does an acquire on a lost root, which hands out no frame. The layer hands out a small fixed number of drawables, so a frame that is neither presented nor abandoned stalls the next acquisition until the layer gives up waiting.
Queue writes update idle resources directly. Writes to resources an earlier submission still uses are copied into bounded host storage and uploaded at the beginning of the next ordinary submission. Repeated writes of the same resource range are coalesced. A batch adds one staging allocation and no queue submission; Metal retains the staging buffer and destinations until completion. Queued and in-flight uploads share a configurable byte budget, defaulting to kMaxBufferByteSize; at most 16,384 writes may await submission. Excess writes return LimitExceeded without changing the batch. The budget counts logical upload bytes, excluding row and alignment padding. Combined queued and in-flight packed staging also stays within the larger of this budget and kMaxBufferByteSize. Packing temporarily retains both the host payload and native staging buffer. Managed resources may have separate host and device backing, so physical RAM/VRAM usage also includes those copies and driver overhead. Destroyed destinations drop unsubmitted writes; completion returns the in-flight byte reservation.
macOS buffer blits require four-byte-aligned offsets and sizes. An unaligned update instead waits for that buffer's last use for as long as the work ahead of it keeps making progress. It returns an error if this device has failed work, its root is lost, or that work makes no progress for the unalignedWriteTimeout given to Create (five seconds by default); it never waits for unrelated later submissions.
Memory model: unified-memory resources use MTLStorageModeShared; other devices use MTLStorageModeManaged. Managed CPU writes publish their ranges, and every submission synchronizes its GPU-written buffers before reporting completion to host readers. Textures reach the CPU through readback buffers, so unrelated buffers and textures need no scan.
Threading: single-threaded use, matching donner::gpu::Device's thread affinity. The thread needs no autorelease pool of its own: every call drains the Objective-C objects it autoreleased before it returns, so a plain worker thread keeps no frame's resources resident after the call that used them. The one exception to single-threaded use is command-buffer completion handlers, which Metal invokes on an internal queue; they touch only atomics, mutex-protected completion state (the error string and the ordered completion watermark), and the root's shared loss condition, observable through completedSerial, Device::waitForSerial, Device::isLost, and lastErrorForTest. A handler that saw work fail also writes one diagnostic line to stderr, after it has published everything a waiter reads.
Every command buffer of a submission reports its outcome, and the submission completes once all of them have. When any of them failed on the GPU, the submission failed: the root is declared lost, with no wait site because the backend reported it, before the submission's serial is reported complete. The completed serial advances only in serial order, so a serial seen complete carries the outcome of every submission through it, whatever order their handlers ran in. Mappings answer loss before readiness and serial waits give up at once, whichever device over the root declared the loss, so teardown after a hang does not wait it out.
Work in flight is bounded in command buffers: a submission that would leave more than kMaxCommandBuffersInFlight committed and uncompleted waits for room before it acquires any native buffer, so acquisition never blocks inside Metal. The wait is bounded. It gives up when no progress has been seen for five seconds, measured from the last progress rather than from the start of the wait; progress is a command buffer of this device completing, or a completion on another device while an uncompleted submission of this one still waits for that device's work. Once that work completes, the other device's later completions no longer count. The wait also gives up after sixty seconds in all. Either way the root is declared lost, attributed to the queue wait, and the submission is refused without consuming a serial. Ordinary frames stay far below the backstop and never wait. Another device's progress is sampled one hop deep: when that device's own work in turn waits on a third device, the third device's completions do not count, so a chain that progresses only at its far end for longer than the stall bound declares the loss.
Every other wait that exists to detect a hung GPU judges a stall on that same progress clock rather than giving the whole backlog a fixed budget: Device::waitForSerialUnlessStalled, the drain at teardown, a present's wait for its frame's work and an unaligned write's wait for its busy buffer. A slow device that keeps completing work is never cut off, however long its backlog, and a device that stops completing it is caught within the bound.
The header is pure C++ (Objective-C state lives behind a pimpl) so it is includable from C++ tests; the implementation is Objective-C++.