การแปลงสีจาก DXGI_FORMAT_B8G8R8A8_UNORM เป็น NV12 ใน GPU โดยใช้ตัวแปลงพิกเซล DirectX11


9

ฉันกำลังทำงานกับโค้ดเพื่อจับภาพเดสก์ท็อปโดยใช้การทำสำเนาเดสก์ท็อปและเข้ารหัสเช่นเดียวกับ h264 โดยใช้ Intel hardwareMFT ตัวเข้ารหัสยอมรับเฉพาะรูปแบบ NV12 เป็นอินพุต ฉันมีตัวแปลง DXGI_FORMAT_B8G8R8A8_UNORM เป็น NV12 ( https://github.com/NVIDIA/video-sdk-samples/blob/master/nvEncDXGIOutputDuplicationSample/Preproc.cpp ) ที่ทำงานได้ดีและเป็นไปตาม DirectXcess

ปัญหาคือ VideoProcessor บนฮาร์ดแวร์กราฟิก Intel บางรุ่นรองรับการแปลงจาก 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 ) ฉันสามารถทำให้ pixel shaders ทำงานได้ฉันยังอัปโหลดรหัสของฉันที่นี่ ( https://codeshare.io/5PJjxP ) สำหรับการอ้างอิง (ทำให้มันง่ายขึ้นมากที่สุด

ตอนนี้ฉันเหลือสองช่องทาง Chroma และ luma ตามลำดับ (พื้นผิว ID3D11Texture2D) และฉันสับสนเกี่ยวกับการบรรจุสองช่องแยกกันอย่างมีประสิทธิภาพในพื้นผิว ID3D11Texture2D หนึ่งเดียวเพื่อที่ฉันจะได้ป้อนตัวเข้ารหัสเดียวกัน มีวิธีบรรจุช่องสัญญาณ Y และ UV อย่างมีประสิทธิภาพลงใน ID3D11Texture2D เดียวใน GPU หรือไม่? ฉันรู้สึกเบื่อหน่ายกับวิธีการทำงานของ CPU เนื่องจากราคาแพงและไม่ได้ให้อัตราเฟรมที่ดีที่สุดเท่าที่จะเป็นไปได้ ที่จริงแล้วฉันลังเลที่จะคัดลอกพื้นผิวไปยัง CPU ฉันกำลังคิดวิธีที่จะทำใน GPU โดยไม่ต้องคัดลอกไปมาระหว่าง CPU และ 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 คู่?
VuVirt

ฉันแน่ใจว่าฉันไม่ได้เรียกใช้บนเครื่องที่มีการ์ดกราฟิกหลายใบ ฉันยังคงสงสัยว่าความไม่ลงรอยกันแบบนี้เกิดขึ้นได้อย่างไร ฉันจะพยายามอัปเดตรายละเอียดเพิ่มเติมในชุดข้อความนี้
Ram

คำตอบ:


5

ฉันกำลังทดลองแปลง RGBA นี้เป็น NV12 ใน GPU เท่านั้นโดยใช้ DirectX11

นี่เป็นความท้าทายที่ดี ฉันไม่คุ้นเคยกับ Directx11 ดังนั้นนี่เป็นการทดลองครั้งแรกของฉัน

ตรวจสอบโครงการนี้เพื่อรับการปรับปรุง: D3D11ShaderNV12

ในการใช้งานปัจจุบันของฉัน (อาจไม่ใช่ครั้งสุดท้าย) นี่คือสิ่งที่ฉันทำ:

  • ขั้นตอนที่ 1: ใช้ DXGI_FORMAT_B8G8R8A8_UNORM เป็นพื้นผิวอินพุต
  • ขั้นตอนที่ 2: สร้าง shader ที่ 1 เพื่อรับ 3 พื้นผิว (Y: Luma, U: ChromaCb และ V: ChromaCr): ดู YCbCrPS2.hlsl
  • ขั้นตอนที่ 3: Y คือ DXGI_FORMAT_R8_UNORM และพร้อมสำหรับพื้นผิว NV12 ขั้นสุดท้าย
  • ขั้นตอนที่ 4: UV ต้องลดลงในตัวอย่างที่สองผ่าน shader: ดู ScreenPS2.hlsl (โดยใช้การกรองเชิงเส้น)
  • ขั้นตอนที่ 5: ตัวส่งผ่านตัวที่สามเพื่อชิมพื้นผิว Y
  • ขั้นตอนที่ 6: ตัวส่งผ่านแบบที่สี่เพื่อทดสอบพื้นผิว UV โดยใช้ shift shift (ฉันคิดว่าสามารถใช้เทคนิคอื่นได้)

ShaderNV12

พื้นผิวสุดท้ายของฉันไม่ใช่ DXGI_FORMAT_NV12 แต่เป็นพื้นผิว DXGI_FORMAT_R8_UNORM ที่คล้ายกัน คอมพิวเตอร์ของฉันคือ Windows7 ดังนั้น DXGI_FORMAT_NV12 จึงไม่ได้รับการจัดการ ฉันจะลองอีกครั้งในคอมพิวเตอร์เครื่องอื่น

กระบวนการที่มีรูปภาพ:

RenderTarget


ยิ่งใหญ่ นี่คือสิ่งที่ฉันกำลังมองหา ขอบคุณ
ราม

คุณสามารถลองแทนที่การเรนเดอร์ครั้งที่สองด้วยการเรียก ID3D11DeviceContext :: GenerateMips มันนำมาใช้ในส่วนลึกของไดรเวอร์ GPU ซึ่งอาจเร็วกว่ารหัสผ่านสำหรับแสดงผลเพิ่มเติม
Soonts

ฉันไม่รู้ว่ามันเร็วกว่านี้หรือไม่ แต่ฉันเพิ่มตัวแปรเพื่อใช้ GenerateMips แทนที่จะเป็น shader มันเป็นเทคนิคที่น่าสนใจ ขอบคุณสำหรับเคล็ดลับ
mofo77
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.