/*
 * Copyright 2025 Rive
 */

#include "rive/renderer/d3d12/render_context_d3d12_impl.hpp"
#include "rive/renderer/d3d/d3d_constants.hpp"
// needed for root sig and heap constants
#include "shaders/d3d/root.sig"

#include <sstream>
#include <D3DCompiler.h>

// this is defined here instead of root_sig becaise the gpu does not care about
// the number of rtvs this is gradient, tess, atlas and color
static constexpr UINT NUM_RTV_HEAP_DESCRIPTORS = 4;
namespace rive::gpu
{

static constexpr D3D12_FILTER filter_for_sampler_options(ImageFilter option)
{
    switch (option)
    {
        case ImageFilter::nearest:
            return D3D12_FILTER_COMPARISON_MIN_MAG_MIP_POINT;
        case ImageFilter::bilinear:
            return D3D12_FILTER_COMPARISON_MIN_MAG_LINEAR_MIP_POINT;
    }

    RIVE_UNREACHABLE();
    return D3D12_FILTER_COMPARISON_MIN_MAG_MIP_LINEAR;
};

static constexpr D3D12_TEXTURE_ADDRESS_MODE address_mode_for_sampler_option(
    ImageWrap option)
{
    switch (option)
    {
        case ImageWrap::clamp:
            return D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
        case ImageWrap::repeat:
            return D3D12_TEXTURE_ADDRESS_MODE_WRAP;
        case ImageWrap::mirror:
            return D3D12_TEXTURE_ADDRESS_MODE_MIRROR;
    }

    RIVE_UNREACHABLE();
    return D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
}

void RenderContextD3D12Impl::blitSubRect(ID3D12GraphicsCommandList* cmdList,
                                         D3D12Texture* dst,
                                         D3D12Texture* src,
                                         const IAABB& rect)
{
    D3D12_BOX updateBox = {
        static_cast<UINT>(rect.left),
        static_cast<UINT>(rect.top),
        0,
        static_cast<UINT>(rect.right),
        static_cast<UINT>(rect.bottom),
        1,
    };

    D3D12_TEXTURE_COPY_LOCATION dstLoc;
    dstLoc.SubresourceIndex = 0;
    dstLoc.pResource = dst->resource();
    dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;

    D3D12_TEXTURE_COPY_LOCATION srcLoc;
    srcLoc.SubresourceIndex = 0;
    srcLoc.pResource = src->resource();
    srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;

    m_resourceManager->transition(cmdList,
                                  src,
                                  D3D12_RESOURCE_STATE_COPY_SOURCE);
    m_resourceManager->transition(cmdList, dst, D3D12_RESOURCE_STATE_COPY_DEST);
    cmdList->CopyTextureRegion(&dstLoc,
                               updateBox.left,
                               updateBox.top,
                               0,
                               &srcLoc,
                               &updateBox);

    // we don't untransition here because we don't
    // know what we want it to be, so instead we just always transition before
    // use
}

class TextureD3D12Impl : public Texture
{
public:
    TextureD3D12Impl(D3D12ResourceManager* manager,
                     UINT width,
                     UINT height,
                     UINT mipLevel,
                     const uint8_t imageDataRGBA[],
                     bool usesCommandList) :
        Texture(width, height),
        m_gpuTexture(manager->make2DTexture(
            width,
            height,
            mipLevel,
            DXGI_FORMAT_R8G8B8A8_UNORM,
            D3D12_RESOURCE_FLAG_NONE,
            usesCommandList ? D3D12_RESOURCE_STATE_COMMON
                            : D3D12_RESOURCE_STATE_COPY_DEST))
    {
        D3D12_SUBRESOURCE_DATA srcData;
        srcData.pData = imageDataRGBA;
        srcData.RowPitch = width * 4;
        srcData.SlicePitch = srcData.RowPitch * height;

        UINT numRows;
        UINT64 rowSizeInBtes;
        UINT64 totalBytes;
        auto desc = m_gpuTexture->resource()->GetDesc();
        SNAME_D3D12_OBJECT(m_gpuTexture, "riveImage");
        manager->device()->GetCopyableFootprints(&desc,
                                                 0,
                                                 1,
                                                 0,
                                                 &m_uploadFootprint,
                                                 &numRows,
                                                 &rowSizeInBtes,
                                                 &totalBytes);

        m_uploadBuffer = manager->makeUploadBuffer(
            static_cast<UINT>(totalBytes) +
            D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);

        D3D12_MEMCPY_DEST DestData = {
            m_uploadBuffer->map(),
            m_uploadFootprint.Footprint.RowPitch,
            SIZE_T(m_uploadFootprint.Footprint.RowPitch * numRows)};

        MemcpySubresource(&DestData,
                          &srcData,
                          static_cast<SIZE_T>(rowSizeInBtes),
                          numRows,
                          m_uploadFootprint.Footprint.Depth);
    }

    D3D12Texture* synchronize(ID3D12GraphicsCommandList* copyList,
                              ID3D12GraphicsCommandList* cmdList,
                              D3D12ResourceManager* manager) const
    {
        if (m_uploadBuffer)
        {
            const CD3DX12_TEXTURE_COPY_LOCATION dst(m_gpuTexture->resource(),
                                                    0);
            const CD3DX12_TEXTURE_COPY_LOCATION src(m_uploadBuffer->resource(),
                                                    m_uploadFootprint);

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

            manager->transition(cmdList,
                                m_gpuTexture.get(),
                                D3D12_RESOURCE_STATE_GENERIC_READ);

            m_uploadBuffer = nullptr;
        }

        return m_gpuTexture.get();
    }

    D3D12Texture* resource() const { return m_gpuTexture.get(); }

private:
    mutable rcp<D3D12Buffer> m_uploadBuffer;
    const rcp<D3D12Texture> m_gpuTexture;
    D3D12_PLACED_SUBRESOURCE_FOOTPRINT m_uploadFootprint;
};

class RenderBufferD3D12Impl
    : public LITE_RTTI_OVERRIDE(RenderBuffer, RenderBufferD3D12Impl)
{
public:
    RenderBufferD3D12Impl(D3D12ResourceManager* manager,
                          RenderBufferType renderBufferType,
                          RenderBufferFlags renderBufferFlags,
                          size_t sizeInBytes) :
        lite_rtti_override(renderBufferType, renderBufferFlags, sizeInBytes),
        m_gpuBuffer(manager->makeBuffer(static_cast<UINT>(sizeInBytes),
                                        D3D12_RESOURCE_FLAG_NONE,
                                        D3D12_RESOURCE_STATE_COMMON))
    {
        m_uploadBuffer =
            manager->makeUploadBuffer(static_cast<UINT>(sizeInBytes));

        if (flags() & RenderBufferFlags::mappedOnceAtInitialization)
        {
            mappedOnceMempry = m_uploadBuffer->map();
        }
    }

    D3D12Buffer* sync(ID3D12GraphicsCommandList* cmdList)
    {
        if (needsUpload)
        {
            needsUpload = false;
            assert(m_uploadBuffer->sizeInBytes() == m_gpuBuffer->sizeInBytes());
            cmdList->CopyBufferRegion(m_gpuBuffer->resource(),
                                      0,
                                      m_uploadBuffer->resource(),
                                      0,
                                      m_uploadBuffer->sizeInBytes());

            const auto barrier = CD3DX12_RESOURCE_BARRIER::Transition(
                m_gpuBuffer->resource(),
                D3D12_RESOURCE_STATE_COPY_DEST,
                D3D12_RESOURCE_STATE_COMMON);

            cmdList->ResourceBarrier(1, &barrier);
            if (flags() & RenderBufferFlags::mappedOnceAtInitialization)
            {
                m_uploadBuffer = nullptr;
            }
        }

        return m_gpuBuffer.get();
    }

protected:
    void* onMap() override
    {
        assert(m_uploadBuffer);
        needsUpload = true;
        if (flags() & RenderBufferFlags::mappedOnceAtInitialization)
        {
            assert(mappedOnceMempry);
            return mappedOnceMempry;
        }
        else
        {
            return m_uploadBuffer->map();
        }
    }

    void onUnmap() override
    {
        assert(m_uploadBuffer);
        if (flags() & RenderBufferFlags::mappedOnceAtInitialization)
        {
            assert(mappedOnceMempry);
            m_uploadBuffer->unmap();
            mappedOnceMempry = nullptr;
        }
        else
        {
            m_uploadBuffer->unmap();
        }
    }

private:
    rcp<D3D12Buffer> m_uploadBuffer;
    const rcp<D3D12Buffer> m_gpuBuffer;
    void* mappedOnceMempry = nullptr;

    bool needsUpload = false;
};

RenderTargetD3D12::RenderTargetD3D12(RenderContextD3D12Impl* impl,
                                     int width,
                                     int height) :
    RenderTarget(width, height),
    m_manager(impl->manager()),
    m_gpuSupportsTypedUAVLoadStore(
        impl->d3dCapabilities().supportsTypedUAVLoadStore)
{}

void RenderTargetD3D12::setTargetTexture(rcp<D3D12Texture> tex)
{
    if (tex && tex->resource())
    {
        D3D12_RESOURCE_DESC desc = tex->desc();
#ifdef DEBUG
        assert(desc.Width == width());
        assert(desc.Height == height());
        assert(desc.Format == DXGI_FORMAT_R8G8B8A8_UNORM ||
               desc.Format == DXGI_FORMAT_B8G8R8A8_UNORM ||
               desc.Format == DXGI_FORMAT_R8G8B8A8_TYPELESS ||
               desc.Format == DXGI_FORMAT_B8G8R8A8_TYPELESS);
#endif
        m_targetTextureSupportsUAV =
            (desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS) &&
            m_gpuSupportsTypedUAVLoadStore;
        m_targetFormat = desc.Format;
    }
    else
    {
        m_targetTextureSupportsUAV = false;
    }

    m_targetTexture = std::move(tex);
    SNAME_D3D12_OBJECT(m_targetTexture, "color");
}

void RenderTargetD3D12::setTargetTexture(ComPtr<ID3D12Resource> tex)
{
    if (tex)
    {
        D3D12_RESOURCE_DESC desc = tex->GetDesc();
#ifdef DEBUG
        assert(desc.Width == width());
        assert(desc.Height == height());
        assert(desc.Format == DXGI_FORMAT_R8G8B8A8_UNORM ||
               desc.Format == DXGI_FORMAT_B8G8R8A8_UNORM ||
               desc.Format == DXGI_FORMAT_R8G8B8A8_TYPELESS ||
               desc.Format == DXGI_FORMAT_B8G8R8A8_TYPELESS);
#endif
        m_targetTextureSupportsUAV =
            (desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS) &&
            m_gpuSupportsTypedUAVLoadStore;
        m_targetFormat = desc.Format;
    }
    else
    {
        m_targetTextureSupportsUAV = false;
    }

    m_targetTexture =
        m_manager->makeExternalTexture(tex, D3D12_RESOURCE_STATE_PRESENT);
    SNAME_D3D12_OBJECT(m_targetTexture, "colorExternal");
}

D3D12Texture* RenderTargetD3D12::offscreenTexture()
{
    if (!m_offscreenTexture)
    {
        m_offscreenTexture =
            m_manager->make2DTexture(width(),
                                     height(),
                                     1,
                                     DXGI_FORMAT_R8G8B8A8_TYPELESS,
                                     D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS,
                                     D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
                                     D3D12_HEAP_FLAG_ALLOW_SHADER_ATOMICS);
        SNAME_D3D12_OBJECT(m_offscreenTexture, "offscreenColor");
    }
    return m_offscreenTexture.get();
}

void RenderTargetD3D12::markTargetUAV(D3D12DescriptorHeap* heap)
{
    assert(m_targetTexture);
    D3D12Texture* targetTexture = m_targetTexture.get();
    if (!m_targetTextureSupportsUAV)
    {
        targetTexture = offscreenTexture();
    }

    if (auto& uavTexture =
            m_targetTextureSupportsUAV ? m_targetTexture : m_offscreenTexture)
    {
        DXGI_FORMAT targetUavFormat;
        switch (m_targetFormat)
        {
            case DXGI_FORMAT_R8G8B8A8_UNORM:
            case DXGI_FORMAT_R8G8B8A8_TYPELESS:
                targetUavFormat = m_gpuSupportsTypedUAVLoadStore
                                      ? DXGI_FORMAT_R8G8B8A8_UNORM
                                      : DXGI_FORMAT_R32_UINT;
                break;
            case DXGI_FORMAT_B8G8R8A8_UNORM:
            case DXGI_FORMAT_B8G8R8A8_TYPELESS:
                targetUavFormat = m_gpuSupportsTypedUAVLoadStore
                                      ? DXGI_FORMAT_B8G8R8A8_UNORM
                                      : DXGI_FORMAT_R32_UINT;
                break;
            default:
                RIVE_UNREACHABLE();
        }

        heap->markUavToIndex(m_manager->device(),
                             targetTexture,
                             targetUavFormat,
                             ATOMIC_COLOR_HEAP_OFFSET);
    }
}

void RenderTargetD3D12::markClipUAV(D3D12DescriptorHeap* heap)
{
    if (m_clipTexture == nullptr)
    {
        m_clipTexture =
            m_manager->make2DTexture(width(),
                                     height(),
                                     1,
                                     DXGI_FORMAT_R32_UINT,
                                     D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS,
                                     D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
        SNAME_D3D12_OBJECT(m_clipTexture, "clip");
    }
    assert(m_clipTexture);
    heap->markUavToIndex(m_manager->device(),
                         m_clipTexture.get(),
                         DXGI_FORMAT_R32_UINT,
                         ATOMIC_CLIP_HEAP_OFFSET);
}

void RenderTargetD3D12::markScratchColorUAV(D3D12DescriptorHeap* heap)
{
    // checks and makes texture
    if (!m_scratchColorTexture)
    {
        m_scratchColorTexture =
            m_manager->make2DTexture(width(),
                                     height(),
                                     1,
                                     DXGI_FORMAT_R8G8B8A8_TYPELESS,
                                     D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS,
                                     D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
                                     D3D12_HEAP_FLAG_ALLOW_SHADER_ATOMICS);
        SNAME_D3D12_OBJECT(m_scratchColorTexture, "scratchColor");
    }

    assert(m_scratchColorTexture);
    heap->markUavToIndex(m_manager->device(),
                         m_scratchColorTexture.get(),
                         m_gpuSupportsTypedUAVLoadStore
                             ? DXGI_FORMAT_R8G8B8A8_UNORM
                             : DXGI_FORMAT_R32_UINT,
                         ATOMIC_SCRATCH_COLOR_HEAP_OFFSET);
}

void RenderTargetD3D12::markCoverageUAV(D3D12DescriptorHeap* heap)
{
    if (m_coverageTexture == nullptr)
    {
        m_coverageTexture =
            m_manager->make2DTexture(width(),
                                     height(),
                                     1,
                                     DXGI_FORMAT_R32_UINT,
                                     D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS,
                                     D3D12_RESOURCE_STATE_UNORDERED_ACCESS);

        SNAME_D3D12_OBJECT(m_coverageTexture, "coverage");
    }
    heap->markUavToIndex(m_manager->device(),
                         m_coverageTexture.get(),
                         DXGI_FORMAT_R32_UINT,
                         ATOMIC_COVERAGE_HEAP_OFFSET);
}

std::unique_ptr<RenderContext> RenderContextD3D12Impl::MakeContext(
    ComPtr<ID3D12Device> device,
    ID3D12GraphicsCommandList* copyCommandList,
    const D3DContextOptions& contextOptions)
{
    D3DCapabilities d3dCapabilities;
    D3D12_FEATURE_DATA_D3D12_OPTIONS d3d12Options;

    if (SUCCEEDED(device->CheckFeatureSupport(
            D3D12_FEATURE_D3D12_OPTIONS,
            &d3d12Options,
            sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS))))
    {
        d3dCapabilities.supportsRasterizerOrderedViews =
            d3d12Options.ROVsSupported && !contextOptions.isIntelArc;
        if (d3d12Options.TypedUAVLoadAdditionalFormats)
        {
            // TypedUAVLoadAdditionalFormats is true. Now check if we can
            // both load and store all formats used by Rive (currently only
            // RGBA8 and BGRA8):
            // https://learn.microsoft.com/en-us/windows/win32/direct3d11/typed-unordered-access-view-loads.
            auto check_typed_uav_load = [device](DXGI_FORMAT format) {
                D3D12_FEATURE_DATA_FORMAT_SUPPORT d3d12Format{};
                d3d12Format.Format = format;
                if (SUCCEEDED(device->CheckFeatureSupport(
                        D3D12_FEATURE_FORMAT_SUPPORT,
                        &d3d12Format,
                        sizeof(d3d12Format))))
                {
                    constexpr UINT loadStoreFlags =
                        D3D12_FORMAT_SUPPORT2_UAV_TYPED_LOAD |
                        D3D12_FORMAT_SUPPORT2_UAV_TYPED_STORE;
                    return (d3d12Format.Support2 & loadStoreFlags) ==
                           loadStoreFlags;
                }
                return false;
            };

            d3dCapabilities.supportsTypedUAVLoadStore =
                check_typed_uav_load(DXGI_FORMAT_R8G8B8A8_UNORM) &&
                check_typed_uav_load(DXGI_FORMAT_B8G8R8A8_UNORM);

            // Check if we can use HLSL minimum precision types (e.g. min16int)
            d3dCapabilities.supportsMin16Precision =
                d3d12Options.MinPrecisionSupport &
                D3D12_SHADER_MIN_PRECISION_SUPPORT_16_BIT;
        }
    }

    if (contextOptions.disableRasterizerOrderedViews)
    {
        d3dCapabilities.supportsRasterizerOrderedViews = false;
    }
    if (contextOptions.disableTypedUAVLoadStore)
    {
        d3dCapabilities.supportsTypedUAVLoadStore = false;
    }

    d3dCapabilities.isIntel = contextOptions.isIntel;

    auto renderContextImpl = std::unique_ptr<RenderContextD3D12Impl>(
        new RenderContextD3D12Impl(device,
                                   copyCommandList,
                                   d3dCapabilities,
                                   contextOptions.shaderCompilationMode));
    return std::make_unique<RenderContext>(std::move(renderContextImpl));
}

RenderContextD3D12Impl::RenderContextD3D12Impl(
    ComPtr<ID3D12Device> device,
    ID3D12GraphicsCommandList* copyCommandList,
    const D3DCapabilities& capabilities,
    ShaderCompilationMode shaderCompilationMode) :
    m_device(device),
    m_capabilities(capabilities),
    m_pipelineManager(device, capabilities, shaderCompilationMode),
    m_resourceManager(make_rcp<D3D12ResourceManager>(device)),
    m_flushUniformBufferPool(m_resourceManager, sizeof(FlushUniforms)),
    m_imageDrawUniformBufferPool(m_resourceManager, sizeof(ImageDrawUniforms)),
    m_pathBufferPool(m_resourceManager),
    m_paintBufferPool(m_resourceManager),
    m_paintAuxBufferPool(m_resourceManager),
    m_contourBufferPool(m_resourceManager),
    m_gradSpanBufferPool(m_resourceManager),
    m_tessSpanBufferPool(m_resourceManager),
    m_triangleBufferPool(m_resourceManager),
    // this is 64kb which is the smallest heap that can be sent to gpu
    m_srvUavCbvHeapPool(m_resourceManager,
                        MAX_DESCRIPTOR_HEAPS_PER_FLUSH,
                        D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
                        D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE),
    m_cpuSrvUavCbvHeapPool(m_resourceManager,
                           NUM_SRV_UAV_HEAP_DESCRIPTORS,
                           D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
                           D3D12_DESCRIPTOR_HEAP_FLAG_NONE),
    m_samplerHeapPool(m_resourceManager,
                      MAX_DESCRIPTOR_SAMPLER_HEAPS_PER_FLUSH,
                      D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER,
                      D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE)
{

    m_platformFeatures.clipSpaceBottomUp = true;
    m_platformFeatures.framebufferBottomUp = false;
    m_platformFeatures.supportsRasterOrderingMode =
        m_capabilities.supportsRasterizerOrderedViews;
    m_platformFeatures.supportsAtomicMode = true;
    m_platformFeatures.maxTextureSize = D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION;

    m_rtvHeap = m_resourceManager->makeHeap(NUM_RTV_HEAP_DESCRIPTORS,
                                            D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
                                            D3D12_DESCRIPTOR_HEAP_FLAG_NONE);

    m_pipelineManager.compileGradientPipeline();
    m_pipelineManager.compileTesselationPipeline();
    m_pipelineManager.compileAtlasPipeline();

    m_linearSampler.Filter = D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT;
    m_linearSampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
    m_linearSampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
    m_linearSampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
    m_linearSampler.MipLODBias = 0.0f;
    m_linearSampler.MaxAnisotropy = 1;
    m_linearSampler.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;
    m_linearSampler.MinLOD = 0;
    m_linearSampler.MaxLOD = 0;

    for (int i = 0; i < ImageSampler::MAX_SAMPLER_PERMUTATIONS; ++i)
    {
        auto wrapX = ImageSampler::GetWrapXOptionFromKey(i);
        auto wrapY = ImageSampler::GetWrapYOptionFromKey(i);
        auto filter = ImageSampler::GetFilterOptionFromKey(i);

        m_imageSamplers[i].Filter = filter_for_sampler_options(filter);
        m_imageSamplers[i].AddressU = address_mode_for_sampler_option(wrapX);
        m_imageSamplers[i].AddressV = address_mode_for_sampler_option(wrapY);
        m_imageSamplers[i].AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
        m_imageSamplers[i].MipLODBias = 0.0f;
        m_imageSamplers[i].MaxAnisotropy = 1;
        m_imageSamplers[i].ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;
        m_imageSamplers[i].MinLOD = 0;
        // this should be D3D12_FLOAT32_MAX but currently we don't generate mips
        m_imageSamplers[i].MaxLOD = 0;
    }

    PatchVertex patchVertices[kPatchVertexBufferCount];
    uint16_t patchIndices[kPatchIndexBufferCount];

    GeneratePatchBufferData(patchVertices, patchIndices);

    // advance to ensure that safe frame does not equal current frame so that
    // our temp upload buffers do not get released automatically
    m_resourceManager->advanceFrameNumber(1, 0);

    m_pathPatchVertexBuffer =
        m_resourceManager->makeImmutableBuffer<>(copyCommandList,
                                                 patchVertices);
    NAME_D3D12_OBJECT(m_pathPatchVertexBuffer);
    m_pathPatchIndexBuffer =
        m_resourceManager->makeImmutableBuffer<>(copyCommandList, patchIndices);
    NAME_D3D12_OBJECT(m_pathPatchIndexBuffer);
    m_tessSpanIndexBuffer =
        m_resourceManager->makeImmutableBuffer<>(copyCommandList,
                                                 kTessSpanIndices);
    NAME_D3D12_OBJECT(m_tessSpanIndexBuffer);
    m_imageRectVertexBuffer =
        m_resourceManager->makeImmutableBuffer<>(copyCommandList,
                                                 kImageRectVertices);
    NAME_D3D12_OBJECT(m_imageRectVertexBuffer);
    m_imageRectIndexBuffer =
        m_resourceManager->makeImmutableBuffer<>(copyCommandList,
                                                 kImageRectIndices);
    NAME_D3D12_OBJECT(m_imageRectIndexBuffer);
    m_featherTexture =
        m_resourceManager->make1DTextureArray(GAUSSIAN_TABLE_SIZE,
                                              FEATHER_TEXTURE_1D_ARRAY_LENGTH,
                                              1,
                                              DXGI_FORMAT_R16_FLOAT,
                                              D3D12_RESOURCE_FLAG_NONE,
                                              D3D12_RESOURCE_STATE_COMMON);
    NAME_D3D12_OBJECT(m_featherTexture);
    auto featherUploadBuffer = m_resourceManager->makeUploadBuffer(
        sizeof(gpu::g_inverseGaussianIntegralTableF16) * 2);

    D3D12_SUBRESOURCE_DATA updateData;
    updateData.pData = g_gaussianIntegralTableF16;
    updateData.RowPitch = sizeof(g_gaussianIntegralTableF16);
    updateData.SlicePitch = updateData.RowPitch;

    UpdateSubresources(copyCommandList,
                       m_featherTexture->resource(),
                       featherUploadBuffer->resource(),
                       0,
                       0,
                       1,
                       &updateData);

    D3D12_SUBRESOURCE_DATA updateData2;
    updateData2.pData = g_inverseGaussianIntegralTableF16;
    updateData2.RowPitch = sizeof(g_inverseGaussianIntegralTableF16);
    updateData2.SlicePitch = updateData.RowPitch;

    UpdateSubresources(copyCommandList,
                       m_featherTexture->resource(),
                       featherUploadBuffer->resource(),
                       sizeof(gpu::g_gaussianIntegralTableF16),
                       1,
                       1,
                       &updateData2);
}

rcp<RenderBuffer> RenderContextD3D12Impl::makeRenderBuffer(
    RenderBufferType type,
    RenderBufferFlags flags,
    size_t size)
{
    return make_rcp<RenderBufferD3D12Impl>(m_resourceManager.get(),
                                           type,
                                           flags,
                                           size);
}

rcp<Texture> RenderContextD3D12Impl::makeImageTexture(
    uint32_t width,
    uint32_t height,
    uint32_t mipLevelCount,
    const uint8_t imageDataRGBAPremul[])
{
    return make_rcp<TextureD3D12Impl>(m_resourceManager.get(),
                                      width,
                                      height,
                                      mipLevelCount,
                                      imageDataRGBAPremul,
                                      m_usesCopyCommandList);
}

void rive::gpu::RenderContextD3D12Impl::resizeGradientTexture(uint32_t width,
                                                              uint32_t height)
{
    if (width == 0 || height == 0)
    {
        m_gradientTexture = nullptr;
    }
    else
    {
        m_gradientTexture = m_resourceManager->make2DTexture(
            width,
            height,
            1,
            DXGI_FORMAT_R8G8B8A8_UNORM,
            D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET);
        // these can be done here since they arent recycled every frame
        m_rtvHeap->markRtvToIndex(m_device.Get(),
                                  m_gradientTexture.get(),
                                  GRAD_RTV_HEAP_OFFSET);
        SNAME_D3D12_OBJECT(m_gradientTexture, "gradient");
    }
}

void rive::gpu::RenderContextD3D12Impl::resizeTessellationTexture(
    uint32_t width,
    uint32_t height)
{
    if (width == 0 || height == 0)
    {
        m_tesselationTexture = nullptr;
    }
    else
    {

        m_tesselationTexture = m_resourceManager->make2DTexture(
            width,
            height,
            1,
            DXGI_FORMAT_R32G32B32A32_UINT,
            D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET);
        // these can be done here since they arent recycled every frame
        m_rtvHeap->markRtvToIndex(m_device.Get(),
                                  m_tesselationTexture.get(),
                                  TESS_RTV_HEAP_OFFSET);
        SNAME_D3D12_OBJECT(m_tesselationTexture, "tesselation");
    }
}

void rive::gpu::RenderContextD3D12Impl::resizeAtlasTexture(uint32_t width,
                                                           uint32_t height)
{
    if (width == 0 || height == 0)
    {
        m_atlasTexture = nullptr;
    }
    else
    {

        D3D12_CLEAR_VALUE clear{DXGI_FORMAT_R32_FLOAT, {}};

        m_atlasTexture = m_resourceManager->make2DTexture(
            width,
            height,
            1,
            DXGI_FORMAT_R32_FLOAT,
            D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET,
            D3D12_RESOURCE_STATE_COMMON,
            D3D12_HEAP_FLAG_NONE,
            &clear);
        // these can be done here since they arent recycled every frame
        m_rtvHeap->markRtvToIndex(m_device.Get(),
                                  m_atlasTexture.get(),
                                  ATLAS_RTV_HEAP_OFFSET);
        SNAME_D3D12_OBJECT(m_atlasTexture, "atlas");
    }
}

void RenderContextD3D12Impl::prepareToFlush(uint64_t nextFrameNumber,
                                            uint64_t safeFrameNumber)
{
    // These should all have gotten recycled at the end of the last frame.
    assert(m_flushUniformBuffer == nullptr);
    assert(m_imageDrawUniformBuffer == nullptr);
    assert(m_pathBuffer == nullptr);
    assert(m_paintBuffer == nullptr);
    assert(m_paintAuxBuffer == nullptr);
    assert(m_contourBuffer == nullptr);
    assert(m_gradSpanBuffer == nullptr);
    assert(m_tessSpanBuffer == nullptr);
    assert(m_triangleBuffer == nullptr);

    assert(m_srvUavCbvHeap == nullptr);
    assert(m_cpuSrvUavCbvHeap == nullptr);
    assert(m_samplerHeap == nullptr);

    // Advance the context frame and delete resources that are no longer
    // referenced by in-flight command buffers.
    m_resourceManager->advanceFrameNumber(nextFrameNumber, safeFrameNumber);

    // Acquire buffers for the flush.
    m_flushUniformBuffer = m_flushUniformBufferPool.acquire();
    m_imageDrawUniformBuffer = m_imageDrawUniformBufferPool.acquire();
    m_pathBuffer = m_pathBufferPool.acquire();
    m_paintBuffer = m_paintBufferPool.acquire();
    m_paintAuxBuffer = m_paintAuxBufferPool.acquire();
    m_contourBuffer = m_contourBufferPool.acquire();
    m_gradSpanBuffer = m_gradSpanBufferPool.acquire();
    m_tessSpanBuffer = m_tessSpanBufferPool.acquire();
    m_triangleBuffer = m_triangleBufferPool.acquire();

    m_srvUavCbvHeap = m_srvUavCbvHeapPool.acquire();
    m_cpuSrvUavCbvHeap = m_cpuSrvUavCbvHeapPool.acquire();
    m_samplerHeap = m_samplerHeapPool.acquire();

    VNAME_D3D12_OBJECT(m_flushUniformBuffer);
    VNAME_D3D12_OBJECT(m_imageDrawUniformBuffer);
    VNAME_D3D12_OBJECT(m_pathBuffer);
    VNAME_D3D12_OBJECT(m_paintBuffer);
    VNAME_D3D12_OBJECT(m_paintAuxBuffer);
    VNAME_D3D12_OBJECT(m_contourBuffer);
    VNAME_D3D12_OBJECT(m_gradSpanBuffer);
    VNAME_D3D12_OBJECT(m_tessSpanBuffer);
    VNAME_D3D12_OBJECT(m_triangleBuffer);

    m_isFirstFlushOfFrame = true;

    m_heapDescriptorOffset = 0;
    m_samplerHeapDescriptorOffset = IMAGE_SAMPLER_HEAP_OFFSET;
    m_lastDynamicSampler = ImageSampler::LinearClamp();
}

void RenderContextD3D12Impl::flush(const FlushDescriptor& desc)
{
    CommandLists* commandLists =
        static_cast<CommandLists*>(desc.externalCommandBuffer);
    assert(commandLists);

    auto copyCmdList = commandLists->copyComandList;
    auto cmdList = commandLists->directComandList;
    // it's ok but less efficient to use one command list for everything
    // and a copy command list may not be guaranteed on certain platforms
    if (!copyCmdList)
    {
        copyCmdList = cmdList;
        m_usesCopyCommandList = false;
    }
    else
    {
        m_usesCopyCommandList = true;
    }

    assert(copyCmdList);
    assert(cmdList);

    // this assert doesn't take into account any potential images
    // there is no way to currently know how many we have though so we have to
    // just do this and assert the image index later
    if (m_heapDescriptorOffset + NUM_SRV_UAV_HEAP_DESCRIPTORS >=
        MAX_DESCRIPTOR_HEAPS_PER_FLUSH)
    {
        fprintf(stderr,
                "heap descriptor ran out of room ! heapOffset %i maxHeap %i\n",
                m_heapDescriptorOffset,
                MAX_DESCRIPTOR_HEAPS_PER_FLUSH);
        assert(false);
        return;
    }

    auto renderTarget = static_cast<RenderTargetD3D12*>(desc.renderTarget);

    auto width = renderTarget->width();
    auto height = renderTarget->height();

    auto targetTexture = renderTarget->targetTexture();

    // offset for where to start writing image descriptors into the heap
    // on every frame but the first, that is just the number of dynamic
    // descriptors the first frame though we need to move it the entire length
    // of descriptors over
    UINT64 imageDescriptorOffset = NUM_DYNAMIC_SRV_HEAP_DESCRIPTORS;
    if (m_isFirstFlushOfFrame)
    {
        m_isFirstFlushOfFrame = false;

        // the -1 is because this size includes the image descriptor itself
        // note we could just use IMAGE_HEAP_OFFSET_START but i thought this
        // was more readable for someone who hasn't seen this code before
        imageDescriptorOffset = (NUM_SRV_UAV_HEAP_DESCRIPTORS - 1);
        // sync everything first logical flush,
        // copies are slow, so do it all at once
        m_flushUniformBuffer->sync(copyCmdList, 0);
        m_imageDrawUniformBuffer->sync(copyCmdList, 0);
        if (desc.pathCount > 0)
        {
            m_pathBuffer->sync(copyCmdList, 0);
            m_paintBuffer->sync(copyCmdList, 0);
            m_paintAuxBuffer->sync(copyCmdList, 0);
        }
        if (desc.contourCount > 0)
        {
            m_contourBuffer->sync(copyCmdList, 0);
        }

        if (desc.tessVertexSpanCount)
        {
            m_tessSpanBuffer->sync(copyCmdList, 0);
        }

        if (desc.gradSpanCount)
        {
            m_gradSpanBuffer->sync(copyCmdList, 0);
        }

        if (desc.hasTriangleVertices)
        {
            m_triangleBuffer->sync(copyCmdList, 0);
        }

        // mark all UAVS, thse only need to happen the fist time since they
        // won't change per logical flush

        renderTarget->markClipUAV(m_cpuSrvUavCbvHeap.get());
        renderTarget->markCoverageUAV(m_cpuSrvUavCbvHeap.get());
        renderTarget->markTargetUAV(m_cpuSrvUavCbvHeap.get());
        renderTarget->markScratchColorUAV(m_cpuSrvUavCbvHeap.get());
        m_rtvHeap->markRtvToIndex(m_device.Get(),
                                  targetTexture,
                                  TARGET_RTV_HEAP_OFFSET);
        // mark all the texture srvs, these also only need to be done once per
        // flush since the texture aren't re created per logical flush
        if (m_gradientTexture)
        {
            m_cpuSrvUavCbvHeap->markSrvToIndex(m_device.Get(),
                                               m_gradientTexture.get(),
                                               GRAD_IMAGE_HEAP_OFFSET);
        }
        if (m_tesselationTexture)
        {
            m_cpuSrvUavCbvHeap->markSrvToIndex(m_device.Get(),
                                               m_tesselationTexture.get(),
                                               TESS_IMAGE_HEAP_OFFSET);
        }
        if (m_atlasTexture)
        {
            m_cpuSrvUavCbvHeap->markSrvToIndex(m_device.Get(),
                                               m_atlasTexture.get(),
                                               ATLAS_IMAGE_HEAP_OFFSET);
        }
        assert(m_featherTexture);
        m_cpuSrvUavCbvHeap->markSrvToIndex(m_device.Get(),
                                           m_featherTexture.get(),
                                           FEATHER_IMAGE_HEAP_OFFSET);

        // do this only once.
        m_pipelineManager.setRootSig(cmdList);

        ID3D12DescriptorHeap* ppHeaps[] = {m_srvUavCbvHeap->heap(),
                                           m_samplerHeap->heap()};
        // copy the static descriptors
        m_device->CopyDescriptorsSimple(
            NUM_STATIC_SRV_UAV_HEAP_DESCRIPTORS,
            m_srvUavCbvHeap->cpuHandleForUpload(
                STATIC_SRV_UAV_HEAP_DESCRIPTOPR_START),
            m_cpuSrvUavCbvHeap->cpuHandleForIndex(
                STATIC_SRV_UAV_HEAP_DESCRIPTOPR_START),
            D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

        // this is extremely expensive as it causes a fence on some hardware
        // (nvidia) so we do this only once per flush
        // some api's may potentially expose a global heap to use, if so, we
        // should use that one instead. but that will have to be addressed once
        // we start integrating
        cmdList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);

        // setup samplers, these don't change,
        // image sampler will eventually but for now keep it here
        m_samplerHeap->markSamplerToIndex(m_device.Get(),
                                          m_linearSampler,
                                          TESS_SAMPLER_HEAP_OFFSET);
        m_samplerHeap->markSamplerToIndex(m_device.Get(),
                                          m_linearSampler,
                                          GRAD_SAMPLER_HEAP_OFFSET);
        m_samplerHeap->markSamplerToIndex(m_device.Get(),
                                          m_linearSampler,
                                          FEATHER_SAMPLER_HEAP_OFFSET);
        m_samplerHeap->markSamplerToIndex(m_device.Get(),
                                          m_linearSampler,
                                          ATLAS_SAMPLER_HEAP_OFFSET);

        // this SHOULD be m_mipSampler but we don't currently generate mips
        m_samplerHeap->markSamplerToIndex(m_device.Get(),
                                          m_linearSampler,
                                          IMAGE_SAMPLER_HEAP_OFFSET);

        cmdList->SetGraphicsRootDescriptorTable(
            STATIC_SRV_SIG_INDEX,
            m_srvUavCbvHeap->gpuHandleForIndex(
                STATIC_SRV_UAV_HEAP_DESCRIPTOPR_START));

        cmdList->SetGraphicsRootDescriptorTable(
            UAV_SIG_INDEX,
            m_srvUavCbvHeap->gpuHandleForIndex(UAV_START_HEAP_INDEX));

        cmdList->SetGraphicsRootDescriptorTable(
            SAMPLER_SIG_INDEX,
            m_samplerHeap->gpuHandleForIndex(0));

        cmdList->SetGraphicsRootDescriptorTable(
            DYNAMIC_SAMPLER_SIG_INDEX,
            m_samplerHeap->gpuHandleForIndex(IMAGE_SAMPLER_HEAP_OFFSET));
    }

    // note that flush could have been left a heap but it would be pointless
    // since it would have been a heap size of 1, so instead we bind to as a
    // root constant buffer view instead
    cmdList->SetGraphicsRootConstantBufferView(
        FLUSH_UNIFORM_BUFFFER_SIG_INDEX,
        m_flushUniformBuffer->resource()->getGPUVirtualAddress() +
            desc.flushUniformDataOffsetInBytes);

    if (desc.pathCount > 0)
    {
        m_cpuSrvUavCbvHeap->markSrvToIndex(
            m_device.Get(),
            m_pathBuffer->resource(),
            PATH_BUFFER_HEAP_OFFSET,
            desc.pathCount,
            sizeof(PathData),
            StorageBufferElementSizeInBytes(PathData::kBufferStructure),
            desc.firstPath);

        m_cpuSrvUavCbvHeap->markSrvToIndex(
            m_device.Get(),
            m_paintBuffer->resource(),
            PAINT_BUFFER_HEAP_OFFSET,
            desc.pathCount,
            sizeof(PaintData),
            StorageBufferElementSizeInBytes(PaintData::kBufferStructure),
            desc.firstPaint);

        m_cpuSrvUavCbvHeap->markSrvToIndex(
            m_device.Get(),
            m_paintAuxBuffer->resource(),
            PAINT_AUX_BUFFER_HEAP_OFFSET,
            desc.pathCount,
            sizeof(PaintAuxData),
            StorageBufferElementSizeInBytes(PaintAuxData::kBufferStructure),
            desc.firstPaintAux);
    }

    if (desc.contourCount > 0)
    {
        m_cpuSrvUavCbvHeap->markSrvToIndex(
            m_device.Get(),
            m_contourBuffer->resource(),
            CONTOUR_BUFFER_HEAP_OFFSET,
            desc.contourCount,
            sizeof(ContourData),
            StorageBufferElementSizeInBytes(ContourData::kBufferStructure),
            desc.firstContour);
    }

    // copy to gpu heap
    m_device->CopyDescriptorsSimple(
        NUM_DYNAMIC_SRV_HEAP_DESCRIPTORS,
        m_srvUavCbvHeap->cpuHandleForUpload(
            m_heapDescriptorOffset + DYNAMIC_SRV_UAV_HEAP_DESCRIPTOPR_START),
        m_cpuSrvUavCbvHeap->cpuHandleForIndex(
            DYNAMIC_SRV_UAV_HEAP_DESCRIPTOPR_START),
        D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

    cmdList->SetGraphicsRootDescriptorTable(
        DYNAMIC_SRV_SIG_INDEX,
        m_srvUavCbvHeap->gpuHandleForIndex(
            m_heapDescriptorOffset + DYNAMIC_SRV_UAV_HEAP_DESCRIPTOPR_START));

    if (desc.gradSpanCount)
    {

        CD3DX12_VIEWPORT viewport(0.0f,
                                  0.0f,
                                  static_cast<float>(kGradTextureWidth),
                                  static_cast<float>(desc.gradDataHeight));
        CD3DX12_RECT scissorRect(0,
                                 0,
                                 static_cast<LONG>(kGradTextureWidth),
                                 static_cast<LONG>(desc.gradDataHeight));

        m_resourceManager->transition(cmdList,
                                      m_gradientTexture.get(),
                                      D3D12_RESOURCE_STATE_RENDER_TARGET);

        auto rtvHandle = m_rtvHeap->cpuHandleForIndex(GRAD_RTV_HEAP_OFFSET);

        cmdList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);

        cmdList->RSSetViewports(1, &viewport);
        cmdList->RSSetScissorRects(1, &scissorRect);

        auto view = m_gradSpanBuffer->resource()->vertexBufferView(
            0,
            sizeof(GradientSpan));

        cmdList->IASetVertexBuffers(0, 1, &view);

        m_pipelineManager.setGradientPipeline(cmdList);

        cmdList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);

        cmdList->SetGraphicsRoot32BitConstant(
            VERTEX_DRAW_UNIFORM_SIG_INDEX,
            math::lossless_numeric_cast<UINT>(desc.firstGradSpan),
            0);

        cmdList->DrawInstanced(
            gpu::GRAD_SPAN_TRI_STRIP_VERTEX_COUNT,
            desc.gradSpanCount,
            0,
            math::lossless_numeric_cast<UINT>(desc.firstGradSpan));

        m_resourceManager->transition(cmdList,
                                      m_gradientTexture.get(),
                                      D3D12_RESOURCE_STATE_GENERIC_READ);

        cmdList->OMSetRenderTargets(0, nullptr, FALSE, nullptr);
    }

    if (desc.tessVertexSpanCount)
    {
        m_resourceManager->transition(cmdList,
                                      m_tesselationTexture.get(),
                                      D3D12_RESOURCE_STATE_RENDER_TARGET);

        auto rtvHandle = m_rtvHeap->cpuHandleForIndex(TESS_RTV_HEAP_OFFSET);

        cmdList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);

        CD3DX12_VIEWPORT viewport(0.0f,
                                  0.0f,
                                  static_cast<float>(kTessTextureWidth),
                                  static_cast<float>(desc.tessDataHeight));
        CD3DX12_RECT scissorRect(0,
                                 0,
                                 static_cast<LONG>(kTessTextureWidth),
                                 static_cast<LONG>(desc.tessDataHeight));

        cmdList->RSSetViewports(1, &viewport);
        cmdList->RSSetScissorRects(1, &scissorRect);

        cmdList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

        auto VBView = m_tessSpanBuffer->resource()->vertexBufferView(
            sizeof(TessVertexSpan));

        auto IBView =
            m_tessSpanIndexBuffer->indexBufferView(0, sizeof(kTessSpanIndices));

        cmdList->IASetVertexBuffers(0, 1, &VBView);
        cmdList->IASetIndexBuffer(&IBView);

        m_pipelineManager.setTesselationPipeline(cmdList);

        cmdList->DrawIndexedInstanced(
            std::size(gpu::kTessSpanIndices),
            desc.tessVertexSpanCount,
            0,
            0,
            math::lossless_numeric_cast<UINT>(desc.firstTessVertexSpan));

        cmdList->OMSetRenderTargets(0, nullptr, FALSE, nullptr);

        m_resourceManager->transition(cmdList,
                                      m_tesselationTexture.get(),
                                      D3D12_RESOURCE_STATE_GENERIC_READ);

        if (m_capabilities.isIntel)
        {
            // workaround similiar to d3d11.
            // Intel needs this which is basically a
            // "VK_DEPENDENCY_BY_REGION_BIT" pipeline barrier
            const auto barrier =
                CD3DX12_RESOURCE_BARRIER::Aliasing(nullptr, nullptr);
            cmdList->ResourceBarrier(1, &barrier);
        }
    }

    D3D12_VERTEX_BUFFER_VIEW NULL_VIEW;
    NULL_VIEW.BufferLocation = 0;
    NULL_VIEW.SizeInBytes = 0;
    NULL_VIEW.StrideInBytes = 0;

    D3D12_VERTEX_BUFFER_VIEW vertexBuffers[3] = {
        m_pathPatchVertexBuffer->vertexBufferView(0,
                                                  kPatchVertexBufferCount,
                                                  sizeof(PatchVertex)),
        desc.hasTriangleVertices
            ? m_triangleBuffer->resource()->vertexBufferView(
                  0,
                  sizeof(TriangleVertex))
            : NULL_VIEW,
        m_imageRectVertexBuffer->vertexBufferView(0,
                                                  std::size(kImageRectVertices),
                                                  sizeof(ImageRectVertex))};

    static_assert(PATCH_VERTEX_DATA_SLOT == 0);
    static_assert(TRIANGLE_VERTEX_DATA_SLOT == 1);
    static_assert(IMAGE_RECT_VERTEX_DATA_SLOT == 2);
    cmdList->IASetVertexBuffers(0, 3, vertexBuffers);

    if ((desc.atlasFillBatchCount | desc.atlasStrokeBatchCount) != 0)
    {
        m_resourceManager->transition(cmdList,
                                      m_atlasTexture.get(),
                                      D3D12_RESOURCE_STATE_RENDER_TARGET);

        auto rtvHandle = m_rtvHeap->cpuHandleForIndex(ATLAS_RTV_HEAP_OFFSET);
        cmdList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);

        float clearZero[4]{};
        cmdList->ClearRenderTargetView(rtvHandle, clearZero, 0, nullptr);

        CD3DX12_VIEWPORT viewport(0.0f,
                                  0.0f,
                                  static_cast<float>(desc.atlasContentWidth),
                                  static_cast<float>(desc.atlasContentHeight));

        cmdList->RSSetViewports(1, &viewport);
        cmdList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
        auto IBV = m_pathPatchIndexBuffer->indexBufferView();
        cmdList->IASetIndexBuffer(&IBV);
        if (desc.atlasFillBatchCount != 0)
        {
            m_pipelineManager.setAtlasFillPipeline(cmdList);

            for (size_t i = 0; i < desc.atlasFillBatchCount; ++i)
            {
                const gpu::AtlasDrawBatch& fillBatch = desc.atlasFillBatches[i];
                CD3DX12_RECT scissorRect(fillBatch.scissor.left,
                                         fillBatch.scissor.top,
                                         fillBatch.scissor.right,
                                         fillBatch.scissor.bottom);
                cmdList->RSSetScissorRects(1, &scissorRect);
                cmdList->SetGraphicsRoot32BitConstant(
                    VERTEX_DRAW_UNIFORM_SIG_INDEX,
                    fillBatch.basePatch,
                    0);
                cmdList->DrawIndexedInstanced(
                    kMidpointFanCenterAAPatchIndexCount,
                    fillBatch.patchCount,
                    kMidpointFanCenterAAPatchBaseIndex,
                    0,
                    fillBatch.basePatch);
            }
        }

        if (desc.atlasStrokeBatchCount != 0)
        {
            m_pipelineManager.setAtlasStrokePipeline(cmdList);

            for (size_t i = 0; i < desc.atlasStrokeBatchCount; ++i)
            {
                const gpu::AtlasDrawBatch& strokeBatch =
                    desc.atlasStrokeBatches[i];
                CD3DX12_RECT scissorRect(strokeBatch.scissor.left,
                                         strokeBatch.scissor.top,
                                         strokeBatch.scissor.right,
                                         strokeBatch.scissor.bottom);
                cmdList->RSSetScissorRects(1, &scissorRect);
                cmdList->SetGraphicsRoot32BitConstant(
                    VERTEX_DRAW_UNIFORM_SIG_INDEX,
                    strokeBatch.basePatch,
                    0);
                cmdList->DrawIndexedInstanced(
                    gpu::kMidpointFanPatchBorderIndexCount,
                    strokeBatch.patchCount,
                    gpu::kMidpointFanPatchBaseIndex,
                    0,
                    strokeBatch.basePatch);
            }
        }
        m_resourceManager->transition(cmdList,
                                      m_atlasTexture.get(),
                                      D3D12_RESOURCE_STATE_GENERIC_READ);

        cmdList->OMSetRenderTargets(0, nullptr, FALSE, nullptr);
    }

    CD3DX12_VIEWPORT viewport(0.0f,
                              0.0f,
                              static_cast<float>(width),
                              static_cast<float>(height));
    CD3DX12_RECT scissorRect(desc.renderTargetUpdateBounds.left,
                             desc.renderTargetUpdateBounds.top,
                             desc.renderTargetUpdateBounds.right,
                             desc.renderTargetUpdateBounds.bottom);
    cmdList->RSSetViewports(1, &viewport);
    cmdList->RSSetScissorRects(1, &scissorRect);

    // Setup and clear the PLS textures.

    if (desc.fixedFunctionColorOutput)
    {
        m_resourceManager->transition(cmdList,
                                      targetTexture,
                                      D3D12_RESOURCE_STATE_RENDER_TARGET);

        auto rtvHandle = m_rtvHeap->cpuHandleForIndex(TARGET_RTV_HEAP_OFFSET);
        cmdList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);

        if (desc.colorLoadAction == gpu::LoadAction::clear)
        {
            float clearColor4f[4];
            UnpackColorToRGBA32FPremul(desc.colorClearValue, clearColor4f);
            cmdList->ClearRenderTargetView(rtvHandle, clearColor4f, 0, nullptr);
        }
    }
    else // !desc.fixedFunctionColorOutput
    {
        if (renderTarget->targetTextureSupportsUAV())
        {
            m_resourceManager->transition(
                cmdList,
                targetTexture,
                D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
        }

        if (desc.colorLoadAction == gpu::LoadAction::clear)
        {
            auto tex = renderTarget->targetTextureSupportsUAV()
                           ? renderTarget->targetTexture()->resource()
                           : renderTarget->offscreenTexture()->resource();

            if (m_capabilities.supportsTypedUAVLoadStore)
            {
                float clearColor4f[4];
                UnpackColorToRGBA32FPremul(desc.colorClearValue, clearColor4f);

                auto gpuHandle = m_srvUavCbvHeap->gpuHandleForIndex(
                    ATOMIC_COLOR_HEAP_OFFSET);
                auto cpuHandle = m_cpuSrvUavCbvHeap->cpuHandleForIndex(
                    ATOMIC_COLOR_HEAP_OFFSET);
                m_resourceManager->clearUAV(cmdList,
                                            tex,
                                            gpuHandle,
                                            cpuHandle,
                                            clearColor4f,
                                            desc.interlockMode ==
                                                InterlockMode::atomics);
            }
            else
            {
                UINT clearColorui[4] = {
                    gpu::SwizzleRiveColorToRGBAPremul(desc.colorClearValue)};

                auto gpuHandle = m_srvUavCbvHeap->gpuHandleForIndex(
                    ATOMIC_COLOR_HEAP_OFFSET);
                auto cpuHandle = m_cpuSrvUavCbvHeap->cpuHandleForIndex(
                    ATOMIC_COLOR_HEAP_OFFSET);

                m_resourceManager->clearUAV(cmdList,
                                            tex,
                                            gpuHandle,
                                            cpuHandle,
                                            clearColorui,
                                            desc.interlockMode ==
                                                InterlockMode::atomics);
            }
        }
        if (desc.colorLoadAction == gpu::LoadAction::preserveRenderTarget &&
            !renderTarget->targetTextureSupportsUAV())
        {
            auto offscreenTex = renderTarget->offscreenTexture();
            blitSubRect(cmdList,
                        offscreenTex,
                        renderTarget->targetTexture(),
                        desc.renderTargetUpdateBounds);

            m_resourceManager->transition(
                cmdList,
                offscreenTex,
                D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
        }
    }

    bool renderPassHasCoalescedResolveAndTransfer =
        desc.interlockMode == gpu::InterlockMode::atomics &&
        !desc.fixedFunctionColorOutput &&
        !renderTarget->targetTextureSupportsUAV();

    if (desc.combinedShaderFeatures & gpu::ShaderFeatures::ENABLE_CLIPPING)
    {
        constexpr static UINT kZero[4]{};
        auto gpuHandle =
            m_srvUavCbvHeap->gpuHandleForIndex(ATOMIC_CLIP_HEAP_OFFSET);
        auto cpuHandle =
            m_cpuSrvUavCbvHeap->cpuHandleForIndex(ATOMIC_CLIP_HEAP_OFFSET);
        m_resourceManager->clearUAV(cmdList,
                                    renderTarget->clip()->resource(),
                                    gpuHandle,
                                    cpuHandle,
                                    kZero,
                                    desc.interlockMode ==
                                        InterlockMode::atomics);
    }
    // always clear coverage
    {
        UINT coverageClear[4]{desc.coverageClearValue};
        auto gpuHandle =
            m_srvUavCbvHeap->gpuHandleForIndex(ATOMIC_COVERAGE_HEAP_OFFSET);
        auto cpuHandle =
            m_cpuSrvUavCbvHeap->cpuHandleForIndex(ATOMIC_COVERAGE_HEAP_OFFSET);

        m_resourceManager->clearUAV(cmdList,
                                    renderTarget->coverage()->resource(),
                                    gpuHandle,
                                    cpuHandle,
                                    coverageClear,
                                    desc.interlockMode ==
                                        InterlockMode::atomics);
    }

    if (renderPassHasCoalescedResolveAndTransfer)
    {
        m_resourceManager->transition(cmdList,
                                      targetTexture,
                                      D3D12_RESOURCE_STATE_RENDER_TARGET);

        auto rtvHandle = m_rtvHeap->cpuHandleForIndex(TARGET_RTV_HEAP_OFFSET);
        cmdList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);
    }

    m_heapDescriptorOffset += imageDescriptorOffset;

    for (const DrawBatch& batch : *desc.drawList)
    {
        assert(batch.elementCount != 0);

        DrawType drawType = batch.drawType;
        auto shaderFeatures = desc.interlockMode == gpu::InterlockMode::atomics
                                  ? desc.combinedShaderFeatures
                                  : batch.shaderFeatures;
        auto shaderMiscFlags = batch.shaderMiscFlags;
        if (drawType == gpu::DrawType::renderPassResolve &&
            renderPassHasCoalescedResolveAndTransfer)
        {
            assert(desc.interlockMode == gpu::InterlockMode::atomics);
            shaderMiscFlags |=
                gpu::ShaderMiscFlags::coalescedResolveAndTransfer;
        }

        auto* pipeline = m_pipelineManager.tryGetPipeline(
            {
                .drawType = drawType,
                .shaderFeatures = shaderFeatures,
                .interlockMode = desc.interlockMode,
                .shaderMiscFlags = shaderMiscFlags,
#ifdef WITH_RIVE_TOOLS
                .synthesizedFailureType = desc.synthesizedFailureType,
#endif
            },
            m_platformFeatures);

        // all atomic barriers are the same for dx12
        if (batch.barriers &
            (BarrierFlags::plsAtomicPreResolve | BarrierFlags::plsAtomic))
        {
            assert(desc.interlockMode == gpu::InterlockMode::atomics);
            auto target = renderTarget->targetTextureSupportsUAV()
                              ? renderTarget->targetTexture()
                              : renderTarget->offscreenTexture();

            D3D12_RESOURCE_BARRIER barriers[] = {
                CD3DX12_RESOURCE_BARRIER::UAV(
                    renderTarget->coverage()->resource()),
                CD3DX12_RESOURCE_BARRIER::UAV(target->resource()),
                CD3DX12_RESOURCE_BARRIER::UAV(
                    renderTarget->clip()->resource())};

            cmdList->ResourceBarrier(
                desc.combinedShaderFeatures &
                        gpu::ShaderFeatures::ENABLE_CLIPPING
                    ? 3
                    : 2,
                barriers);
        }

        if (pipeline == nullptr)
        {
            // There was an issue getting either the requested pipeline state or
            // its ubershader counterpart so we cannot draw anything.
            continue;
        }

        cmdList->SetPipelineState(pipeline->m_d3dPipelineState.Get());

        if (auto imageTextureD3D12 =
                static_cast<const TextureD3D12Impl*>(batch.imageTexture))
        {
            auto imageTexture =
                imageTextureD3D12->synchronize(copyCmdList,
                                               cmdList,
                                               m_resourceManager.get());
            RNAME_D3D12_OBJECT(imageTexture, m_heapDescriptorOffset);
            if (m_heapDescriptorOffset >= MAX_DESCRIPTOR_HEAPS_PER_FLUSH)
            {
                fprintf(stderr,
                        "heap descriptor ran out of room ! heapOffset %i "
                        "maxHeap %i\n",
                        m_heapDescriptorOffset,
                        MAX_DESCRIPTOR_HEAPS_PER_FLUSH);
                assert(false);
                // break out of loop and let transitions happen
                break;
            }
            m_srvUavCbvHeap->markSrvToIndex(m_device.Get(),
                                            imageTexture,
                                            m_heapDescriptorOffset);
            cmdList->SetGraphicsRootDescriptorTable(
                IMAGE_SIG_INDEX,
                m_srvUavCbvHeap->gpuHandleForIndex(m_heapDescriptorOffset));
            ++m_heapDescriptorOffset;

            // we want to update this as little as possible, so only set it if
            // it's changed
            if (m_lastDynamicSampler != batch.imageSampler)
            {
                if (++m_samplerHeapDescriptorOffset >=
                    MAX_DESCRIPTOR_SAMPLER_HEAPS_PER_FLUSH)
                {
                    auto oldHeap = m_samplerHeap;
                    m_samplerHeap = m_samplerHeapPool.acquire();
                    m_samplerHeapDescriptorOffset = IMAGE_SAMPLER_HEAP_OFFSET;
                    // copy the imutable sampelrs to the new heap
                    m_device->CopyDescriptorsSimple(
                        IMAGE_SAMPLER_HEAP_OFFSET,
                        m_samplerHeap->cpuHandleForUpload(0),
                        oldHeap->cpuHandleForUpload(0),
                        D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER);

                    ID3D12DescriptorHeap* ppHeaps[] = {m_srvUavCbvHeap->heap(),
                                                       m_samplerHeap->heap()};

                    cmdList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);
                }

                m_samplerHeap->markSamplerToIndex(
                    m_device.Get(),
                    m_imageSamplers[batch.imageSampler.asKey()],
                    m_samplerHeapDescriptorOffset);

                cmdList->SetGraphicsRootDescriptorTable(
                    DYNAMIC_SAMPLER_SIG_INDEX,
                    m_samplerHeap->gpuHandleForIndex(
                        m_samplerHeapDescriptorOffset));

                m_lastDynamicSampler = batch.imageSampler;
            }
        }

        switch (drawType)
        {
            case DrawType::midpointFanPatches:
            case DrawType::midpointFanCenterAAPatches:
            case DrawType::outerCurvePatches:
            {
                cmdList->IASetPrimitiveTopology(
                    D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
                auto IBV = m_pathPatchIndexBuffer->indexBufferView();
                cmdList->IASetIndexBuffer(&IBV);
                cmdList->SetGraphicsRoot32BitConstant(
                    VERTEX_DRAW_UNIFORM_SIG_INDEX,
                    batch.baseElement,
                    0);
                cmdList->DrawIndexedInstanced(PatchIndexCount(drawType),
                                              batch.elementCount,
                                              PatchBaseIndex(drawType),
                                              0,
                                              batch.baseElement);
                break;
            }
            case DrawType::interiorTriangulation:
            case DrawType::atlasBlit:
            {
                cmdList->IASetPrimitiveTopology(
                    D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
                cmdList->DrawInstanced(batch.elementCount,
                                       1,
                                       batch.baseElement,
                                       0);
                break;
            }
            case DrawType::imageRect:
            {
                cmdList->IASetPrimitiveTopology(
                    D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
                auto IBV = m_imageRectIndexBuffer->indexBufferView();
                cmdList->IASetIndexBuffer(&IBV);

                cmdList->SetGraphicsRootConstantBufferView(
                    IMAGE_UNIFORM_BUFFFER_SIG_INDEX,
                    m_imageDrawUniformBuffer->resource()
                            ->getGPUVirtualAddress() +
                        batch.imageDrawDataOffset);

                cmdList->DrawIndexedInstanced(std::size(gpu::kImageRectIndices),
                                              1,
                                              0,
                                              0,
                                              0);
                break;
            }
            case DrawType::imageMesh:
            {
                LITE_RTTI_CAST_OR_BREAK(vertexBuffer,
                                        RenderBufferD3D12Impl*,
                                        batch.vertexBuffer);
                LITE_RTTI_CAST_OR_BREAK(uvBuffer,
                                        RenderBufferD3D12Impl*,
                                        batch.uvBuffer);
                LITE_RTTI_CAST_OR_BREAK(indexBuffer,
                                        RenderBufferD3D12Impl*,
                                        batch.indexBuffer);

                auto vBuffer = vertexBuffer->sync(copyCmdList);
                auto uBuffer = uvBuffer->sync(copyCmdList);
                auto iBuffer = indexBuffer->sync(copyCmdList);

                D3D12_VERTEX_BUFFER_VIEW imageMeshBuffers[] = {
                    vBuffer->vertexBufferView(0, sizeof(Vec2D)),
                    uBuffer->vertexBufferView(0, sizeof(Vec2D))};
                cmdList->IASetVertexBuffers(IMAGE_MESH_VERTEX_DATA_SLOT,
                                            2,
                                            imageMeshBuffers);
                static_assert(IMAGE_MESH_UV_DATA_SLOT ==
                              IMAGE_MESH_VERTEX_DATA_SLOT + 1);
                cmdList->IASetPrimitiveTopology(
                    D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
                auto IBV = iBuffer->indexBufferView();
                cmdList->IASetIndexBuffer(&IBV);

                cmdList->SetGraphicsRootConstantBufferView(
                    IMAGE_UNIFORM_BUFFFER_SIG_INDEX,
                    m_imageDrawUniformBuffer->resource()
                            ->getGPUVirtualAddress() +
                        batch.imageDrawDataOffset);

                cmdList->DrawIndexedInstanced(batch.elementCount,
                                              1,
                                              batch.baseElement,
                                              0,
                                              0);
                break;
            }
            case DrawType::renderPassResolve:
                assert(desc.interlockMode == gpu::InterlockMode::atomics);
                cmdList->IASetPrimitiveTopology(
                    D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
                cmdList->DrawInstanced(4, 1, 0, 0);
                break;
            case DrawType::msaaStrokes:
            case DrawType::msaaMidpointFanBorrowedCoverage:
            case DrawType::msaaMidpointFans:
            case DrawType::msaaMidpointFanStencilReset:
            case DrawType::msaaMidpointFanPathsStencil:
            case DrawType::msaaMidpointFanPathsCover:
            case DrawType::msaaOuterCubics:
            case DrawType::msaaStencilClipReset:
            case DrawType::renderPassInitialize:
                RIVE_UNREACHABLE();
        }
    }

    if (desc.interlockMode == gpu::InterlockMode::rasterOrdering &&
        !renderTarget->targetTextureSupportsUAV())
    {
        // We rendered to an offscreen UAV and did not resolve to the
        // renderTarget. Copy back to the main target.
        assert(!desc.fixedFunctionColorOutput);
        assert(!renderPassHasCoalescedResolveAndTransfer);
        blitSubRect(cmdList,
                    renderTarget->targetTexture(),
                    renderTarget->offscreenTexture(),
                    desc.renderTargetUpdateBounds);

        m_resourceManager->transition(cmdList,
                                      targetTexture,
                                      D3D12_RESOURCE_STATE_COMMON);

        // we create the offscreen texture in UAV state and always assume it is
        // in it. so put it back
        m_resourceManager->transition(cmdList,
                                      renderTarget->offscreenTexture(),
                                      D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
    }

    if (desc.fixedFunctionColorOutput ||
        renderPassHasCoalescedResolveAndTransfer ||
        (renderTarget->targetTextureSupportsUAV() &&
         !desc.fixedFunctionColorOutput))
    {
        m_resourceManager->transition(cmdList,
                                      targetTexture,
                                      D3D12_RESOURCE_STATE_COMMON);
    }
}

void RenderContextD3D12Impl::postFlush(const RenderContext::FlushResources&)
{
    // Recycle buffers.
    m_flushUniformBufferPool.recycle(std::move(m_flushUniformBuffer));
    m_imageDrawUniformBufferPool.recycle(std::move(m_imageDrawUniformBuffer));
    m_pathBufferPool.recycle(std::move(m_pathBuffer));
    m_paintBufferPool.recycle(std::move(m_paintBuffer));
    m_paintAuxBufferPool.recycle(std::move(m_paintAuxBuffer));
    m_contourBufferPool.recycle(std::move(m_contourBuffer));
    m_gradSpanBufferPool.recycle(std::move(m_gradSpanBuffer));
    m_tessSpanBufferPool.recycle(std::move(m_tessSpanBuffer));
    m_triangleBufferPool.recycle(std::move(m_triangleBuffer));

    m_srvUavCbvHeapPool.recycle(std::move(m_srvUavCbvHeap));
    m_cpuSrvUavCbvHeapPool.recycle(std::move(m_cpuSrvUavCbvHeap));
    m_samplerHeapPool.recycle(std::move(m_samplerHeap));
}
}; // namespace rive::gpu
