Add CommandBuffer abstraction with backend-specific downcasting#1033
Add CommandBuffer abstraction with backend-specific downcasting#1033
Conversation
378ec7a to
97e6af6
Compare
lib/API/DX/Device.cpp
Outdated
| if (!CBOrErr) | ||
| return CBOrErr.takeError(); | ||
| auto CBOwner = std::move(*CBOrErr); | ||
| State.CB = &CBOwner->as<DXCommandBuffer>(); |
There was a problem hiding this comment.
Seems like it would be better if the Invocation state owned the command buffer rather than a temporary object in the function here. If we really should have two copies of it we should probably make it a shared_ptr rather than a unique_ptr.
There was a problem hiding this comment.
I don't think using a shared_ptr for a command buffer is a good idea. The usage of shared_ptr in the rendering backend API is there for resources that can be accessed across multiple threads. However, command buffers should be access from one thread at a time.
I am open for either storing it as a unique_ptr in the InvocationState or just passing it as an argument each time.
There was a problem hiding this comment.
Good points, the InvocationState also lives on the stack here and owns all members, except for the backend-specific-typed command buffer which is merely a pointer reference to another stack member (which becomes dangling when CBOwner is dropped from the stack before InvocationState).
I also vouch for keeping it uniquely owned, that way command buffers can only be passed by reference and not accidentally held and modified in multiple places (trying very hard to forget Rust's natural ownership semantics and page in C++'s nonsensical ones 🙃).
There was a problem hiding this comment.
The only downside of that is needing the backend-specific pointer easily accessible. Either the field is stored twice, or this backend-specific executeProgram() call uses the specific XxxCommandBuffer::create() call to immediately have the right type until every internal field access is replaced with a generic abstraction.
8803d26 to
dadf52d
Compare
dadf52d to
4efc0b0
Compare
095fc23 to
4a67b8e
Compare
4a67b8e to
66bb416
Compare
Command buffer creation and management was previously spread across each backend's executeProgram() with no shared interface, making it impossible to manage command buffers from backend-agnostic code. This introduces a CommandBuffer base class on Device so that higher-level code can create and pass around command buffers without knowing the backend. Per-object allocator/pool ownership also prepares for future async execution where multiple command buffers may be in-flight with independent lifetimes. - DX: DXCommandBuffer owns Allocator, CmdList, Fence, Event - VK: VKCommandBuffer owns CmdPool, CmdBuffer; each submission creates a new CommandBuffer for independent lifetime management - MTL: MTLCommandBuffer wraps MTL::CommandBuffer Device::createCommandBuffer() returns Expected<unique_ptr<CommandBuffer>> with a default "not supported" implementation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
66bb416 to
9a1bfe5
Compare
Command buffer creation and management was previously spread across each backend's
executeProgram()with no shared interface, making it impossible to manage command buffers from backend-agnostic code. This introduces aCommandBufferbase class onDeviceso that higher-level code can create and pass around command buffers without knowing the backend. Per-object allocator/pool ownership also prepares for future async execution where multiple command buffers may be in-flight with independent lifetimes.DXCommandBufferowns Allocator, CmdList, Fence, EventVKCommandBufferowns CmdPool, CmdBuffer; each submission creates a new CommandBuffer for independent lifetime managementMTLCommandBufferwrapsMTL::CommandBufferDevice::createCommandBuffer()returnsExpected<unique_ptr<CommandBuffer>>with a default "not supported" implementation.Test plan
🤖 Generated with Claude Code