[Proof of Concept] 3D Graphics (DirectX) on Windows

I was inspired by @STREGAsGate's interest in building a game on Windows with DirectX and Swift. DirectX is a large framework and provides multiple sub-frameworks including Direct 3D and Direct 2D for accelerated graphics as well as things such as Direct Input and Direct Audio for audio and input handling.

In order to demonstrate that using this functionality is both possible and how far Swift on Windows has actually come, I decided to write one of the time honoured traditional demos for 3D graphics with Swift (and HLSL) to show how to use Direct 3D on Windows with Swift - an orthographic projection of a cube.

Note that this proof of concept implementation does not always follow best practices (e.g. does not use v-sync) but it does show the viability of implementing 3D graphics on Windows with Swift. The code patterns are not entirely idiomatic Swift, but would be very familiar to someone who is experienced with DirectX.

The source code for this is available at GitHub - compnerd/DXSample: Sample Program for DirectX 12 + Swift.

Welcome to hardware accelerated graphics programming on Windows!

Saleem

39 Likes

Cool!

Seeing it all working is awesome! So who here is gonna use this to make the first Swift + D3D12 Minecraft clone?

5 Likes

Very very cool. I have been toying with the idea of doing a similar hello cube using Google's Dawn WebGPU C API, but I haven't found the time yet.

Amazing! You can always count on compnerd to be pushing the bounds of Swift.

2 Likes

@compnerd I get "no such module 'WinSDK.DirectX'" when compiling this sample with the latest trunk toolchain. Do you know how to fix it?

I've not seen that; could you please provide more information on the failure in the clang importer? It may just be that WinSDK module map may be out of date?

Well, just the swift build output from the command prompt gives the following:

C:\Users\yeswolf\CLionProjects\DXSample>swift build
[1/5] Copying C:\Users\yeswolf\CLionProjects\DXSample\Sources\DXSample\Shaders
[2/119] Compiling SwiftCOM IErrorLog.swift
C:\Users\yeswolf\CLionProjects\DXSample\.build\checkouts\swift-com\Sources\SwiftCOM\Interfaces\Human\ID3D12VersionedRootSignatureDeserializer.swift:9:8: error: no such module 'WinSDK.DirectX'
import WinSDK.DirectX
       ^
[3/119] Compiling SwiftCOM IFileOperation.swift
C:\Users\yeswolf\CLionProjects\DXSample\.build\checkouts\swift-com\Sources\SwiftCOM\Interfaces\Human\ID3D12VersionedRootSignatureDeserializer.swift:9:8: error: no such module 'WinSDK.DirectX'
import WinSDK.DirectX
       ^
[4/119] Compiling SwiftCOM IMalloc.swift
C:\Users\yeswolf\CLionProjects\DXSample\.build\checkouts\swift-com\Sources\SwiftCOM\Interfaces\Human\ID3D12VersionedRootSignatureDeserializer.swift:9:8: error: no such module 'WinSDK.DirectX'
import WinSDK.DirectX
       ^
[5/119] Compiling SwiftCOM IMoniker.swift
C:\Users\yeswolf\CLionProjects\DXSample\.build\checkouts\swift-com\Sources\SwiftCOM\Interfaces\Human\ID3D12VersionedRootSignatureDeserializer.swift:9:8: error: no such module 'WinSDK.DirectX'
import WinSDK.DirectX
       ^
[6/119] Compiling SwiftCOM IObjectWithPropertyKey.swift
C:\Users\yeswolf\CLionProjects\DXSample\.build\checkouts\swift-com\Sources\SwiftCOM\Interfaces\Human\ID3D12VersionedRootSignatureDeserializer.swift:9:8: error: no such module 'WinSDK.DirectX'
import WinSDK.DirectX
       ^
[7/119] Compiling SwiftCOM IOperationsProgressDialog.swift
C:\Users\yeswolf\CLionProjects\DXSample\.build\checkouts\swift-com\Sources\SwiftCOM\Interfaces\Human\ID3D12VersionedRootSignatureDeserializer.swift:9:8: error: no such module 'WinSDK.DirectX'
import WinSDK.DirectX
       ^
[8/119] Compiling SwiftCOM IPersist.swift
C:\Users\yeswolf\CLionProjects\DXSample\.build\checkouts\swift-com\Sources\SwiftCOM\Interfaces\Human\ID3D12VersionedRootSignatureDeserializer.swift:9:8: error: no such module 'WinSDK.DirectX'
import WinSDK.DirectX
       ^
[9/119] Compiling SwiftCOM IDXGIDebug.swift
C:\Users\yeswolf\CLionProjects\DXSample\.build\checkouts\swift-com\Sources\SwiftCOM\Interfaces\Human\ID3D12VersionedRootSignatureDeserializer.swift:9:8: error: no such module 'WinSDK.DirectX'
import WinSDK.DirectX
       ^
[10/119] Compiling SwiftCOM IDXGIDevice.swift
C:\Users\yeswolf\CLionProjects\DXSample\.build\checkouts\swift-com\Sources\SwiftCOM\Interfaces\Human\ID3D12VersionedRootSignatureDeserializer.swift:9:8: error: no such module 'WinSDK.DirectX'
import WinSDK.DirectX
       ^
[11/119] Compiling SwiftCOM IDXGIDeviceSubObject.swift
C:\Users\yeswolf\CLionProjects\DXSample\.build\checkouts\swift-com\Sources\SwiftCOM\Interfaces\Human\ID3D12VersionedRootSignatureDeserializer.swift:9:8: error: no such module 'WinSDK.DirectX'
import WinSDK.DirectX
       ^
[12/119] Compiling SwiftCOM IDXGIFactory.swift
C:\Users\yeswolf\CLionProjects\DXSample\.build\checkouts\swift-com\Sources\SwiftCOM\Interfaces\Human\ID3D12VersionedRootSignatureDeserializer.swift:9:8: error: no such module 'WinSDK.DirectX'
import WinSDK.DirectX
       ^
[13/119] Compiling SwiftCOM IDXGIFactory1.swift
C:\Users\yeswolf\CLionProjects\DXSample\.build\checkouts\swift-com\Sources\SwiftCOM\Interfaces\Human\ID3D12VersionedRootSignatureDeserializer.swift:9:8: error: no such module 'WinSDK.DirectX'
import WinSDK.DirectX
       ^
[14/119] Compiling SwiftCOM IDXGIFactory2.swift
C:\Users\yeswolf\CLionProjects\DXSample\.build\checkouts\swift-com\Sources\SwiftCOM\Interfaces\Human\ID3D12VersionedRootSignatureDeserializer.swift:9:8: error: no such module 'WinSDK.DirectX'
import WinSDK.DirectX
       ^
[15/119] Compiling SwiftCOM IDXGIFactory3.swift
C:\Users\yeswolf\CLionProjects\DXSample\.build\checkouts\swift-com\Sources\SwiftCOM\Interfaces\Human\ID3D12VersionedRootSignatureDeserializer.swift:9:8: error: no such module 'WinSDK.DirectX'
import WinSDK.DirectX
       ^
[16/119] Compiling SwiftCOM IPersistStream.swift
C:\Users\yeswolf\CLionProjects\DXSample\.build\checkouts\swift-com\Sources\SwiftCOM\Interfaces\Human\ID3D12VersionedRootSignatureDeserializer.swift:9:8: error: no such module 'WinSDK.DirectX'
import WinSDK.DirectX
       ^
[17/119] Compiling SwiftCOM IPortableDeviceKeyCollection.swift
C:\Users\yeswolf\CLionProjects\DXSample\.build\checkouts\swift-com\Sources\SwiftCOM\Interfaces\Human\ID3D12VersionedRootSignatureDeserializer.swift:9:8: error: no such module 'WinSDK.DirectX'
import WinSDK.DirectX
       ^
[18/119] Compiling SwiftCOM IPortableDevicePropVariantCollection.swift
C:\Users\yeswolf\CLionProjects\DXSample\.build\checkouts\swift-com\Sources\SwiftCOM\Interfaces\Human\ID3D12VersionedRootSignatureDeserializer.swift:9:8: error: no such module 'WinSDK.DirectX'
import WinSDK.DirectX
       ^
