D3d12 system library package?

I'm trying to get d3d12 bridged to swift. It's not included in WinSDK yet.

Has anyone already done this? It's a larger undertaking than I was expecting. The header has includes that chain on and on and each of those has many ifdef macros from other headers. d3d12.h does have a C interface and with some editing I can get Xcode to generate the Swift interface. It still needs swift chrome for a lot of things but I can't waste time on that yet, the d3d12.h file is massive. But it should be possible to get it bridged.

I'm new to windows dev in general. I'm not sure how to handle all the ifdefs. For linux I simply resolved them by editing the headers manually and sticking them in the package source and then putting them in the shim, but windows headers seem to be 70% macros and I'm not sure how to handle it. If anyone has some tips or an example of a system library package for windows I could check out, that would be super helpful. I'm using VSCode and Swift 5.3-dev from the official development snapshots.

The interfaces that are part of the Direct3D 12 are covered here: Interface Hierarchy - Win32 apps | Microsoft Docs. These aren't really C interfaces but COM interfaces. They have C bindings from COM, which is what you might be thinking of.

The best idea that I have to deal with this at this point is through something like Swift/COM. You would need to bind the interfaces that you wish to expose, either as a client for the interface or as a base interface to provide the ability to derive and override the implementation. There are examples for both of those uses currently.

While I admit that this isn't without a certain effort, the work is rather mechanical.

There are a couple of ideas that still need to be explored further:

It would definitely be pretty cool to see some DX demos with Swift though.

1 Like

BTW, if you are curious what it ends up looking like in practice: swift-win32/Image.swift at master · compnerd/swift-win32 (github.com) is an example of the use of the wrapped COM interface to load images.

The COM interface is constructed here.

The forced error handling at each call is intentional - each call is an RPC, and the remote process could have terminated in between any successive call or the call itself can fail for any reason.

Okay, this is helpful. I'm completely new to windows though so I'm a little lost.

For context, when I did OpenGL I duplicated the headers on each platform (GLKit, GLEW, etc..) into a system module and then wrapped those system modules in a library package that called into them. I used the wrapper to convert unmanged, pointers, and C types to swift types, like String. I also sprinkled the duplicated headers in the system libraries with __attribute__s like swift_name, swift_private, etc... to make things like enums more Swift like.

I've been taking a similar approach for d3d12.h. It looks like d3d12 has a legit C interface that calls directly into the d3d12.lib/d3d12.dll. Because performance is important using an out of process lookup would be a waste of time that could otherwise be used on the simulation. So I'd like to avoid that if possible. I'm using a custom 3D engine completely written in Swift and performance, especially in debug builds is a concern for me. Here's videos of it if interested.

So is there a specific benefit to using the RPC lookup over hard coding it? Is hard coding it going to break or not work correctly on different system configurations or anything like that? I've never coded any apps on any version of Windows so I don't really know what's going on. An out of process call would add variable synchronization time, and through it's minor it would add up and be a bad design for this module if it could be avoided.

I'm also doing a partial system module for UniversalWindowsPlatform Core to generate the window and view for d3d, with the hope of being able to deploy on Xbox someday. The C calls are similarly setup, but unlike d3d12 none of them are user friendly and all will require __attribute__s and wrappers. So that module seems like a really good candidate for SwiftCOM. And it looks like the header documentation says it calls into the com library so I guess I wont have a choice there?

I don't think that is related to be being new to Windows development - there is a bunch of infrastructure that has been built up over decades.

The header mapping makes sense as the OpenGL interfaces are all C (and they take care for the ABI aspects). Here, the interfaces are not pure C but rather an ABI definition mapping C++ calls through macros for lifting the implicit this parameter and indexing through the vtable. I don't think that the same technique can necessarily be applied as nicely here (at least not as readily).

Could you please point me to the C interfaces? Perhaps I have forgotten about them, but my recollection was that D3D was largely a COM interface.

You can create the endpoint in process if you prefer (CLSCTX_INPROC_SERVER) much as I do here. This is relying on a wrapper that simply hides the context from Swift/COM from the base IUknown type. See swift-com/IUnknown.swift at master · compnerd/swift-com (github.com) for how that is setup. However, in order to support the transparency, it will still require the error handling (which effectively just forces you to be more explicit about the error handling at each call).

You cannot avoid the "RPC"-like interfaces, but as I said above, you can choose to do this in-process if you so desire. The error handling is something you cannot get away from is all.

