Skip to content

Instantly share code, notes, and snippets.

@philipturner
Created May 9, 2025 11:59
Show Gist options
  • Save philipturner/3c58b9d4a637659a96c286674360a959 to your computer and use it in GitHub Desktop.
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
#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
#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
#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
#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
#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
#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