การรับข้อมูลจาก backbuffer D3D11 โดยใช้แผนที่ [ซ้ำ]

Nov 13 2020

ฉันมีปัญหาในการใช้ ID3D11DeviceContext :: Map () อย่างถูกต้อง

นี่คือฟังก์ชั่นทั้งหมดจนถึงตอนนี้:

 Color Graphics::GetPixel(int x, int y) const
{
    //Temp frame buffer descriptor;
    CD3D11_TEXTURE2D_DESC pFrameDesc;
    pFrameDesc.Width = WindowWidth;
    pFrameDesc.Height = WindowHeight;
    pFrameDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
    pFrameDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    pFrameDesc.MipLevels = 1u;
    pFrameDesc.ArraySize = 1u;
    pFrameDesc.SampleDesc.Count = 1u;
    pFrameDesc.SampleDesc.Quality = 0u;
    pFrameDesc.Usage = D3D11_USAGE_DEFAULT;
    pFrameDesc.BindFlags = D3D11_BIND_UNORDERED_ACCESS;
    pFrameDesc.MiscFlags = 0u;

    // Create temp frame buffer(2d texture)
    wrl::ComPtr<ID3D11Texture2D>pFrame = nullptr;
    HRESULT hr = pDevice->CreateTexture2D(&pFrameDesc, nullptr, &pFrame);
    GFX_THROW_INFO(hr);

    hr = pSwapChain->GetBuffer(0, __uuidof(pFrame), &pFrame);
    GFX_THROW_INFO(hr);
    
    
    D3D11_MAPPED_SUBRESOURCE map;
    map.RowPitch = WindowWidth * 4;
    map.DepthPitch = WindowHeight * 4;
    
    //Throwing here, CPU access flags problem. 
    hr = pContext->Map(pFrame.Get(), 0u, D3D11_MAP_READ, 0u, &map);
    GFX_THROW_INFO(hr);

    pContext->Unmap(pFrame.Get(), 0u);
    


    return { 0.0f, 0.4f, 0.0f, 0.0f };
}

ฉันได้รับข้อยกเว้นที่เกิดขึ้นที่ pContext-> Map ()

"ไม่สามารถเรียกแผนที่ด้วยการเข้าถึง MAP_READ เนื่องจากทรัพยากรไม่ได้สร้างขึ้นด้วยแฟล็ก D3D11_CPU_ACCESS_READ"

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

การใช้ฟังก์ชันนี้ขั้นสูงสุดคือสามารถสร้างอาร์เรย์ด้าน CPU ของวัตถุสีได้ (ปัจจุบันสีเป็นโครงสร้างของโฟลตสี่ตัว) ซึ่งฉันสามารถส่งไปยัง NewTek NDI sdk ได้ แต่ฉันรู้สึกว่ามันจะมีประโยชน์สำหรับการบันทึกภาพหน้าจอการสร้างตัวเลือกสีเป็นต้น

ยินดีต้อนรับความคิดใด ๆ !

คำตอบ

5 ChuckWalbourn Nov 14 2020 at 08:33

D3D11_CPU_ACCESS_READรหัสของคุณไม่ได้ใช้เนื้อสร้างของคุณด้วย

มันสร้างพื้นผิว:

// Create temp frame buffer(2d texture)
wrl::ComPtr<ID3D11Texture2D>pFrame = nullptr;
HRESULT hr = pDevice->CreateTexture2D(&pFrameDesc, nullptr, &pFrame);
GFX_THROW_INFO(hr);

จากนั้นจะปล่อยพื้นผิวนั้นและใช้การอ้างอิงจาก swapchain (ซึ่งไม่เคยตั้งค่าสำหรับ CPU READ):

hr = pSwapChain->GetBuffer(0, __uuidof(pFrame), &pFrame);
GFX_THROW_INFO(hr);

จากนั้นคุณลองแมปบัฟเฟอร์ swapchain:

hr = pContext->Map(pFrame.Get(), 0u, D3D11_MAP_READ, 0u, &map);
GFX_THROW_INFO(hr);
1 PandaPajama Nov 14 2020 at 15:08