The UWP interfaces should be okay and you should be able to mostly deploy to XBox (when I originally did the port for Windows, I did try to restrict myself to UWP approved APIs, though I admit that I've not been as strict about that as of late, but the idea is that most of the development tools are run on a development environment and used to cross-compile to the target).

The rest of this sounds ... more challenging. From what it sounds like, it seems that you are talking about the WinRT interfaces? If so, that isn't really ready as of yet, but that absolutely works through layers built a top of COM. Though, in order to do that, you will need to deal with code generation to create bindings for the interfaces. If you are interested in that aspect you may want to checkout compnerd/swift-winmd: Windows Metadata Parser in Swift (github.com). That has some of the basis for building up the code generation by processing the WinMD content which describes the interfaces. Using that and building a top of Swift/COM should allow binding to IInspectable and building the WinRT interfaces on top.

Oh, that sounds really cool. Is any of that open source? Do you have any benchmarks or write up on what the Swift performance is like? I think that this would be something that others would also be interested in reading about other's experiences.

Thanks a lot for the detailed replies. I've looked through all the examples and determined that what I was going to do, was wrap the com interface as you said. D3d12 appears to share types with many things you've already implemented. So I've decided to do as you recommend. That way types will match up and work with each other when there is overlap.

I've created a basic repository with a minimal example of what I'm going to do. The biggest takeaways are, I'm going to create a swift-like interface while also exposing the original interfaces prepended with an underscore. It'll be a lot of extra work but the result will be pleasant for new and returning devs.

If you wouldn't mind giving it a quick look to make sure I didn't overlook something, that would be great. It builds and links just fine, but it'll be a while before I can actually use it in a program.
SwiftD3D12

The UWP stuff I was looking at was from WinRT. I think I'm going to make a companion package the same way as above that exposes windows and views. Maybe I'll just include it in the above package since most apps will also want to make a window to draw in anyway :thinking:

As for my engine, I have major performance problems in Debug builds. Swift's safety stuff absolutely tanks matrix math performance. But release builds are a silky smooth 60 fps on my decade old Mac Pro. I don't have any benchmark numbers; there's nothing to baseline compare them with. Plus this is my first complex game engine so I've probably made some questionable decisions here and there and those will probably be the bottle necks, not Swift.

It's not open source, but I am considering making a special "source available" version for people who support my games. I need an income or I won't be able to do this anymore, so if enough people are interested I'd put the time in. But either way once my games are out the door, or if I have to throw in the towel, the engine will become open source.

1 Like

Happy to try to guide you through getting this setup :slight_smile:

The reason for the overlap is that I was exploring what it would take to get generic coverage of arbitrary COM interfaces. Because the D3D API is based on COM, it simply extends to it.

Ah, so you would actually provide a nicer interface by default? That definitely is going to make a much nicer user experience. For the COM interfaces that I've thus far wrapped, I've taken the approach of writing the most cookie cutter (as in, it is a pattern being stamped out) of the interfaces. The idea is that in some ideal future, the interfaces that are being vended can be fully replaced by automated generation.

As to the _d3d12 module is something that I think is something which should be ephemeral. Once the tree is unlocked, Platform: add D3D12 to modulemap by compnerd · Pull Request #35221 · apple/swift (github.com) should alleviate the need for that. The WinSDK module will provide the D3D12 submodule which should allow access to the data types.

You can see what this would look like at Interfaces: begin wrapping parts of D3D API by compnerd · Pull Request #21 · compnerd/swift-com (github.com).

1 Like

I'm just going to implement it the way you've done it, rather then making it pretty. As much as I would like a pretty interface, the auto generated version will certainly be very different. And people are going to want D3D11, and who knows D3D13, at some point so it would be better if they all get generated similarly. Maybe I'll know enough about the toolchain to be able to help with that at some point. For now I'll just brute force it the way you've done.

Is that alright with you if I add a little boiler plate to IUnknown?

This:

public func GetGPUDescriptorHandleForHeapStart() throws -> D3D12_GPU_DESCRIPTOR_HANDLE {
    guard let pUnk = UnsafeMutableRawPointer(self.pUnk) else {
        throw COMError(hr: E_INVALIDARG)
    }
    let pThis = pUnk.bindMemory(to: WinSDK.ID3D12DescriptorHeap.self, capacity: 1)
    
    return pThis.pointee.lpVtbl.pointee.GetGPUDescriptorHandleForHeapStart(pThis)
}

Becomes:

public func GetGPUDescriptorHandleForHeapStart() throws -> D3D12_GPU_DESCRIPTOR_HANDLE {
    return try performComOperation(WinSDK.ID3D12DescriptorHeap.self) { (this, pThis) in
        return this.lpVtbl.pointee.GetGPUDescriptorHandleForHeapStart(pThis)
    }
}

And This:

public func GetDevice(_ riid: REFIID) throws -> UnsafeMutableRawPointer? throws {
    guard let pUnk = UnsafeMutableRawPointer(self.pUnk) else {
        throw COMError(hr: E_INVALIDARG)
    }
    let pThis = pUnk.bindMemory(to: WinSDK.ID3D12DeviceChild.self, capacity: 1)
    
    var ppvDevice: UnsafeMutableRawPointer?
    let hr: HRESULT = pThis.pointee.lpVtbl.pointee.GetDevice(pThis, riid, &ppvDevice)
    guard hr == S_OK else { throw COMError(hr: hr) }
    return ppvDevice
}

Becomes:

public func GetDevice(_ riid: REFIID) throws -> UnsafeMutableRawPointer? {
    return try performComOperation(WinSDK.ID3D12DeviceChild.self) { (this, pThis, hresult) in
        var ppvDevice: UnsafeMutableRawPointer?
        hresult = this.lpVtbl.pointee.GetDevice(pThis, riid, &ppvDevice)
        return ppvDevice
    }
}

^ hresult throws when != S_OK here. I'll add a way to specify the "don't throw" HRESULT(s) if I come across an API that has a success(s) other then S_OK

And also this, for handling passed in objects from within the performComOperation closure:
let pSrc = try pSrcResource.getThisPointer(WinSDK.ID3D12Resource.self)

I know the goal is to auto-generate all this, but in the mean time this would be a nice quality of life improvement. The copy paste got tedious pretty quick. Some of these API's are really complex and being able to simplify the implementation for those complex ones would be super helpful. If you're cool with it I'll do the refactor of existing stuff in a separate commit too so everything is consistent.

Edit: I'm having decision remorse. I was just gonna update SwiftCOM, but I really, really want a pretty Swift API. I might just keep doing a separate package but using the WinSDK.D3D12 submodule when it's merged; I need to sleep on it.

Patches to add functionality to Swift/COM are welcome :) I think I want to think a bit about the implementation although I am very keen on the idea, but we can discuss that on GH.

1 Like