DirectX11 piksel gölgelendiricileri kullanarak GPU'da DXGI_FORMAT_B8G8R8A8_UNORM'dan NV12'ye renk dönüşümü


9

Masaüstü çoğaltmasını kullanarak masaüstünü yakalamak ve Intel HardwareMFT kullanarak aynı h264 kodlamak için bir kod üzerinde çalışıyorum. Kodlayıcı yalnızca NV12 biçimini giriş olarak kabul eder. İyi çalışan ve DirectX VideoProcessor'a dayanan bir DXGI_FORMAT_B8G8R8A8_UNORM - NV12 dönüştürücüm var ( https://github.com/NVIDIA/video-sdk-samples/blob/master/nvEncDXGIOutputDuplicationSample/Preproc.cpp ).

Sorun şu ki, bazı intel grafik donanımındaki VideoProcessor, sadece DXGI_FORMAT_B8G8R8A8_UNORM'dan YUY2'ye dönüşümleri destekliyor ancak NV12'yi desteklemiyor, GetVideoProcessorOutputFormats aracılığıyla desteklenen formatları numaralandırarak aynı şeyi onayladım. VideoProcessor Blt herhangi bir hata olmadan başarılı olsa ve çıkış video kareleri biraz pikselli görebiliyordu, ben yakından bakarsam fark edebilir.

Sanırım, VideoProcessor sadece bir sonraki desteklenen çıkış formatına (YUY2) başarısız oldu ve bilmeden giriş yapılandırılmış olarak NV12 içinde olduğunu düşünen kodlayıcı besliyoruz. NV12 ve YUY2 arasında bayt sırası ve alt örnekleme gibi çok az fark olması nedeniyle karelerde hata veya büyük bozulma yoktur. Ayrıca, NV12 dönüşümünü destekleyen donanımda pikselleme sorunlarım yok.

Bu yüzden bu kodu temel alan piksel gölgelendiricileri kullanarak renk dönüştürme yapmaya karar verdim ( https://github.com/bavulapati/DXGICaptureDXColorSpaceConversionIntelEncode/blob/master/DXGICaptureDXColorSpaceConversionIntelEncode/DuplicationManager.cpp ). Piksel gölgelendiricilerin çalışmasını sağlayabiliyorum, ayrıca kodumu buraya da yükledim ( https://codeshare.io/5PJjxP ) (mümkün olduğunca basitleştirildi).

Şimdi sırasıyla iki kanal, chroma ve luma kaldım (ID3D11Texture2D dokular). Ve iki ayrı kanalı verimli bir şekilde bir ID3D11Texture2D dokusuna verimli bir şekilde paketlemekten gerçekten kafam karıştı, böylece aynı kodlayıcıya besleyebilirim. Y ve UV kanallarını GPU'da tek bir ID3D11Texture2D'ye verimli bir şekilde paketlemenin bir yolu var mı? Pahalı olması ve mümkün olan en iyi kare hızlarını sunmaması nedeniyle CPU tabanlı yaklaşımlardan gerçekten bıktım. Aslında, dokuları CPU'ya bile kopyalamak konusunda isteksizim. CPU ve GPU arasında ileri geri kopya olmadan GPU'da yapmanın bir yolunu düşünüyorum.

Bunu bir süredir herhangi bir ilerleme olmadan araştırıyorum, herhangi bir yardım takdir edilecektir.

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

NV12 çizin:

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

Gölgelendiricileri başlat:

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

Bu kontrol edilmelidir, ancak VideoProcessor sadece YUY2 için çıkış yapabileceği donanımda, donanım kodlayıcı YUY2 kabul edecektir düşünüyorum. Böylece, bu durumda VideoProcessor çıkışını doğrudan kodlayıcıya besleyebilirsiniz.
VuVirt

@VuVirt, ben de aynı düşünüyorum, ama girdi türü olarak YUY2 ile donanım kodlayıcı numaralandırmak için çalıştığımda hiçbir kodlayıcı döndürülmedi.
Ram

Çift bir GPU bilgisayarda denemeniz mümkün mü?
VuVirt

Eminim, birden fazla grafik kartı olan bir makinede çalıştırmıyorum. Hala bu tür uyumsuzluğun nasıl olabileceğini merak ediyorum. Bu konuda daha fazla ayrıntı güncellemeye çalışacağım.
Ram

Yanıtlar:


5

DirectX11 kullanarak, yalnızca GPU'da NV12'ye bu RGBA dönüşümünü deniyorum.

Bu iyi bir mücadeledir. Directx11'e aşina değilim, bu yüzden bu benim ilk denemem.

Bu projeyi güncellemeler için kontrol edin: D3D11ShaderNV12

Mevcut uygulamamda (son olmayabilir), işte yaptığım şey:

  • Adım 1: Giriş dokusu olarak bir DXGI_FORMAT_B8G8R8A8_UNORM kullanın
  • Adım 2: 3 doku elde etmek için 1. geçiş gölgelendirici yapın (Y: Luma, U: ChromaCb ve V: ChromaCr): bkz. YCbCrPS2.hlsl
  • Adım 3: Y, DXGI_FORMAT_R8_UNORM'dir ve son NV12 dokusu için hazırdır
  • Adım 4: UV'nin 2. geçişli bir gölgelendiricide aşağı örneklenmesi gerekir: bkz. ScreenPS2.hlsl (doğrusal filtreleme kullanarak)
  • Adım 5: Y dokusunu örneklemek için üçüncü geçiş gölgelendirici
  • Adım 6: Vardiya dokusu kullanarak UV dokusunu örneklemek için dördüncü geçişli bir gölgelendirici (diğer tekniklerin kullanılabileceğini düşünüyorum)

ShaderNV12

Son doku DXGI_FORMAT_NV12 değil, benzer bir DXGI_FORMAT_R8_UNORM dokusu. Bilgisayarım Windows7, bu nedenle DXGI_FORMAT_NV12 işlenmiyor. Daha sonra başka bir bilgisayarda deneyeceğim.

Resimlerle süreci:

RenderTarget


Harika. Tam da aradığım şey buydu. Teşekkürler.
Ram

İkinci render geçişinizi ID3D11DeviceContext :: GenerateMips çağrısı ile değiştirmeyi deneyebilirsiniz. GPU sürücüsünün derinliklerinde uygulanır, kodunuzda fazladan render geçişinden daha hızlı olabilir.
Yakında

Daha hızlı olup olmadığını bilmiyorum, ancak gölgelendirici yerine GenerateMips kullanmak için bir varyant ekledim. Bu ilginç bir teknik. İpuçları için teşekkürler.
mofo77
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.