diff --git a/src/platform/windows/display.h b/src/platform/windows/display.h index a60e9587eea..e4d43b336ef 100644 --- a/src/platform/windows/display.h +++ b/src/platform/windows/display.h @@ -31,6 +31,7 @@ using device_ctx_t = util::safe_ptr>; using output_t = util::safe_ptr>; using output1_t = util::safe_ptr>; +using output5_t = util::safe_ptr>; using dup_t = util::safe_ptr>; using texture2d_t = util::safe_ptr>; using texture1d_t = util::safe_ptr>; @@ -118,7 +119,7 @@ class display_base_t : public display_t { device_ctx_t device_ctx; duplication_t dup; - DXGI_FORMAT format; + DXGI_FORMAT capture_format; D3D_FEATURE_LEVEL feature_level; typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS { @@ -134,8 +135,13 @@ class display_base_t : public display_t { protected: int get_pixel_pitch() { - return (format == DXGI_FORMAT_R16G16B16A16_FLOAT) ? 8 : 4; + return (capture_format == DXGI_FORMAT_R16G16B16A16_FLOAT) ? 8 : 4; } + + const char *dxgi_format_to_string(DXGI_FORMAT format); + + virtual int complete_img(img_t *img, bool dummy) = 0; + virtual std::vector get_supported_sdr_capture_formats() = 0; }; class display_ram_t : public display_base_t { @@ -146,6 +152,8 @@ class display_ram_t : public display_base_t { std::shared_ptr alloc_img() override; int dummy_img(img_t *img) override; + int complete_img(img_t *img, bool dummy) override; + std::vector get_supported_sdr_capture_formats() override; int init(int framerate, const std::string &display_name); @@ -161,6 +169,8 @@ class display_vram_t : public display_base_t, public std::enable_shared_from_thi std::shared_ptr alloc_img() override; int dummy_img(img_t *img_base) override; + int complete_img(img_t *img_base, bool dummy) override; + std::vector get_supported_sdr_capture_formats() override; int init(int framerate, const std::string &display_name); @@ -174,7 +184,6 @@ class display_vram_t : public display_base_t, public std::enable_shared_from_thi ps_t scene_ps; vs_t scene_vs; - texture2d_t src; gpu_cursor_t cursor; }; } // namespace platf::dxgi diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index 53b329cc763..bc10b5a5932 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "display.h" #include "misc.h" @@ -79,22 +80,21 @@ duplication_t::~duplication_t() { } int display_base_t::init(int framerate, const std::string &display_name) { - /* Uncomment when use of IDXGIOutput5 is implemented + std::once_flag windows_cpp_once_flag; + std::call_once(windows_cpp_once_flag, []() { DECLARE_HANDLE(DPI_AWARENESS_CONTEXT); - const auto DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = ((DPI_AWARENESS_CONTEXT)-4); typedef BOOL (*User32_SetProcessDpiAwarenessContext)(DPI_AWARENESS_CONTEXT value); auto user32 = LoadLibraryA("user32.dll"); - auto f = (User32_SetProcessDpiAwarenessContext)GetProcAddress(user32, "SetProcessDpiAwarenessContext"); + auto f = (User32_SetProcessDpiAwarenessContext)GetProcAddress(user32, "SetProcessDpiAwarenessContext"); if(f) { f(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); } FreeLibrary(user32); }); -*/ // Ensure we can duplicate the current display syncThreadDesktop(); @@ -291,27 +291,57 @@ int display_base_t::init(int framerate, const std::string &display_name) { } //FIXME: Duplicate output on RX580 in combination with DOOM (2016) --> BSOD - //TODO: Use IDXGIOutput5 for improved performance { dxgi::output1_t output1 {}; + dxgi::output5_t output5 {}; + + // IDXGIOutput5 is optional, but can provide improved performance and wide color support + status = output->QueryInterface(IID_IDXGIOutput5, (void **)&output5); + if(FAILED(status)) { + BOOST_LOG(warning) << "Failed to query IDXGIOutput5 from the output"sv; + } + status = output->QueryInterface(IID_IDXGIOutput1, (void **)&output1); if(FAILED(status)) { BOOST_LOG(error) << "Failed to query IDXGIOutput1 from the output"sv; return -1; } - // We try this twice, in case we still get an error on reinitialization - for(int x = 0; x < 2; ++x) { - status = output1->DuplicateOutput((IUnknown *)device.get(), &dup.dup); - if(SUCCEEDED(status)) { - break; + if(output5) { + // Ask the display implementation which formats it supports + auto supported_formats = get_supported_sdr_capture_formats(); + if(supported_formats.empty()) { + BOOST_LOG(warning) << "No compatible capture formats for this encoder"sv; + return -1; + } + + // We try this twice, in case we still get an error on reinitialization + for(int x = 0; x < 2; ++x) { + status = output5->DuplicateOutput1((IUnknown *)device.get(), 0, supported_formats.size(), supported_formats.data(), &dup.dup); + if(SUCCEEDED(status)) { + break; + } + std::this_thread::sleep_for(200ms); + } + + if(FAILED(status)) { + BOOST_LOG(warning) << "DuplicateOutput1 Failed [0x"sv << util::hex(status).to_string_view() << ']'; } - std::this_thread::sleep_for(200ms); } - if(FAILED(status)) { - BOOST_LOG(error) << "DuplicateOutput Failed [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; + if(!output5 || FAILED(status)) { + for(int x = 0; x < 2; ++x) { + status = output1->DuplicateOutput((IUnknown *)device.get(), &dup.dup); + if(SUCCEEDED(status)) { + break; + } + std::this_thread::sleep_for(200ms); + } + + if(FAILED(status)) { + BOOST_LOG(error) << "DuplicateOutput Failed [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } } } @@ -319,16 +349,10 @@ int display_base_t::init(int framerate, const std::string &display_name) { dup.dup->GetDesc(&dup_desc); BOOST_LOG(info) << "Desktop resolution ["sv << dup_desc.ModeDesc.Width << 'x' << dup_desc.ModeDesc.Height << ']'; - BOOST_LOG(info) << "Desktop format ["sv << format_str[dup_desc.ModeDesc.Format] << ']'; + BOOST_LOG(info) << "Desktop format ["sv << dxgi_format_to_string(dup_desc.ModeDesc.Format) << ']'; - // For IDXGIOutput1::DuplicateOutput(), the format of the desktop image we receive from AcquireNextFrame() is - // converted to DXGI_FORMAT_B8G8R8A8_UNORM, even if the current mode (as returned in dup_desc) differs. - // See https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/desktop-dup-api for details. - // - // TODO: When we implement IDXGIOutput5, we will need to actually call AcquireNextFrame(), then call GetDesc() - // on the the texture we receive to determine which format in our list that it has decided to use. - format = DXGI_FORMAT_B8G8R8A8_UNORM; - BOOST_LOG(info) << "Capture format ["sv << format_str[format] << ']'; + // Capture format will be determined from the first call to AcquireNextFrame() + capture_format = DXGI_FORMAT_UNKNOWN; return 0; } @@ -458,6 +482,10 @@ const char *format_str[] = { "DXGI_FORMAT_V408" }; +const char *display_base_t::dxgi_format_to_string(DXGI_FORMAT format) { + return format_str[format]; +} + } // namespace platf::dxgi namespace platf { diff --git a/src/platform/windows/display_ram.cpp b/src/platform/windows/display_ram.cpp index 33072c32791..b5c026dbb6d 100644 --- a/src/platform/windows/display_ram.cpp +++ b/src/platform/windows/display_ram.cpp @@ -210,6 +210,14 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise return capture_status; } + const bool mouse_update_flag = frame_info.LastMouseUpdateTime.QuadPart != 0 || frame_info.PointerShapeBufferSize > 0; + const bool frame_update_flag = frame_info.AccumulatedFrames != 0 || frame_info.LastPresentTime.QuadPart != 0; + const bool update_flag = mouse_update_flag || frame_update_flag; + + if(!update_flag) { + return capture_e::timeout; + } + if(frame_info.PointerShapeBufferSize > 0) { auto &img_data = cursor.img_data; @@ -230,8 +238,7 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise cursor.visible = frame_info.PointerPosition.Visible; } - // If frame has been updated - if(frame_info.LastPresentTime.QuadPart != 0) { + if(frame_update_flag) { { texture2d_t src {}; status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src); @@ -241,35 +248,82 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise return capture_e::error; } + D3D11_TEXTURE2D_DESC desc; + src->GetDesc(&desc); + + // If we don't know the capture format yet, grab it from this texture and create the staging texture + if(capture_format == DXGI_FORMAT_UNKNOWN) { + capture_format = desc.Format; + BOOST_LOG(info) << "Capture format ["sv << dxgi_format_to_string(capture_format) << ']'; + + D3D11_TEXTURE2D_DESC t {}; + t.Width = width; + t.Height = height; + t.MipLevels = 1; + t.ArraySize = 1; + t.SampleDesc.Count = 1; + t.Usage = D3D11_USAGE_STAGING; + t.Format = capture_format; + t.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + + auto status = device->CreateTexture2D(&t, nullptr, &texture); + + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create staging texture [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + } + + // It's possible for our display enumeration to race with mode changes and result in + // mismatched image pool and desktop texture sizes. If this happens, just reinit again. + if(desc.Width != width || desc.Height != height) { + BOOST_LOG(info) << "Capture size changed ["sv << width << 'x' << height << " -> "sv << desc.Width << 'x' << desc.Height << ']'; + return capture_e::reinit; + } + + // It's also possible for the capture format to change on the fly. If that happens, + // reinitialize capture to try format detection again and create new images. + if(capture_format != desc.Format) { + BOOST_LOG(info) << "Capture format changed ["sv << dxgi_format_to_string(capture_format) << " -> "sv << dxgi_format_to_string(desc.Format) << ']'; + return capture_e::reinit; + } + //Copy from GPU to CPU device_ctx->CopyResource(texture.get(), src.get()); } + } - if(img_info.pData) { - device_ctx->Unmap(texture.get(), 0); - img_info.pData = nullptr; - } + // If we don't know the final capture format yet, encode a dummy image + if(capture_format == DXGI_FORMAT_UNKNOWN) { + BOOST_LOG(debug) << "Capture format is still unknown. Encoding a blank image"sv; + if(dummy_img(img)) { + return capture_e::error; + } + } + else { + // Map the staging texture for CPU access (making it inaccessible for the GPU) status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info); if(FAILED(status)) { BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']'; return capture_e::error; } - } - const bool mouse_update = - (frame_info.LastMouseUpdateTime.QuadPart || frame_info.PointerShapeBufferSize > 0) && - (cursor_visible && cursor.visible); + // Now that we know the capture format, we can finish creating the image + if(complete_img(img, false)) { + device_ctx->Unmap(texture.get(), 0); + img_info.pData = nullptr; + return capture_e::error; + } - const bool update_flag = frame_info.LastPresentTime.QuadPart != 0 || mouse_update; + std::copy_n((std::uint8_t *)img_info.pData, height * img_info.RowPitch, (std::uint8_t *)img->data); - if(!update_flag) { - return capture_e::timeout; + // Unmap the staging texture to allow GPU access again + device_ctx->Unmap(texture.get(), 0); + img_info.pData = nullptr; } - std::copy_n((std::uint8_t *)img_info.pData, height * img_info.RowPitch, (std::uint8_t *)img->data); - if(cursor_visible && cursor.visible) { blend_cursor(cursor, *img); } @@ -280,45 +334,56 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise std::shared_ptr display_ram_t::alloc_img() { auto img = std::make_shared(); - img->pixel_pitch = get_pixel_pitch(); - img->row_pitch = img_info.RowPitch; - img->width = width; - img->height = height; - img->data = new std::uint8_t[img->row_pitch * height]; + // Initialize fields that are format-independent + img->width = width; + img->height = height; return img; } -int display_ram_t::dummy_img(platf::img_t *img) { - return 0; -} - -int display_ram_t::init(int framerate, const std::string &display_name) { - if(display_base_t::init(framerate, display_name)) { +int display_ram_t::complete_img(platf::img_t *img, bool dummy) { + // If this is not a dummy image, we must know the format by now + if(!dummy && capture_format == DXGI_FORMAT_UNKNOWN) { + BOOST_LOG(error) << "display_ram_t::complete_img() called with unknown capture format!"; return -1; } - D3D11_TEXTURE2D_DESC t {}; - t.Width = width; - t.Height = height; - t.MipLevels = 1; - t.ArraySize = 1; - t.SampleDesc.Count = 1; - t.Usage = D3D11_USAGE_STAGING; - t.Format = format; - t.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + img->pixel_pitch = get_pixel_pitch(); + + if(dummy && !img->row_pitch) { + // Assume our dummy image will have no padding + img->row_pitch = img->pixel_pitch * img->width; + } + + // Reallocate the image buffer if the pitch changes + if(!dummy && img->row_pitch != img_info.RowPitch) { + img->row_pitch = img_info.RowPitch; + delete img->data; + img->data = nullptr; + } - auto status = device->CreateTexture2D(&t, nullptr, &texture); + if(!img->data) { + img->data = new std::uint8_t[img->row_pitch * height]; + } - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create texture [0x"sv << util::hex(status).to_string_view() << ']'; + return 0; +} + +int display_ram_t::dummy_img(platf::img_t *img) { + if(complete_img(img, true)) { return -1; } - // map the texture simply to get the pitch and stride - status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to map the texture [0x"sv << util::hex(status).to_string_view() << ']'; + std::fill_n((std::uint8_t *)img->data, height * img->row_pitch, 0); + return 0; +} + +std::vector display_ram_t::get_supported_sdr_capture_formats() { + return std::vector { DXGI_FORMAT_B8G8R8A8_UNORM }; +} + +int display_ram_t::init(int framerate, const std::string &display_name) { + if(display_base_t::init(framerate, display_name)) { return -1; } diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp index cf6058334b6..ae84bfb0efd 100644 --- a/src/platform/windows/display_vram.cpp +++ b/src/platform/windows/display_vram.cpp @@ -92,6 +92,7 @@ struct img_d3d_t : public platf::img_t { render_target_t scene_rt; texture2d_t texture; + bool dummy = false; ~img_d3d_t() override = default; }; @@ -215,25 +216,14 @@ blob_t compile_vertex_shader(LPCSTR file) { return compile_shader(file, "main_vs", "vs_5_0"); } -int init_rt(device_t::pointer device, shader_res_t &shader_res, render_target_t &render_target, int width, int height, DXGI_FORMAT format, texture2d_t::pointer tex) { - D3D11_SHADER_RESOURCE_VIEW_DESC shader_resource_desc { - format, - D3D11_SRV_DIMENSION_TEXTURE2D - }; - shader_resource_desc.Texture2D.MipLevels = 1; - - auto status = device->CreateShaderResourceView(tex, &shader_resource_desc, &shader_res); +int init_rt(device_t::pointer device, shader_res_t &shader_res, render_target_t &render_target, int width, int height, texture2d_t::pointer tex) { + auto status = device->CreateShaderResourceView(tex, nullptr, &shader_res); if(status) { - BOOST_LOG(error) << "Failed to create render target texture for luma [0x"sv << util::hex(status).to_string_view() << ']'; + BOOST_LOG(error) << "Failed to create shader resource view for luma [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - D3D11_RENDER_TARGET_VIEW_DESC render_target_desc { - format, - D3D11_RTV_DIMENSION_TEXTURE2D - }; - - status = device->CreateRenderTargetView(tex, &render_target_desc, &render_target); + status = device->CreateRenderTargetView(tex, nullptr, &render_target); if(status) { BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; return -1; @@ -242,28 +232,6 @@ int init_rt(device_t::pointer device, shader_res_t &shader_res, render_target_t return 0; } -int init_rt(device_t::pointer device, shader_res_t &shader_res, render_target_t &render_target, int width, int height, DXGI_FORMAT format) { - D3D11_TEXTURE2D_DESC desc {}; - - desc.Width = width; - desc.Height = height; - desc.Format = format; - desc.Usage = D3D11_USAGE_DEFAULT; - desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; - desc.MipLevels = 1; - desc.ArraySize = 1; - desc.SampleDesc.Count = 1; - - texture2d_t tex; - auto status = device->CreateTexture2D(&desc, nullptr, &tex); - if(status) { - BOOST_LOG(error) << "Failed to create render target texture for luma [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - return init_rt(device, shader_res, render_target, width, height, format, tex.get()); -} - class hwdevice_t : public platf::hwdevice_t { public: int convert(platf::img_t &img_base) override { @@ -275,7 +243,7 @@ class hwdevice_t : public platf::hwdevice_t { device_ctx_p->OMSetRenderTargets(1, &nv12_Y_rt, nullptr); device_ctx_p->VSSetShader(scene_vs.get(), nullptr, 0); device_ctx_p->PSSetShader(convert_Y_ps.get(), nullptr, 0); - device_ctx_p->PSSetShaderResources(0, 1, &back_img.input_res); + device_ctx_p->PSSetShaderResources(0, 1, &((img_d3d_t *)back_img.get())->input_res); device_ctx_p->Draw(3, 0); device_ctx_p->RSSetViewports(1, &outY_view); @@ -290,7 +258,7 @@ class hwdevice_t : public platf::hwdevice_t { device_ctx_p->OMSetRenderTargets(1, &nv12_UV_rt, nullptr); device_ctx_p->VSSetShader(convert_UV_vs.get(), nullptr, 0); device_ctx_p->PSSetShader(convert_UV_ps.get(), nullptr, 0); - device_ctx_p->PSSetShaderResources(0, 1, &back_img.input_res); + device_ctx_p->PSSetShaderResources(0, 1, &((img_d3d_t *)back_img.get())->input_res); device_ctx_p->Draw(3, 0); device_ctx_p->RSSetViewports(1, &outUV_view); @@ -386,7 +354,7 @@ class hwdevice_t : public platf::hwdevice_t { } D3D11_RENDER_TARGET_VIEW_DESC nv12_rt_desc { - DXGI_FORMAT_R8_UNORM, + format == DXGI_FORMAT_P010 ? DXGI_FORMAT_R16_UNORM : DXGI_FORMAT_R8_UNORM, D3D11_RTV_DIMENSION_TEXTURE2D }; @@ -396,7 +364,7 @@ class hwdevice_t : public platf::hwdevice_t { return -1; } - nv12_rt_desc.Format = DXGI_FORMAT_R8G8_UNORM; + nv12_rt_desc.Format = (format == DXGI_FORMAT_P010) ? DXGI_FORMAT_R16G16_UNORM : DXGI_FORMAT_R8G8_UNORM; status = device_p->CreateRenderTargetView(img.texture.get(), &nv12_rt_desc, &nv12_UV_rt); if(FAILED(status)) { @@ -485,23 +453,12 @@ class hwdevice_t : public platf::hwdevice_t { // Color the background black, so that the padding for keeping the aspect ratio // is black - if(img.display->dummy_img(&back_img)) { + back_img = img.display->alloc_img(); + if(img.display->dummy_img(back_img.get())) { BOOST_LOG(warning) << "Couldn't create an image to set background color to black"sv; return -1; } - D3D11_SHADER_RESOURCE_VIEW_DESC desc { - DXGI_FORMAT_B8G8R8A8_UNORM, - D3D11_SRV_DIMENSION_TEXTURE2D - }; - desc.Texture2D.MipLevels = 1; - - status = device_p->CreateShaderResourceView(back_img.texture.get(), &desc, &back_img.input_res); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create input shader resource view [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - device_ctx_p->IASetInputLayout(input_layout.get()); device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix); device_ctx_p->VSSetConstantBuffers(0, 1, &info_scene); @@ -548,7 +505,7 @@ class hwdevice_t : public platf::hwdevice_t { img_d3d_t img; // Clear nv12 render target to black - img_d3d_t back_img; + std::shared_ptr back_img; vs_t convert_UV_vs; ps_t convert_UV_ps; @@ -656,15 +613,9 @@ capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::millisec return capture_e::error; } - D3D11_SHADER_RESOURCE_VIEW_DESC desc { - DXGI_FORMAT_B8G8R8A8_UNORM, - D3D11_SRV_DIMENSION_TEXTURE2D - }; - desc.Texture2D.MipLevels = 1; - // Free resources before allocating on the next line. cursor.input_res.reset(); - status = device->CreateShaderResourceView(texture.get(), &desc, &cursor.input_res); + status = device->CreateShaderResourceView(texture.get(), nullptr, &cursor.input_res); if(FAILED(status)) { BOOST_LOG(error) << "Failed to create cursor shader resource view [0x"sv << util::hex(status).to_string_view() << ']'; return capture_e::error; @@ -674,21 +625,60 @@ capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::millisec } if(frame_info.LastMouseUpdateTime.QuadPart) { - cursor.set_pos(frame_info.PointerPosition.Position.x, frame_info.PointerPosition.Position.y, frame_info.PointerPosition.Visible && cursor_visible); + cursor.set_pos(frame_info.PointerPosition.Position.x, frame_info.PointerPosition.Position.y, frame_info.PointerPosition.Visible); } - if(frame_update_flag) { - src.reset(); - status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src); + if(capture_format != DXGI_FORMAT_UNKNOWN || frame_update_flag) { + texture2d_t src {}; + // Get the texture object from this frame + status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src); if(FAILED(status)) { BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']'; return capture_e::error; } + + D3D11_TEXTURE2D_DESC desc; + src->GetDesc(&desc); + + // If we don't know the capture format yet, grab it from this texture + if(capture_format == DXGI_FORMAT_UNKNOWN) { + capture_format = desc.Format; + BOOST_LOG(info) << "Capture format ["sv << dxgi_format_to_string(capture_format) << ']'; + } + + // It's possible for our display enumeration to race with mode changes and result in + // mismatched image pool and desktop texture sizes. If this happens, just reinit again. + if(desc.Width != width || desc.Height != height) { + BOOST_LOG(info) << "Capture size changed ["sv << width << 'x' << height << " -> "sv << desc.Width << 'x' << desc.Height << ']'; + return capture_e::reinit; + } + + // It's also possible for the capture format to change on the fly. If that happens, + // reinitialize capture to try format detection again and create new images. + if(capture_format != desc.Format) { + BOOST_LOG(info) << "Capture format changed ["sv << dxgi_format_to_string(capture_format) << " -> "sv << dxgi_format_to_string(desc.Format) << ']'; + return capture_e::reinit; + } + + // Now that we know the capture format, we can finish creating the image + if(complete_img(img, false)) { + return capture_e::error; + } + + // Copy the texture into this image + device_ctx->CopyResource(img->texture.get(), src.get()); + } + else { + // We don't know the final capture format yet, so we will encode a dummy image + BOOST_LOG(debug) << "Capture format is still unknown. Encoding a blank image"sv; + + if(dummy_img(img)) { + return capture_e::error; + } } - device_ctx->CopyResource(img->texture.get(), src.get()); - if(cursor.visible) { + if(cursor.visible && cursor_visible) { D3D11_VIEWPORT view { 0.0f, 0.0f, (float)width, (float)height, @@ -698,8 +688,8 @@ capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::millisec device_ctx->VSSetShader(scene_vs.get(), nullptr, 0); device_ctx->PSSetShader(scene_ps.get(), nullptr, 0); device_ctx->RSSetViewports(1, &view); - device_ctx->OMSetRenderTargets(1, &img->scene_rt, nullptr); device_ctx->PSSetShaderResources(0, 1, &cursor.input_res); + device_ctx->OMSetRenderTargets(1, &img->scene_rt, nullptr); device_ctx->OMSetBlendState(blend_enable.get(), nullptr, 0xFFFFFFFFu); device_ctx->RSSetViewports(1, &cursor.cursor_view); device_ctx->Draw(3, 0); @@ -758,85 +748,84 @@ int display_vram_t::init(int framerate, const std::string &display_name) { std::shared_ptr display_vram_t::alloc_img() { auto img = std::make_shared(); + // Initialize format-independent fields + img->width = width; + img->height = height; + img->display = shared_from_this(); + + return img; +} + +int display_vram_t::complete_img(platf::img_t *img_base, bool dummy) { + auto img = (img_d3d_t *)img_base; + + // If this already has a texture and it's not switching dummy state, nothing to do + if(img->texture && img->dummy == dummy) { + return 0; + } + + // If this is not a dummy image, we must know the format by now + if(!dummy && capture_format == DXGI_FORMAT_UNKNOWN) { + BOOST_LOG(error) << "display_vram_t::complete_img() called with unknown capture format!"; + return -1; + } + + // Reset the image (in case this was previously a dummy) + img->texture.reset(); + img->input_res.reset(); + img->scene_rt.reset(); + img->data = nullptr; + + // Initialize format-dependent fields img->pixel_pitch = get_pixel_pitch(); - img->row_pitch = img->pixel_pitch * width; - img->width = width; - img->height = height; - img->display = shared_from_this(); - - auto dummy_data = std::make_unique(img->row_pitch * height); - D3D11_SUBRESOURCE_DATA data { - dummy_data.get(), - (UINT)img->row_pitch - }; - std::fill_n(dummy_data.get(), img->row_pitch * height, 0); + img->row_pitch = img->pixel_pitch * img->width; + img->dummy = dummy; D3D11_TEXTURE2D_DESC t {}; - t.Width = width; - t.Height = height; + t.Width = img->width; + t.Height = img->height; t.MipLevels = 1; t.ArraySize = 1; t.SampleDesc.Count = 1; t.Usage = D3D11_USAGE_DEFAULT; - t.Format = format; + t.Format = (capture_format == DXGI_FORMAT_UNKNOWN) ? DXGI_FORMAT_B8G8R8A8_UNORM : capture_format; t.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; - auto status = device->CreateTexture2D(&t, &data, &img->texture); + auto status = device->CreateTexture2D(&t, nullptr, &img->texture); if(FAILED(status)) { BOOST_LOG(error) << "Failed to create img buf texture [0x"sv << util::hex(status).to_string_view() << ']'; - return nullptr; + return -1; } - if(init_rt(device.get(), img->input_res, img->scene_rt, width, height, format, img->texture.get())) { - return nullptr; + if(init_rt(device.get(), img->input_res, img->scene_rt, img->width, img->height, img->texture.get())) { + return -1; } img->data = (std::uint8_t *)img->texture.get(); - return img; + return 0; } int display_vram_t::dummy_img(platf::img_t *img_base) { auto img = (img_d3d_t *)img_base; - if(img->texture) { - return 0; - } - - img->pixel_pitch = get_pixel_pitch(); - img->row_pitch = img->pixel_pitch * width; - auto dummy_data = std::make_unique(img->row_pitch * height); - D3D11_SUBRESOURCE_DATA data { - dummy_data.get(), - (UINT)img->row_pitch - }; - std::fill_n(dummy_data.get(), img->row_pitch * height, 0); - - D3D11_TEXTURE2D_DESC t {}; - t.Width = width; - t.Height = height; - t.MipLevels = 1; - t.ArraySize = 1; - t.SampleDesc.Count = 1; - t.Usage = D3D11_USAGE_DEFAULT; - t.Format = format; - t.BindFlags = D3D11_BIND_SHADER_RESOURCE; - - dxgi::texture2d_t tex; - auto status = device->CreateTexture2D(&t, &data, &tex); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create dummy texture [0x"sv << util::hex(status).to_string_view() << ']'; + if(complete_img(img, true)) { return -1; } - img->texture = std::move(tex); - img->data = (std::uint8_t *)img->texture.get(); + auto dummy_data = std::make_unique(img->row_pitch * img->height); + std::fill_n(dummy_data.get(), img->row_pitch * img->height, 0); + device_ctx->UpdateSubresource(img->texture.get(), 0, nullptr, dummy_data.get(), img->row_pitch, 0); return 0; } +std::vector display_vram_t::get_supported_sdr_capture_formats() { + return std::vector { DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM }; +} + std::shared_ptr display_vram_t::make_hwdevice(pix_fmt_e pix_fmt) { - if(pix_fmt != platf::pix_fmt_e::nv12) { + if(pix_fmt != platf::pix_fmt_e::nv12 && pix_fmt != platf::pix_fmt_e::p010) { BOOST_LOG(error) << "display_vram_t doesn't support pixel format ["sv << from_pix_fmt(pix_fmt) << ']'; return nullptr;