| // Copyright 2021 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/capture/video/win/gpu_memory_buffer_tracker.h" |
| |
| #include "base/check.h" |
| #include "base/notreached.h" |
| #include "base/win/scoped_handle.h" |
| #include "gpu/ipc/common/dxgi_helpers.h" |
| #include "media/base/win/mf_helpers.h" |
| #include "media/capture/video/video_capture_buffer_handle.h" |
| #include "ui/gfx/geometry/size.h" |
| |
| #include <dxgi1_2.h> |
| |
| namespace media { |
| |
| namespace { |
| |
| base::win::ScopedHandle CreateNV12Texture(ID3D11Device* d3d11_device, |
| const gfx::Size& size) { |
| const DXGI_FORMAT dxgi_format = DXGI_FORMAT_NV12; |
| D3D11_TEXTURE2D_DESC desc = { |
| .Width = static_cast<UINT>(size.width()), |
| .Height = static_cast<UINT>(size.height()), |
| .MipLevels = 1, |
| .ArraySize = 1, |
| .Format = dxgi_format, |
| .SampleDesc = {1, 0}, |
| .Usage = D3D11_USAGE_DEFAULT, |
| .BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET, |
| .CPUAccessFlags = 0, |
| .MiscFlags = D3D11_RESOURCE_MISC_SHARED_NTHANDLE | |
| D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX}; |
| |
| Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture; |
| |
| HRESULT hr = d3d11_device->CreateTexture2D(&desc, nullptr, &d3d11_texture); |
| if (FAILED(hr)) { |
| DLOG(ERROR) << "Failed to create D3D11 texture: " |
| << logging::SystemErrorCodeToString(hr); |
| return base::win::ScopedHandle(); |
| } |
| hr = SetDebugName(d3d11_texture.Get(), "Camera_MemoryBufferTracker"); |
| if (FAILED(hr)) { |
| DLOG(ERROR) << "Failed to label D3D11 texture: " |
| << logging::SystemErrorCodeToString(hr); |
| return base::win::ScopedHandle(); |
| } |
| |
| Microsoft::WRL::ComPtr<IDXGIResource1> dxgi_resource; |
| hr = d3d11_texture.As(&dxgi_resource); |
| CHECK(SUCCEEDED(hr)); |
| |
| HANDLE texture_handle; |
| hr = dxgi_resource->CreateSharedHandle( |
| nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, nullptr, |
| &texture_handle); |
| if (FAILED(hr)) { |
| DLOG(ERROR) << "Failed to create shared D3D11 texture handle: " |
| << logging::SystemErrorCodeToString(hr); |
| return base::win::ScopedHandle(); |
| } |
| return base::win::ScopedHandle(texture_handle); |
| } |
| |
| } // namespace |
| |
| GpuMemoryBufferTracker::GpuMemoryBufferTracker( |
| scoped_refptr<DXGIDeviceManager> dxgi_device_manager) |
| : dxgi_device_manager_(std::move(dxgi_device_manager)), |
| d3d_device_(dxgi_device_manager_->GetDevice()) {} |
| |
| GpuMemoryBufferTracker::~GpuMemoryBufferTracker() = default; |
| |
| bool GpuMemoryBufferTracker::Init(const gfx::Size& dimensions, |
| VideoPixelFormat format, |
| const mojom::PlaneStridesPtr& strides) { |
| // Only support NV12 |
| if (format != PIXEL_FORMAT_NV12) { |
| NOTREACHED() << "Unsupported VideoPixelFormat " << format; |
| return false; |
| } |
| |
| buffer_size_ = dimensions; |
| |
| return CreateBufferInternal(); |
| } |
| |
| bool GpuMemoryBufferTracker::CreateBufferInternal() { |
| gfx::GpuMemoryBufferHandle buffer_handle; |
| buffer_handle.dxgi_handle = |
| CreateNV12Texture(d3d_device_.Get(), buffer_size_); |
| |
| buffer_ = gpu::GpuMemoryBufferImplDXGI::CreateFromHandle( |
| std::move(buffer_handle), buffer_size_, |
| gfx::BufferFormat::YUV_420_BIPLANAR, gfx::BufferUsage::GPU_READ, |
| gpu::GpuMemoryBufferImpl::DestructionCallback(), nullptr, nullptr); |
| if (!buffer_) { |
| NOTREACHED() << "Failed to create GPU memory buffer"; |
| return false; |
| } |
| |
| region_ = base::UnsafeSharedMemoryRegion::Create(GetMemorySizeInBytes()); |
| |
| return true; |
| } |
| |
| bool GpuMemoryBufferTracker::EnsureD3DDevice() { |
| // Check for and handle device loss by recreating the texture |
| if (FAILED(d3d_device_->GetDeviceRemovedReason())) { |
| DVLOG(1) << "Detected device loss."; |
| dxgi_device_manager_->ResetDevice(); |
| d3d_device_ = dxgi_device_manager_->GetDevice(); |
| if (!d3d_device_) { |
| return false; |
| } |
| |
| return CreateBufferInternal(); |
| } |
| return true; |
| } |
| |
| bool GpuMemoryBufferTracker::IsReusableForFormat( |
| const gfx::Size& dimensions, |
| VideoPixelFormat format, |
| const mojom::PlaneStridesPtr& strides) { |
| return (format == PIXEL_FORMAT_NV12) && (dimensions == buffer_->GetSize()); |
| } |
| |
| std::unique_ptr<VideoCaptureBufferHandle> |
| GpuMemoryBufferTracker::GetMemoryMappedAccess() { |
| NOTREACHED() << "Unsupported operation"; |
| return std::make_unique<NullHandle>(); |
| } |
| |
| base::UnsafeSharedMemoryRegion |
| GpuMemoryBufferTracker::DuplicateAsUnsafeRegion() { |
| if (!buffer_) { |
| return base::UnsafeSharedMemoryRegion(); |
| } |
| |
| CHECK(region_.IsValid()); |
| |
| if (!gpu::CopyDXGIBufferToShMem(buffer_->GetHandle(), region_.Duplicate(), |
| d3d_device_.Get(), &staging_texture_)) { |
| DLOG(ERROR) << "Couldn't copy DXGI buffer to shmem"; |
| return base::UnsafeSharedMemoryRegion(); |
| } |
| |
| return region_.Duplicate(); |
| } |
| |
| mojo::ScopedSharedBufferHandle GpuMemoryBufferTracker::DuplicateAsMojoBuffer() { |
| NOTREACHED() << "Unsupported operation"; |
| return mojo::ScopedSharedBufferHandle(); |
| } |
| |
| gfx::GpuMemoryBufferHandle GpuMemoryBufferTracker::GetGpuMemoryBufferHandle() { |
| if (!EnsureD3DDevice()) { |
| return gfx::GpuMemoryBufferHandle(); |
| } |
| auto handle = buffer_->CloneHandle(); |
| handle.region = region_.Duplicate(); |
| return handle; |
| } |
| |
| uint32_t GpuMemoryBufferTracker::GetMemorySizeInBytes() { |
| DCHECK(buffer_); |
| return (buffer_->GetSize().width() * buffer_->GetSize().height() * 3) / 2; |
| } |
| |
| } // namespace media |