完成代码结构优化,将渲染部分与截图部分分开。
上回说到,将gxdi截图获取的纹理渲染到指定窗口中,但是耦合性太高,渲染逻辑直接写在负责截图的类中,今天讲怎么将他们分开。
首先创建一个类负责渲染,初始化渲染需要的设备、上下文、以及对应的顶点索引与着色器
MyDx11::MyDx11(HWND _hwnd, int _windowWidth, int _windowHeight, const Vertex* _vertices, size_t _verticesSize, const UINT16* _indices, size_t _indicesSize)
{
// 创建设备及交换链
DXGI_MODE_DESC bufferDesc;
ZeroMemory(&bufferDesc, sizeof(DXGI_MODE_DESC));
bufferDesc.Width = _windowWidth;
bufferDesc.Height = _windowHeight;
bufferDesc.RefreshRate.Numerator = 0;
bufferDesc.RefreshRate.Denominator = 1;
bufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
bufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
bufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
DXGI_SWAP_CHAIN_DESC swapChainDesc;
ZeroMemory(&swapChainDesc, sizeof(DXGI_SWAP_CHAIN_DESC));
swapChainDesc.BufferDesc = bufferDesc;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = 1;
swapChainDesc.OutputWindow = _hwnd;
swapChainDesc.Windowed = TRUE;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, NULL, NULL, NULL, NULL, D3D11_SDK_VERSION, &swapChainDesc,
&this->m_swapChain, &this->m_device, NULL, &this->m_context);
// 创建呈现目标
ID3D11Texture2D* backBuffer;
this->m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&backBuffer);
this->m_device->CreateRenderTargetView(backBuffer, nullptr, &this->m_renderTargetView);
SAFE_RELEASE(backBuffer);
// 绑定呈现目标
this->m_context->OMSetRenderTargets(1, &this->m_renderTargetView, nullptr);
// 将视区数组绑定到管道的光栅器阶段
D3D11_VIEWPORT viewPort;
viewPort.TopLeftX = 0;
viewPort.TopLeftY = 0;
viewPort.Width = _windowWidth;
viewPort.Height = _windowHeight;
viewPort.MinDepth = 0.0f;
viewPort.MaxDepth = 1.0f;
this->m_context->RSSetViewports(1, &viewPort);
// 顶点缓存
D3D11_BUFFER_DESC bd = {};
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bd.ByteWidth = _verticesSize;
bd.StructureByteStride = sizeof(Vertex);
D3D11_SUBRESOURCE_DATA sd = {};
sd.pSysMem = _vertices;
this->m_device->CreateBuffer(&bd, &sd, &this->m_vertexBuffer);
// 顶点索引
D3D11_BUFFER_DESC ibd = {};
ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
ibd.ByteWidth = _indicesSize;
ibd.StructureByteStride = sizeof(UINT16);
D3D11_SUBRESOURCE_DATA isd = {};
isd.pSysMem = _indices;
this->m_device->CreateBuffer(&ibd, &isd, &this->m_indexBuffer);
this->m_indicesSize = _indicesSize / sizeof(UINT16);
// 顶点着色器
D3D11_INPUT_ELEMENT_DESC ied[] = {
{"POSITION", 0, DXGI_FORMAT::DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"TEXCOORD", 0, DXGI_FORMAT::DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}
};
this->m_device->CreateInputLayout(ied, std::size(ied), g_main_VS, sizeof(g_main_VS), &this->m_inputLayout);
this->m_device->CreateVertexShader(g_main_VS, sizeof(g_main_VS), nullptr, &this->m_vertexShader);
// 采样器
D3D11_SAMPLER_DESC samplerDesc = {};
samplerDesc.Filter = D3D11_FILTER::D3D11_FILTER_ANISOTROPIC;
samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
samplerDesc.MaxAnisotropy = 16;
this->m_device->CreateSamplerState(&samplerDesc, &this->m_sampler);
// 像素着色器
this->m_device->CreatePixelShader(g_main_PS, sizeof(g_main_PS), nullptr, &this->m_pixelShader);
// 绑定到管线
UINT stride = sizeof(Vertex);
UINT offset = 0u;
this->m_context->IASetVertexBuffers(0, 1, &this->m_vertexBuffer, &stride, &offset);
this->m_context->IASetIndexBuffer(this->m_indexBuffer, DXGI_FORMAT_R16_UINT, 0);
this->m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY::D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
this->m_context->IASetInputLayout(this->m_inputLayout);
this->m_context->VSSetShader(this->m_vertexShader, 0, 0);
this->m_context->PSSetShader(this->m_pixelShader, 0, 0);
/*ID3D11ShaderResourceView* srvs[] = { this->m_shaderResourceView };
this->m_context->PSSetShaderResources(0, 1, srvs);*/
ID3D11SamplerState* samplers[] = { this->m_sampler };
this->m_context->PSSetSamplers(0, 1, samplers);
}
qt组件采用dx渲染时,需要重载一些方法
public:
QPaintEngine* paintEngine() const { return nullptr; }
public:
void paintEvent(QPaintEvent* event);
void resizeEvent(QResizeEvent* event);
首先将paintEngine关闭,重写绘制与改变大小方法既可。
void ShowScreenWindow::initD3D()
{
delete this->m_d3d11;
const Vertex vertices[] = {
{-1, 1, 0, 0, 0},
{1, 1, 0, 1, 0},
{1, -1, 0, 1, 1},
{-1, -1, 0, 0, 1},
};
const UINT16 indices[] = {
0,1,2, 0,2,3
};
this->m_d3d11 = new MyDx11(reinterpret_cast<HWND>(this->winId()), this->width(), this->height(), vertices, sizeof(vertices), indices, sizeof(indices));
}
void ShowScreenWindow::resizeD3D()
{
this->initD3D();
}
这里只是简单写法,目前窗口给定的是不能修改大小,所以修改窗口大小的回调随便写写,不需要关心性能问题了。
杂七杂八的代码就不贴出来了,主要涉及到的是如何将纹理信息在不同设备中拷贝,根据api给出的信息,首先需要将texture中的MiscFlags属性设置为D3D11_RESOURCE_MISC_SHARED,在去到sharedHandle,根据sharedHandle获取纹理地址进行拷贝。
void MyDx11::createTexture(int _srcWidth, int _srcHeight)
{
// 创建纹理与视图
D3D11_TEXTURE2D_DESC tdesc;
ZeroMemory(&tdesc, sizeof(D3D11_TEXTURE2D_DESC));
tdesc.Width = _srcWidth;
tdesc.Height = _srcHeight;
tdesc.MipLevels = 1;
tdesc.ArraySize = 1;
tdesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
tdesc.SampleDesc.Count = 1;
tdesc.SampleDesc.Quality = 0;
tdesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
tdesc.CPUAccessFlags = 0;
tdesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED;
this->m_device->CreateTexture2D(&tdesc, nullptr, &this->m_texture);
IDXGIResource* sharedTexture = nullptr;
this->m_texture->QueryInterface(__uuidof(IDXGIResource), (void**)&sharedTexture);
sharedTexture->GetSharedHandle(&this->m_sharedHandle);
SAFE_RELEASE(sharedTexture);
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = CD3D11_SHADER_RESOURCE_VIEW_DESC(
this->m_texture,
D3D11_SRV_DIMENSION_TEXTURE2D,
DXGI_FORMAT_B8G8R8A8_UNORM
);
this->m_device->CreateShaderResourceView(this->m_texture, &srvDesc, &this->m_shaderResourceView);
ID3D11ShaderResourceView* srvs[] = { this->m_shaderResourceView };
this->m_context->PSSetShaderResources(0, 1, srvs);
}
这里拿到m_sharedHandle之后,每次需要渲染的时候,从截图类中将获取到的纹理拷贝过来
bool Monitor::copy_frame_data(HANDLE _sharedHandle, uint8_t* _buffer, size_t _size)
{
if (_sharedHandle != nullptr) {
ID3D11Texture2D* _texture = nullptr;
this->m_device->OpenSharedResource(_sharedHandle, __uuidof(ID3D11Texture2D), (void**)&_texture);
if (_texture != nullptr) {
this->m_context->CopySubresourceRegion(_texture, 0, 0, 0, 0, this->m_image, 0, 0);
// this->m_context->CopyResource(_texture, this->m_image);
}
}
D3D11_TEXTURE2D_DESC desc;
this->m_image->GetDesc(&desc);
ID3D11Texture2D* _target = nullptr;
D3D11_TEXTURE2D_DESC target_desc;
memcpy_s(&target_desc, sizeof(target_desc), &desc, sizeof(desc));
target_desc.Usage = D3D11_USAGE_STAGING;
target_desc.BindFlags = 0;
target_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
target_desc.MiscFlags = 0;
HRESULT hr = this->m_device->CreateTexture2D(&target_desc, nullptr, &_target);
if (FAILED(hr)) {
return false;
}
this->m_context->CopyResource(_target, this->m_image);
D3D11_MAPPED_SUBRESOURCE target_map_res;
UINT subresource = D3D11CalcSubresource(0, 0, 0);
hr = this->m_context->Map(_target, subresource, D3D11_MAP_READ, 0, &target_map_res);
if (FAILED(hr)) {
_target->Release();
this->m_duplication->ReleaseFrame();
return false;
}
memcpy_s(_buffer, _size, target_map_res.pData, _size);
this->m_context->Unmap(_target, subresource);
_target->Release();
this->m_duplication->ReleaseFrame();
return true;
}
OpenSharedResource负责根据sharedHandle获取可以供不同设备共享的纹理地址,拷贝过去后就可以显示了,此处调用copyResource与CopySubresourceRegion都可。下面是windows给出的解释。具体不调用flush会出现什么,这里还没看出来。
向设备授予对在不同设备上创建的共享资源的访问权限。
从 D3D9 共享到 D3D11 的纹理具有以下限制。
- 纹理必须是 2D
- 仅允许 1 个 mip 级别
- 纹理必须具有默认用法
- 纹理必须仅写入
- 不允许使用 MSAA 纹理
- 绑定标志必须设置SHADER_RESOURCE和RENDER_TARGET
- 仅允许R10G10B10A2_UNORM、R16G16B16A16_FLOAT和R8G8B8A8_UNORM格式
如果在一台设备上更新了共享纹理,则必须在该设备上调用 ID3D11DeviceContext::Flush 。