#include "fiddle_context.hpp"

#if !defined(_WIN32) || defined(RIVE_UNREAL)

std::unique_ptr<FiddleContext> FiddleContext::MakeD3D12PLS(
    FiddleContextOptions fiddleOptions)
{
    return nullptr;
}

#else

#include "rive/renderer/rive_renderer.hpp"
#include "rive/renderer/d3d/d3d_utils.hpp"
#include "rive/renderer/d3d12/render_context_d3d12_impl.hpp"
#include <dxgi1_6.h>

#define GLFW_INCLUDE_NONE
#define GLFW_EXPOSE_NATIVE_WIN32
#include "GLFW/glfw3.h"
#include <GLFW/glfw3native.h>

using namespace rive;
using namespace rive::gpu;

void SetIntelInformation(const DXGI_ADAPTER_DESC& desc,
                         D3DContextOptions& contextOptions)
{
    contextOptions.isIntel = desc.VendorId == 0x163C ||
                             desc.VendorId == 0x8086 || desc.VendorId == 0x8087;

    contextOptions.isIntelArc =
        contextOptions.isIntel &&
        std::wstring(desc.Description).find(L"Arc") != std::wstring::npos;
}

bool GetHardwareAdapter(IDXGIFactory1* pFactory,
                        const char* gpuNameFilter,
                        IDXGIAdapter1** ppAdapter)
{
    using namespace Microsoft::WRL;

    *ppAdapter = nullptr;

    bool shouldUseNameFilter = false;
    std::wstring gpuNameFilterW;
    if (gpuNameFilter)
    {
        shouldUseNameFilter =
            d3d_utils::GetWStringFromString(gpuNameFilter, gpuNameFilterW);
    }

    ComPtr<IDXGIAdapter1> adapter;
    ComPtr<IDXGIFactory6> factory6;
    if (SUCCEEDED(pFactory->QueryInterface(IID_PPV_ARGS(&factory6))))
    {
        for (UINT adapterIndex = 0;
             SUCCEEDED(factory6->EnumAdapterByGpuPreference(
                 adapterIndex,
                 DXGI_GPU_PREFERENCE_UNSPECIFIED,
                 IID_PPV_ARGS(&adapter)));
             ++adapterIndex)
        {
            DXGI_ADAPTER_DESC1 desc;
            adapter->GetDesc1(&desc);

            if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
            {
                // Don't select the Basic Render Driver adapter.
                continue;
            }

            if (shouldUseNameFilter)
            {
                std::wstring gpuName(desc.Description,
                                     wcslen(desc.Description));
                if (gpuName.find(gpuNameFilterW) == std::wstring::npos)
                {
                    continue;
                }
            }

            // Check to see whether the adapter supports Direct3D 12, but don't
            // create the actual device yet.
            if (SUCCEEDED(D3D12CreateDevice(adapter.Get(),
                                            D3D_FEATURE_LEVEL_11_0,
                                            _uuidof(ID3D12Device),
                                            nullptr)))
            {
                *ppAdapter = adapter.Detach();
                return true;
            }
        }
    }

    if (adapter.Get() == nullptr)
    {
        for (UINT adapterIndex = 0;
             SUCCEEDED(pFactory->EnumAdapters1(adapterIndex, &adapter));
             ++adapterIndex)
        {
            DXGI_ADAPTER_DESC1 desc;
            adapter->GetDesc1(&desc);

            if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
            {
                // Don't select the Basic Render Driver adapter.
                // If you want a software adapter, pass in "/warp" on the
                // command line.
                continue;
            }

            if (shouldUseNameFilter)
            {
                std::wstring gpuName(desc.Description,
                                     wcslen(desc.Description));
                if (gpuName.find(gpuNameFilterW) == std::wstring::npos)
                {
                    continue;
                }
            }

            // Check to see whether the adapter supports Direct3D 12, but don't
            // create the actual device yet.
            if (SUCCEEDED(D3D12CreateDevice(adapter.Get(),
                                            D3D_FEATURE_LEVEL_11_0,
                                            _uuidof(ID3D12Device),
                                            nullptr)))
            {
                *ppAdapter = adapter.Detach();
                return true;
            }
        }
    }

    return false;
}

