Last active
October 16, 2025 12:48
-
-
Save philipturner/ab167e25542d5d425132f335469999bf to your computer and use it in GitHub Desktop.
Inspect remove and add
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
| extension Application { | |
| // Will eventually remove the public modifier and automatically invoke this | |
| // inside 'application.render()'. | |
| public func updateBVH(inFlightFrameID: Int) { | |
| updateBVH1(inFlightFrameID: inFlightFrameID) | |
| updateBVH2(inFlightFrameID: inFlightFrameID) | |
| } | |
| public func updateBVH1(inFlightFrameID: Int) { | |
| let transaction = atoms.registerChanges() | |
| device.commandQueue.withCommandList { commandList in | |
| // Bind the descriptor heap. | |
| #if os(Windows) | |
| commandList.setDescriptorHeap(descriptorHeap) | |
| #endif | |
| bvhBuilder.purgeResources( | |
| commandList: commandList) | |
| bvhBuilder.setupGeneralCounters( | |
| commandList: commandList) | |
| bvhBuilder.upload( | |
| transaction: transaction, | |
| commandList: commandList, | |
| inFlightFrameID: inFlightFrameID) | |
| // Encode the remove process. | |
| bvhBuilder.removeProcess1( | |
| commandList: commandList, | |
| inFlightFrameID: inFlightFrameID) | |
| bvhBuilder.removeProcess2( | |
| commandList: commandList) | |
| bvhBuilder.removeProcess3( | |
| commandList: commandList) | |
| bvhBuilder.removeProcess4( | |
| commandList: commandList) | |
| // Encode the add process. | |
| bvhBuilder.addProcess1( | |
| commandList: commandList, | |
| inFlightFrameID: inFlightFrameID) | |
| } | |
| } | |
| public func updateBVH2(inFlightFrameID: Int) { | |
| device.commandQueue.withCommandList { commandList in | |
| // Bind the descriptor heap. | |
| #if os(Windows) | |
| commandList.setDescriptorHeap(descriptorHeap) | |
| #endif | |
| bvhBuilder.addProcess2( | |
| commandList: commandList) | |
| bvhBuilder.addProcess3( | |
| commandList: commandList, | |
| inFlightFrameID: inFlightFrameID) | |
| // Encode the rebuild process. | |
| bvhBuilder.rebuildProcess1( | |
| commandList: commandList) | |
| bvhBuilder.rebuildProcess2( | |
| commandList: commandList) | |
| bvhBuilder.counters.crashBuffer.download( | |
| commandList: commandList, | |
| inFlightFrameID: inFlightFrameID) | |
| } | |
| } | |
| // Invoke this during 'application.render()', at the very end. | |
| public func forgetIdleState(inFlightFrameID: Int) { | |
| device.commandQueue.withCommandList { commandList in | |
| // Bind the descriptor heap. | |
| #if os(Windows) | |
| commandList.setDescriptorHeap(descriptorHeap) | |
| #endif | |
| bvhBuilder.resetMotionVectors( | |
| commandList: commandList, | |
| inFlightFrameID: inFlightFrameID) | |
| bvhBuilder.resetVoxelMarks( | |
| commandList: commandList) | |
| #if os(Windows) | |
| bvhBuilder.computeUAVBarrier(commandList: commandList) | |
| #endif | |
| } | |
| // Delete the transactionArgs state variable. | |
| bvhBuilder.transactionArgs = nil | |
| } | |
| } | |
| extension Application { | |
| // Circumvent a flaky crash by holding a reference to the buffer while the | |
| // command list executes. Do not abuse this by calling any of the 'Debug' | |
| // functions more than once in a single program execution. | |
| nonisolated(unsafe) | |
| private static var uploadBuffers: [Buffer] = [] | |
| nonisolated(unsafe) | |
| private static var downloadBuffers: [Buffer] = [] | |
| public func uploadAssignedVoxelCoords( | |
| _ inputData: [UInt32] | |
| ) { | |
| func copyDestinationBuffer() -> Buffer { | |
| bvhBuilder.voxels.sparse.assignedVoxelCoords | |
| } | |
| #if os(macOS) | |
| let inputBuffer = copyDestinationBuffer() | |
| #else | |
| let nativeBuffer = copyDestinationBuffer() | |
| var bufferDesc = BufferDescriptor() | |
| bufferDesc.device = device | |
| bufferDesc.size = nativeBuffer.size | |
| bufferDesc.type = .input | |
| let inputBuffer = Buffer(descriptor: bufferDesc) | |
| #endif | |
| Self.uploadBuffers.append(inputBuffer) | |
| device.commandQueue.flush() | |
| inputData.withUnsafeBytes { bufferPointer in | |
| inputBuffer.write(input: bufferPointer) | |
| } | |
| #if os(Windows) | |
| device.commandQueue.withCommandList { commandList in | |
| commandList.upload( | |
| inputBuffer: inputBuffer, | |
| nativeBuffer: nativeBuffer) | |
| } | |
| #endif | |
| } | |
| public func downloadGeneralCounters() -> [UInt32] { | |
| func copySourceBuffer() -> Buffer { | |
| bvhBuilder.counters.general | |
| } | |
| var output = [UInt32](repeating: .zero, count: 10) | |
| downloadDebugOutput( | |
| &output, copySourceBuffer: copySourceBuffer()) | |
| return output | |
| } | |
| public func downloadAtomicCounters() -> [SIMD8<UInt32>] { | |
| func copySourceBuffer() -> Buffer { | |
| bvhBuilder.voxels.dense.atomicCounters | |
| } | |
| var output = [SIMD8<UInt32>](repeating: .zero, count: 4096) | |
| downloadDebugOutput( | |
| &output, copySourceBuffer: copySourceBuffer()) | |
| return output | |
| } | |
| public func downloadAssignedSlotIDs() -> [UInt32] { | |
| func copySourceBuffer() -> Buffer { | |
| bvhBuilder.voxels.dense.assignedSlotIDs | |
| } | |
| var output = [UInt32](repeating: .zero, count: 4096) | |
| downloadDebugOutput( | |
| &output, copySourceBuffer: copySourceBuffer()) | |
| return output | |
| } | |
| public func downloadMemorySlots() -> [UInt32] { | |
| func copySourceBuffer() -> Buffer { | |
| bvhBuilder.voxels.sparse.memorySlots | |
| } | |
| var arraySize = bvhBuilder.voxels.memorySlotCount | |
| arraySize *= MemorySlot.totalSize | |
| arraySize /= 4 | |
| var output = [UInt32](repeating: .zero, count: arraySize) | |
| downloadDebugOutput( | |
| &output, copySourceBuffer: copySourceBuffer()) | |
| return output | |
| } | |
| public func downloadAtomsRemovedVoxelCoords() -> [UInt32] { | |
| func copySourceBuffer() -> Buffer { | |
| bvhBuilder.voxels.sparse.atomsRemovedVoxelCoords | |
| } | |
| let arraySize = bvhBuilder.voxels.memorySlotCount | |
| var output = [UInt32](repeating: .zero, count: arraySize) | |
| downloadDebugOutput( | |
| &output, copySourceBuffer: copySourceBuffer()) | |
| return output | |
| } | |
| public func downloadRebuiltVoxelCoords() -> [UInt32] { | |
| func copySourceBuffer() -> Buffer { | |
| bvhBuilder.voxels.sparse.rebuiltVoxelCoords | |
| } | |
| let arraySize = bvhBuilder.voxels.memorySlotCount | |
| var output = [UInt32](repeating: .zero, count: arraySize) | |
| downloadDebugOutput( | |
| &output, copySourceBuffer: copySourceBuffer()) | |
| return output | |
| } | |
| private func downloadDebugOutput<T>( | |
| _ outputData: inout [T], | |
| copySourceBuffer: Buffer | |
| ) { | |
| #if os(macOS) | |
| let outputBuffer = copySourceBuffer | |
| #else | |
| let nativeBuffer = copySourceBuffer | |
| var bufferDesc = BufferDescriptor() | |
| bufferDesc.device = device | |
| bufferDesc.size = nativeBuffer.size | |
| bufferDesc.type = .output | |
| let outputBuffer = Buffer(descriptor: bufferDesc) | |
| #endif | |
| Self.downloadBuffers.append(outputBuffer) | |
| #if os(Windows) | |
| device.commandQueue.withCommandList { commandList in | |
| commandList.download( | |
| nativeBuffer: nativeBuffer, | |
| outputBuffer: outputBuffer) | |
| } | |
| #endif | |
| device.commandQueue.flush() | |
| outputData.withUnsafeMutableBytes { bufferPointer in | |
| outputBuffer.read(output: bufferPointer) | |
| } | |
| } | |
| } |
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
| import HDL | |
| import MolecularRenderer | |
| // Current task: | |
| // - Rigorous test for correct functionality during add/remove: experiment on | |
| // atoms 0...99, 4000...4049, and 4051...4099. | |
| // - First test: these atoms are not rewritten, the rest register as 'moved'. | |
| // - Second test: these atoms are moved, the rest are either 'moved', 'added', | |
| // or unchanged. Test each of the 3 cases. | |
| // - Third test: these atoms are either 'moved' or unchanged. The rest are | |
| // removed. Test each of the 2 cases. | |
| // Helpful facts about the test setup: | |
| // atom count: 8631 | |
| // memory slot count: 3616 | |
| // memory slot size: 55304 B | |
| // .headerLarge = 0 B | |
| // .headerSmall = 8 B | |
| // .referenceLarge = 2056 B | |
| // .referenceSmall = 14344 B | |
| // voxel group count: 64 | |
| // voxel count: 4096 | |
| @MainActor | |
| func createApplication() -> Application { | |
| // Set up the device. | |
| var deviceDesc = DeviceDescriptor() | |
| deviceDesc.deviceID = Device.fastestDeviceID | |
| let device = Device(descriptor: deviceDesc) | |
| // Set up the display. | |
| var displayDesc = DisplayDescriptor() | |
| displayDesc.device = device | |
| displayDesc.frameBufferSize = SIMD2<Int>(1080, 1080) | |
| displayDesc.monitorID = device.fastestMonitorID | |
| let display = Display(descriptor: displayDesc) | |
| // Set up the application. | |
| var applicationDesc = ApplicationDescriptor() | |
| applicationDesc.device = device | |
| applicationDesc.display = display | |
| applicationDesc.upscaleFactor = 1 | |
| applicationDesc.addressSpaceSize = 2_000_000 | |
| applicationDesc.voxelAllocationSize = 200_000_000 | |
| applicationDesc.worldDimension = 32 | |
| let application = Application(descriptor: applicationDesc) | |
| return application | |
| } | |
| let application = createApplication() | |
| let lattice = Lattice<Cubic> { h, k, l in | |
| Bounds { 10 * (h + k + l) } | |
| Material { .checkerboard(.silicon, .carbon) } | |
| } | |
| @MainActor | |
| func uploadDebugInput() { | |
| var input = [UInt32](repeating: UInt32.max, count: 3616) | |
| input[5] = 0 | |
| input[120] = 1 | |
| input[121] = 2 | |
| input[184] = 3 | |
| application.uploadAssignedVoxelCoords(input) | |
| } | |
| uploadDebugInput() | |
| #if false | |
| application.run { | |
| for atomID in lattice.atoms.indices { | |
| let atom = lattice.atoms[atomID] | |
| application.atoms[atomID] = atom | |
| } | |
| let image = application.render() | |
| application.present(image: image) | |
| } | |
| #else | |
| func pad<T: BinaryInteger>(_ integer: T) -> String { | |
| var output = "\(integer)" | |
| while output.count < 4 { | |
| output = " " + output | |
| } | |
| return output | |
| } | |
| @MainActor | |
| func analyzeGeneralCounters() { | |
| let output = application.downloadGeneralCounters() | |
| print("atoms removed voxel count:", output[0]) | |
| guard output[1] == 1, | |
| output[2] == 1 else { | |
| fatalError("Indirect dispatch arguments were malformatted.") | |
| } | |
| print("vacant slot count:", output[4]) | |
| print("allocated slot count:", output[5]) | |
| print("rebuilt voxel count:", output[6]) | |
| guard output[7] == 1, | |
| output[8] == 1 else { | |
| fatalError("Indirect dispatch arguments were malformatted.") | |
| } | |
| } | |
| @MainActor | |
| func inspectAtomsRemovedVoxels() { | |
| let voxelCoords = application.downloadAtomsRemovedVoxelCoords() | |
| for i in voxelCoords.indices { | |
| let encoded = voxelCoords[i] | |
| guard encoded != UInt32.max else { | |
| continue | |
| } | |
| let decoded = SIMD3<UInt32>( | |
| encoded & 1023, | |
| (encoded >> 10) & 1023, | |
| encoded >> 20 | |
| ) | |
| let lowerCorner = SIMD3<Float>(decoded) * 2 - (Float(32) / 2) | |
| print(pad(i), lowerCorner) | |
| } | |
| } | |
| @MainActor | |
| func inspectRebuiltVoxels() { | |
| let voxelCoords = application.downloadRebuiltVoxelCoords() | |
| for i in voxelCoords.indices { | |
| let encoded = voxelCoords[i] | |
| guard encoded != UInt32.max else { | |
| continue | |
| } | |
| let decoded = SIMD3<UInt32>( | |
| encoded & 1023, | |
| (encoded >> 10) & 1023, | |
| encoded >> 20 | |
| ) | |
| let lowerCorner = SIMD3<Float>(decoded) * 2 - (Float(32) / 2) | |
| print(pad(i), lowerCorner) | |
| } | |
| } | |
| @MainActor | |
| func inspectMemorySlots() { | |
| let assignedSlotIDs = application.downloadAssignedSlotIDs() | |
| let memorySlots = application.downloadMemorySlots() | |
| var atomDuplicatedReferences = [Int](repeating: .zero, count: 8631) | |
| var outputArray: [Int] = [] | |
| for i in assignedSlotIDs.indices { | |
| let assignedSlotID = assignedSlotIDs[i] | |
| guard assignedSlotID != UInt32.max else { | |
| continue | |
| } | |
| outputArray.append(i) | |
| let headerAddress = Int(assignedSlotID) * 55304 / 4 | |
| let atomCount = memorySlots[headerAddress] | |
| print(pad(i), pad(assignedSlotID), pad(atomCount), terminator: " ") | |
| let listAddress = headerAddress + 2056 / 4 | |
| for j in 0..<Int(atomCount) { | |
| let atomID = memorySlots[listAddress + j] | |
| if j < 12 { | |
| print(pad(atomID), terminator: " ") | |
| } | |
| if atomID >= atomDuplicatedReferences.count { | |
| fatalError("Invalid atom ID: \(atomID)") | |
| } | |
| atomDuplicatedReferences[Int(atomID)] += 1 | |
| } | |
| print() | |
| } | |
| var summary = [Int](repeating: .zero, count: 17) | |
| for atomID in atomDuplicatedReferences.indices { | |
| let referenceCount = atomDuplicatedReferences[atomID] | |
| if referenceCount > 16 { | |
| fatalError("Invalid reference count: \(referenceCount)") | |
| } | |
| summary[referenceCount] += 1 | |
| } | |
| print() | |
| for referenceCount in summary.indices { | |
| let atomCount = summary[referenceCount] | |
| print("\(pad(referenceCount)): \(pad(atomCount))") | |
| } | |
| print("total atom count: \(summary[1...].reduce(0, +))") | |
| print("total reference count: \(atomDuplicatedReferences.reduce(0, +))") | |
| } | |
| for frameID in 0...5 { | |
| for atomID in lattice.atoms.indices { | |
| let atom = lattice.atoms[atomID] | |
| // voxels spanned: 24 | |
| // | |
| // 0: 8432 | |
| // 1: 63 | |
| // 2: 97 | |
| // 3: 0 | |
| // 4: 35 | |
| // 5: 0 | |
| // 6: 0 | |
| // 7: 0 | |
| // 8: 4 | |
| // 9: 0 | |
| // 10: 0 | |
| // 11: 0 | |
| // 12: 0 | |
| // 13: 0 | |
| // 14: 0 | |
| // 15: 0 | |
| // 16: 0 | |
| // total atom count: 199 | |
| // total reference count: 429 | |
| var isSelected1 = false | |
| if atomID >= 0 && atomID <= 99 { | |
| isSelected1 = true | |
| } | |
| if atomID >= 4000 && atomID <= 4049 { | |
| isSelected1 = true | |
| } | |
| if atomID >= 4051 && atomID <= 4099 { | |
| isSelected1 = true | |
| } | |
| // voxels spanned: 14 | |
| // | |
| // 0: 8330 | |
| // 1: 107 | |
| // 2: 147 | |
| // 3: 0 | |
| // 4: 44 | |
| // 5: 0 | |
| // 6: 0 | |
| // 7: 0 | |
| // 8: 3 | |
| // 9: 0 | |
| // 10: 0 | |
| // 11: 0 | |
| // 12: 0 | |
| // 13: 0 | |
| // 14: 0 | |
| // 15: 0 | |
| // 16: 0 | |
| // total atom count: 301 | |
| // total reference count: 601 | |
| var isSelected2 = false | |
| if atomID >= 500 && atomID <= 800 { | |
| isSelected2 = true | |
| } | |
| // voxels spanned: 60 | |
| // | |
| // 0: 500 | |
| // 1: 4743 | |
| // 2: 2792 | |
| // 3: 0 | |
| // 4: 557 | |
| // 5: 0 | |
| // 6: 0 | |
| // 7: 0 | |
| // 8: 39 | |
| // 9: 0 | |
| // 10: 0 | |
| // 11: 0 | |
| // 12: 0 | |
| // 13: 0 | |
| // 14: 0 | |
| // 15: 0 | |
| // 16: 0 | |
| // total atom count: 8131 | |
| // total reference count: 12867 | |
| let isSelected3 = !(isSelected1 || isSelected2) | |
| enum TransactionType { | |
| case remove | |
| case move | |
| case add | |
| } | |
| var transactionType: TransactionType? | |
| if isSelected1 { | |
| if frameID == 0 { | |
| transactionType = .add | |
| } else if frameID == 1 { | |
| } else if frameID == 2 { | |
| transactionType = .move | |
| } else if frameID == 3 { | |
| transactionType = .remove | |
| } | |
| } else if isSelected2 { | |
| if frameID == 1 { | |
| transactionType = .add | |
| } else if frameID == 2 { | |
| } else if frameID == 3 { | |
| transactionType = .move | |
| } else if frameID == 4 { | |
| transactionType = .remove | |
| } | |
| } else if isSelected3 { | |
| if frameID == 2 { | |
| transactionType = .add | |
| } else if frameID == 3 { | |
| } else if frameID == 4 { | |
| transactionType = .move | |
| } else if frameID == 5 { | |
| transactionType = .remove | |
| } | |
| } | |
| if let transactionType { | |
| switch transactionType { | |
| case .remove: | |
| application.atoms[atomID] = nil | |
| case .move: | |
| var movedAtom = atom | |
| movedAtom.position += SIMD3<Float>(1, 1, 1) | |
| application.atoms[atomID] = movedAtom | |
| case .add: | |
| application.atoms[atomID] = atom | |
| } | |
| } | |
| } | |
| application.updateBVH1(inFlightFrameID: frameID % 3) | |
| print() | |
| print("===============") | |
| print("=== frame \(frameID) ===") | |
| print("===============") | |
| print() | |
| analyzeGeneralCounters() | |
| print() | |
| // inspectAtomsRemovedVoxels() | |
| inspectMemorySlots() | |
| application.updateBVH2(inFlightFrameID: frameID % 3) | |
| print() | |
| analyzeGeneralCounters() | |
| print() | |
| // inspectRebuiltVoxels() | |
| inspectMemorySlots() | |
| application.forgetIdleState(inFlightFrameID: frameID % 3) | |
| } | |
| #endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment