HRESULT DX12Framework::LoadMip()

in TechniqueDemos/D3D12MemoryManagement/src/Framework.cpp [1561:1940]


HRESULT DX12Framework::LoadMip(Resource* pResource, UINT32 Mip)
{
    HRESULT hr;

    LOG_MESSAGE("Loading mip %d", Mip);

    UINT32 MipHeap = Mip;

    UINT NumMips = GetResourceMipCount(pResource);
    if (Mip >= NumMips)
    {
        LOG_WARNING("Trying to load mip %d for pResource 0x%p, but the image file only contains %d mip levels", Mip, pResource, NumMips);
        return S_FALSE;
    }

    BitmapFrameInfo MipFrameInfo = {};

    ComPtr<IWICDdsDecoder> pDdsDecoder;
    ComPtr<IWICBitmapFrameDecode> pBitmapFrame;
    ComPtr<IWICDdsFrameDecode> pDdsFrame;
    ComPtr<IWICBitmapSource> pSourceBitmap;

    //
    // Different behavior is performed depending on whether this is a generated image,
    // DDS file, or other image format, since it may contain block compressed pixel data.
    //
    if (pResource->pDecoder)
    {
        hr = pResource->pDecoder->QueryInterface(IID_PPV_ARGS(&pDdsDecoder));
        if (SUCCEEDED(hr))
        {
            hr = pDdsDecoder->GetFrame(0, Mip, 0, &pBitmapFrame);
            if (FAILED(hr))
            {
                LOG_ERROR("Failed to load decode frame for Resource 0x%p, mip %d, hr=0x%.8x", pResource, Mip, hr);
                return hr;
            }

            hr = pBitmapFrame.As(&pDdsFrame);
            if (FAILED(hr))
            {
                LOG_ERROR("Failed to query DDS frame for mip %d, hr=0x%.8x", Mip, hr);
                return hr;
            }

            hr = GetDdsFrameInfo(pDdsFrame.Get(), &MipFrameInfo);
            if (FAILED(hr))
            {
                LOG_ERROR("Failed to load frame information for mip %d, hr=0x%.8x", Mip, hr);
                return hr;
            }
        }
        else
        {
            hr = pResource->pDecoder->GetFrame(0, &pBitmapFrame);
            if (FAILED(hr))
            {
                LOG_ERROR("Failed to decode bitmap for Resource 0x%p, mip %d, hr=0x%.8x", pResource, Mip, hr);
                return hr;
            }

            hr = GetBitmapFrameInfo(pBitmapFrame.Get(), &MipFrameInfo);
            if (FAILED(hr))
            {
                LOG_ERROR("Failed to load bitmap information for mip %d, hr=0x%.8x", Mip, hr);
                return hr;
            }

            //
            // Non-DDS images may need a pixel converter to convert between the source and
            // target pixel formats.
            //
            ComPtr<IWICFormatConverter> pConverter;
            hr = m_pWICFactory->CreateFormatConverter(&pConverter);
            if (FAILED(hr))
            {
                LOG_ERROR("Failed to create pixel format converter, hr=0x%.8x", hr);
                return hr;
            }

            hr = pConverter->Initialize(
                pBitmapFrame.Get(),
                MipFrameInfo.TargetPixelFormat,
                WICBitmapDitherTypeNone,
                nullptr,
                0.0f,
                WICBitmapPaletteTypeCustom);
            if (FAILED(hr))
            {
                LOG_ERROR("Failed to initialize pixel format converter, hr=0x%.8x", hr);
                return hr;
            }

            pSourceBitmap = pConverter;
        }
    }
    else
    {
        D3D12_RESOURCE_DESC resourceDesc = pResource->pDeviceState->pD3DResource->GetDesc();
        MipFrameInfo.BlockWidth = 1;
        MipFrameInfo.BlockHeight = 1;
        MipFrameInfo.DxgiFormat = resourceDesc.Format;

        MipFrameInfo.WidthInBlocks = (UINT)(resourceDesc.Width) >> Mip;
        MipFrameInfo.HeightInBlocks = resourceDesc.Height >> Mip;
    }

    UINT NumTiles;
    UINT WidthInTiles;

    //
    // Mip maps beyond NumStandardMips are packed, and so the entire set of mipmaps
    // under it is packed into a single heap.
    //
    if (MipHeap >= pResource->PackedMipHeapIndex)
    {
        MipHeap = pResource->PackedMipHeapIndex;
        WidthInTiles = pResource->PackedMipTileCount;
        NumTiles = pResource->PackedMipTileCount;
    }
    else
    {
        WidthInTiles = pResource->pDeviceState->Mips[MipHeap].Desc.WidthInTiles;
        NumTiles = WidthInTiles * pResource->pDeviceState->Mips[MipHeap].Desc.HeightInTiles;
    }

    //
    // Round up the number of heaps needed for this mip.
    //
    UINT NumHeapsInMip = ((NumTiles * TILE_SIZE) + MAX_HEAP_SIZE - 1) / MAX_HEAP_SIZE;

    //
    // Although we have a fixed heap count that is already calculated, we still calculate
    // the required size so we don't allocate more space than necessary. We do not want
    // to create 16MB heaps for mipmaps that are much smaller, or mipmaps which only
    // excced multiples of 16MB by a small amount (e.g. 33MB == 3 heaps == 16+16+1)
    //
    UINT64 RemainingSize = NumTiles * TILE_SIZE;
    ID3D12Heap** ppHeaps = pResource->pDeviceState->Mips[MipHeap].ppHeaps;
    for (UINT i = 0; i < NumHeapsInMip; ++i)
    {
        ID3D12Heap* pHeap = ppHeaps[i];
        if (pHeap == nullptr)
        {
            D3D12_HEAP_DESC heapDesc = {};

            heapDesc.SizeInBytes = min(RemainingSize, MAX_HEAP_SIZE);
            heapDesc.Alignment = 0;
            heapDesc.Properties.Type = D3D12_HEAP_TYPE_DEFAULT;
            heapDesc.Properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
            heapDesc.Properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;

            //
            // Tier 1 heaps have restrictions on the type of information that can be stored in
            // a heap. To accommodate this, we will retsrict the content to only shader resources.
            // The heap cannot store textures that are used as render targets, depth-stencil
            // output, or buffers. But this is okay, since we do not use these heaps for those
            // purposes.
            //
            heapDesc.Flags = D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES | D3D12_HEAP_FLAG_DENY_BUFFERS;

            hr = m_pDevice->CreateHeap(&heapDesc, IID_PPV_ARGS(&pHeap));
            if (FAILED(hr))
            {
                LOG_ERROR("Failed to create D3D12 heap, hr=0x%.8x", hr);
                return hr;
            }

            ppHeaps[i] = pHeap;
            RemainingSize -= heapDesc.SizeInBytes;
        }
    }

    //
    // Calculate the required size of the upload buffer for transferring the contents
    // to the new heaps.
    //
    UINT64 UploadBufferSize = GetRequiredIntermediateSize(pResource->pDeviceState->pD3DResource, Mip, 1);

    ID3D12Resource* pUploadSurface;
    void* pUploadData;

    //
    // If we are using a shared staging surface, we don't have to create a new upload
    // resource for each transfer. This feature is currently experimental, and disabled
    // by default.
    //
    ComPtr<ID3D12Resource> pUploadBuffer;
    if (m_bUseSharedStagingSurface)
    {
        pUploadSurface = m_pStagingSurface;
        pUploadData = m_pStagingSurfaceData;
    }
    else
    {
        hr = m_pDevice->CreateCommittedResource(
            &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
            D3D12_HEAP_FLAG_NONE,
            &CD3DX12_RESOURCE_DESC::Buffer(UploadBufferSize),
            D3D12_RESOURCE_STATE_GENERIC_READ,
            nullptr,
            IID_PPV_ARGS(&pUploadBuffer));
        if (FAILED(hr))
        {
            LOG_ERROR("Failed to create upload buffer, hr=0x%.8x", hr);
            return hr;
        }

        CD3DX12_RANGE readRange(0, 0);
        hr = pUploadBuffer->Map(0, &readRange, &pUploadData);
        if (FAILED(hr))
        {
            LOG_ERROR("Failed to map upload buffer, hr=0x%.8x", hr);
            return hr;
        }

        pUploadSurface = pUploadBuffer.Get();
    }

    //
    // Map the reserved resource (e.g. the virtual address we created with the resource)
    // to the heaps that back them. Page table updates for tiled resources can be costly,
    // so we will split up the mapping into one heap at a time (16MB of updates per call).
    //
    {
        static const UINT MaxTilesPerUpdate = MAX_HEAP_SIZE / TILE_SIZE;

        UINT32 NumTilesRemaining = NumTiles;

        while (NumTilesRemaining)
        {
            UINT TilesInUpdate = min(MaxTilesPerUpdate, NumTilesRemaining);

            UINT32 TileOffset = NumTiles - NumTilesRemaining;

            D3D12_TILED_RESOURCE_COORDINATE Coordinates = {};
            Coordinates.Subresource = Mip;
            Coordinates.X = TileOffset % WidthInTiles;
            Coordinates.Y = TileOffset / WidthInTiles;

            D3D12_TILE_REGION_SIZE RegionSize = {};
            RegionSize.NumTiles = TilesInUpdate;

            D3D12_TILE_RANGE_FLAGS RangeFlags = D3D12_TILE_RANGE_FLAG_NONE;
            UINT RangeOffset = 0;
            UINT RangeCount = TilesInUpdate;
            m_PagingContext.GetCommandQueue()->UpdateTileMappings(
                pResource->pDeviceState->pD3DResource,
                1,
                &Coordinates,
                &RegionSize,
                *ppHeaps,
                1,
                &RangeFlags,
                &RangeOffset,
                &RangeCount,
                D3D12_TILE_MAPPING_FLAG_NO_HAZARD);

            NumTilesRemaining -= TilesInUpdate;
            ++ppHeaps;
        }
    }

    //
    // Copy the pixel data into the staging resource, and then transfer it to the
    // reserved resource via CopyTextureRegion.
    //
    D3D12_PLACED_SUBRESOURCE_FOOTPRINT Layout;

    UINT NumRows;
    UINT64 RowSizeInBytes;
    UINT64 TotalBytes;
    D3D12_RESOURCE_DESC Desc = pResource->pDeviceState->pD3DResource->GetDesc();
    m_pDevice->GetCopyableFootprints(&Desc, Mip, 1, 0, &Layout, &NumRows, &RowSizeInBytes, &TotalBytes);
    UINT64 RemainingBytes = TotalBytes;

    UINT32 CurrentRow = 0;

    while (RemainingBytes > 0)
    {
        UINT32 MaxTransferHeightInBlocks;

        //
        // Begin a paging frame. Each paging operation (i.e. a copy/transfer) must be contained
        // within a paging frame so we can track and synchronize the operation on the context.
        //
        m_PagingContext.Begin();

        UINT64 BytesInTransfer;
        if (m_bUseSharedStagingSurface)
        {
            MaxTransferHeightInBlocks = static_cast<UINT32>(MAX_TRANSFER_SIZE / Layout.Footprint.RowPitch);
            BytesInTransfer = min(MAX_TRANSFER_SIZE, RemainingBytes);
        }
        else
        {
            MaxTransferHeightInBlocks = NumRows;
            BytesInTransfer = RemainingBytes;
        }

        UINT32 TransferHeightInBlocks = static_cast<UINT32>((BytesInTransfer + Layout.Footprint.RowPitch - 1) / Layout.Footprint.RowPitch);
        TransferHeightInBlocks = min(TransferHeightInBlocks, MaxTransferHeightInBlocks);

        UINT32 TransferHeightInRows = TransferHeightInBlocks * MipFrameInfo.BlockHeight;

        WICRect SourceRect;
        SourceRect.X = 0;
        SourceRect.Y = CurrentRow / MipFrameInfo.BlockHeight;
        SourceRect.Width = MipFrameInfo.WidthInBlocks;
        SourceRect.Height = TransferHeightInBlocks;

        //
        // The copy differs slightly based on whether or not this is a DDS file with block compressed data.
        //
        if (pDdsFrame)
        {
            hr = pDdsFrame->CopyBlocks(&SourceRect, Layout.Footprint.RowPitch, Layout.Footprint.RowPitch * TransferHeightInBlocks, ((BYTE*)pUploadData));
        }
        else if (pSourceBitmap.Get())
        {
            hr = pSourceBitmap->CopyPixels(&SourceRect, Layout.Footprint.RowPitch, Layout.Footprint.RowPitch * TransferHeightInBlocks, ((BYTE*)pUploadData));
        }
        else
        {
            hr = GenerateMip(pResource->GeneratedImageIndex, &SourceRect, Layout.Footprint.RowPitch, Layout.Footprint.RowPitch * TransferHeightInBlocks, (UINT*)pUploadData);
        }
        if (FAILED(hr))
        {
            LOG_ERROR("Failed to copy frame data to upload staging buffer, hr=0x%.8x", hr);
            return hr;
        }

        //
        // Copy the texture region on the copy command queue.
        //
        Frame* pPagingFrame = m_PagingContext.GetCurrentFrame();

        D3D12_BOX SrcBox =
        {
            0,                      // UINT left;
            0,                      // UINT top;
            0,                      // UINT front;
            MipFrameInfo.WidthInBlocks * MipFrameInfo.BlockWidth, // UINT right;
            TransferHeightInRows,   // UINT bottom;
            1,                      // UINT back;
        };

        CD3DX12_TEXTURE_COPY_LOCATION Dst(pResource->pDeviceState->pD3DResource, Mip);
        CD3DX12_TEXTURE_COPY_LOCATION Src(pUploadSurface, Layout);
        Src.PlacedFootprint.Footprint.Height = TransferHeightInRows;
        Src.PlacedFootprint.Offset = 0;
        pPagingFrame->pCommandList->CopyTextureRegion(&Dst, 0, CurrentRow, 0, &Src, &SrcBox);

        //
        // Synchronize on this transfer. An application may extend this implementation
        // to support multiple operations at a time (by allowing multiple paging frames
        // to run concurrently). However, in the interest of simplifying the concepts
        // in the sample, it was opted to serialize paging operations to make things
        // easier to understand.
        //
        hr = m_PagingContext.Execute();
        if (FAILED(hr))
        {
            LOG_WARNING("Failed to transfer content for resource 0x%p, mip %d. hr=0x%.8x", pResource, Mip, hr);
            return hr;
        }

        m_PagingContext.End();
        m_PagingContext.Flush();

        CurrentRow += TransferHeightInRows;
        RemainingBytes -= BytesInTransfer;
    }

    pResource->MostDetailedMipResident = Mip;

    AddResourceCommitment(pResource);

    return S_OK;
}