class FiddleContextD3D12PLS : public FiddleContext
{
public:
    FiddleContextD3D12PLS(ComPtr<IDXGIFactory4> factory,
                          ComPtr<ID3D12Device> device,
                          bool isHeadless,
                          D3DContextOptions& contextOptions) :
        m_isHeadless(isHeadless), m_factory(factory), m_device(device)
    {

        D3D12_COMMAND_QUEUE_DESC queueDesc = {};
        queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;

        VERIFY_OK(m_device->CreateCommandQueue(&queueDesc,
                                               IID_PPV_ARGS(&m_commandQueue)));

        NAME_RAW_D3D12_OBJECT(m_commandQueue);

        queueDesc.Type = D3D12_COMMAND_LIST_TYPE_COPY;
        VERIFY_OK(
            m_device->CreateCommandQueue(&queueDesc,
                                         IID_PPV_ARGS(&m_copyCommandQueue)));
        NAME_RAW_D3D12_OBJECT(m_copyCommandQueue);

        for (auto i = 0; i < FrameCount; ++i)
        {
            VERIFY_OK(m_device->CreateCommandAllocator(
                D3D12_COMMAND_LIST_TYPE_DIRECT,
                IID_PPV_ARGS(&m_allocators[i])));

            NAME_RAW_D3D12_OBJECT(m_allocators[i]);

            VERIFY_OK(m_device->CreateCommandAllocator(
                D3D12_COMMAND_LIST_TYPE_COPY,
                IID_PPV_ARGS(&m_copyAllocators[i])));
            NAME_RAW_D3D12_OBJECT(m_copyAllocators[i]);
        }

        VERIFY_OK(m_device->CreateCommandList(0,
                                              D3D12_COMMAND_LIST_TYPE_DIRECT,
                                              m_allocators[0].Get(),
                                              NULL,
                                              IID_PPV_ARGS(&m_commandList)));
        NAME_RAW_D3D12_OBJECT(m_commandList);

        VERIFY_OK(
            m_device->CreateCommandList(0,
                                        D3D12_COMMAND_LIST_TYPE_COPY,
                                        m_copyAllocators[0].Get(),
                                        NULL,
                                        IID_PPV_ARGS(&m_copyCommandList)));
        NAME_RAW_D3D12_OBJECT(m_copyCommandList);

        m_renderContext =
            RenderContextD3D12Impl::MakeContext(m_device,
                                                m_copyCommandList.Get(),
                                                contextOptions);
        VERIFY_OK(m_copyCommandList->Close());
        VERIFY_OK(m_commandList->Close());

        ID3D12CommandList* ppCommandLists[] = {m_copyCommandList.Get()};
        m_copyCommandQueue->ExecuteCommandLists(1, ppCommandLists);

        VERIFY_OK(m_device->CreateFence(m_currentFrame,
                                        D3D12_FENCE_FLAG_NONE,
                                        IID_PPV_ARGS(&m_fence)));

        // NOTE: Originally the code was setting this to -1 and then waiting on
        // a signal of 0, but this does not work in practice because some D3D12
        // implementations only allow the signaled value to increase - so
        // starting the fence at unsigned -1 meant that it can never be changed
        // again.
        VERIFY_OK(m_device->CreateFence(m_currentFrame,
                                        D3D12_FENCE_FLAG_NONE,
                                        IID_PPV_ARGS(&m_copyFence)));

        // Increment m_currentFrame since the value we initialized the fences to
        // cannot be waited on (it'll return immediately).
        m_currentFrame++;

        m_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
        VERIFY_OK(HRESULT_FROM_WIN32(GetLastError()));

        assert(m_fenceEvent);

        // Signal the fence to ensure that we wait for creation to be complete.
        // Start at m_currentFrame which should currently be set to 1.
        VERIFY_OK(
            m_copyCommandQueue->Signal(m_copyFence.Get(), m_currentFrame));

        if (m_copyFence->GetCompletedValue() != m_currentFrame)
        {
            VERIFY_OK(m_copyFence->SetEventOnCompletion(m_currentFrame,
                                                        m_fenceEvent));
            WaitForSingleObjectEx(m_fenceEvent, INFINITE, FALSE);

            assert(m_copyFence->GetCompletedValue() == m_currentFrame);
        }

        // Increment the current frame one more to get past this wait.
        m_currentFrame++;
    }

    float dpiScale(GLFWwindow*) const override { return 1; }

    rive::Factory* factory() override { return m_renderContext.get(); }

    rive::gpu::RenderContext* renderContextOrNull() override
    {
        return m_renderContext.get();
    }

    rive::gpu::RenderTarget* renderTargetOrNull() override
    {
        return m_renderTargets[m_frameIndex].get();
    }

    void onSizeChanged(GLFWwindow* window,
                       int width,
                       int height,
                       uint32_t sampleCount) override
    {
        // wait for all frames to finish
        waitForLastFrame();

        if (!m_isHeadless)
        {
            if (!m_swapChain)
            {
                DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
                swapChainDesc.BufferCount = FrameCount;
                swapChainDesc.Width = width;
                swapChainDesc.Height = height;
                swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
                swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
                swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
                swapChainDesc.SampleDesc.Count = 1;
                swapChainDesc.Flags = 0;

                ComPtr<IDXGISwapChain1> swapChain;
                VERIFY_OK(m_factory->CreateSwapChainForHwnd(
                    m_commandQueue.Get(), // Swap chain needs the queue so that
                                          // it can force a flush on it.
                    glfwGetWin32Window(window),
                    &swapChainDesc,
                    nullptr,
                    nullptr,
                    &swapChain));

                VERIFY_OK(swapChain.As(&m_swapChain));
            }
            else
            {
                // we must release all references to resize swapchain
                for (auto i = 0; i < FrameCount; ++i)
                {
                    m_renderTargets[i]->releaseTexturesImmediately();
                }
                // invalidates all previous buffers
                m_swapChain->ResizeBuffers(FrameCount,
                                           width,
                                           height,
                                           DXGI_FORMAT_R8G8B8A8_UNORM,
                                           0);
            }

            for (auto i = 0; i < FrameCount; ++i)
            {
                ComPtr<ID3D12Resource> backbuffer;
                VERIFY_OK(m_swapChain->GetBuffer(i, IID_PPV_ARGS(&backbuffer)));
                auto renderContextImpl =
                    m_renderContext->static_impl_cast<RenderContextD3D12Impl>();
                m_renderTargets[i] =
                    renderContextImpl->makeRenderTarget(width, height);
                m_renderTargets[i]->setTargetTexture(backbuffer);
            }
        }

        if (m_isHeadless)
        {
            ComPtr<ID3D12Resource> backbuffer;
            auto desc = CD3DX12_RESOURCE_DESC::Tex2D(DXGI_FORMAT_R8G8B8A8_UNORM,
                                                     width,
                                                     height);
            desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
            D3D12_CLEAR_VALUE clearValue{DXGI_FORMAT_R8G8B8A8_UNORM, {}};
            auto heapProperties =
                CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT);
            m_device->CreateCommittedResource(&heapProperties,
                                              D3D12_HEAP_FLAG_NONE,
                                              &desc,
                                              D3D12_RESOURCE_STATE_PRESENT,
                                              &clearValue,
                                              IID_PPV_ARGS(&backbuffer));
            auto renderContextImpl =
                m_renderContext->static_impl_cast<RenderContextD3D12Impl>();
            m_renderTargets[0] =
                renderContextImpl->makeRenderTarget(width, height);
            m_renderTargets[0]->setTargetTexture(backbuffer);
        }
    }

    void toggleZoomWindow() override {}

    std::unique_ptr<Renderer> makeRenderer(int width, int height) override
    {
        return std::make_unique<RiveRenderer>(m_renderContext.get());
    }

    void begin(const rive::gpu::RenderContext::FrameDescriptor& frameDescriptor)
        override
    {
        m_renderContext->beginFrame(frameDescriptor);
    }

    UINT64 getFrameIndex() const
    {
        // when headless we only ever use one frames worth of textures for
        // rendering
        if (m_isHeadless)
        {
            return 0;
        }
        else
        {
            return m_swapChain->GetCurrentBackBufferIndex();
        }
    }

    UINT64 waitForNextSafeFrame()
    {
        // wait in case this swapchain still hasnt finished
        auto safeFrame = m_fence->GetCompletedValue();
        if (safeFrame < m_previousFrames[m_frameIndex])
        {
            VERIFY_OK(
                m_fence->SetEventOnCompletion(m_previousFrames[m_frameIndex],
                                              m_fenceEvent));
            WaitForSingleObjectEx(m_fenceEvent, INFINITE, FALSE);
            safeFrame = m_previousFrames[m_frameIndex];
        }

        auto copySafeFrame = m_copyFence->GetCompletedValue();
        if (copySafeFrame < m_previousFrames[m_frameIndex])
        {
            VERIFY_OK(m_copyFence->SetEventOnCompletion(
                m_previousFrames[m_frameIndex],
                m_fenceEvent));
            WaitForSingleObjectEx(m_fenceEvent, INFINITE, FALSE);
            copySafeFrame = m_previousFrames[m_frameIndex];
        }

        return std::min(copySafeFrame, safeFrame);
    }

    void waitForLastFrame()
    {
        auto frame = m_currentFrame++;
        m_previousFrames[m_frameIndex] = frame;
        VERIFY_OK(m_copyCommandQueue->Signal(m_copyFence.Get(), frame));
        VERIFY_OK(m_commandQueue->Signal(m_fence.Get(), frame));

        VERIFY_OK(m_fence->SetEventOnCompletion(frame, m_fenceEvent));
        WaitForSingleObjectEx(m_fenceEvent, INFINITE, FALSE);

        VERIFY_OK(m_copyFence->SetEventOnCompletion(frame, m_fenceEvent));
        WaitForSingleObjectEx(m_fenceEvent, INFINITE, FALSE);
    }

    void moveToNextFrame()
    {
        VERIFY_OK(m_commandList->Close());
        VERIFY_OK(m_copyCommandList->Close());

        ID3D12CommandList* ppCopyCommandLists[] = {m_copyCommandList.Get()};

        ID3D12CommandList* ppCommandLists[] = {m_commandList.Get()};

        m_previousFrames[m_frameIndex] = m_currentFrame++;

        m_copyCommandQueue->ExecuteCommandLists(_countof(ppCopyCommandLists),
                                                ppCopyCommandLists);

        VERIFY_OK(m_copyCommandQueue->Signal(m_copyFence.Get(),
                                             m_previousFrames[m_frameIndex]));

        // tell the direct command que to wait for the copy command que to
        // finish
        m_commandQueue->Wait(m_copyFence.Get(), m_previousFrames[m_frameIndex]);

        m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists),
                                            ppCommandLists);

        VERIFY_OK(m_commandQueue->Signal(m_fence.Get(),
                                         m_previousFrames[m_frameIndex]));
    }

    void flushPLSContext(RenderTarget* offscreenRenderTarget) final
    {
        m_frameIndex = getFrameIndex();

        auto safeFrame = waitForNextSafeFrame();

        VERIFY_OK(m_allocators[m_frameIndex]->Reset());

        VERIFY_OK(m_copyAllocators[m_frameIndex]->Reset());

        VERIFY_OK(m_commandList->Reset(m_allocators[m_frameIndex].Get(), NULL));

        VERIFY_OK(m_copyCommandList->Reset(m_copyAllocators[m_frameIndex].Get(),
                                           NULL));

        RenderContextD3D12Impl::CommandLists cmdLists = {
            m_copyCommandList.Get(),
            m_commandList.Get()};

        m_renderContext->flush({
            .renderTarget = offscreenRenderTarget != nullptr
                                ? offscreenRenderTarget
                                : m_renderTargets[m_frameIndex].get(),
            .externalCommandBuffer = &cmdLists,
            .currentFrameNumber = m_currentFrame,
            .safeFrameNumber = safeFrame,
        });

        moveToNextFrame();
    }

    void readBackPixels(std::vector<uint8_t>* pixelData)
    {
        waitForLastFrame();

        VERIFY_OK(m_allocators[m_frameIndex]->Reset());
        VERIFY_OK(m_copyAllocators[m_frameIndex]->Reset());
        VERIFY_OK(m_commandList->Reset(m_allocators[m_frameIndex].Get(), NULL));
        VERIFY_OK(m_copyCommandList->Reset(m_copyAllocators[m_frameIndex].Get(),
                                           NULL));

        ComPtr<ID3D12Resource> readbackBuffer;
        auto w = m_renderTargets[m_frameIndex]->width();
        auto h = m_renderTargets[m_frameIndex]->height();
        size_t outputBufferSize = w * h * 4;
        D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint;

        auto targetTexture =
            m_renderTargets[m_frameIndex]->targetTexture()->resource();

        D3D12_HEAP_PROPERTIES readbackHeapProperties{
            CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_READBACK)};

        D3D12_RESOURCE_DESC readbackBufferDesc{
            CD3DX12_RESOURCE_DESC::Buffer(outputBufferSize)};

        VERIFY_OK(
            m_device->CreateCommittedResource(&readbackHeapProperties,
                                              D3D12_HEAP_FLAG_NONE,
                                              &readbackBufferDesc,
                                              D3D12_RESOURCE_STATE_COPY_DEST,
                                              nullptr,
                                              IID_PPV_ARGS(&readbackBuffer)));

        {
            D3D12_RESOURCE_BARRIER outputBufferResourceBarrier{
                CD3DX12_RESOURCE_BARRIER::Transition(
                    targetTexture,
                    D3D12_RESOURCE_STATE_PRESENT,
                    D3D12_RESOURCE_STATE_COPY_SOURCE)};
            m_commandList->ResourceBarrier(1, &outputBufferResourceBarrier);
        }

        UINT numRows;
        UINT64 rowSizeInBtes;
        UINT64 totalBytes;
        auto desc = targetTexture->GetDesc();
        m_device->GetCopyableFootprints(&desc,
                                        0,
                                        1,
                                        0,
                                        &footprint,
                                        &numRows,
                                        &rowSizeInBtes,
                                        &totalBytes);
        footprint.Footprint.RowPitch = w * 4;

        const CD3DX12_TEXTURE_COPY_LOCATION dst(readbackBuffer.Get(),
                                                footprint);
        const CD3DX12_TEXTURE_COPY_LOCATION src(targetTexture, 0);

        m_commandList->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);

        footprint.Footprint.RowPitch = static_cast<UINT>(rowSizeInBtes);

        {
            D3D12_RESOURCE_BARRIER outputBufferResourceBarrier{
                CD3DX12_RESOURCE_BARRIER::Transition(
                    targetTexture,
                    D3D12_RESOURCE_STATE_COPY_SOURCE,
                    D3D12_RESOURCE_STATE_PRESENT)};
            m_commandList->ResourceBarrier(1, &outputBufferResourceBarrier);
        }

        moveToNextFrame();

        waitForLastFrame();

        assert(pixelData);
        pixelData->resize(outputBufferSize);

        uint8_t* mappedData;
        D3D12_RANGE range{0, outputBufferSize};
        VERIFY_OK(readbackBuffer->Map(0,
                                      &range,
                                      reinterpret_cast<void**>(&mappedData)));

        for (uint32_t y = 0; y < h; ++y)
        {
            auto row = reinterpret_cast<const char*>(mappedData) +
                       footprint.Footprint.RowPitch * y;
            memcpy(pixelData->data() + (h - y - 1) * w * 4, row, w * 4);
        }

        D3D12_RANGE emptyRange{0, 0};
        readbackBuffer->Unmap(0, &emptyRange);
    }

    void end(GLFWwindow*, std::vector<uint8_t>* pixelData = nullptr) override
    {
        flushPLSContext(nullptr);

        if (pixelData != nullptr)
        {
            auto w = m_renderTargets[m_frameIndex]->width();
            auto h = m_renderTargets[m_frameIndex]->height();
            pixelData->resize(w * h * 4);
            readBackPixels(pixelData);
        }

        if (!m_isHeadless)
            m_swapChain->Present(0, 0);
    }

private:
    static constexpr SIZE_T FrameCount = 2;

    ComPtr<ID3D12CommandAllocator> m_allocators[FrameCount];
    ComPtr<ID3D12CommandAllocator> m_copyAllocators[FrameCount];
    rcp<RenderTargetD3D12> m_renderTargets[FrameCount];
    ComPtr<ID3D12Fence> m_fence;
    ComPtr<ID3D12Fence> m_copyFence;
    HANDLE m_fenceEvent = NULL;
    UINT64 m_previousFrames[FrameCount] = {0};
    UINT64 m_currentFrame = 0;
    UINT64 m_frameIndex = 0;
    const bool m_isHeadless;

    ComPtr<IDXGIFactory4> m_factory;
    ComPtr<IDXGISwapChain3> m_swapChain;
    ComPtr<ID3D12CommandQueue> m_commandQueue;
    ComPtr<ID3D12CommandQueue> m_copyCommandQueue;
    ComPtr<ID3D12GraphicsCommandList> m_commandList;
    ComPtr<ID3D12GraphicsCommandList> m_copyCommandList;
    ComPtr<ID3D12Device> m_device;
    std::unique_ptr<RenderContext> m_renderContext;
};

std::unique_ptr<FiddleContext> FiddleContext::MakeD3D12PLS(
    FiddleContextOptions fiddleOptions)
{
    UINT dxgiFactoryFlags = 0;

#ifdef DEBUG
    // Enable the debug layer (requires the Graphics Tools "optional feature").
    // NOTE: Enabling the debug layer after device creation will invalidate the
    // active device.
    {
        ComPtr<ID3D12Debug> debugController;
        if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
        {
            debugController->EnableDebugLayer();

            // Enable additional debug layers.
            dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
        }

        // Query the newer interface for GPU-based validation
        ComPtr<ID3D12Debug1> debugController1;
        if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController1))))
        {
            debugController1->SetEnableGPUBasedValidation(TRUE);
        }
    }
