163 lines
14 KiB
Markdown
163 lines
14 KiB
Markdown
# Juliet\include\Graphics\Graphics
|
|
|
|
## Source Files
|
|
- Header: `Juliet\include\Graphics\Graphics.h`
|
|
- Source: `Juliet\src\Graphics\Graphics.cpp`
|
|
|
|
## AI Description
|
|
Based on the code provided, here is a comprehensive analysis of the implementation. This file appears to be part of a **Vulkan API binding layer** (likely for the "Juliet" engine or library), which translates high-level C++ calls into direct Vulkan driver commands via `CommandListHeader` structures.
|
|
|
|
### 1. Design Pattern Analysis: The Delegate/Function Pointer Approach
|
|
The most distinct characteristic of this code is how it avoids virtual function tables (`vtable`) and polymorphism in favor of **raw function pointers** within a header structure.
|
|
|
|
* **Structure:** You have a `CommandListHeader` (likely defined elsewhere). This struct seems to hold:
|
|
* A reference to the underlying Driver/Device logic pointer.
|
|
* Optional Function Pointers (e.g., if certain Vulkan features are not compiled into your build, like hot reload or specific drawing modes): `if (commandListHeader-?Device-?DrawIndexedPrimitives)`.
|
|
|
|
* **Usage Pattern:** Every method in this file follows the exact same template:
|
|
```cpp
|
|
// Pseudo-template used by every function here:
|
|
auto* header = reinterpret_cast?CommandListHeader*?(ptr);
|
|
if (header-?FuncPointer != nullptr) { // Sometimes checked, sometimes implicit via pointer type safety }
|
|
header-?Device-?Function(commandLineArg);
|
|
```
|
|
|
|
**Pros:**
|
|
* **Extremely Lightweight:** No overhead of vtable lookups or virtual calls. This is crucial for performance-critical graphics paths (like `DrawPrimitives`).
|
|
* **Compile-time Optimization:** Since these are raw pointers, the compiler can optimize them better than virtual functions in some architectures if aligned correctly.
|
|
|
|
**Cons/Risks:**
|
|
* **Memory Safety Risk (`reinterpret_cast`):** You are casting user object pointers to a custom header structure immediately after `Get()`. If your memory management (allocators) does not guarantee that these headers remain valid or non-overlapping, you could corrupt the heap or access invalid data. Ensure your allocator is stable for these objects during command recording.
|
|
* **Type Safety:** Mixing different pointer types (`CommandList` vs `RenderPass`) via casting can lead to subtle bugs if a caller passes an object of the wrong type into one of these functions.
|
|
|
|
---
|
|
|
|
### 2. Code Review ? Observations by Category
|
|
|
|
#### A. Command Recording Functions (`SetViewPort`, `DrawPrimitives`, etc.)
|
|
* **Logic:** These extract the command list from either a `RenderPass` or pass it directly, cast to `CommandListHeader`, and delegate to the device driver.
|
|
* **Optimization Note:** In your `WaitUntilGPUIsIdle`, you are passing `device-?Driver`. However, most other functions (`SetViewPort`, etc.) likely operate on a specific *queue family*. Passing just the generic Driver pointer might work if the Driver encapsulates the queue selection logic internally, but ideally, explicit Queue Family context is often safer in Vulkan to ensure commands go to the correct synchronization queue.
|
|
* **Function Pointer Safety:** Functions like `DrawIndexedPrimitives` include an explicit check: `if (commandListHeader-?Device-?DrawIndexedPrimitives)`. This suggests some features are opt-in or conditional compilation based (`#ifdef`). Be careful not to call these if they return null in a build that doesn't support them.
|
|
|
|
#### B. Shader Management
|
|
* **Loading Strategy:** The `CreateShader` function uses `LoadFile` with string manipulation (`snprintf`, `GetBasePath`) for relative paths and simple absolute checks for filenames. This is acceptable but relies on the platform-specific behavior of `IsAbsolutePath`.
|
|
* **Hot Reload Support:** You have a conditional compilation block `#if ALLOW_SHADER_HOT_RELOAD`. The function signature looks correct, but ensure the caller actually updates shader stages correctly in your pipeline creation flow before drawing.
|
|
|
|
#### C. Resource Management (Buffers ? Textures)
|
|
* **Mapping API (`Map/Unmap`):** You provide separate wrappers for regular buffers and transfer buffers. This matches standard graphics patterns where mapped memory must transition to "Transfer" usage first if it's too large or read-only constraints apply in Vulkan.
|
|
* **Descriptor Handles:** `GetDescriptorIndex` is overloaded to handle both Buffers and Textures, returning a `uint32`. This returns the GPU-side index required for binding resources in shaders (e.g., uniform buffers).
|
|
|
|
#### D. Potential Issues ? Recommendations
|
|
|
|
1. **Memory Leaks / Double Free Risk:**
|
|
* The code uses smart pointers (`NonNullPtr`). If your engine relies on RAII to manage these objects, this looks safe. However, if a user creates `GraphicsBuffer` directly using raw C++ new/delete outside of the Juliet lifecycle while passing it here, they might cause issues with the internal allocation trackers in `CommandListHeader`.
|
|
|
|
2. **Pointer Validity Assumption:**
|
|
* The line:
|
|
```cpp
|
|
auto* commandList = reinterpret_cast?GPUPass*?(renderPass.Get())-?CommandList; // Cast assumed to exist here? Or is GPUPass a wrapper around CommandListContainer?
|
|
auto* commandListHeader = reinterpret_cast?CommandListHeader*?(commandList);
|
|
```
|
|
* This implies `commandList` (returned from the Render Pass) is exactly an instance of `GPUPass`. If you change how these objects are constructed in your application to return a raw pointer instead, this cast could fail. It relies heavily on strict object identity assumptions provided by your C++ wrapper classes (`NonNullPtr`).
|
|
|
|
3. **Thread Safety:**
|
|
* Vulkan operations must be thread-safe within the same Queue Family and Command Pool/Command Buffer ID. Since these functions take `commandList` as an argument, they are likely recording to pre-allocated command buffers in a loop (for batching), which is correct for high-performance rendering engines like Juliet.
|
|
|
|
4. **Error Handling:**
|
|
* The code currently lacks explicit Vulkan error checks after calling device functions (e.g., checking `vkResult` or internal driver errors). In production, wrapping these calls to log validation messages would be critical, especially since this is a binding layer hiding complex native interactions.
|
|
|
|
### Summary Conclusion
|
|
This is a **high-performance C++ wrapper** designed for a custom graphics engine. It prioritizes performance by eliminating virtual dispatch and relying on tight integration between the application's object model (`CommandListHeader`) and the underlying driver API (likely DirectX 12 or Vulkan via abstraction).
|
|
|
|
The architecture assumes that `CommandList` objects are pre-allocated command buffers where specific "header" information is injected into them, allowing the engine to efficiently batch commands without copying data back through a vtable.
|
|
|
|
## Symbols
|
|
|
|
### Namespace `Juliet`
|
|
|
|
#### Classes, Structs & Unions
|
|
- `struct IndirectDrawCommand`
|
|
- `struct IndexedIndirectDrawCommand`
|
|
- `struct IndirectDispatchCommand`
|
|
- `struct GraphicsViewPort`
|
|
|
|
#### Enums
|
|
- `enum class QueueType`
|
|
- `enum class IndexFormat`
|
|
- `enum struct`
|
|
- `enum struct`
|
|
|
|
#### Functions & Methods
|
|
- `extern JULIET_API GraphicsDevice* CreateGraphicsDevice(GraphicsConfig config)`
|
|
- `extern JULIET_API void DestroyGraphicsDevice(NonNullPtr<GraphicsDevice> device)`
|
|
- `// Attach To Window
|
|
extern JULIET_API bool AttachToWindow(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window)`
|
|
- `extern JULIET_API void DetachFromWindow(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window)`
|
|
- `// SwapChain
|
|
extern JULIET_API bool AcquireSwapChainTexture(NonNullPtr<CommandList> commandList, NonNullPtr<Window> window,
|
|
Texture** swapChainTexture)`
|
|
- `extern JULIET_API bool WaitAndAcquireSwapChainTexture(NonNullPtr<CommandList> commandList,
|
|
NonNullPtr<Window> window, Texture** swapChainTexture)`
|
|
- `extern JULIET_API bool WaitForSwapchain(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window)`
|
|
- `extern JULIET_API TextureFormat GetSwapChainTextureFormat(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window)`
|
|
- `// Textures
|
|
extern JULIET_API Texture* CreateTexture(NonNullPtr<GraphicsDevice> device, const TextureCreateInfo& createInfo)`
|
|
- `extern JULIET_API void DestroyTexture(NonNullPtr<GraphicsDevice> device, NonNullPtr<Texture> texture)`
|
|
- `// Command List
|
|
extern JULIET_API CommandList* AcquireCommandList(NonNullPtr<GraphicsDevice> device, QueueType queueType = QueueType::Graphics)`
|
|
- `extern JULIET_API void SubmitCommandLists(NonNullPtr<CommandList> commandList)`
|
|
- `// RenderPass
|
|
extern JULIET_API RenderPass* BeginRenderPass(NonNullPtr<CommandList> commandList, ColorTargetInfo& colorTargetInfo,
|
|
DepthStencilTargetInfo* depthStencilTargetInfo = nullptr)`
|
|
- `extern JULIET_API RenderPass* BeginRenderPass(NonNullPtr<CommandList> commandList,
|
|
NonNullPtr<const ColorTargetInfo> colorTargetInfos, uint32 colorTargetInfoCount,
|
|
DepthStencilTargetInfo* depthStencilTargetInfo = nullptr)`
|
|
- `extern JULIET_API void EndRenderPass(NonNullPtr<RenderPass> renderPass)`
|
|
- `extern JULIET_API void SetGraphicsViewPort(NonNullPtr<RenderPass> renderPass, const GraphicsViewPort& viewPort)`
|
|
- `extern JULIET_API void SetScissorRect(NonNullPtr<RenderPass> renderPass, const Rectangle& rectangle)`
|
|
- `extern JULIET_API void SetBlendConstants(NonNullPtr<RenderPass> renderPass, FColor blendConstants)`
|
|
- `extern JULIET_API void SetStencilReference(NonNullPtr<RenderPass> renderPass, uint8 reference)`
|
|
- `extern JULIET_API void BindGraphicsPipeline(NonNullPtr<RenderPass> renderPass, NonNullPtr<GraphicsPipeline> graphicsPipeline)`
|
|
- `extern JULIET_API void DrawPrimitives(NonNullPtr<RenderPass> renderPass, uint32 numVertices, uint32 numInstances,
|
|
uint32 firstVertex, uint32 firstInstance)`
|
|
- `extern JULIET_API void DrawIndexedPrimitives(NonNullPtr<RenderPass> renderPass, uint32 numIndices, uint32 numInstances,
|
|
uint32 firstIndex, uint32 vertexOffset, uint32 firstInstance)`
|
|
- `extern JULIET_API void SetIndexBuffer(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> buffer,
|
|
IndexFormat format, size_t indexCount, index_t offset)`
|
|
- `extern JULIET_API void SetPushConstants(NonNullPtr<CommandList> commandList, ShaderStage stage,
|
|
uint32 rootParameterIndex, uint32 numConstants, const void* constants)`
|
|
- `// Fences
|
|
extern JULIET_API bool WaitUntilGPUIsIdle(NonNullPtr<GraphicsDevice> device)`
|
|
- `// Shaders
|
|
extern JULIET_API Shader* CreateShader(NonNullPtr<GraphicsDevice> device, String filename, ShaderCreateInfo& shaderCreateInfo)`
|
|
- `extern JULIET_API void DestroyShader(NonNullPtr<GraphicsDevice> device, NonNullPtr<Shader> shader)`
|
|
- `// Pipelines
|
|
extern JULIET_API GraphicsPipeline* CreateGraphicsPipeline(NonNullPtr<GraphicsDevice> device,
|
|
const GraphicsPipelineCreateInfo& createInfo)`
|
|
- `extern JULIET_API void DestroyGraphicsPipeline(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsPipeline> graphicsPipeline)`
|
|
- `#if ALLOW_SHADER_HOT_RELOAD
|
|
// Allows updating the graphics pipeline shaders. Can update either one or both shaders.
|
|
extern JULIET_API bool UpdateGraphicsPipelineShaders(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsPipeline> graphicsPipeline,
|
|
Shader* optional_vertexShader, Shader* optional_fragmentShader)`
|
|
- `#endif
|
|
|
|
// Buffers
|
|
extern JULIET_API GraphicsBuffer* CreateGraphicsBuffer(NonNullPtr<GraphicsDevice> device, const BufferCreateInfo& createInfo)`
|
|
- `extern JULIET_API GraphicsTransferBuffer* CreateGraphicsTransferBuffer(NonNullPtr<GraphicsDevice> device,
|
|
const TransferBufferCreateInfo& createInfo)`
|
|
- `extern JULIET_API void* MapGraphicsBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsBuffer> buffer)`
|
|
- `extern JULIET_API void UnmapGraphicsBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsBuffer> buffer)`
|
|
- `extern JULIET_API void* MapGraphicsTransferBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsTransferBuffer> buffer)`
|
|
- `extern JULIET_API void UnmapGraphicsTransferBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsTransferBuffer> buffer)`
|
|
- `extern JULIET_API void CopyBuffer(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> dst,
|
|
NonNullPtr<GraphicsTransferBuffer> src, size_t size, size_t dstOffset = 0,
|
|
size_t srcOffset = 0)`
|
|
- `extern JULIET_API void CopyBufferToTexture(NonNullPtr<CommandList> commandList, NonNullPtr<Texture> dst,
|
|
NonNullPtr<GraphicsTransferBuffer> src)`
|
|
- `extern JULIET_API void TransitionBufferToReadable(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> buffer)`
|
|
- `extern JULIET_API uint32 GetDescriptorIndex(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsBuffer> buffer)`
|
|
- `extern JULIET_API uint32 GetDescriptorIndex(NonNullPtr<GraphicsDevice> device, NonNullPtr<Texture> texture)`
|
|
- `extern JULIET_API void DestroyGraphicsBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsBuffer> buffer)`
|
|
- `extern JULIET_API void DestroyGraphicsTransferBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsTransferBuffer> buffer)`
|
|
|