คำตอบชัค Walbourn เป็นที่ถูกต้อง แต่เพื่อให้คุณบริบทอีกเล็กน้อยเนื้อคุณกำลังสร้างด้วยไม่ได้เป็นเช่นเดียวกับสิ่งที่คุณได้รับด้วยpDevice->CreateTexture2DpSwapChain->GetBuffer

ด้วยpDevice->CreateTexture2Dคุณกำลังสร้างพื้นผิวบนหน่วยความจำใหม่ล่าสุด (ซึ่งแน่นอนว่าตั้งค่าให้อ่านจาก CPU) pFrameพื้นผิวใหม่จะถูกเก็บไว้ใน

จากนั้นpSwapChain->GetBufferคุณจะได้รับบัฟเฟอร์ย้อนกลับที่มีอยู่จาก swap chain (ซึ่งไม่ได้สร้างด้วยการเข้าถึงการอ่าน CPU) สิ่งนี้ถูกเก็บไว้ในการpFrameทิ้งค่าก่อนหน้านี้อย่างมีประสิทธิภาพ

คุณมีทางเลือกสองทางที่นี่ (ฉันไม่ได้แตะ D3D11 มาหลายปีแล้วนี่เป็นไปตามความรู้คอนโซล D3D12, Vulkan และรุ่นปัจจุบันของฉัน)

ตัวเลือก Aคือการทิ้งพื้นผิวบนหน่วยความจำใหม่และตั้งค่าบัฟเฟอร์ด้านหลังในห่วงโซ่การแลกเปลี่ยนสำหรับการเข้าถึง CPU แทน ฉันไม่แนะนำให้คุณทำเช่นนี้เนื่องจากพื้นผิวที่มีไว้สำหรับการอ่านกลับของ CPU ไม่จำเป็นต้องทำงานได้ดีเท่ากับที่สร้างขึ้นโดยไม่ต้องเข้าถึง CPU

นอกจากนี้ในขณะที่คุณทำการแมปหน่วยความจำ GPU จะต้องล้างเนื้อหาของพื้นผิวและหยุดการทำงานของ GPU ทั้งหมดจนกว่าคุณจะยกเลิกการแมปหน่วยความจำ ขึ้นอยู่กับว่าคุณกำลังทำสิ่งนี้อยู่ในขั้นตอนไหนการดำเนินการนี้จะเป็นคอขวดใหญ่

อย่างไรก็ตามฉันไม่แน่ใจด้วยซ้ำว่า Direct3D จะอนุญาตให้คุณสร้าง swap chain ที่มีการเข้าถึงการอ่านของ CPU หรือไม่ดังนั้นคุณอาจไม่สามารถลองใช้งานได้

ตัวเลือก Bคือการดำเนินการวาดด้วยรูปสี่เหลี่ยมที่มีพื้นผิวแบบเต็มหน้าจอโดยที่พื้นผิวเป็นบัฟเฟอร์ด้านหลังและเป้าหมายการเรนเดอร์คือพื้นผิวที่ CPU อ่านได้ โดยทั่วไปเป็นวิธีการที่ต้องการเนื่องจากสามารถแก้ไขปัญหาทั้งหมดที่ตัวเลือก A มีโดยเสียค่าใช้จ่ายในการวาดแบบเต็มหน้าจอหนึ่งครั้ง

โปรดทราบว่าเป็นไปได้ (ฉันจำไม่ได้กับ D3D11) ที่คุณจะไม่สามารถตั้งค่าบัฟเฟอร์ด้านหลังเป็นพื้นผิวสำหรับการวาดภาพได้ ในกรณีนั้นแทนที่จะทำการเรียกเกมของคุณไปยังบัฟเฟอร์ด้านหลังโดยตรงคุณอาจต้องสร้างเป้าหมายการเรนเดอร์อื่นดึงทุกอย่างไปที่มันจากนั้นทำสำเนาสองชุด: หนึ่งชุดไปที่บัฟเฟอร์แบ็คของโซ่แลกเปลี่ยนและอีกอันหนึ่งเป็น พื้นผิวที่อ่านได้ของ CPU

วิธีนี้ซับซ้อนกว่าเล็กน้อย แต่รับประกันว่าจะใช้ได้กับ D3D12 รวมถึงแพลตฟอร์มอื่น ๆ ที่ทันสมัยที่สุด