DirectX11 픽셀 쉐이더를 사용하여 GPU에서 DXGI_FORMAT_B8G8R8A8_UNORM에서 NV12 로의 색상 변환


9

데스크탑 복제를 사용하여 데스크탑을 캡처하고 Intel hardwareMFT를 사용하여 h264로 인코딩하는 코드를 작성 중입니다. 인코더는 NV12 형식 만 입력으로 받아들입니다. DXGI_FORMAT_B8G8R8A8_UNORM to NV12 변환기 ( https://github.com/NVIDIA/video-sdk-samples/blob/master/nvEncDXGIOutputDuplicationSample/Preproc.cpp )가 DXX_FORMAT_B8G8R8A8_UNORM에 잘 작동하며 DirectX VideoProcessor를 기반으로합니다.

문제는 특정 인텔 그래픽 하드웨어의 VideoProcessor가 DXGI_FORMAT_B8G8R8A8_UNORM에서 YUY2 로의 변환 만 지원하지만 NV12에서는 지원하지 않는다는 것입니다 .GetVideoProcessorOutputFormats를 통해 지원되는 형식을 열거하여 동일한 것을 확인했습니다. VideoProcessor Blt는 오류없이 성공했지만 출력 비디오의 프레임이 약간 픽셀 화 된 것을 볼 수 있지만 자세히 살펴보면 알 수 있습니다.

VideoProcessor는 단순히 다음 지원되는 출력 형식 (YUY2)으로 장애 조치를했으며 무의식적으로 입력이 구성된대로 NV12에 있다고 생각하는 인코더에 공급하고 있습니다. NV12와 YUY2 사이의 바이트 순서 및 서브 샘플링과 같은 차이가 거의 없기 때문에 프레임이 실패하거나 크게 손상되지 않습니다. 또한 NV12 변환을 지원하는 하드웨어에서는 픽셀 화 문제가 없습니다.

그래서이 코드 ( https://github.com/bavulapati/DXGICaptureDXColorSpaceConversionIntelEncode/blob/master/DXGICaptureDXColorSpaceConversionIntelEncode/DuplicationManager.cpp )를 기반으로 한 픽셀 쉐이더를 사용하여 색상 변환을하기로 결정했습니다 . 픽셀 셰이더를 작동시킬 수 있으며 참조 용으로 코드를 업로드했습니다 ( https://codeshare.io/5PJjxP ).

이제 두 개의 채널 인 채도 및 루마가 각각 남았습니다 (ID3D11Texture2D 텍스처). 그리고 두 개의 개별 채널을 하나의 ID3D11Texture2D 텍스처로 효율적으로 패킹하여 인코더에 동일한 피드를 제공하는 것에 대해 정말로 혼란 스럽습니다. Y 및 UV 채널을 GPU에서 단일 ID3D11Texture2D로 효율적으로 포장하는 방법이 있습니까? 비용이 많이 들고 가능한 최고의 프레임 속도를 제공하지 않기 때문에 CPU 기반 접근 방식에 정말 지쳤습니다. 사실, 텍스처를 CPU에 복사조차 꺼려합니다. CPU와 GPU간에 앞뒤로 복사하지 않고 GPU에서 수행하는 방법을 생각하고 있습니다.

나는 진전없이 꽤 오랫동안 이것을 연구 해 왔으며 도움을 주시면 감사하겠습니다.

/**
* 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 그리기 :

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

초기화 셰이더 :

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

이것은 확인해야하지만 VideoProcessor가 YUY2로만 출력 할 수있는 하드웨어에서는 하드웨어 인코더가 YUY2도 허용한다고 생각합니다. 따라서이 경우이를 확인하고 VideoProcessor 출력을 인코더에 직접 공급할 수 있습니다.
VuVirt

@VuVirt도 마찬가지라고 생각하지만 입력 유형으로 YUY2가있는 하드웨어 인코더를 열거하려고하면 인코더가 반환되지 않았습니다.
Ram

듀얼 GPU PC에서 시도했을 수 있습니까?
VuVirt

여러 그래픽 카드가있는 컴퓨터에서 실행하고 있지 않습니다. 나는 이런 종류의 비 호환이 어떻게 일어날 수 있는지 아직도 궁금합니다. 이 스레드에서 자세한 내용을 업데이트하려고합니다.
Ram

답변:


5

DirectX11을 사용하여 GPU에서만이 RGBA 변환을 NV12로 실험하고 있습니다.

이것은 좋은 도전이다. Directx11에 익숙하지 않으므로 이것이 첫 번째 실험입니다.

이 프로젝트에서 업데이트를 확인하십시오. D3D11ShaderNV12

현재 구현에서 (마지막이 아닐 수도 있음) 다음은 내가하는 일입니다.

  • 1 단계 : DXGI_FORMAT_B8G8R8A8_UNORM을 입력 텍스처로 사용
  • 2 단계 : 1 패스 쉐이더를 만들어 3 개의 텍스처를 얻습니다 (Y : Luma, U : ChromaCb 및 V : ChromaCr) : YCbCrPS2.hlsl 참조
  • 3 단계 : Y는 DXGI_FORMAT_R8_UNORM이며 최종 NV12 텍스처 준비
  • 4 단계 : 2 차 패스 쉐이더에서 UV를 다운 샘플링해야합니다. ScreenPS2.hlsl (선형 필터링 사용)을 참조하십시오.
  • 5 단계 : Y 텍스처를 샘플링하는 세 번째 패스 셰이더
  • 6 단계 : 시프트 텍스처를 사용하여 UV 텍스처를 샘플링하는 네 번째 패스 셰이더 (다른 기법을 사용할 수 있다고 생각합니다)

셰이더 NV12

내 최종 텍스처는 DXGI_FORMAT_NV12가 아니라 유사한 DXGI_FORMAT_R8_UNORM 텍스처입니다. 내 컴퓨터는 Windows7이므로 DXGI_FORMAT_NV12는 처리되지 않습니다. 나중에 다른 컴퓨터에서 시도하겠습니다.

사진 처리 과정 :

RenderTarget


큰. 이것이 바로 내가 찾던 것입니다. 감사.
Ram

두 번째 렌더 패스를 ID3D11DeviceContext :: GenerateMips 호출로 교체 할 수 있습니다. GPU 드라이버 내부에 깊이 구현되어 코드의 추가 렌더링 패스보다 빠를 수 있습니다.
Soonts

그것이 더 빠른지 아닌지는 모르겠지만 셰이더 대신 GenerateMips를 사용하는 변형을 추가했습니다. 흥미로운 기술입니다. 팁 주셔서 감사합니다.
mofo77 '12
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.