[19/119] Compiling SwiftCOM IPortableDeviceValues.swift
C:\Users\yeswolf\CLionProjects\DXSample\.build\checkouts\swift-com\Sources\SwiftCOM\Interfaces\Human\ID3D12VersionedRootSignatureDeserializer.swift:9:8: error: no such module 'WinSDK.DirectX'
import WinSDK.DirectX
       ^
[20/119] Compiling SwiftCOM IPortableDeviceValuesCollection.swift
C:\Users\yeswolf\CLionProjects\DXSample\.build\checkouts\swift-com\Sources\SwiftCOM\Interfaces\Human\ID3D12VersionedRootSignatureDeserializer.swift:9:8: error: no such module 'WinSDK.DirectX'
import WinSDK.DirectX
       ^
[21/119] Compiling SwiftCOM IPropertyBag2.swift
C:\Users\yeswolf\CLionProjects\DXSample\.build\checkouts\swift-com\Sources\SwiftCOM\Interfaces\Human\ID3D12VersionedRootSignatureDeserializer.swift:9:8: error: no such module 'WinSDK.DirectX'
import WinSDK.DirectX
       ^
[22/119] Compiling SwiftCOM IPropertyChange.swift
C:\Users\yeswolf\CLionProjects\DXSample\.build\checkouts\swift-com\Sources\SwiftCOM\Interfaces\Human\ID3D12VersionedRootSignatureDeserializer.swift:9:8: error: no such module 'WinSDK.DirectX'
import WinSDK.DirectX
       ^
[23/119] Compiling SwiftCOM IDXGIFactory4.swift
C:\Users\yeswolf\CLionProjects\DXSample\.build\checkouts\swift-com\Sources\SwiftCOM\Interfaces\Human\ID3D12VersionedRootSignatureDeserializer.swift:9:8: error: no such module 'WinSDK.DirectX'
import WinSDK.DirectX
       ^
[24/119] Compiling SwiftCOM IDXGIFactory5.swift
C:\Users\yeswolf\CLionProjects\DXSample\.build\checkouts\swift-com\Sources\SwiftCOM\Interfaces\Human\ID3D12VersionedRootSignatureDeserializer.swift:9:8: error: no such module 'WinSDK.DirectX'
import WinSDK.DirectX
       ^
[25/119] Compiling SwiftCOM IDXGIFactory6.swift
C:\Users\yeswolf\CLionProjects\DXSample\.build\checkouts\swift-com\Sources\SwiftCOM\Interfaces\Human\ID3D12VersionedRootSignatureDeserializer.swift:9:8: error: no such module 'WinSDK.DirectX'
import WinSDK.DirectX
       ^

And so on.

Contents of winsdk.modulemap are the following:

//===--- WinSDK.modulemap -------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

module WinSDK [system] {
  module WinSock2 {
    header "WinSock2.h"
    header "WS2tcpip.h"
    header "MSWSock.h"
    header "../shared/afunix.h"
    export *

    link "WS2_32.Lib"
  }

  module WinSock {
    header "winsock.h"
  }

  module core {
    module acl {
      header "AclAPI.h"
      export *
    }

    module com {
      header "combaseapi.h"
      export *
    }

    module compress {
      header "compressapi.h"
      export *
    }

    module console {
      header "consoleapi.h"
      header "consoleapi2.h"
      header "consoleapi3.h"
      export *
    }

    // api-ms-win-core-errhandling-l1-1-0.dll
    module errhandling {
      header "errhandlingapi.h"
      export *
    }

    // api-ms-win-core-file-l1-1-0.dll
    module file {
      header "fileapi.h"
      export *
    }

    // api-ms-win-core-handle-l1-1-0.dll
    module handle {
      header "handleapi.h"
      export *
    }

    // api-ms-win-heapapi-l1-1-0.dll
    module heap {
      header "heapapi.h"
      export *
    }

    // api-ms-win-core-interlocked-l1-1-0.dll
    module interlocked {
      header "interlockedapi.h"
      export *
    }

    // iphlpapi.dll
    module iphlp {
      header "iphlpapi.h"
      export *

      link "iphlpapi.lib"
    }

    // api-ms-win-core-libloader-l1-1-0.dll
    module libloader {
      header "libloaderapi.h"
      export *
    }

