Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ID3D11Device is not thread-safe #26

Closed
khoabui1412 opened this issue May 30, 2022 · 9 comments
Closed

ID3D11Device is not thread-safe #26

khoabui1412 opened this issue May 30, 2022 · 9 comments

Comments

@khoabui1412
Copy link

Hi,
At first, thanks for creating DirectN and currently, I used DirectN for my project.
As I know ID3D11Device is thread-safe and ID3D11DeviceContext is not, but when I use ID3D11Device to create texture or any buffer in other thread, it meets the error:

'Unable to cast COM object of type 'System.__ComObject' to interface type 'DirectN.ID3D11Device'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{DB6F6DDB-AC77-4E88-8253-819DF9BBF140}' failed due to the following error: No such interface supported (0x80004002

I can work around by just using a single thread, but I'd like to use multithread in my case.
Am I missing something on this?

Thank you.

@smourier
Copy link
Owner

Hi,

Indeed, this is the typical marshaling error message when you want to pass an object that has no proxy/stub defined between thread.

Do you have some more reproducing code?

@khoabui1412
Copy link
Author

Hi,
Here're reproducing code:

    public sealed partial class MainWindow : Window
    {
        private IComObject<ID3D11Device> _d3dDevice;
        private IComObject<ID3D11DeviceContext> _d3dDeviceContext;
        public MainWindow()
        {
            this.InitializeComponent();
        }

        private void myButton_Click(object sender, RoutedEventArgs e)
        {
            myButton.Content = "Clicked";

            //Task.Run(() =>
            //{
                var flags = D3D11_CREATE_DEVICE_FLAG.D3D11_CREATE_DEVICE_BGRA_SUPPORT;
                var fac = DXGIFunctions.CreateDXGIFactory2();
                _d3dDevice = D3D11Functions.D3D11CreateDevice(null, D3D_DRIVER_TYPE.D3D_DRIVER_TYPE_HARDWARE, flags, out _d3dDeviceContext);
                var mt = _d3dDevice.As<ID3D11Multithread>();
                mt?.SetMultithreadProtected(true);
            //});

            System.Threading.Thread.Sleep(2000);
            Task.Run(() =>
            {
                var access = _d3dDevice.Object;   // Error at here
            });
        }
    }

However, If the _d3dDevice is not created in UI thread, but in another thread instead (uncomment Task.Run above), it will work well. So, is it a expected behavior?

@smourier
Copy link
Owner

What framework are you using .NET Framework? .NET Core? 5, 6?

@khoabui1412
Copy link
Author

I used .Net6

@smourier
Copy link
Owner

Ok, I can reproduce, and here is what's happening.

First of all, this is not related to DirectN but only to .NET.

What happens is ID3D11Device doesn't declare how it behaves with regards to COM threading rules (it's more "nano" COM than full COM), so it's considered MTA by default by .NET. So, for .NET (for the RCW wrapper), it must be marshaled when used from one apartment to another, see here https://docs.microsoft.com/en-us/dotnet/framework/interop/interop-marshalling

The problem is ID3D11Device registers no way to be marshaled because... it doesn't need to be! Hence the .NET error (0x80004002)

In your reproducing code, since you're creating the device from the main WPF or Winforms thread, the thread that creates the D3D11 device is marked as STA. When you want to use this object from another thread, .NET will try to marshal the device object reference and fails.

One solution is to create the device in an MTA thread and keep using it in MTA threads:

  private void Button_Click(object sender, RoutedEventArgs e)
  {
      // we're running in an STA here
      Task.Run(() =>
      {
          // we're running in an MTA here
          var flags = D3D11_CREATE_DEVICE_FLAG.D3D11_CREATE_DEVICE_BGRA_SUPPORT;
          _d3dDevice = D3D11Functions.D3D11CreateDevice(null, D3D_DRIVER_TYPE.D3D_DRIVER_TYPE_HARDWARE, flags, out _);
          Task.Run(() =>
          {
              // we're also running in an MTA here
              var access = _d3dDevice.Object; // works
          });
      });
  }

Note you won't be able to use the D3D11 device from an STA thread with .NET.

@khoabui1412
Copy link
Author

Thanks, I understood.
In my case, I also need to bind swapChainPanel UI element (which must be run on STA thread) with swapchain:

                var nativePanel = _swapChainPanel.As<ISwapChainPanelNative>();
                nativePanel.SetSwapChain(swapChain?.Object); //Error if I create swapchain on MTA threads

so it seems that I cannot use with MTA threads.

@smourier
Copy link
Owner

smourier commented May 30, 2022

What you should be able to do in this case if it's just for passing objects around, is avoid using interface .NET objects, but use IntPtr variables which are opaque pointers to .NET.

So, convert your objects into IntPtr using Marshal.GetIUnknownForObject (or modify interface to get back IntPtr directly) and you should also modify interfaces you use in this case (like ISwapChainPanelNative) so they use IntPtr too.

@khoabui1412
Copy link
Author

Thanks, I will try with that

@smourier
Copy link
Owner

smourier commented Jun 5, 2022

See also this for the reasons why it doesn't work (and will never) with COM interop interface : dotnet/runtime#69979

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants