Chuyển đổi màu từ DXGI_FORMAT_B8G8R8A8_UNORM sang NV12 trong GPU bằng cách sử dụng trình đổ bóng pixel DirectX11


9

Tôi đang làm việc với một mã để chụp máy tính để bàn bằng cách nhân đôi Desktop và mã hóa tương tự thành h264 bằng phần cứng IntelMFT. Bộ mã hóa chỉ chấp nhận định dạng NV12 làm đầu vào. Tôi đã có một trình chuyển đổi DXGI_FORMAT_B8G8R8A8_UNORM sang NV12 ( https://github.com/NVIDIA/video-sdk-samples/blob/master/nvEncDXGIOutputD repeatationSample / Preproc.cpp ) hoạt động tốt và dựa trên video.

Vấn đề là Bộ xử lý video trên phần cứng đồ họa intel nhất định chỉ hỗ trợ chuyển đổi từ DXGI_FORMAT_B8G8R8A8_UNORM sang YUY2 chứ không phải NV12, tôi đã xác nhận tương tự bằng cách liệt kê các định dạng được hỗ trợ thông qua GetVideoProcessorOutputFormats. Mặc dù VideoProcessor Blt đã thành công mà không có bất kỳ lỗi nào và tôi có thể thấy rằng các khung hình trong video đầu ra được pixel hóa một chút, tôi có thể nhận thấy nó nếu tôi nhìn kỹ vào nó.

Tôi đoán, VideoProcessor đơn giản đã thất bại với định dạng đầu ra được hỗ trợ tiếp theo (YUY2) và tôi vô tình cung cấp nó cho bộ mã hóa nghĩ rằng đầu vào nằm trong NV12 như được định cấu hình. Không có sự thất bại hoặc tham nhũng lớn của các khung do thực tế là có rất ít sự khác biệt như thứ tự byte và mẫu phụ giữa NV12 và YUY2. Ngoài ra, tôi không gặp vấn đề về pixel trên phần cứng hỗ trợ chuyển đổi NV12.

Vì vậy, tôi quyết định thực hiện việc chuyển đổi màu sắc sử dụng pixel shaders mà là dựa trên mã này ( https://github.com/bavulapati/DXGICaptureDXColorSpaceConversionIntelEncode/blob/master/DXGICaptureDXColorSpaceConversionIntelEncode/DuplicationManager.cpp ). Tôi có thể làm cho các trình đổ bóng pixel hoạt động, tôi cũng đã tải lên mã của mình tại đây ( https://codeshare.io/5PJjxP ) để tham khảo (đơn giản hóa nó càng nhiều càng tốt).

Bây giờ, tôi còn lại hai kênh, sắc độ và độ sáng tương ứng (kết cấu ID3D11Texture2D). Và tôi thực sự bối rối về việc đóng gói hiệu quả hai kênh riêng biệt vào một kết cấu ID3D11Texture2D để tôi có thể cung cấp cùng một bộ mã hóa. Có cách nào để đóng gói hiệu quả các kênh Y và UV vào một ID3D11Texture2D trong GPU không? Tôi thực sự mệt mỏi với các cách tiếp cận dựa trên CPU do thực tế là nó tốn kém và không cung cấp tốc độ khung hình tốt nhất có thể. Trên thực tế, tôi không muốn sao chép kết cấu vào CPU. Tôi đang nghĩ cách để làm điều đó trong GPU mà không có bất kỳ bản sao qua lại nào giữa CPU và GPU.

Tôi đã nghiên cứu điều này trong một thời gian khá dài mà không có tiến triển gì, bất kỳ trợ giúp sẽ được đánh giá cao.

/**
* This method is incomplete. It's just a template of what I want to achieve.
*/

HRESULT CreateNV12TextureFromLumaAndChromaSurface(ID3D11Texture2D** pOutputTexture)
{
    HRESULT hr = S_OK;

    try
    {
        //Copying from GPU to CPU. Bad :(
        m_pD3D11DeviceContext->CopyResource(m_CPUAccessibleLuminanceSurf, m_LuminanceSurf);

        D3D11_MAPPED_SUBRESOURCE resource;
        UINT subresource = D3D11CalcSubresource(0, 0, 0);

        HRESULT hr = m_pD3D11DeviceContext->Map(m_CPUAccessibleLuminanceSurf, subresource, D3D11_MAP_READ, 0, &resource);

        BYTE* sptr = reinterpret_cast<BYTE*>(resource.pData);
        BYTE* dptrY = nullptr; // point to the address of Y channel in output surface

        //Store Image Pitch
        int m_ImagePitch = resource.RowPitch;

        int height = GetImageHeight();
        int width = GetImageWidth();

        for (int i = 0; i < height; i++)
        {
            memcpy_s(dptrY, m_ImagePitch, sptr, m_ImagePitch);

            sptr += m_ImagePitch;
            dptrY += m_ImagePitch;
        }

        m_pD3D11DeviceContext->Unmap(m_CPUAccessibleLuminanceSurf, subresource);

        //Copying from GPU to CPU. Bad :(
        m_pD3D11DeviceContext->CopyResource(m_CPUAccessibleChrominanceSurf, m_ChrominanceSurf);
        hr = m_pD3D11DeviceContext->Map(m_CPUAccessibleChrominanceSurf, subresource, D3D11_MAP_READ, 0, &resource);

        sptr = reinterpret_cast<BYTE*>(resource.pData);
        BYTE* dptrUV = nullptr; // point to the address of UV channel in output surface

        m_ImagePitch = resource.RowPitch;
        height /= 2;
        width /= 2;

        for (int i = 0; i < height; i++)
        {
            memcpy_s(dptrUV, m_ImagePitch, sptr, m_ImagePitch);

            sptr += m_ImagePitch;
            dptrUV += m_ImagePitch;
        }

        m_pD3D11DeviceContext->Unmap(m_CPUAccessibleChrominanceSurf, subresource);
    }
    catch(HRESULT){}

    return hr;
}

Vẽ NV12:

 //
// Draw frame for NV12 texture
//
HRESULT DrawNV12Frame(ID3D11Texture2D* inputTexture)
{
    HRESULT hr;

    // If window was resized, resize swapchain
    if (!m_bIntialized)
    {
        HRESULT Ret = InitializeNV12Surfaces(inputTexture);
        if (!SUCCEEDED(Ret))
        {
            return Ret;
        }

        m_bIntialized = true;
    }

    m_pD3D11DeviceContext->CopyResource(m_ShaderResourceSurf, inputTexture);

    D3D11_TEXTURE2D_DESC FrameDesc;
    m_ShaderResourceSurf->GetDesc(&FrameDesc);

    D3D11_SHADER_RESOURCE_VIEW_DESC ShaderDesc;
    ShaderDesc.Format = FrameDesc.Format;
    ShaderDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
    ShaderDesc.Texture2D.MostDetailedMip = FrameDesc.MipLevels - 1;
    ShaderDesc.Texture2D.MipLevels = FrameDesc.MipLevels;

    // Create new shader resource view
    ID3D11ShaderResourceView* ShaderResource = nullptr;
    hr = m_pD3D11Device->CreateShaderResourceView(m_ShaderResourceSurf, &ShaderDesc, &ShaderResource);

    IF_FAILED_THROW(hr);

    m_pD3D11DeviceContext->PSSetShaderResources(0, 1, &ShaderResource);

    // Set resources
    m_pD3D11DeviceContext->OMSetRenderTargets(1, &m_pLumaRT, nullptr);
    m_pD3D11DeviceContext->PSSetShader(m_pPixelShaderLuma, nullptr, 0);
    m_pD3D11DeviceContext->RSSetViewports(1, &m_VPLuminance);

    // Draw textured quad onto render target
    m_pD3D11DeviceContext->Draw(NUMVERTICES, 0);

    m_pD3D11DeviceContext->OMSetRenderTargets(1, &m_pChromaRT, nullptr);
    m_pD3D11DeviceContext->PSSetShader(m_pPixelShaderChroma, nullptr, 0);
    m_pD3D11DeviceContext->RSSetViewports(1, &m_VPChrominance);

    // Draw textured quad onto render target
    m_pD3D11DeviceContext->Draw(NUMVERTICES, 0);

    // Release shader resource
    ShaderResource->Release();
    ShaderResource = nullptr;

    return S_OK;
}

Ban đầu shader:

void SetViewPort(D3D11_VIEWPORT* VP, UINT Width, UINT Height)
{
    VP->Width = static_cast<FLOAT>(Width);
    VP->Height = static_cast<FLOAT>(Height);
    VP->MinDepth = 0.0f;
    VP->MaxDepth = 1.0f;
    VP->TopLeftX = 0;
    VP->TopLeftY = 0;
}

HRESULT MakeRTV(ID3D11RenderTargetView** pRTV, ID3D11Texture2D* pSurf)
{
    if (*pRTV)
    {
        (*pRTV)->Release();
        *pRTV = nullptr;
    }
    // Create a render target view
    HRESULT hr = m_pD3D11Device->CreateRenderTargetView(pSurf, nullptr, pRTV);

    IF_FAILED_THROW(hr);

    return S_OK;
}

HRESULT InitializeNV12Surfaces(ID3D11Texture2D* inputTexture)
{
    ReleaseSurfaces();

    D3D11_TEXTURE2D_DESC lOutputDuplDesc;
    inputTexture->GetDesc(&lOutputDuplDesc);


    // Create shared texture for all duplication threads to draw into
    D3D11_TEXTURE2D_DESC DeskTexD;
    RtlZeroMemory(&DeskTexD, sizeof(D3D11_TEXTURE2D_DESC));
    DeskTexD.Width = lOutputDuplDesc.Width;
    DeskTexD.Height = lOutputDuplDesc.Height;
    DeskTexD.MipLevels = 1;
    DeskTexD.ArraySize = 1;
    DeskTexD.Format = lOutputDuplDesc.Format;
    DeskTexD.SampleDesc.Count = 1;
    DeskTexD.Usage = D3D11_USAGE_DEFAULT;
    DeskTexD.BindFlags = D3D11_BIND_SHADER_RESOURCE;

    HRESULT hr = m_pD3D11Device->CreateTexture2D(&DeskTexD, nullptr, &m_ShaderResourceSurf);
    IF_FAILED_THROW(hr);

    DeskTexD.Format = DXGI_FORMAT_R8_UNORM;
    DeskTexD.BindFlags = D3D11_BIND_RENDER_TARGET;

    hr = m_pD3D11Device->CreateTexture2D(&DeskTexD, nullptr, &m_LuminanceSurf);
    IF_FAILED_THROW(hr);

    DeskTexD.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
    DeskTexD.Usage = D3D11_USAGE_STAGING;
    DeskTexD.BindFlags = 0;

    hr = m_pD3D11Device->CreateTexture2D(&DeskTexD, NULL, &m_CPUAccessibleLuminanceSurf);
    IF_FAILED_THROW(hr);

    SetViewPort(&m_VPLuminance, DeskTexD.Width, DeskTexD.Height);

    HRESULT Ret = MakeRTV(&m_pLumaRT, m_LuminanceSurf);
    if (!SUCCEEDED(Ret))
        return Ret;

    DeskTexD.Width = lOutputDuplDesc.Width / 2;
    DeskTexD.Height = lOutputDuplDesc.Height / 2;
    DeskTexD.Format = DXGI_FORMAT_R8G8_UNORM;

    DeskTexD.Usage = D3D11_USAGE_DEFAULT;
    DeskTexD.CPUAccessFlags = 0;
    DeskTexD.BindFlags = D3D11_BIND_RENDER_TARGET;

    hr = m_pD3D11Device->CreateTexture2D(&DeskTexD, nullptr, &m_ChrominanceSurf);
    IF_FAILED_THROW(hr);

    DeskTexD.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
    DeskTexD.Usage = D3D11_USAGE_STAGING;
    DeskTexD.BindFlags = 0;

    hr = m_pD3D11Device->CreateTexture2D(&DeskTexD, NULL, &m_CPUAccessibleChrominanceSurf);
    IF_FAILED_THROW(hr);

    SetViewPort(&m_VPChrominance, DeskTexD.Width, DeskTexD.Height);
    return MakeRTV(&m_pChromaRT, m_ChrominanceSurf);
}

HRESULT InitVertexShader(ID3D11VertexShader** ppID3D11VertexShader)
{
    HRESULT hr = S_OK;
    UINT Size = ARRAYSIZE(g_VS);

    try
    {
        IF_FAILED_THROW(m_pD3D11Device->CreateVertexShader(g_VS, Size, NULL, ppID3D11VertexShader));;

        m_pD3D11DeviceContext->VSSetShader(m_pVertexShader, nullptr, 0);

        // Vertices for drawing whole texture
        VERTEX Vertices[NUMVERTICES] =
        {
            { XMFLOAT3(-1.0f, -1.0f, 0), XMFLOAT2(0.0f, 1.0f) },
            { XMFLOAT3(-1.0f, 1.0f, 0), XMFLOAT2(0.0f, 0.0f) },
            { XMFLOAT3(1.0f, -1.0f, 0), XMFLOAT2(1.0f, 1.0f) },
            { XMFLOAT3(1.0f, -1.0f, 0), XMFLOAT2(1.0f, 1.0f) },
            { XMFLOAT3(-1.0f, 1.0f, 0), XMFLOAT2(0.0f, 0.0f) },
            { XMFLOAT3(1.0f, 1.0f, 0), XMFLOAT2(1.0f, 0.0f) },
        };

        UINT Stride = sizeof(VERTEX);
        UINT Offset = 0;

        D3D11_BUFFER_DESC BufferDesc;
        RtlZeroMemory(&BufferDesc, sizeof(BufferDesc));
        BufferDesc.Usage = D3D11_USAGE_DEFAULT;
        BufferDesc.ByteWidth = sizeof(VERTEX) * NUMVERTICES;
        BufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
        BufferDesc.CPUAccessFlags = 0;
        D3D11_SUBRESOURCE_DATA InitData;
        RtlZeroMemory(&InitData, sizeof(InitData));
        InitData.pSysMem = Vertices;

        // Create vertex buffer
        IF_FAILED_THROW(m_pD3D11Device->CreateBuffer(&BufferDesc, &InitData, &m_VertexBuffer));

        m_pD3D11DeviceContext->IASetVertexBuffers(0, 1, &m_VertexBuffer, &Stride, &Offset);
        m_pD3D11DeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

        D3D11_INPUT_ELEMENT_DESC Layout[] =
        {
            { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
            { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 }
        };

        UINT NumElements = ARRAYSIZE(Layout);
        hr = m_pD3D11Device->CreateInputLayout(Layout, NumElements, g_VS, Size, &m_pVertexLayout);

        m_pD3D11DeviceContext->IASetInputLayout(m_pVertexLayout);
    }
    catch (HRESULT) {}

    return hr;
}

HRESULT InitPixelShaders()
{
    HRESULT hr = S_OK;
    // Refer https://codeshare.io/5PJjxP for g_PS_Y & g_PS_UV blobs
    try
    {
        UINT Size = ARRAYSIZE(g_PS_Y);
        hr = m_pD3D11Device->CreatePixelShader(g_PS_Y, Size, nullptr, &m_pPixelShaderChroma);

        IF_FAILED_THROW(hr);

        Size = ARRAYSIZE(g_PS_UV);
        hr = m_pD3D11Device->CreatePixelShader(g_PS_UV, Size, nullptr, &m_pPixelShaderLuma);

        IF_FAILED_THROW(hr);
    }
    catch (HRESULT) {}

    return hr;
}

Điều này cần được kiểm tra, nhưng tôi nghĩ rằng trên phần cứng nơi VideoProcessor chỉ có thể xuất ra YUY2, bộ mã hóa phần cứng cũng sẽ chấp nhận YUY2. Vì vậy, bạn có thể kiểm tra nó và cung cấp đầu ra VideoProcessor cho bộ mã hóa trực tiếp trong trường hợp này.
VuVirt

@VuVirt, tôi cũng nghĩ như vậy, nhưng khi tôi cố gắng liệt kê bộ mã hóa phần cứng với YUY2 làm kiểu đầu vào, tôi không nhận được bộ mã hóa nào.
Ram

Có thể là bạn đã thử trên PC GPU kép?
VuVirt

Tôi chắc chắn, tôi không chạy nó trên một máy có nhiều card đồ họa. Tôi vẫn đang tự hỏi làm thế nào loại không tương thích này có thể xảy ra. Tôi sẽ cố gắng cập nhật thêm chi tiết trong chủ đề này.
Ram

Câu trả lời:


5

Tôi đang thử nghiệm chuyển đổi RGBA này thành NV12 trong GPU, sử dụng DirectX11.

Đây là một thử thách tốt. Tôi không quen thuộc với Directx11, vì vậy đây là thử nghiệm đầu tiên của tôi.

Kiểm tra dự án này để cập nhật: D3D11ShaderNV12

Trong triển khai hiện tại của tôi (có thể không phải là lần cuối cùng), đây là những gì tôi làm:

  • Bước 1: sử dụng DXGI_FORMAT_B8G8R8A8_UNORM làm kết cấu đầu vào
  • Bước 2: tạo shader pass 1 để có 3 hoạ tiết (Y: Luma, U: ChromaCb và V: ChromaCr): xem YCbCrPS2.hlsl
  • Bước 3: Y là DXGI_FORMAT_R8_UNORM và đã sẵn sàng cho kết cấu NV12 cuối cùng
  • Bước 4: UV cần được ghép xuống trong bộ đổ bóng thứ 2: xem ScreenPS2.hlsl (sử dụng bộ lọc tuyến tính)
  • Bước 5: một shader pass thứ ba để lấy mẫu Y kết cấu
  • Bước 6: một shader pass thứ tư để lấy mẫu kết cấu UV bằng cách sử dụng kết cấu dịch chuyển (tôi nghĩ rằng kỹ thuật khác có thể được sử dụng)

ShaderNV12

Kết cấu cuối cùng của tôi không phải là DXGI_FORMAT_NV12, mà là một kết cấu DXGI_FORMAT_R8_UNORM tương tự. Máy tính của tôi là Windows7, vì vậy DXGI_FORMAT_NV12 không được xử lý. Tôi sẽ thử sau trên một máy tính khác.

Quá trình với hình ảnh:

Mục tiêu kết xuất


Tuyệt quá. Điều này thật đúng với gì mà tôi đã tìm kiếm. Cảm ơn.
Ram

Bạn có thể thử thay thế thẻ kết xuất thứ hai của mình bằng ID3D11DeviceContext :: lệnh gọi GenerateMips. Nó được triển khai sâu bên trong trình điều khiển GPU, có thể nhanh hơn vượt qua kết xuất thêm trong mã của bạn.
Soonts

Tôi không biết nó có nhanh hơn hay không, nhưng tôi đã thêm một biến thể để sử dụng GenerateMips, thay vì shader. Đó là một kỹ thuật thú vị. Cảm ơn vì những lời khuyên.
mofo77
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.