Created
May 9, 2025 11:59
-
-
Save philipturner/3c58b9d4a637659a96c286674360a959 to your computer and use it in GitHub Desktop.
More files saved for easy reference, while cleaning up an iteration of the Windows port of Molecular Renderer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#if false | |
import SwiftCOM | |
import WinSDK | |
// Exercise in usage of the DirectX 12 API. | |
// Implementing the CommandQueue API design from: | |
// https://www.3dgep.com/learning-directx-12-2/#The_Command_Queue_Class | |
// As literal of a translation as possible from the C++ origin. | |
public class CommandQueue { | |
// MARK: - Private | |
struct CommandAllocatorEntry { | |
var fenceValue: UInt64 | |
var commandAllocator: SwiftCOM.ID3D12CommandAllocator | |
} | |
private var commandListType: D3D12_COMMAND_LIST_TYPE | |
private var d3d12Device: SwiftCOM.ID3D12Device | |
private var d3d12CommandQueue: SwiftCOM.ID3D12CommandQueue | |
private var d3d12Fence: SwiftCOM.ID3D12Fence | |
private var fenceEvent: HANDLE | |
private var fenceValue: UInt64 | |
private var commandAllocatorQueue: [CommandAllocatorEntry] = [] | |
private var commandListQueue: [SwiftCOM.ID3D12GraphicsCommandList] = [] | |
// MARK: - Public | |
public init(device: SwiftCOM.ID3D12Device, type: D3D12_COMMAND_LIST_TYPE) { | |
self.fenceValue = 0 | |
self.commandListType = type | |
self.d3d12Device = device | |
var commandQueueDesc = D3D12_COMMAND_QUEUE_DESC() | |
commandQueueDesc.Type = type | |
commandQueueDesc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL.rawValue | |
commandQueueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE | |
commandQueueDesc.NodeMask = 0 | |
d3d12CommandQueue = try! d3d12Device.CreateCommandQueue(commandQueueDesc) | |
d3d12Fence = try! d3d12Device.CreateFence(fenceValue, D3D12_FENCE_FLAG_NONE) | |
let fenceEvent = CreateEventA(nil, false, false, nil) | |
guard let fenceEvent else { | |
fatalError("Failed to create fence event handle.") | |
} | |
self.fenceEvent = fenceEvent | |
} | |
/// Get an available command list from the command queue. | |
public func GetCommandList() -> SwiftCOM.ID3D12GraphicsCommandList { | |
// Check whether we can recycle an existing command allocator. | |
var commandAllocatorExists = false | |
if commandAllocatorQueue.count > 0 { | |
let frontEntry = commandAllocatorQueue.first! | |
let frontFenceValue = frontEntry.fenceValue | |
if IsFenceComplete(fenceValue: frontFenceValue) { | |
commandAllocatorExists = true | |
} | |
} | |
// Materialize a command allocator. | |
var commandAllocator: SwiftCOM.ID3D12CommandAllocator | |
if commandAllocatorExists { | |
// Remove the first element from the queue. | |
let frontEntry = commandAllocatorQueue.first! | |
commandAllocatorQueue.removeFirst() | |
commandAllocator = frontEntry.commandAllocator | |
try! commandAllocator.Reset() | |
} else { | |
commandAllocator = CreateCommandAllocator() | |
} | |
// Materialize a command list. | |
var commandList: SwiftCOM.ID3D12GraphicsCommandList | |
if commandListQueue.count > 0 { | |
// Remove the first element from the queue. | |
commandList = commandListQueue.first! | |
commandListQueue.removeFirst() | |
try! commandList.Reset(commandAllocator, nil) | |
} else { | |
commandList = CreateCommandList(allocator: commandAllocator) | |
} | |
// Associate the command allocator with the command list so that it can be | |
// retrieved when the command list is executed. | |
var guid: _GUID = ID3D12CommandAllocator.IID | |
try! commandList.SetPrivateDataInterface(&guid, commandAllocator) | |
return commandList | |
} | |
/// Execute a command list. | |
/// - Returns: A fence value to wait for for this command list. | |
public func ExecuteCommandList(_ commandList: SwiftCOM.ID3D12GraphicsCommandList) -> UInt64 { | |
try! commandList.Close() | |
// Get the private data for the command allocator. | |
func getPrivateData( | |
commandList: SwiftCOM.ID3D12GraphicsCommandList | |
) -> SwiftCOM.ID3D12CommandAllocator { | |
var guid: _GUID = ID3D12CommandAllocator.IID | |
var dataSize: UInt32 = 8 | |
var pData = UnsafeMutableRawPointer(bitPattern: 0).unsafelyUnwrapped | |
try! commandList.GetPrivateData(&guid, &dataSize, &pData) | |
// Inspect the object pointer you were handed. | |
guard Int(bitPattern: pData) != 0 else { | |
fatalError("pData was invalid.") | |
} | |
// Create an IUnknown with the 'consuming' initializer, where the | |
// reference count will change by -1 once it eventually deinitializes. | |
let interface = SwiftCOM.ID3D12CommandAllocator(pUnk: pData) | |
return interface | |
} | |
let commandAllocator = getPrivateData(commandList: commandList) | |
// Execute an array of command lists | |
do { | |
let commandListArray = [commandList] | |
try! d3d12CommandQueue.ExecuteCommandLists(commandListArray) | |
} | |
// Retrieve the fence value. | |
let fenceValue = Signal() | |
// Append to the command allocator queue. | |
do { | |
let entry = CommandAllocatorEntry( | |
fenceValue: fenceValue, | |
commandAllocator: commandAllocator) | |
commandAllocatorQueue.append(entry) | |
} | |
// Append to the command list queue. | |
commandListQueue.append(commandList) | |
// No need to explicitly call 'Release()', as done in the tutorial. The | |
// C++ version explicitly managed COM references, while the Swift version | |
// already has the deinit primed to happen. | |
return fenceValue | |
} | |
// Increments the internal counter for the fence value. | |
public func Signal() -> UInt64 { | |
fenceValue += 1 | |
try! d3d12CommandQueue.Signal(d3d12Fence, fenceValue) | |
return fenceValue | |
} | |
public func IsFenceComplete(fenceValue: UInt64) -> Bool { | |
let fenceCompletedValue = try! d3d12Fence.GetCompletedValue() | |
return fenceCompletedValue >= fenceValue | |
} | |
public func WaitForFenceValue(_ fenceValue: UInt64) { | |
let fenceCompletedValue = try! d3d12Fence.GetCompletedValue() | |
if fenceCompletedValue < fenceValue { | |
try! d3d12Fence.SetEventOnCompletion(fenceValue, fenceEvent) | |
// Determine the wait time in milliseconds. | |
// | |
// Using a wait time of 1000 seconds (~20 minutes) | |
let waitTimeInMilliseconds: UInt32 = 1000 * 1000 | |
WaitForSingleObject(fenceEvent, waitTimeInMilliseconds) | |
} | |
} | |
// Increments the internal counter for the fence value. | |
public func Flush() { | |
let newFenceValue = Signal() | |
WaitForFenceValue(newFenceValue) | |
} | |
// MARK: - Protected | |
private func CreateCommandAllocator() -> SwiftCOM.ID3D12CommandAllocator { | |
let commandAllocator: SwiftCOM.ID3D12CommandAllocator = try! d3d12Device.CreateCommandAllocator(commandListType) | |
return commandAllocator | |
} | |
private func CreateCommandList(allocator: SwiftCOM.ID3D12CommandAllocator) -> SwiftCOM.ID3D12GraphicsCommandList { | |
let commandList: SwiftCOM.ID3D12GraphicsCommandList = try! d3d12Device.CreateCommandList(0, commandListType, allocator, nil) | |
return commandList | |
} | |
} | |
#endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#if false | |
import SwiftCOM | |
import WinSDK | |
// Exercise in usage of the DirectX 12 API. | |
// Implementing the DescriptorAllocator API design from: | |
// https://www.3dgep.com/learning-directx-12-3/#Descriptor_Allocator | |
// As literal of a translation as possible from the C++ origin. | |
public class DescriptorAllocation { | |
// MARK: - Private | |
// The base descriptor. | |
var descriptor: D3D12_CPU_DESCRIPTOR_HANDLE | |
// The number of descriptors in this allocation. | |
var numHandles: UInt32 | |
// The offset to the next descriptor. | |
var descriptorSize: UInt32 | |
// A pointer back to the original page where this allocation came from. | |
var page: DescriptorAllocatorPage | |
// MARK: - Public | |
/// Creates a NULL descriptor. | |
public init() { | |
fatalError("Not implemented.") | |
} | |
public init( | |
descriptor: D3D12_CPU_DESCRIPTOR_HANDLE, | |
numHandles: UInt32, | |
descriptorSize: UInt32, | |
page: DescriptorAllocatorPage | |
) { | |
fatalError("Not implemented.") | |
} | |
/// The destructor will automatically free the allocation. | |
deinit { | |
fatalError("Not implemented.") | |
} | |
/// Get a descriptor at a particular offset in the allocation. | |
public func GetDescriptorHandle( | |
offset: UInt32 = 0 | |
) -> D3D12_CPU_DESCRIPTOR_HANDLE { | |
fatalError("Not implemented.") | |
} | |
/// Get the number of (consecutive) handles for this allocation. | |
public func GetNumHandles() -> UInt32 { | |
fatalError("Not implemented.") | |
} | |
/// Get the heap that this allocation came from. | |
/// | |
/// For internal use only. | |
public func GetDescriptorAllocatorPage() -> DescriptorAllocatorPage { | |
fatalError("Not implemented.") | |
} | |
// MARK: - Private | |
// Free the descriptor back to the heap it came from. | |
private func Free() { | |
fatalError("Not implemented.") | |
} | |
} | |
#endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#if false | |
import SwiftCOM | |
import WinSDK | |
// Exercise in usage of the DirectX 12 API. | |
// Implementing the DescriptorAllocator API design from: | |
// https://www.3dgep.com/learning-directx-12-3/#Descriptor_Allocator | |
// As literal of a translation as possible from the C++ origin. | |
public class DescriptorAllocator { | |
// MARK: - Private | |
private var heapType: D3D12_DESCRIPTOR_HEAP_TYPE | |
private var numDescriptorsPerHeap: UInt32 | |
private var heapPool: [DescriptorAllocatorPage] = [] | |
// Indices of available heaps in the pool. | |
private var availableHeaps: Set<Int> = [] | |
// MARK: - Public | |
public init( | |
type: D3D12_DESCRIPTOR_HEAP_TYPE, | |
numDescriptorsPerHeap: UInt32 = 256 | |
) { | |
self.heapType = type | |
self.numDescriptorsPerHeap = numDescriptorsPerHeap | |
} | |
/// Allocate a number of contiguous descriptors from a CPU visible descriptor | |
/// heap. | |
/// - Parameter numDescriptors: The number of contiguous descriptors to | |
/// allocate. Cannot be more than the number of descriptors per descriptor | |
/// heap. | |
public func Allocate( | |
numDescriptors: UInt32 = 1 | |
) -> DescriptorAllocation { | |
// Declare a descriptor with the null initializer. This clashes with Swift's | |
// typical use of the Optional<> type. It would be simpler to declare the | |
// allocation as 'nil'. | |
// | |
// It was so bad that I had to modify the interface to DescriptorAllocation. | |
var allocation: DescriptorAllocation? | |
// Iterate through the heaps, while at the same time resizing the heaps | |
// list. This approach creates a lot of confusion. | |
// | |
// It was so bad that I had to rewrite the function from scratch. | |
for availableHeapID in availableHeaps { | |
// Retrieve the allocator page. | |
let allocatorPage = heapPool[availableHeapID] | |
// Create and inspect the allocation. | |
let candidateAllocation = allocatorPage.Allocate( | |
numDescriptors: numDescriptors) | |
if let candidateAllocation { | |
allocation = candidateAllocation | |
break | |
} | |
} | |
// Garbage collect the heaps after possibly modifying one of them. | |
PurgeAvailableHeaps() | |
// No available heap could satisfy the requested number of descriptors. | |
if allocation == nil { | |
// Grow the internal variable for descriptors per heap. | |
if numDescriptors > numDescriptorsPerHeap { | |
numDescriptorsPerHeap = numDescriptors | |
} | |
// Create a new page. | |
let newPage = CreateAllocatorPage() | |
// Create an allocation. | |
let candidateAllocation = newPage.Allocate( | |
numDescriptors: numDescriptors) | |
// The candidate allocation should always be created successfully. Merge | |
// the failure test with the other case, where failure should never | |
// happen. | |
allocation = candidateAllocation | |
} | |
// Execute a precondition. It is guaranteed to never be 'nil'. | |
guard let allocation else { | |
fatalError("This should never happen.") | |
} | |
return allocation | |
} | |
/// When the frame has completed, the stale descriptors can be released. | |
public func ReleaseStaleDescriptors( | |
frameNumber: UInt64 | |
) { | |
// Iterate through all of the heaps. | |
for heapID in heapPool.indices { | |
// Retrieve the page. | |
let page = heapPool[heapID] | |
// Modify the page. | |
page.ReleaseStaleDescriptors( | |
frameNumber: frameNumber) | |
} | |
// Garbage collect the heaps after possibly modifying one of them. | |
RefreshAvailableHeaps() | |
} | |
// MARK: - Private | |
// Create a new heap with a specific number of descriptors. | |
private func CreateAllocatorPage() -> DescriptorAllocatorPage { | |
let newPage = DescriptorAllocatorPage( | |
type: heapType, | |
numDescriptors: numDescriptorsPerHeap) | |
let availableHeapID = heapPool.count | |
heapPool.append(newPage) | |
availableHeaps.insert(availableHeapID) | |
return newPage | |
} | |
// TODO: Merge these two methods into one. Or, even better, transform the | |
// 'available heaps' set into a computed property. | |
// Separate pass to clean up the heaps list. | |
private func PurgeAvailableHeaps() { | |
// Iterate through the available pages. | |
var removedFromAvailable: [Int] = [] | |
for availableHeapID in availableHeaps { | |
// Retrieve the allocator page. | |
let allocatorPage = heapPool[availableHeapID] | |
// Inspect the allocator page. | |
if allocatorPage.NumFreeHandles() == 0 { | |
removedFromAvailable.append(availableHeapID) | |
} | |
} | |
// Remove the invalid heaps. | |
for removedHeapID in removedFromAvailable { | |
availableHeaps.remove(removedHeapID) | |
} | |
} | |
// Separate pass to clean up the heaps list. | |
private func RefreshAvailableHeaps() { | |
// Iterate through all of the heaps. | |
var addedToAvailable: [Int] = [] | |
for heapID in heapPool.indices { | |
// Retrieve the page. | |
let page = heapPool[heapID] | |
// Inspect the page. | |
if page.NumFreeHandles() > 0 { | |
addedToAvailable.append(heapID) | |
} | |
} | |
// Add the valid heaps. | |
for addedHeapID in addedToAvailable { | |
availableHeaps.insert(addedHeapID) | |
} | |
} | |
} | |
#endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#if false | |
import SwiftCOM | |
import WinSDK | |
// Exercise in usage of the DirectX 12 API. | |
// Implementing the DescriptorAllocator API design from: | |
// https://www.3dgep.com/learning-directx-12-3/#Descriptor_Allocator | |
// As literal of a translation as possible from the C++ origin. | |
public class DescriptorAllocatorPage { | |
// MARK: - Private | |
// A map that lists the free blocks by the offset within the descriptor heap. | |
private var freeListByOffset: [OffsetType: FreeBlockInfo] = [:] | |
// A map that lists the free blocks by size. | |
// | |
// Needs to be a multimap since multiple blocks can have the same size. | |
private var freeListBySize: [SizeType: [OffsetType]] = [:] | |
// Stale descriptors are queued for release until the frame that they were | |
// freed has completed. | |
private var staleDescriptors: [StaleDescriptorInfo] = [] | |
private var d3d12DescriptorHeap: SwiftCOM.ID3D12DescriptorHeap | |
private var heapType: D3D12_DESCRIPTOR_HEAP_TYPE | |
private var baseDescriptor: D3D12_CPU_DESCRIPTOR_HANDLE | |
private var descriptorHandleIncrementSize: UInt32 | |
private var numDescriptorsInHeap: UInt32 | |
private var numFreeHandles: UInt32 | |
// MARK: - Public | |
public init( | |
type: D3D12_DESCRIPTOR_HEAP_TYPE, | |
numDescriptors: UInt32 | |
) { | |
self.heapType = type | |
self.numDescriptorsInHeap = numDescriptors | |
// Not concerning myself with how to reference the global DirectX device. | |
func createGlobalDevice() -> SwiftCOM.ID3D12Device { | |
fatalError("Ignoring the implementation.") | |
} | |
do { | |
// Create a heap descriptor. | |
var heapDesc = D3D12_DESCRIPTOR_HEAP_DESC() | |
heapDesc.Type = type | |
heapDesc.NumDescriptors = numDescriptors | |
// Create the descriptor heap. | |
let device = createGlobalDevice() | |
d3d12DescriptorHeap = try! device.CreateDescriptorHeap( | |
heapDesc) | |
} | |
// Assign the remaining stored properties. | |
do { | |
baseDescriptor = try! d3d12DescriptorHeap | |
.GetCPUDescriptorHandleForHeapStart() | |
let device = createGlobalDevice() | |
descriptorHandleIncrementSize = try! device | |
.GetDescriptorHandleIncrementSize(heapType) | |
numFreeHandles = numDescriptorsInHeap | |
} | |
// Initialize the free lists. | |
AddNewBlock(offset: 0, numDescriptors: numFreeHandles) | |
} | |
public func GetHeapType() -> D3D12_DESCRIPTOR_HEAP_TYPE { | |
return heapType | |
} | |
/// Check to see if this descriptor page has a contiguous block of descriptors | |
/// large enough to satisfy the request. | |
public func HasSpace( | |
numDescriptors: UInt32 | |
) -> Bool { | |
// Retrieve the offsets. | |
let offsetsForSize = freeListBySize[numDescriptors] | |
// Branch on whether the offsets are nil. | |
if let offsetsForSize { | |
return offsetsForSize.count > 0 | |
} else { | |
return false | |
} | |
} | |
/// Get the number of available handles in the heap. | |
public func NumFreeHandles() -> UInt32 { | |
return numFreeHandles | |
} | |
/// Allocate a number of descriptors from this descriptor heap. | |
/// | |
/// If the allocation cannot be satisfied, then a NULL descriptor is returned. | |
public func Allocate( | |
numDescriptors: UInt32 | |
) -> DescriptorAllocation? { | |
// There are less than the requested number of descriptors left in the heap. | |
// Return a NULL descriptor and try another heap. | |
if numDescriptors > numFreeHandles { | |
return nil | |
} | |
// Get the first block that is large enough to satisfy the request. | |
do { | |
// The C++ std::map::lower_bound function is too complex for now. | |
// Reverting from an O(logn) to an O(nlogn) algorithm for simplicity. | |
let unsortedKeys = Array(freeListBySize.keys) | |
// Sort the keys in ascending order. | |
let sortedKeys = unsortedKeys.sorted(by: { $0 < $1 }) | |
// Iterate over the keys, searching for the smallest size whose list | |
// contains elements. | |
fatalError("Not implemented.") | |
} | |
} | |
/// Return a descriptor back to the heap. | |
/// - Parameter frameNumber: Stale descriptors are not freed directly, but put | |
/// on a stale allocations queue. Stale allocations are returned to the heap | |
/// using the DescriptorAllocatorPage::ReleaseStaleAllocations method. | |
public func Free( | |
descriptorHandle: inout DescriptorAllocation, | |
frameNumber: UInt64 | |
) { | |
fatalError("Not implemented.") | |
} | |
/// Returned the stale descriptors back to the descriptor heap. | |
public func ReleaseStaleDescriptors( | |
frameNumber: UInt64 | |
) { | |
fatalError("Not implemented.") | |
} | |
// MARK: - Protected | |
// Compute the offset of the descriptor handle from the start of the heap. | |
private func ComputeOffset( | |
handle: D3D12_CPU_DESCRIPTOR_HANDLE | |
) -> UInt32 { | |
fatalError("Not implemented.") | |
} | |
// Adds a new block to the free list. | |
private func AddNewBlock( | |
offset: UInt32, | |
numDescriptors: UInt32 | |
) { | |
// If a block list for the specified size doesn't exist, create a new one. | |
do { | |
let sizeList = freeListBySize[numDescriptors] | |
if sizeList == nil { | |
freeListBySize[numDescriptors] = [] | |
} | |
} | |
var freeListBySizeIt: UInt32 | |
do { | |
// Retrieve the list of blocks. | |
let sizeList = freeListBySize[numDescriptors] | |
guard var sizeList else { | |
fatalError("This size list should exist.") | |
} | |
// Retrieve the new block's position in the size list. | |
freeListBySizeIt = UInt32(sizeList.count) | |
// Append the new entry. | |
sizeList.append(offset) | |
// Return the list to its origin. | |
freeListBySize[numDescriptors] = sizeList | |
} | |
// Create a new block, with an undefined 'freeListBySizeIt'. | |
let newFreeBlock = FreeBlockInfo( | |
size: numDescriptors, | |
freeListBySizeIt: freeListBySizeIt) | |
// Add a new entry at the specified offset. We assume that the caller has | |
// already ensured it's okay to insert here. | |
freeListByOffset[offset] = newFreeBlock | |
} | |
// Free a block of descriptors. | |
// | |
// This will also merge free blocks in the free list to form larger blocks | |
// that can be reused. | |
private func FreeBlock( | |
offset: UInt32, | |
numDescriptors: UInt32 | |
) { | |
fatalError("Not implemented.") | |
} | |
} | |
extension DescriptorAllocatorPage { | |
// The offset (in descriptors) within the descriptor heap. | |
typealias OffsetType = UInt32 | |
// The number of descriptors that are available. | |
typealias SizeType = UInt32 | |
struct FreeBlockInfo { | |
var size: SizeType | |
var freeListBySizeIt: UInt32 | |
} | |
struct StaleDescriptorInfo { | |
// The offset within the descriptor heap. | |
var offset: OffsetType | |
// The number of descriptors | |
var size: SizeType | |
// The frame number that the descriptor was freed. | |
var frameNumber: UInt64 | |
} | |
} | |
#endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#if os(Windows) | |
// Next steps to refactor the codebase: | |
// - Reorganize the files in molecular-renderer to a loose grouping of what | |
// "should be" common vs. macOS vs. Windows [DONE] | |
// - Redirect the executable links in the package manifest to | |
// molecular-renderer [DONE] | |
// - Create utility files that can smoke test the following: | |
// - Create a DirectX device [DONE] | |
// - DXC symbol linking [DONE] | |
// - FidelityFX symbol linking [DONE] | |
// | |
// After that: | |
// - Do not jump ahead to merging any code with that for Apple yet. Need more | |
// experience with the entirety of the command dispatching workflow. | |
// - Implement the CommandQueue exercise. | |
let device = DirectXDevice() | |
let d3d12Device = device.d3d12Device | |
print(d3d12Device) | |
// The CommandQueue class has finished being translated. Next, how do we test | |
// it? The code base does not have good unit tests. | |
// | |
// Start by studying the tutorials. What is the first function I should call | |
// to initiate a testing procedure? | |
// | |
// Study the functions, what they're supposed to do, and how they're supposed to | |
// affect the internal state variables. There will be tests that monitor the | |
// internal state variables and check for unexpected behavior. | |
// - Ensure the tests cover the case where lists grow to >1 in size. | |
// - Ensure there are no memory leaks. This is a good question; I wonder how to | |
// debug/search for memory leaks in practice. | |
// var commandListType | |
// var d3d12Device | |
// var d3d12CommandQueue | |
// var d3d12Fence | |
// var fenceEvent | |
// var fenceValue | |
// var commandAllocatorQueue: [] | |
// var commandListQueue: [] | |
// | |
// init(device:type:) | |
// func GetCommandList() | |
// func ExecuteCommandList() | |
// func Signal() | |
// func IsFenceComplete(fenceValue:) | |
// func WaitForFenceValue() | |
// func Flush() | |
// func CreateCommandAllocator() | |
// func CreateCommandList(allocator:) | |
// I don't need to invest time testing and debugging this functionality. Just | |
// read along through the tutorials. The translation exercise will reinforce my | |
// knowledge of DirectX. Once it's complete, I can devise simpler tests. | |
// | |
// In a later tutorial, the command allocator is refactored out into the | |
// 'CommandList' class. There is less risk for memory leaks from manual COM | |
// memory management. Debugging the code above would waste time unnecessarily. | |
// Pausing progress on translating the 3DGEP tutorials. I don't feel | |
// motivated to work on it right now. Instead, I'm returning to the 'logins' | |
// tutorial about compute shaders. Perhaps there's a faster way to achieve | |
// 'hello world' for vector addition. | |
// | |
// https://logins.github.io/graphics/2020/10/31/D3D12ComputeShaders.html#compute-shaders-in-d3d12 | |
// Issues: | |
// - How to create resources | |
// - How to create pipelines | |
// - What the heck is going on with descriptors | |
// - What the heck is going on with root signatures | |
// - How to bind resources to commands | |
// - How to dispatch GPU threads | |
// - What the heck is going on with resource state (transitions) | |
// - How to test the results of GPU execution | |
// | |
// Can I do this without reading/translating the 3DGEP tutorials? Can I just | |
// read them without copying the code? There's a lot of non-DirectX stuff there, | |
// like boilerplate logic for memory allocation algorithms. I only care about | |
// the API calls. | |
// - The 'logins' code in FirstDX12Renderer relies on the utilities in the | |
// 3DGEP tutorials, so there's a Catch 22. You can't escape the dependency | |
// on the utilities. | |
// | |
// Sort through all of these issues and find a way to proceed, without wasting | |
// time unnecessarily. | |
#endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#if false | |
import SwiftCOM | |
import WinSDK | |
// Exercise in usage of the DirectX 12 API. | |
// Implementing the UploadBuffer API design from: | |
// https://www.3dgep.com/learning-directx-12-3/#uploadbuffer-class | |
// As literal of a translation as possible from the C++ origin. | |
public class UploadBuffer { | |
// MARK: - Private | |
private var pagePool: [Page] = [] | |
private var availablePages: [Page] = [] | |
private var currentPage: Page? | |
// The size of each page in memory. | |
private var pageSize: Int | |
// MARK: - Public | |
/// Use to upload data to the GPU. | |
public struct Allocation { | |
public var CPU: UnsafeMutableRawPointer? | |
public var GPU: UInt64 = 0 | |
} | |
/// - Parameter pageSize: The size to use to allocate new pages in GPU memory. | |
public init(pageSize: Int = 2 * 1024 * 1024) { | |
self.pageSize = pageSize | |
} | |
/// The maximum size of an allocation is the size of a single page. | |
public func GetPageSize() -> Int { | |
return pageSize | |
} | |
/// Allocate memory in an Upload heap. | |
/// | |
/// An allocation must not exceed the size of a page. Use a memcpy or similar | |
/// method to copy the buffer data to CPU pointer in the Allocation structure | |
/// returned from this function. | |
public func Allocate( | |
sizeInBytes: Int, | |
alignment: Int | |
) -> Allocation { | |
guard sizeInBytes <= pageSize else { | |
fatalError("Allocation size exceeded page size.") | |
} | |
// If there is no current page, or the requested allocation exceeds the | |
// remaining space in the current page, request a new page. | |
var shouldRequestPage = true | |
if let currentPage { | |
let hasSpace = currentPage.HasSpace( | |
sizeInBytes: sizeInBytes, alignment: alignment) | |
if hasSpace { | |
shouldRequestPage = false | |
} | |
} | |
if shouldRequestPage { | |
currentPage = RequestPage() | |
} | |
// Avoid confusion from a name conflict with the stored property, | |
// 'currentPage'. | |
guard let requestedPage = currentPage else { | |
fatalError("This should never happen.") | |
} | |
let allocation = requestedPage.Allocate( | |
sizeInBytes: sizeInBytes, alignment: alignment) | |
return allocation | |
} | |
/// Release all allocated pages. This should only be done when the command | |
/// list is finished executing on the CommandQueue. | |
public func Reset() { | |
currentPage = nil | |
// Reset all available pages. | |
availablePages = pagePool | |
// Pages are reference types ('class'), so you can mutate their contents | |
// while outside the list. This design choice doesn't mesh well with Swift. | |
for page in availablePages { | |
// Reset the page for new allocations. | |
page.Reset() | |
} | |
} | |
// MARK: - Private | |
// Request a page from the pool of available pages or create a new page if | |
// there are no available pages. | |
private func RequestPage() -> Page { | |
var output: Page | |
if availablePages.count > 0 { | |
// Retrieve a page from the cache. | |
let removedPage = availablePages.first! | |
availablePages.removeFirst() | |
output = removedPage | |
} else { | |
// Add a new page to the cache. | |
let newPage = Page(sizeInBytes: pageSize) | |
pagePool.append(newPage) | |
output = newPage | |
} | |
return output | |
} | |
} | |
// A single page for the allocator. | |
extension UploadBuffer { | |
class Page { | |
// MARK: - Private | |
private var d3d12Resource: SwiftCOM.ID3D12Resource | |
// Base pointer. | |
private var CPUPtr: UnsafeMutableRawPointer? | |
private var GPUPtr: UInt64 | |
// Allocated page size. | |
private var pageSize: Int | |
// Current allocation offset in bytes. | |
private var offset: Int | |
// MARK: - Public | |
init(sizeInBytes: Int) { | |
self.pageSize = sizeInBytes | |
self.offset = 0 | |
self.CPUPtr = nil | |
self.GPUPtr = 0 | |
// Not concerning myself with how to reference the global DirectX device. | |
func createGlobalDevice() -> SwiftCOM.ID3D12Device { | |
fatalError("Ignoring the implementation.") | |
} | |
// Utility function for creating a committed resource. | |
func createHeapProperties( | |
type: D3D12_HEAP_TYPE | |
) -> D3D12_HEAP_PROPERTIES { | |
var heapProperties = D3D12_HEAP_PROPERTIES() | |
heapProperties.Type = type | |
heapProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN | |
heapProperties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN | |
heapProperties.CreationNodeMask = 0 | |
heapProperties.VisibleNodeMask = 0 | |
return heapProperties | |
} | |
// Utility function for creating a committed resource. | |
func createResourceDesc(size: Int) -> D3D12_RESOURCE_DESC { | |
var resourceDesc = D3D12_RESOURCE_DESC() | |
resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER | |
resourceDesc.Alignment = 0 | |
resourceDesc.Width = UINT64(size) | |
resourceDesc.Height = 1 | |
resourceDesc.DepthOrArraySize = 1 | |
resourceDesc.MipLevels = 1 | |
resourceDesc.Format = DXGI_FORMAT_UNKNOWN | |
resourceDesc.SampleDesc = DXGI_SAMPLE_DESC(Count: 1, Quality: 0) | |
resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR | |
resourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE | |
return resourceDesc | |
} | |
// Create the committed resource. | |
func createCommittedResource(size: Int) -> SwiftCOM.ID3D12Resource { | |
let device = createGlobalDevice() | |
let heapProperties = createHeapProperties(type: D3D12_HEAP_TYPE_UPLOAD) | |
let resourceDesc = createResourceDesc(size: size) | |
let resource: SwiftCOM.ID3D12Resource = | |
try! device.CreateCommittedResource( | |
heapProperties, | |
D3D12_HEAP_FLAG_NONE, | |
resourceDesc, | |
D3D12_RESOURCE_STATE_GENERIC_READ, | |
nil) | |
return resource | |
} | |
self.d3d12Resource = createCommittedResource(size: pageSize) | |
self.CPUPtr = try! d3d12Resource.Map(0, nil) | |
self.GPUPtr = try! d3d12Resource.GetGPUVirtualAddress() | |
} | |
deinit { | |
try! d3d12Resource.Unmap(0, nil) | |
CPUPtr = nil | |
GPUPtr = 0 | |
} | |
// Check to see if the page has room to satisfy the requested allocation. | |
func HasSpace( | |
sizeInBytes: Int, | |
alignment: Int | |
) -> Bool { | |
// Utility function for rounding integers up. | |
func alignUp(size: Int, alignment: Int) -> Int { | |
guard alignment > 0, | |
alignment.nonzeroBitCount == 1 else { | |
fatalError("Invalid alignment.") | |
} | |
let mask = alignment - 1 | |
return (size + mask) & ~mask | |
} | |
let alignedSize = alignUp( | |
size: sizeInBytes, alignment: alignment) | |
let alignedOffset = alignUp( | |
size: offset, alignment: alignment) | |
let allocationEnd = alignedOffset + alignedSize | |
return allocationEnd <= pageSize | |
} | |
// Allocate memory from the page. | |
func Allocate( | |
sizeInBytes: Int, | |
alignment: Int | |
) -> Allocation { | |
// It would be better to just have an internal utility that reports the | |
// end of buffer. That way, the boolean comparison can be made explicitly | |
// in the caller. | |
let canAllocateSpace = HasSpace( | |
sizeInBytes: sizeInBytes, alignment: alignment) | |
guard canAllocateSpace else { | |
fatalError("Can't allocate space from page.") | |
} | |
// Utility function for rounding integers up. | |
func alignUp(size: Int, alignment: Int) -> Int { | |
guard alignment > 0, | |
alignment.nonzeroBitCount == 1 else { | |
fatalError("Invalid alignment.") | |
} | |
let mask = alignment - 1 | |
return (size + mask) & ~mask | |
} | |
// Utility function for adding to a CPU pointer. | |
func addCPUPtr( | |
_ CPUPtr: UnsafeMutableRawPointer?, | |
offset: Int | |
) -> UnsafeMutableRawPointer { | |
// This API ought to be refactored, so the CPU pointer is not nullable. | |
guard let CPUPtr else { | |
fatalError("CPU pointer was invalid.") | |
} | |
let casted = CPUPtr.assumingMemoryBound(to: UInt8.self) | |
let incremented = casted + offset | |
return UnsafeMutableRawPointer(incremented) | |
} | |
// First, round up the internal offset. | |
let alignedSize = alignUp( | |
size: sizeInBytes, alignment: alignment) | |
self.offset = alignUp( | |
size: offset, alignment: alignment) | |
// Assign the internal offst to the pointers. | |
var allocation = Allocation() | |
allocation.CPU = addCPUPtr(CPUPtr, offset: offset) | |
allocation.GPU = GPUPtr + UInt64(offset) | |
// Then, add the allocation size to the internal offset. | |
self.offset += alignedSize | |
return allocation | |
} | |
// Reset the page for reuse. | |
func Reset() { | |
self.offset = 0 | |
} | |
} | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment