Farbkonvertierung von DXGI_FORMAT_B8G8R8A8_UNORM zu NV12 in der GPU mithilfe von DirectX11-Pixel-Shadern

9

Ich arbeite an einem Code, um den Desktop mithilfe der Desktop-Duplizierung zu erfassen und ihn mithilfe von Intel hardwareMFT in h264 zu codieren. Der Encoder akzeptiert nur das NV12-Format als Eingabe. Ich habe einen Konverter von DXGI_FORMAT_B8G8R8A8_UNORM zu NV12 ( https://github.com/NVIDIA/video-sdk-samples/blob/master/nvEncDXGIOutputDuplicationSample/Preproc.cpp ), der einwandfrei funktioniert und auf DirectX Video basiert.

Das Problem ist, dass der VideoProcessor auf bestimmter Intel-Grafikhardware nur Konvertierungen von DXGI_FORMAT_B8G8R8A8_UNORM nach YUY2 unterstützt, nicht jedoch NV12. Ich habe dies durch Auflisten der unterstützten Formate über GetVideoProcessorOutputFormats bestätigt. Obwohl der VideoProcessor Blt ohne Fehler erfolgreich war und ich feststellen konnte, dass die Frames im Ausgabevideo etwas pixelig sind, konnte ich es feststellen, wenn ich es mir genauer ansah.

Ich denke, der VideoProcessor ist einfach auf das nächste unterstützte Ausgabeformat (YUY2) umgestiegen, und ich füge ihn unwissentlich dem Encoder zu, der glaubt, dass sich der Eingang in NV12 wie konfiguriert befindet. Es gibt keinen Fehler oder eine größere Beschädigung von Frames aufgrund der Tatsache, dass es kaum Unterschiede wie die Bytereihenfolge und die Unterabtastung zwischen NV12 und YUY2 gibt. Außerdem habe ich keine Pixelprobleme auf Hardware, die die NV12-Konvertierung unterstützt.

Daher habe ich mich für die Farbkonvertierung mit Pixel-Shadern entschieden, die auf diesem Code basieren ( https://github.com/bavulapati/DXGICaptureDXColorSpaceConversionIntelEncode/blob/master/DXGICaptureDXColorSpaceConversionIntelEncode/DuplicationManager.c ). Ich kann die Pixel-Shader zum Laufen bringen. Ich habe meinen Code auch hier ( https://codeshare.io/5PJjxP ) als Referenz hochgeladen (so weit wie möglich vereinfacht).

Jetzt habe ich zwei Kanäle, Chroma und Luma (ID3D11Texture2D-Texturen). Und ich bin wirklich verwirrt darüber, die beiden separaten Kanäle effizient in eine ID3D11Texture2D-Textur zu packen, damit ich dem Encoder dasselbe zuführen kann. Gibt es eine Möglichkeit, die Y- und UV-Kanäle effizient in eine einzige ID3D11Texture2D in der GPU zu packen? Ich bin es leid, CPU-basierte Ansätze zu verwenden, da diese teuer sind und nicht die bestmöglichen Bildraten bieten. Tatsächlich zögere ich es sogar, die Texturen auf die CPU zu kopieren. Ich denke über eine Möglichkeit nach, dies in der GPU ohne Hin- und Her-Kopien zwischen CPU und GPU zu tun.

Ich habe dies seit einiger Zeit ohne Fortschritte erforscht, jede Hilfe wäre dankbar.

/**
* 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;
}

Zeichnen Sie 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;
}

Init 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;
}
RAM
quelle
Dies sollte überprüft werden, aber ich denke, dass auf Hardware, auf der VideoProcessor nur auf YUY2 ausgeben kann, der Hardware-Encoder auch YUY2 akzeptiert. In diesem Fall können Sie dies überprüfen und den VideoProcessor-Ausgang direkt dem Encoder zuführen.
VuVirt
@VuVirt, ich denke auch das gleiche, aber als ich versuchte, den Hardware-Encoder mit YUY2 als Eingangstyp aufzulisten, wurde kein Encoder zurückgegeben.
Ram
Ist es möglich, dass Sie es auf einem Dual-GPU-PC versucht haben?
VuVirt
Ich bin sicher, ich verwende es nicht auf einem Computer mit mehreren Grafikkarten. Ich frage mich immer noch, wie diese Art von Inkompatibilität passieren kann. Ich werde versuchen, weitere Details in diesem Thread zu aktualisieren.
Ram

Antworten:

5

Ich experimentiere diese RGBA-Konvertierung auf NV12 nur in der GPU mit DirectX11.

Das ist eine gute Herausforderung. Ich bin nicht mit Directx11 vertraut, daher ist dies mein erstes Experiment.

Überprüfen Sie dieses Projekt auf Updates: D3D11ShaderNV12

In meiner aktuellen Implementierung (möglicherweise nicht die letzte) mache ich Folgendes:

  • Schritt 1: Verwenden Sie ein DXGI_FORMAT_B8G8R8A8_UNORM als Eingabetextur
  • Schritt 2: Erstellen Sie einen 1st-Pass-Shader, um 3 Texturen zu erhalten (Y: Luma, U: ChromaCb und V: ChromaCr): siehe YCbCrPS2.hlsl
  • Schritt 3: Y ist DXGI_FORMAT_R8_UNORM und ist bereit für die endgültige NV12-Textur
  • Schritt 4: UV muss in einem 2nd-Pass-Shader heruntergesampelt werden: siehe ScreenPS2.hlsl (unter Verwendung der linearen Filterung)
  • Schritt 5: Ein Shader für den dritten Durchgang, um die Y-Textur abzutasten
  • Schritt 6: Ein Shader für den vierten Durchgang zum Abtasten der UV-Textur mithilfe einer Shift-Textur (ich denke, eine andere Technik könnte verwendet werden).

ShaderNV12

Meine endgültige Textur ist nicht DXGI_FORMAT_NV12, sondern eine ähnliche DXGI_FORMAT_R8_UNORM-Textur. Mein Computer ist Windows7, daher wird DXGI_FORMAT_NV12 nicht behandelt. Ich werde es später auf einem anderen Computer versuchen.

Der Prozess mit Bildern:

RenderTarget

mofo77
quelle
Großartig. Genau das habe ich gesucht. Vielen Dank.
Ram
Sie können versuchen, Ihren zweiten Renderpass durch den Aufruf ID3D11DeviceContext :: GenerateMips zu ersetzen. Es ist tief im GPU-Treiber implementiert und möglicherweise schneller als ein zusätzlicher Render-Pass in Ihrem Code.
Soonts
Ich weiß nicht, ob es schneller ist oder nicht, aber ich habe eine Variante hinzugefügt, um GenerateMips anstelle des Shaders zu verwenden. Es ist eine interessante Technik. Danke für die Tipps.
mofo77