Introduce a new Buffer type with DX,VK,MTL implementations.#976
Introduce a new Buffer type with DX,VK,MTL implementations.#976
Conversation
include/API/Device.h
Outdated
|
|
||
| struct BufferCreateDesc { | ||
| MemoryLocation Location; | ||
| BufferUsage Usage; |
There was a problem hiding this comment.
I don't think I see this Usage flag used anywhere. Is the idea that buffer usage describes the buffer as CBV/UAV/SRV (in DX12 parlance)?
There was a problem hiding this comment.
The Usage tag will later be used to specify the type of buffer (Storage, AccelerationStructure, Constant, etc). I included it here so that the field should be filled in from the start. Perhaps we should apply a constructor on this type to enforce filling in all fields?
There was a problem hiding this comment.
FWIW, I think I'd hold off on adding fields / enums until we can include code demonstrating what they're for, so I'd recommend just removing the Usage field & enum until we have a need for it.
There was a problem hiding this comment.
Let's go with that, I have removed the field.
| }; | ||
|
|
||
| enum class MemoryLocation { | ||
| GpuOnly, |
There was a problem hiding this comment.
This may be confusing if later on you want to add CPU-visible memory which is still GPU local (ReBAR).
I wonder if the memory locations should just be GpuLocal or HostLocal and then the usage of Readback, Upload, ShaderMutable, or ShaderVisible then dictates the heap/memory-type in conjunction with "MemoryLocation"
There was a problem hiding this comment.
In our closed-source render framework, we use CpuToGpu to express that the resource has to be HOST visible and won't be read from. Whether that resource is actually in GPU Local ReBAR, some form of shared memory or on HOST. I am basically using that as a starting point as it has proven to have worked so far, but I am open for suggestion and changes.
That's why the terms GpuLocal and HostLocal may not fit as well, since it's a detail that the rendering backend should deal with and not necessarily the cross-platform rendering code.
There was a problem hiding this comment.
Sure, but the enum name here is MemoryLocation after all. I think hiding that as an implementation detail totally makes sense, but it may make sense for the type name to then indicate as such. Personally, if the goal is to expose strictly semantics to the user of this API, and the specific memory location is an implementation detail, I would probably opt to just expose the BufferUsage flag alone.
There was a problem hiding this comment.
Fortunately, we can change code in the future if we decide that the abstraction we've picked doesn't work out well. At this point I think that categorizing memory as GpuOnly, CpuToGpu and GpuToCpu is a reasonable categorization that works well across all the APIs. I do agree that the name MemoryLocation could be tightened up - MemoryType perhaps? I'm biased as I'm more familiar with D3D12_HEAP_TYPE that has 1:1 mappings with these values :)
There was a problem hiding this comment.
MemoryType doesn't match with Vulkan memory types, though. The name of this enum is not as important to me as long as I can quickly understand if it's CpuToGpu, GpuOnly, or GpuToCpu.
The reason for MemoryLocation was just purely based on it being called that in our in-house rendering framework.
There was a problem hiding this comment.
Ah, trying to pick good names is hard, trying to pick good names that don't conflict with names used by 3 graphics APIs is even harder :)
I don't think we should block this PR going in on getting the right name. I agree that the abstraction here is a good one, and I think we'll have to accept that the name picked here is somewhat arbitrary and the least-obviously-bad one.
| } | ||
|
|
||
| const D3D12_RESOURCE_FLAGS Flags = | ||
| D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; |
There was a problem hiding this comment.
These flags aren't yet used, but I'm assuming UAV access should be conditional depending on usage
There was a problem hiding this comment.
On Buffers UAV access is kind-of the default. There is no special swizzling or compression like on textures. There may be some exceptions when it comes to acceleration structures that according to the spec shouldn't be UAV. However, I would like to solve that problem once we reach that point.
There was a problem hiding this comment.
Yea that's definitely true (re: difference between buffers and textures), although the semantic difference for buffers is often still meaningful in most engines -- you wouldn't expect to create a UAV descriptor for a read-only buffer for example (unless you live in a BDA-only world)
There was a problem hiding this comment.
From my experience, having worked in various game engines, I have never seen an advantage to marking buffers as read/SRV-only at buffer creation time. Just binding it as read or write has been sufficient as that also guides barrier placement.
I would also like to point out what this flag is for. This flag is just purely there to tell the driver what kind of optimizations (swizzling/compression) it can apply on a resource. That's why resources with no flags passed disallow a lot of use cases. However, in the case of buffers (which use the same resource creation API) there are no optimziations to apply. So there is no point to not passing this flag in the rendering backend.
There was a problem hiding this comment.
The advantage just in a dx12 sense is that you can validate against incorrect usage if anyone tries to grab a UAV descriptor basically. Not a hill I'd ever die on. (I've worked on console games for over a decade and 5 different game engines now so I'm familiar with delta color compression and the other concepts you are mentioning FWIW). Some engines map descriptors/addresses persistently and expose different accessors depending on declared usage, so it's a question of style and user preference I suppose.
llvm-beanz
left a comment
There was a problem hiding this comment.
A couple comments. As a meta-comment, we do use clang-tidy to enforce consistent style and coding standards. That is run with -Werror in our bots, so some of the CI failures you're seeing as build failures are Clang-Tidy failures.
Our ReadMe documents setting up Clang-Tidy once you have it installed: https://github.com/llvm/offload-test-suite?tab=readme-ov-file#enabling-clang-tidy
| class DXBuffer : public offloadtest::Buffer { | ||
| public: | ||
| ComPtr<ID3D12Resource> Buffer; | ||
| std::string Name; |
There was a problem hiding this comment.
Should we put the Name and SizeInBytes in the base class? Seems like all the sub-classes have those fields.
There was a problem hiding this comment.
I am not sure! I am a big fan of using Pure Virtual Classes and only doing functionality inheritance without any data inheritance, much like traits in Rust. What is the LLVM approach to this?
There was a problem hiding this comment.
I don't think LLVM has a strict opinion here, both patterns are present depending on the context. I think the functionality inheritance here is reasonable.
Yes I saw something along the lines of that. I'll check it out! <3 I have some local changes to the README with steps and pointers that I found difficult to figure out to improve the onboarding in the future. |
1bd0067 to
7f99fc7
Compare
7f99fc7 to
b67a337
Compare
damyanp
left a comment
There was a problem hiding this comment.
LGTM, just a couple of observations.
| ComPtr<ID3D12Resource> Buffer; | ||
| std::string Name; | ||
| BufferCreateDesc Desc; | ||
| size_t SizeInBytes; |
There was a problem hiding this comment.
Did we turn off the clang-tidy rule about public data members?
There was a problem hiding this comment.
clang-tidy doesn't seem to mind. Personally, I don't see the advantage of private data members and using getters and setters for everything. It adds a lot of boilerplate to solve issues that are these days solved by tools like clang-tidy (for example: if (MyObject->Field = 3). On top of that, if you write MyObject->GetField() everywhere, assuming that that is a cheap (should-be-free) operation, you can get in the situation where GetField() is refactored to be an expensive operation, you suddenly have introduced a lot of performance overhead. By just accessing the fields directly you are force to refactor the call site as well when changes are made and can catch these types of issues more easily.
If, however, using private fields with getters and setters is the LLVM way, we will adapt to that.
There was a problem hiding this comment.
I tend to agree with you here - I only mentioned it because I've had to fight with clang-tidy over this in the past :)
There was a problem hiding this comment.
Our clang-tidy rules are setup to match LLVM's coding standards, and LLVM allows public data members in classes.
offload-test-suite/.clang-tidy
Line 1 in bc74ffb
There was a problem hiding this comment.
It functionally 100% equivalent, so I'm happy with whatever you do here, but typically LLVM uses struct for all-public objects like this. Saves a line of code since it's the default and there's a very weak argument that it makes things clearer.
deed800 to
5d60f9f
Compare
|
Can you fix the code formatting errors? Then I can merge this. |
bogner
left a comment
There was a problem hiding this comment.
This generally looks reasonable, but for future reference it's always nice if we can add some usage and/or tests whenever we add API surface like this. This both helps in clarifying the motivation and makes it easier to review when you can see the usage in context.
| class DXBuffer : public offloadtest::Buffer { | ||
| public: | ||
| ComPtr<ID3D12Resource> Buffer; | ||
| std::string Name; |
There was a problem hiding this comment.
I don't think LLVM has a strict opinion here, both patterns are present depending on the context. I think the functionality inheritance here is reasonable.
| ComPtr<ID3D12Resource> Buffer; | ||
| std::string Name; | ||
| BufferCreateDesc Desc; | ||
| size_t SizeInBytes; |
There was a problem hiding this comment.
It functionally 100% equivalent, so I'm happy with whatever you do here, but typically LLVM uses struct for all-public objects like this. Saves a line of code since it's the default and there's a very weak argument that it makes things clearer.
|
|
||
| virtual Queue &getGraphicsQueue() = 0; | ||
|
|
||
| virtual llvm::Expected<std::shared_ptr<Buffer>> |
There was a problem hiding this comment.
I'd prefer to avoid shared_ptr unless we really need it. Since this code just introduces the APIs and doesn't use them yet it's hard for me to tell if we do, but generally I think shared_ptr can be used in ways that obscure ownership and we should use it sparingly.
|
bah - github is failing to refresh these pages lately. I didn't realize my review was post commit. |
A first small step towards creating a Render Backend API layer on top of DX12, Vulkan, and Metal.
This introduces a new Buffer type with an implementation for each API as well as a function on the Device class to create one.
The change is smaller than it looks due to a naming conflict that needed to be resolved. The type previously called
Bufferhas been renamed toCPUBuffer.