#endif

    ComPtr<IDXGIFactory4> factory;
    VERIFY_OK(CreateDXGIFactory2(dxgiFactoryFlags, IID_PPV_ARGS(&factory)));

    ComPtr<ID3D12Device> device;

    D3DContextOptions contextOptions;

    contextOptions.shaderCompilationMode = fiddleOptions.shaderCompilationMode;

    if (fiddleOptions.d3d12UseWarpDevice)
    {
        ComPtr<IDXGIAdapter> warpAdapter;
        DXGI_ADAPTER_DESC adapterDesc{};
        VERIFY_OK(factory->EnumWarpAdapter(IID_PPV_ARGS(&warpAdapter)));
        warpAdapter->GetDesc(&adapterDesc);
        SetIntelInformation(adapterDesc, contextOptions);
        VERIFY_OK(D3D12CreateDevice(warpAdapter.Get(),
                                    D3D_FEATURE_LEVEL_11_0,
                                    IID_PPV_ARGS(&device)));
        printf("D3D12 device: %S\n", adapterDesc.Description);
    }
    else
    {
        ComPtr<IDXGIAdapter1> hardwareAdapter;
        DXGI_ADAPTER_DESC adapterDesc{};

        if (!GetHardwareAdapter(factory.Get(),
                                fiddleOptions.gpuNameFilter,
                                &hardwareAdapter))
        {
            std::cout << "Failed to get adapter with name filter \""
                      << fiddleOptions.gpuNameFilter
                      << "\" trying again with out one.\n";
            // try again with now filter
            if (!GetHardwareAdapter(factory.Get(), nullptr, &hardwareAdapter))
            {
                std::cerr << "Failed to get hardware adaptor for dx12.\n";
                assert(false);
            }
        }
        hardwareAdapter->GetDesc(&adapterDesc);

        SetIntelInformation(adapterDesc, contextOptions);

        VERIFY_OK(D3D12CreateDevice(hardwareAdapter.Get(),
                                    D3D_FEATURE_LEVEL_11_0,
                                    IID_PPV_ARGS(&device)));
        printf("D3D12 device: %S\n", adapterDesc.Description);
    }

    if (!device)
    {
        return nullptr;
    }

    if (fiddleOptions.disableRasterOrdering)
    {
        contextOptions.disableRasterizerOrderedViews = true;
        // Also disable typed UAVs in atomic mode, to get more complete test
        // coverage.
        contextOptions.disableTypedUAVLoadStore = true;
    }

    return std::make_unique<FiddleContextD3D12PLS>(
        std::move(factory),
        std::move(device),
        fiddleOptions.allowHeadlessRendering,
        contextOptions);
}

#endif
