dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
6.99k stars 1.16k forks source link

Provide DX12 WPF island #7871

Open mprevot opened 1 year ago

mprevot commented 1 year ago

DX12 windows are interesting for performance (speed, latency, colors etc), bringing for instance higher colors depth, HDR, and extremely low latency for display to WPF framework.

I am doing so "by hand", involving actual c code for D3D12 and c-c# interop to provide integration, but it involve a lot of work/code for connection, sizing, injection into a XAML content, interop.

Something more standardized and implemented could be a great thing and could bring many new possibilities to WPF and to a broader audience of developers and users.

dipeshmsft commented 1 year ago

DX9 to DX12 migration is not currently slated for the current roadmap. However, we are open to discussing what features we would like to incorporate during migration.

Could you also provide more details about your request, especially what you mean by "by hand"?

Symbai commented 1 year ago

There is already an issue about DX12 request: #7062

mprevot commented 1 year ago

@Symbai This is not the same request at all.

Symbai commented 1 year ago

WPF currently only supports DX9. The same I linked is about adding DX 12 support which is necessary for DX12 Island. So its about 97% the same request.

mprevot commented 1 year ago

@dipeshmsft by hand I mean: on c# side I create a subclass of HwndHost and override BuildWindowCore, and add this window as a child of say Border (XAML).

/// <summary>
/// Create asset, create window, initialize asset, give a handle so the window can be injected in WPF.
/// </summary>
/// <param name="hwndParent">parent handle</param>
/// <returns>DX12Asset/host handle</returns>
protected override HandleRef BuildWindowCore(HandleRef hwndParent)
{
    if (ImSize.Surface == 0)
        throw new InteropException("ImSize must be set with non 0 surface (w*h)" +
                                   " in order to use CreateWindowEx() or CreateAsset().");

    CreateAsset(); // create new instance of DX12 asset (a class with D3D12 objects, pielines etc)

    OnAssetTitleChangedCallback = TitleUpdater;
    DX12.SetTitleChangedCallback(DX12ptr, OnAssetTitleChangedCallback);

    HwndHostPtr = UserKernel32.CreateWindowEx(0, "static", "DX12 CudaUpdate asset", WS.CHILD | WS.VISIBLE, 0, 0,
        ImSize.Width, ImSize.Height, hwndParent.Handle, IntPtr.Zero, IntPtr.Zero, DX12ptr);
    if (HwndHostPtr == IntPtr.Zero)
    {
        var lastErrorMessage = UserKernel32.GetLastErrorString();
        LogMessages.Add(lastErrorMessage);
        throw new InteropException($"UserKernel32.CreateWindowExW failed ({lastErrorMessage})");
    }

    try
    {
        DX12.OnInit(DX12ptr, HwndHostPtr);
        AssetCreated?.Invoke(this, null);
    }
    catch (Exception e) when (e is SEHException || e is AccessViolationException)
    {
        var hasShBinInPath = _dllSafe.PathContainsShNativeBinaries;
        var exeWasMoved = _dllSafe.CurrentDirEqualsExecDir;
        var hasTexShaderhlsl = _dllSafe.IsFilelInPath("texShader.hlsl");

        throw new InteropException($"Exception from OnInit(): {e.Source}. Last LogMessage: {LogMessages.LastOrDefault()}");
    }

    DX12.OnUpdateAndRender(DX12ptr);
    HwndHandleRef = new HandleRef(this, HwndHostPtr);
    return HwndHandleRef;
}

On c / c++ side I have functions such as:


HWND CurrentHwnd{};

Pinvoke auto __cdecl OnInit(DX12CudaInterop* asset, HWND hwnd) -> void
{
    if (asset == nullptr) return;
    if (CurrentHwnd != nullptr)
    {
        asset->LogMessage(L"CurrentHwnd already set. You should call OnInit() once.");
        throw exception("CurrentHwnd already set. You should call OnInit() once.");
    }
    CurrentHwnd = hwnd;
    asset->OnInit(hwnd);
}

auto DX12CudaInterop::OnInit(HWND hwnd) -> void
{
    UploadTitle();
    LoadPipeline(hwnd);
    InitCuda();
    LoadAssets();
}

Pinvoke auto __cdecl OnUpdateAndRender(DX12CudaInterop* asset) -> void
{
    asset->OnRender();
}

auto DX12CudaInterop::OnRender() -> void
{
    m_AnimTime += 0.1f;
    //CheckCudaErrors(cudaDeviceSynchronize());
    PopulateCommandList();
    ID3D12CommandList* ppCommandLists[] = { m_commandList.Get() };
    m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), &ppCommandLists[0]);
    CheckErrors(m_swapChain->Present(1, 0));
    const auto currentFenceValue = m_fenceValues[m_frameIndex];
    CheckErrors(m_commandQueue->Signal(m_fence.Get(), currentFenceValue));
    MoveToNextFrame();
}

Here things are a bit more complicated, since I update a DX12 texture with cuda (code not shown), so I actually have an interop chain WPF-window-DX12-cuda-c#, but the point is that I inject a D3D12 asset in a XAML border.

So the point of my request is: it would be great to have a more standardized way to inject and manage (create, destroy) a D3D12 asset into a WPF UIElement.

I aknowledge this can be technical and specialized, but this can be a good base for having DX12 or high performance display in WPF. I suspect many use case can benefit from that.

I'm not sure if this is the same as "supporting DX12 in WPF" or not.

mprevot commented 1 year ago

@Symbai When I read "having WPF supporting DX12", I understand that the WPF UIelements are working on the top of DX9, but not yet DX12. When I write "WPF DX12 islands" I mean custom DX12 assets living as a XAML UIelement child, where the developer has its own DX12 asset, code, as "pictured" in my previous post.

Even though there is no official support (maintenance ? norm ? existing class distributed as part of WPF?), I am injecting DX12 assets into XAML/WPF since years, since it's technically possible.

lindexi commented 1 year ago

Reference https://github.com/dotnet/wpf/discussions/7555#discussioncomment-5059491

naughtykid001 commented 1 month ago

any progress after a year or so?