之前编码数据是直接从gxdi获取的纹理中复制出来,但是参照obs的渲染模式,编码所需的数据不仅仅是屏幕图像,还可能需要如用户自定义场景(比如屏幕上方会有一些文字),包括鼠标的绘制等。因此,传入编码器的数据应该是经过渲染之后的数据,这里就要用到dx11的离屏渲染。
离屏渲染
之前一直使用的渲染方式是将数据渲染到backBuffer中,而离屏渲染指的是先将数据渲染到指定纹理中,再将纹理渲染到屏幕上。类比一下就行,原来的方式是直接在墙上作画,现在的方式是先画到一张纸上,再将纸贴到墙上。
代码改动不多,离屏渲染关键点在RenderTarget,也就是呈现的目标。我们需要做的就是,创建出来一个纹理,根据纹理创建出RenderTarget以绑定到context中,这一步保证将数据渲染到该纹理中。第二部根据纹理创建出ShaderResourceView,这一步保证该纹理将来被渲染到backBuffer中。
D3D11_TEXTURE2D_DESC tdesc;
ZeroMemory(&tdesc, sizeof(D3D11_TEXTURE2D_DESC));
tdesc.Width = _width;
tdesc.Height = _height;
tdesc.MipLevels = 1;
tdesc.ArraySize = 1;
tdesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
tdesc.SampleDesc.Count = 1;
tdesc.SampleDesc.Quality = 0;
tdesc.Usage = D3D11_USAGE_DEFAULT;
tdesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
tdesc.CPUAccessFlags = 0;
tdesc.MiscFlags = 0;
_device->CreateTexture2D(&tdesc, nullptr, this->m_texture.GetAddressOf());
需要注意的就是创建纹理时的bindFlags,需要绑定到两个阶段。
在渲染时,将该纹理的RenderTarget绑定至纹理中,接下来正常执行渲染逻辑既可。
_context->OMSetRenderTargets(1, this->m_render_target_view.GetAddressOf(), nullptr);
if (this->m_monitor_texture_draw_able != nullptr) {
this->m_monitor_texture_draw_able->draw(this);
}
if (_draw_cursor) {
if (this->m_cursor_texture_draw_able != nullptr) {
this->m_cursor_texture_draw_able->draw(this);
}
else {
this->m_cursor_texture_draw_able = new CursorTextureDrawAble(this, 32, 32, 2560.0f, 1440.0f);
this->m_cursor_texture_draw_able->draw(this);
}
}
随后将该纹理渲染至backBuffer,将RenderTarget绑定为原有背景的renderTarget,渲染逻辑与正常纹理渲染逻辑相同。
this->m_context->OMSetRenderTargets(1, &this->m_render_target_view, nullptr);
this->m_render_target_texture->bind(this->m_context);
void RenderTargerTexture::bind(ID3D11DeviceContext* _context)
{
for (auto item : *this->m_bind_able) {
item->bind(_context);
}
_context->DrawIndexed(this->m_index_size, 0u, 0u);
}
随后可以将该纹理数据保存起来,以供拷贝到编码器。