diff --git a/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc b/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc index 6ac97542f6e04..2953b5420b6f1 100644 --- a/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc +++ b/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc @@ -3111,6 +3111,15 @@ HRESULT MediaFoundationVideoEncodeAccelerator::PerformD3DCopy( D3D11_TEXTURE2D_DESC input_desc; input_texture->GetDesc(&input_desc); + // CopySubresourceRegion requires that the source and destination formats + // be identical or in the same type group, otherwise the runtime silently + // drops the copy and the destination retains its uninitialized contents. + if (input_desc.Format != DXGI_FORMAT_NV12) { + LOG(ERROR) << "Source texture format " << input_desc.Format + << " is not compatible with NV12 destination"; + return E_INVALIDARG; + } + if (visible_rect.x() < 0 || visible_rect.y() < 0 || visible_rect.right() > static_cast(input_desc.Width) || visible_rect.bottom() > static_cast(input_desc.Height)) { diff --git a/media/gpu/windows/media_foundation_video_encode_accelerator_win_unittest.cc b/media/gpu/windows/media_foundation_video_encode_accelerator_win_unittest.cc index a2bf2a13b4a77..57c4513dd24d5 100644 --- a/media/gpu/windows/media_foundation_video_encode_accelerator_win_unittest.cc +++ b/media/gpu/windows/media_foundation_video_encode_accelerator_win_unittest.cc @@ -292,10 +292,10 @@ TEST_P(MediaFoundationVideoEncodeAcceleratorAlignmentTest, EncoderStatus::Codes::kInvalidInputFrame); } -class MediaFoundationVideoEncodeAcceleratorKeyedMutexTimeoutTest +class MediaFoundationVideoEncodeAcceleratorD3DCopyTest : public ::testing::Test { public: - MediaFoundationVideoEncodeAcceleratorKeyedMutexTimeoutTest() = default; + MediaFoundationVideoEncodeAcceleratorD3DCopyTest() = default; void SetUp() override { mock_d3d11_device_ = Microsoft::WRL::Make(); @@ -378,7 +378,7 @@ class MediaFoundationVideoEncodeAcceleratorKeyedMutexTimeoutTest // Tests that if the keyed mutex cannot be acquired (e.g. returns WAIT_TIMEOUT), // the encoder drops the frame and signals an error instead of silently // proceeding and encoding uninitialized GPU VRAM. -TEST_F(MediaFoundationVideoEncodeAcceleratorKeyedMutexTimeoutTest, +TEST_F(MediaFoundationVideoEncodeAcceleratorD3DCopyTest, RejectFrameOnKeyedMutexTimeout) { // 1. Set up a mock keyed mutex that simulates a WAIT_TIMEOUT scenario. auto mock_keyed_mutex = Microsoft::WRL::Make(); @@ -451,6 +451,62 @@ TEST_F(MediaFoundationVideoEncodeAcceleratorKeyedMutexTimeoutTest, task_environment_.RunUntilIdle(); } +// Tests that if the source texture has a format that is not compatible with +// the NV12 destination, the encoder rejects the frame instead of issuing a +// CopySubresourceRegion call that the D3D runtime would silently drop. +TEST_F(MediaFoundationVideoEncodeAcceleratorD3DCopyTest, + RejectMismatchedSourceTextureFormat) { + // 1. Set up a mock texture whose description reports a non-NV12 format. No + // keyed mutex is provided so the format check is exercised directly. + auto mock_texture = + Microsoft::WRL::Make(mock_d3d11_device_); + + D3D11_TEXTURE2D_DESC desc = {}; + gfx::Size input_visible_size(1280, 720); + desc.Width = input_visible_size.width(); + desc.Height = input_visible_size.height(); + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + EXPECT_CALL(*mock_texture.Get(), GetDesc(_)) + .WillRepeatedly([desc](D3D11_TEXTURE2D_DESC* pDesc) { *pDesc = desc; }); + + // 2. Setup the mock D3D11 device to return our mock texture when opening the + // shared resource. + EXPECT_CALL(*mock_d3d11_device_.Get(), OpenSharedResource1(_, _, _)) + .WillOnce([&mock_texture](HANDLE, REFIID riid, void** ppv) { + return mock_texture.CopyTo(riid, ppv); + }); + + // 3. Create a dummy Native Texture backed VideoFrame whose metadata reports + // NV12 even though the underlying shared texture is BGRA8. + auto test_sii = base::MakeRefCounted(); + gfx::GpuMemoryBufferHandle gmb_handle{gfx::DXGIHandle::CreateFakeForTest()}; + const auto si_usage = gpu::SHARED_IMAGE_USAGE_CPU_WRITE_ONLY | + gpu::SHARED_IMAGE_USAGE_DISPLAY_READ; + + auto shared_image = test_sii->CreateSharedImage( + {viz::MultiPlaneFormat::kNV12, input_visible_size, gfx::ColorSpace(), + gpu::SharedImageUsageSet(si_usage), + "MediaFoundationVideoEncodeAcceleratorTest"}, + gpu::kNullSurfaceHandle, gfx::BufferUsage::GPU_READ, + std::move(gmb_handle)); + + scoped_refptr frame = VideoFrame::WrapMappableSharedImage( + std::move(shared_image), test_sii->GenVerifiedSyncToken(), + base::NullCallback(), gfx::Rect(input_visible_size), input_visible_size, + base::TimeDelta()); + + VideoEncoder::EncodeOptions options(false); + + // 4. Encoding the frame must reject it before issuing the copy. + EXPECT_CALL(*mock_d3d11_device_context_.Get(), CopySubresourceRegion) + .Times(0); + EXPECT_CALL(client_, NotifyErrorStatus(_)); + + // 5. Act: Attempt to encode. + encoder_->Encode(frame, options); + task_environment_.RunUntilIdle(); +} + INSTANTIATE_TEST_SUITE_P(All, MediaFoundationVideoEncodeAcceleratorAlignmentTest, ::testing::Values(PIXEL_FORMAT_NV12,