最近碰到个伪需求: 游戏串流。 游戏引擎用D3D12渲染, 再把游戏画面做视频编码, 通过网络发送到远端做解码显示。
第一反应就是走全GPU的流程, 不要用CPU把显存里的数据拷来拷去。 所以先获取渲染完的D3D12的frame buffer, 然后送给Intel MediaSDK去做编码。 查了一下MediaSDK文档, 只支持D3D11的输入buffer, 需要想办法把D3D12 Resource转换成D3D11 Texture2D。 可以试试D3D11/D3D12的Texture2D资源共享。查了一下网上的讨论的帖子 Sharing ID3D11Buffer and ID3D12Resource,微软自家的DirectX不同版本间可以用CreateSharedHandle/OpenSharedResource来互相访问。
DX12我不熟,先找个最简单的DX12 Texture的例子。我选了微软官方的例子 https://github.com/microsoft/DirectX-Graphics-Samples/tree/master/Samples/Desktop/D3D12HelloWorld/src/HelloTexture
有关的Texture2D的初始化代码在这里,m_texture就是一块黑白棋盘格样式的Texture2D,所有的初始化和像素填充都是基于D3D12的API来完成。 然后在主循环里在WM_PAINT消息OnRender()函数里把m_texture贴到一个三角形上并显示
//创建m_texture, 一块256x256 R8G8B8A8的texture2D的资源, GPU访问
// Describe and create a Texture2D.
D3D12_RESOURCE_DESC textureDesc = {};
textureDesc.MipLevels = 1;
textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
textureDesc.Width = TextureWidth;
textureDesc.Height = TextureHeight;
textureDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
textureDesc.DepthOrArraySize = 1;
textureDesc.SampleDesc.Count = 1;
textureDesc.SampleDesc.Quality = 0;
textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
ThrowIfFailed(m_device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&textureDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(&m_texture)));
const UINT64 uploadBufferSize = GetRequiredIntermediateSize(m_texture.Get(), 0, 1);
//创建textureUploadHeap, 一块CPU可以读写的staging buffer
// Create the GPU upload buffer.
ThrowIfFailed(m_device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(uploadBufferSize),
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&textureUploadHeap)));
//生成一块黑白相间的棋盘格样式的图像
// Copy data to the intermediate upload heap and then schedule a copy
// from the upload heap to the Texture2D.
std::vector<UINT8> texture = GenerateTextureData();
D3D12_SUBRESOURCE_DATA textureData = {};
textureData.pData = &texture[0];
textureData.RowPitch = TextureWidth * TexturePixelSize;
textureData.SlicePitch = textureData.RowPitch * TextureHeight;
//将棋盘格式的图像填充进textureUploadHeap里,再拷贝进m_texture内
UpdateSubresources(m_commandList.Get(), m_texture.Get(), textureUploadHeap.Get(), 0, 0, 1, &textureData);
m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_texture.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE));
运行的输出, 可以看到三角形上贴的纹理是黑白棋盘格
接下来要做的是按照Sharing ID3D11Buffer and ID3D12Resource的流程在m_texture初始化后做一个和D3D11 Texture2D的共享,并且通过D3D11的API函数修改这个m_texture的像素纹理
1. 修改创建mtexture的参数,设为可共享
- 这里需要修改D3D12_RESOURCE_DESC的Flags属性为D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS
- 同时ID3D12Device::CreateCommittedResource()的HeapFlags参数设为D3D12_HEAP_FLAG_SHARED
// Describe and create a Texture2D.
D3D12_RESOURCE_DESC textureDesc = {};
textureDesc.MipLevels = 1;
textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
textureDesc.Width = TextureWidth;
textureDesc.Height = TextureHeight;
textureDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS;
textureDesc.DepthOrArraySize = 1;
textureDesc.SampleDesc.Count = 1;
textureDesc.SampleDesc.Quality = 0;
textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
ThrowIfFailed(m_device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_SHARED,
&textureDesc,
D3D12_RESOURCE_STATE_COPY_DEST, // D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(&m_texture)));
2. 为m_texture创建一个共享句柄m_sharedTextureHandle
HRESULT hr;
HANDLE m_sharedTextureHandle;
ThrowIfFailed(hr = m_device->CreateSharedHandle(
m_texture.Get(),
nullptr,
GENERIC_ALL,
nullptr,
&m_sharedTextureHandle));
3. 创建一个新的d3d11 device和context,
static D3D_FEATURE_LEVEL FeatureLevels[] = {
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0
};
IDXGIFactory2* DxgiFactory;
IDXGIAdapter* DxgiAdapter;
DXGI_ADAPTER_DESC DxgiAdapterDesc;
ID3D11Texture2D* Texture;
ID3D11Device* D3d11Device;
D3D_FEATURE_LEVEL FeatureLevel;
ID3D11DeviceContext* D3d11DeviceContext;
hr = CreateDXGIFactory2(0, IID_PPV_ARGS(&DxgiFactory));
hr = DxgiFactory->EnumAdapters(0, &DxgiAdapter);
hr = DxgiAdapter->GetDesc(&DxgiAdapterDesc);
hr = D3D11CreateDevice(DxgiAdapter, D3D_DRIVER_TYPE_UNKNOWN, nullptr, D3D11_CREATE_DEVICE_DEBUG, FeatureLevels, 4, D3D11_SDK_VERSION, &D3d11Device, &FeatureLevel, &D3d11DeviceContext);
创建一个和m_texture一样大小的D3D11 Texture2D对象,填充成灰红蓝绿色,等下用ID3D11DeviceContext::CopyResource()来覆盖d3d12的m_texture,用来验证共享的texture2D资源是否成功
//这里需要创建一个和DX12纹理资源相同尺寸的Texture2D
//图省事,没有提取前面DX12的纹理资源的尺寸,直接定义个相同的尺寸
#define SHARED_TEXTURE_WIDTH 256
#define SHARED_TEXTURE_HEIGHT 256
CD3D11_TEXTURE2D_DESC TextureDesc(DXGI_FORMAT_R8G8B8A8_UNORM, SHARED_TEXTURE_WIDTH, SHARED_TEXTURE_HEIGHT, 1, 1);
//TextureDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED | D3D11_RESOURCE_MISC_SHARED_NTHANDLE;
unsigned long texture_data_1[SHARED_TEXTURE_WIDTH * SHARED_TEXTURE_HEIGHT];
D3D11_SUBRESOURCE_DATA Data = {};
//将texture的左上,右上,左下,右下分别填充成灰红绿蓝
for (int i = 0; i < SHARED_TEXTURE_HEIGHT; i++)
{
for (int j = 0; j < SHARED_TEXTURE_WIDTH; j++)
{
if (i >= SHARED_TEXTURE_HEIGHT / 2)
{
if (j >= SHARED_TEXTURE_WIDTH / 2)
{
texture_data_1[i * SHARED_TEXTURE_WIDTH + j] = 0xFF00FF00; //G Formet:0xAaBbGgRr
}
else
{
texture_data_1[i * SHARED_TEXTURE_WIDTH + j] = 0xFFFF0000; //B
}
}
else
{
if (j >= SHARED_TEXTURE_WIDTH / 2)
{
texture_data_1[i * SHARED_TEXTURE_WIDTH + j] = 0xFF0000FF; //R
}
else
{
texture_data_1[i * SHARED_TEXTURE_WIDTH + j] = 0xFF808080; //Gray
}
}
}
}
Data.pSysMem = texture_data_1;
Data.SysMemPitch = SHARED_TEXTURE_WIDTH * sizeof(unsigned long); //п
Data.SysMemSlicePitch = 0;
hr = D3d11Device->CreateTexture2D(&TextureDesc, &Data, &Texture);
4. 在d3d11下用m_texture的共享句柄创建一个共享设备, 对应的是D3D11Texture2D类型的SharedTexture
ID3D11Device1* pDevice1;
hr = D3d11Device->QueryInterface(__uuidof(ID3D11Device1), (void**)&pDevice1);
ComPtr<ID3D11Texture2D> SharedTexture;
hr = pDevice1->OpenSharedResource1(m_sharedTextureHandle, IID_PPV_ARGS(SharedTexture.GetAddressOf()));
5. 修改SharedTexture的内容, 对应的D3D12的m_texture的内容也同步变了。
D3d11DeviceContext->CopyResource(SharedTexture.Get(), Texture);
D3d11DeviceContext->Flush();
最后运行一下修改过的程序, 可以看到三角形上的纹理变成了我们在D3D11下面创建的灰红蓝绿的纹理,看来共享成功了
搞定收工
最后老规矩,代码奉上,仅供参考
https://gitee.com/tisandman/dx12-hello-texture/tree/master
PS:
- D3D12和D3D11下面的command queue应该是2个独立的queue, 所以操作D3D11的命令一定等D3D12的CommandList执行完了以后再执行,否则会互相影响 (我开始的时候把D3D11的所有操作放到了 D3D12 commandQueue的ExecuteCommandLists()前面了,结果所有的D3D11下的操作先执行,D3D12的操作后执行,导致对D3D11 Texture2D的所有像素改动操作都被覆盖了,查了好久代码)。话说也没有自带个靠谱的同步信号量
- 微软自家的框架之间的共享真是好弄,毕竟是同一家的东西。
- D3D12的编程思路一点不像以前版本的DirectX, 反倒是和前面的Vulkan的操作顺序很像...