    // api-ms-win-core-namedpipe-l1-1-2-0.dll
    module namedpipe {
      header "namedpipeapi.h"
      export *
    }

    // api-ms-win-core-Path-l1-0.dll
    module path {
      header "PathCch.h"
      export *

      link "pathcch.lib"
    }

    // api-ms-win-core-processthreads-l1-1-2.dll
    module processthreads {
      header "processthreadsapi.h"
      export *
    }

    // api-ms-win-core-synch-l1-2-0.dll
    module synch {
      header "synchapi.h"
      export *
    }

    // api-ms-win-core-sysinfo-l1-1-0.dll
    module sysinfo {
      header "sysinfoapi.h"
      export *
    }

    // api-ms-win-core-timezone-l1-1-0.dll
    module timezone {
      header "timezoneapi.h"
      export *
    }

    // api-ms-win-core-version-l1-1-0.dll
    module version {
      header "winver.h"
      export *

      link "Version.Lib"
    }
  }

  module ActiveX {
    header "OCIdl.h"
    export *
  }

  module Controls {
    module CommCtrl {
      header "CommCtrl.h"
      export *
    }

    module CommDlg {
      header "commdlg.h"
      export *

      link "ComDlg32.Lib"
    }
  }

  explicit module DirectX {
    module Direct3D11 {
      header "d3d11.h"
      header "d3d11_1.h"
      header "d3d11_2.h"
      header "d3d11_3.h"
      header "d3d11_4.h"
      export *

      link "d3d11.lib"
      link "dxgi.lib"
    }

    module Direct3D12 {
      header "d3d12.h"
      export *

      link "d3d12.lib"
      link "dxgi.lib"
    }

    // FIXME(compnerd) DXGI is part of the Direct3D interfaces currently; we
    // should split it out, but because it is part of the D3D11 interfaces, this
    // separate module is meant to augment the uncovered portions only.
    module _DXGI {
      header "../shared/dxgi1_6.h"
      header "dxgidebug.h"
      export *

      link "dxgi.lib"
    }

    module D3DCompiler {
      header "d3dcompiler.h"
      export *

      link "d3dcompiler.lib"
    }

    module XAudio29 {
      header "xaudio2.h"
      header "xaudio2fx.h"
      export *

      link "xaudio2.lib"

      requires cplusplus
    }

    // XInput 1.4 (Windows 10, XBox) is newer than the XInput 9.1.0 which was
    // part of Vista.
    module XInput14 {
      header "Xinput.h"
      export *

      link "xinput.lib"
    }

    link "dxguid.lib"
  }

  // FIXME(compnerd) this is a hack for the HWND typedef for DbgHelp
  module __DirectX {
    header "directmanipulation.h"
    export *
  }

  module DFS {
    header "LMDFS.h"
    header "LM.h"
    export *

    link "NetAPI32.Lib"
  }

  module DWM {
    header "dwmapi.h"
    export *

    link "dwmapi.lib"
  }

  module FCI {
    header "fci.h"
    export *

    link "Cabinet.Lib"
  }

  module Internationalization {
    module WinNLS {
      header "winnls.h"
      export *
    }

    module IMM {
      header "immdev.h"
      header "imm.h"
      export *

      link "Imm32.lib"
    }
  }

  module Multimedia {
    module DigitalVideo {
      header "Digitalv.h"
      export *
    }

    module Video {
      header "Vfw.h"
      export *

      link "Vfw32.Lib"
    }

    header "mmeapi.h"
    header "mmddk.h"
    header "mmsystem.h"
    header "mmiscapi.h"
    header "timeapi.h"
    header "joystickapi.h"
    export *

    link "WinMM.Lib"
  }

  module Networking {
    header "winnetwk.h"
    export *

    link "Mpr.Lib"
  }

  module Security {
    module AuthZ {
      header "AuthZ.h"
      export *

      link "AuthZ.Lib"
    }

    module SmartCard {
      header "winscard.h"
      export *

      link "winscard.lib"
    }

    module WinCrypt {
      header "wincrypt.h"
      export *

      link "Crypt32.Lib"
    }
  }

  module Shell {
    header "ShlObj.h"
    export *
  }

  module AppNotify {
    header "appnotify.h"
    export *

    link "appnotify.lib"
  }

  module ShellAPI {
    header "shellapi.h"
    header "Shlwapi.h"
    export *

    link "shell32.lib"
    link "ShLwApi.Lib"
  }

  module ShellCore {
    header "ShellScalingApi.h"
    export *

    link "shcore.lib"
  }

  module System {
    module DbgHelp {
      header "DbgHelp.h"
      export *

      link "DbgHelp.Lib"
    }

    module IOCTL {
      header "winioctl.h"
      export *
    }

    module MCX {
      header "mcx.h"
      export *
    }
  }

  module OLE32 {
    header "oaidl.h"
    export *

    link "OleAut32.Lib"
  }

  module Performance {
    module PerfLib {
      header "perflib.h"
      export *

      link "AdvAPI32.Lib"
    }

    module PDH {
      header "Pdh.h"
      export *

      link "Pdh.Lib"
    }

    header "winperf.h"
    export *
  }

  module Printing {
    header "winspool.h"
    export *

    link "WinSpool.Lib"
  }

  module RichEdit {
    header "Richedit.h"
    export *
  }

  module Sensors {
    header "sensors.h"
    header "SensorsApi.h"
    export *

    link "sensorsapi.lib"
  }

  module UI {
    module XAML {
      module Hosting {
        header "windows.ui.xaml.hosting.desktopwindowxamlsource.h"
        export *
      }
    }
  }

  module User {
    header "WinUser.h"
    export *
  }

  module WIC {
    header "wincodec.h"
    export *

    link "windowscodecs.lib"
  }

  module WinBase {
    header "winbase.h"
    export *
  }

  module WinDNS {
    header "WinDNS.h"
    export *

    link "DnsAPI.Lib"
  }

  module WinGDI {
    header "wingdi.h"
    export *

    link "Gdi32.Lib"
  }

  module WinNT {
    header "winnt.h"
    export *
  }

  module WinReg {
    header "winreg.h"
    export *

    link "AdvAPI32.Lib"
  }

  module WinRPC {
    header "../shared/rpc.h"
    header "../shared/rpcndr.h"
    export *

    link "RpcRT4.Lib"
  }

  module WinSVC {
    header "winsvc.h"
    export *

    link "AdvAPI32.Lib"
  }

  module WinSafer {
    header "winsafer.h"
    export *

    link "AdvAPI32.Lib"
  }

  // TODO(compnerd) does it make sense to implicitly export this API surface?
  // It seems to be meant for device drivers.
  module WLANAPI {
    header "wlanapi.h"
    header "wlanihv.h"
    header "wlclient.h"

    link "wlanapi.lib"
  }
}

Hmm, I would then guess that there could be a mismatch between the SDK version in use and where the module map is currently present. This can happen if you have more than one SDK installed or if you have a mismatch between the UCRT and WinSDK. Perhaps a recent update to Visual Studio put things out of sync?

So, I missed this "Cannot load underlying module for WinSDK" - #2 by compnerd step after toolchain reinstallation. After I've copied module maps again, everything started to build. Thanks @compnerd!

Fun fact. This works in the latest VirtualBox release when using the new experimental 3D acceleration driver.

Running the demo made everything unresponsive but switching from a while loop to a queue based one or using vsync callback would probably fix that. Cool stuff.

1 Like

Yes, the implementation of the demo leaves much to be desired. I tried to call that out in the README, but if its not clear, please send a PR. I think that there could be value in providing a better implementation to make it easier for people to get started with DirectX, but that was beyond the scope of that particular attempt - but I definitely see the value in a separate attempt to provide something which can serve as a starting point for others.

I think this example is fantastic as is. It just needs some adjusting specifically when someone wants to use a VM for development as the VM and host become unresponsive, however on a native machine it runs great. I've already used this example to help me make a basic window, which was really difficult for me to understand. As a mac/Linux programmer the way windows does it's references and com is a challenge for me and this example really helped me begin to grasp that stuff.

I'm still working on my Direct3D12 abstraction layer but because of SR-8671 I need to reevaluate how I'm doing that before I can continue. I think I'll follow your lead and make a more swifty version of this demo for it when it's usable.