Fix streaming to multiple clients from hardware encoder on Windows (#798)

This commit is contained in:
Cameron Gutman 2023-01-18 07:47:00 -06:00 committed by GitHub
parent 49fc618682
commit c4c0413f9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 55 additions and 33 deletions

View File

@ -195,6 +195,8 @@ public:
gpu_cursor_t cursor_xor;
texture2d_t last_frame_copy;
std::atomic<uint32_t> next_image_id;
};
} // namespace platf::dxgi

View File

@ -101,16 +101,16 @@ struct img_d3d_t : public platf::img_t {
render_target_t capture_rt;
keyed_mutex_t capture_mutex;
// These objects are owned by the hwdevice_t's ID3D11Device
texture2d_t encoder_texture;
shader_res_t encoder_input_res;
keyed_mutex_t encoder_mutex;
// This is the shared handle used by hwdevice_t to open capture_texture
HANDLE encoder_texture_handle = {};
// Set to true if the image corresponds to a dummy texture used prior to
// the first successful capture of a desktop frame
bool dummy = false;
// Unique identifier for this image
uint32_t id = 0;
virtual ~img_d3d_t() override {
if(encoder_texture_handle) {
CloseHandle(encoder_texture_handle);
@ -300,15 +300,16 @@ blob_t compile_vertex_shader(LPCSTR file) {
class hwdevice_t : public platf::hwdevice_t {
public:
int convert(platf::img_t &img_base) override {
auto &img = (img_d3d_t &)img_base;
auto &img = (img_d3d_t &)img_base;
auto &img_ctx = img_ctx_map[img.id];
// Open the shared capture texture with our ID3D11Device
if(share_img(&img_base)) {
if(initialize_image_context(img, img_ctx)) {
return -1;
}
// Acquire encoder mutex to synchronize with capture code
auto status = img.encoder_mutex->AcquireSync(0, INFINITE);
auto status = img_ctx.encoder_mutex->AcquireSync(0, INFINITE);
if(status != S_OK) {
BOOST_LOG(error) << "Failed to acquire encoder mutex [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
@ -318,7 +319,7 @@ public:
device_ctx->VSSetShader(scene_vs.get(), nullptr, 0);
device_ctx->PSSetShader(convert_Y_ps.get(), nullptr, 0);
device_ctx->RSSetViewports(1, &outY_view);
device_ctx->PSSetShaderResources(0, 1, &img.encoder_input_res);
device_ctx->PSSetShaderResources(0, 1, &img_ctx.encoder_input_res);
device_ctx->Draw(3, 0);
// Artifacts start appearing on the rendered image if Sunshine doesn't flush
@ -332,7 +333,7 @@ public:
device_ctx->Draw(3, 0);
// Release encoder mutex to allow capture code to reuse this image
img.encoder_mutex->ReleaseSync(0);
img_ctx.encoder_mutex->ReleaseSync(0);
return 0;
}
@ -439,8 +440,8 @@ public:
auto out_width = frame->width;
auto out_height = frame->height;
float in_width = img.display->width;
float in_height = img.display->height;
float in_width = display->width;
float in_height = display->height;
// Ensure aspect ratio is maintained
auto scalar = std::fminf(out_width / in_width, out_height / in_height);
@ -456,13 +457,7 @@ public:
// The underlying frame pool owns the texture, so we must reference it for ourselves
frame_texture->AddRef();
img.encoder_texture.reset(frame_texture);
img.width = out_width;
img.height = out_height;
img.data = (std::uint8_t *)img.encoder_texture.get();
img.row_pitch = out_width * 4;
img.pixel_pitch = 4;
hwframe_texture.reset(frame_texture);
float info_in[16 / sizeof(float)] { 1.0f / (float)out_width_f }; //aligned to 16-byte
info_scene = make_buffer(device.get(), info_in);
@ -477,7 +472,7 @@ public:
D3D11_RTV_DIMENSION_TEXTURE2D
};
auto status = device->CreateRenderTargetView(img.encoder_texture.get(), &nv12_rt_desc, &nv12_Y_rt);
auto status = device->CreateRenderTargetView(hwframe_texture.get(), &nv12_rt_desc, &nv12_Y_rt);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
@ -485,7 +480,7 @@ public:
nv12_rt_desc.Format = (format == DXGI_FORMAT_P010) ? DXGI_FORMAT_R16G16_UNORM : DXGI_FORMAT_R8G8_UNORM;
status = device->CreateRenderTargetView(img.encoder_texture.get(), &nv12_rt_desc, &nv12_UV_rt);
status = device->CreateRenderTargetView(hwframe_texture.get(), &nv12_rt_desc, &nv12_UV_rt);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
@ -590,7 +585,7 @@ public:
convert_UV_vs_hlsl->GetBufferPointer(), convert_UV_vs_hlsl->GetBufferSize(),
&input_layout);
img.display = std::move(display);
this->display = std::move(display);
blend_disable = make_blend(device.get(), false, false);
if(!blend_disable) {
@ -624,14 +619,33 @@ public:
}
private:
int share_img(platf::img_t *img_base) {
auto img = (img_d3d_t *)img_base;
struct encoder_img_ctx_t {
// Used to determine if the underlying texture changes.
// Not safe for actual use by the encoder!
texture2d_t::pointer capture_texture_p;
texture2d_t encoder_texture;
shader_res_t encoder_input_res;
keyed_mutex_t encoder_mutex;
void reset() {
capture_texture_p = nullptr;
encoder_texture.reset();
encoder_input_res.reset();
encoder_mutex.reset();
}
};
int initialize_image_context(const img_d3d_t &img, encoder_img_ctx_t &img_ctx) {
// If we've already opened the shared texture, we're done
if(img->encoder_texture) {
if(img_ctx.encoder_texture && img.capture_texture.get() == img_ctx.capture_texture_p) {
return 0;
}
// Reset this image context in case it was used before with a different texture.
// Textures can change when transitioning from a dummy image to a real image.
img_ctx.reset();
device1_t device1;
auto status = device->QueryInterface(__uuidof(ID3D11Device1), (void **)&device1);
if(FAILED(status)) {
@ -640,26 +654,27 @@ private:
}
// Open a handle to the shared texture
status = device1->OpenSharedResource1(img->encoder_texture_handle, __uuidof(ID3D11Texture2D), (void **)&img->encoder_texture);
status = device1->OpenSharedResource1(img.encoder_texture_handle, __uuidof(ID3D11Texture2D), (void **)&img_ctx.encoder_texture);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to open shared image texture [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
// Get the keyed mutex to synchronize with the capture code
status = img->encoder_texture->QueryInterface(__uuidof(IDXGIKeyedMutex), (void **)&img->encoder_mutex);
status = img_ctx.encoder_texture->QueryInterface(__uuidof(IDXGIKeyedMutex), (void **)&img_ctx.encoder_mutex);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to query IDXGIKeyedMutex [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
// Create the SRV for the encoder texture
status = device->CreateShaderResourceView(img->encoder_texture.get(), nullptr, &img->encoder_input_res);
status = device->CreateShaderResourceView(img_ctx.encoder_texture.get(), nullptr, &img_ctx.encoder_input_res);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to create shader resource view for encoding [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
img_ctx.capture_texture_p = img.capture_texture.get();
return 0;
}
@ -680,8 +695,15 @@ public:
render_target_t nv12_UV_rt;
// The image referenced by hwframe
// The resulting image is stored here.
img_d3d_t img;
texture2d_t hwframe_texture;
// d3d_img_t::id -> encoder_img_ctx_t
// These store the encoder textures for each img_t that passes through
// convert(). We can't store them in the img_t itself because it is shared
// amongst multiple hwdevice_t objects (and therefore multiple ID3D11Devices).
std::map<uint32_t, encoder_img_ctx_t> img_ctx_map;
std::shared_ptr<platf::display_t> display;
vs_t convert_UV_vs;
ps_t convert_UV_ps;
@ -984,6 +1006,7 @@ std::shared_ptr<platf::img_t> display_vram_t::alloc_img() {
img->width = width;
img->height = height;
img->display = shared_from_this();
img->id = next_image_id++;
return img;
}
@ -1007,9 +1030,6 @@ int display_vram_t::complete_img(platf::img_t *img_base, bool dummy) {
img->capture_texture.reset();
img->capture_rt.reset();
img->capture_mutex.reset();
img->encoder_texture.reset();
img->encoder_input_res.reset();
img->encoder_mutex.reset();
img->data = nullptr;
if(img->encoder_texture_handle) {
CloseHandle(img->encoder_texture_handle);