blob: ea48622d28aef49d22a4dbfc45c039298e45b1ec [file] [log] [blame]
// Copyright 2019 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/gpu/windows/d3d11_texture_wrapper.h"
#include <list>
#include <memory>
#include <utility>
#include <vector>
#include "components/viz/common/resources/resource_format_utils.h"
#include "gpu/command_buffer/common/constants.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/service/mailbox_manager.h"
#include "gpu/command_buffer/service/shared_image_backing_d3d.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/win/hresult_status_helper.h"
#include "media/base/win/mf_helpers.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "ui/gl/gl_image.h"
namespace media {
namespace {
bool SupportsFormat(DXGI_FORMAT dxgi_format) {
switch (dxgi_format) {
case DXGI_FORMAT_NV12:
case DXGI_FORMAT_P010:
case DXGI_FORMAT_B8G8R8A8_UNORM:
case DXGI_FORMAT_R10G10B10A2_UNORM:
case DXGI_FORMAT_R16G16B16A16_FLOAT:
return true;
default:
return false;
}
}
size_t NumPlanes(DXGI_FORMAT dxgi_format) {
switch (dxgi_format) {
case DXGI_FORMAT_NV12:
case DXGI_FORMAT_P010:
return 2;
case DXGI_FORMAT_B8G8R8A8_UNORM:
case DXGI_FORMAT_R10G10B10A2_UNORM:
case DXGI_FORMAT_R16G16B16A16_FLOAT:
return 1;
default:
NOTREACHED();
return 0;
}
}
} // anonymous namespace
Texture2DWrapper::Texture2DWrapper() = default;
Texture2DWrapper::~Texture2DWrapper() = default;
DefaultTexture2DWrapper::DefaultTexture2DWrapper(const gfx::Size& size,
DXGI_FORMAT dxgi_format)
: size_(size), dxgi_format_(dxgi_format) {}
DefaultTexture2DWrapper::~DefaultTexture2DWrapper() = default;
Status DefaultTexture2DWrapper::AcquireKeyedMutexIfNeeded() {
// keyed_mutex_acquired_ should be false when calling this API.
// For non-shareable resource, the keyed_mutex_acquired_ should
// never be reset.
// For shareable resource, it lives behind use_single_texture flag
// and decoder should always follow acquire-release operation pairs.
DCHECK(!keyed_mutex_acquired_);
// No need to acquire key mutex for non-shared resource.
if (!keyed_mutex_) {
return OkStatus();
}
// Handled shared resource with no key mutex acquired.
HRESULT hr =
keyed_mutex_->AcquireSync(gpu::kDXGIKeyedMutexAcquireKey, INFINITE);
if (FAILED(hr)) {
keyed_mutex_acquired_ = false;
DPLOG(ERROR) << "Unable to acquire the key mutex, error: " << hr;
return Status(StatusCode::kAcquireKeyedMutexFailed)
.AddCause(HresultToStatus(hr));
}
// Key mutex has been acquired for shared resource.
keyed_mutex_acquired_ = true;
return OkStatus();
}
Status DefaultTexture2DWrapper::ProcessTexture(
const gfx::ColorSpace& input_color_space,
MailboxHolderArray* mailbox_dest,
gfx::ColorSpace* output_color_space) {
// If the decoder acquired the key mutex before, it should be released now.
if (keyed_mutex_) {
DCHECK(keyed_mutex_acquired_);
HRESULT hr = keyed_mutex_->ReleaseSync(gpu::kDXGIKeyedMutexAcquireKey);
if (FAILED(hr)) {
DPLOG(ERROR) << "Unable to release the keyed mutex, error: " << hr;
return Status(StatusCode::kReleaseKeyedMutexFailed)
.AddCause(HresultToStatus(hr));
}
keyed_mutex_acquired_ = false;
}
// If we've received an error, then return it to our caller. This is probably
// from some previous operation.
// TODO(liberato): Return the error.
if (received_error_)
return Status(StatusCode::kProcessTextureFailed);
// TODO(liberato): make sure that |mailbox_holders_| is zero-initialized in
// case we don't use all the planes.
for (size_t i = 0; i < VideoFrame::kMaxPlanes; i++)
(*mailbox_dest)[i] = mailbox_holders_[i];
// We're just binding, so the output and output color spaces are the same.
*output_color_space = input_color_space;
return OkStatus();
}
Status DefaultTexture2DWrapper::Init(
scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner,
GetCommandBufferHelperCB get_helper_cb,
ComD3D11Texture2D texture,
size_t array_slice) {
if (!SupportsFormat(dxgi_format_))
return Status(StatusCode::kUnsupportedTextureFormatForBind);
// Init IDXGIKeyedMutex when using shared handle.
if (texture) {
// Cannot use shared handle for swap chain output texture.
D3D11_TEXTURE2D_DESC desc = {};
texture->GetDesc(&desc);
if (desc.MiscFlags & D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX) {
DCHECK(!keyed_mutex_acquired_);
HRESULT hr = texture.As(&keyed_mutex_);
if (FAILED(hr)) {
DPLOG(ERROR) << "Failed to get key_mutex from output resource, error "
<< std::hex << hr;
return Status(StatusCode::kGetKeyedMutexFailed)
.AddCause(HresultToStatus(hr));
}
}
}
// Generate mailboxes and holders.
// TODO(liberato): Verify that this is really okay off the GPU main thread.
// The current implementation is.
std::vector<gpu::Mailbox> mailboxes;
for (size_t plane = 0; plane < NumPlanes(dxgi_format_); plane++) {
mailboxes.push_back(gpu::Mailbox::GenerateForSharedImage());
mailbox_holders_[plane] = gpu::MailboxHolder(
mailboxes[plane], gpu::SyncToken(), GL_TEXTURE_EXTERNAL_OES);
}
// Start construction of the GpuResources.
// We send the texture itself, since we assume that we're using the angle
// device for decoding. Sharing seems not to work very well. Otherwise, we
// would create the texture with KEYED_MUTEX and NTHANDLE, then send along
// a handle that we get from |texture| as an IDXGIResource1.
auto on_error_cb = BindToCurrentLoop(base::BindOnce(
&DefaultTexture2DWrapper::OnError, weak_factory_.GetWeakPtr()));
gpu_resources_ = base::SequenceBound<GpuResources>(
std::move(gpu_task_runner), std::move(on_error_cb),
std::move(get_helper_cb), std::move(mailboxes), size_, dxgi_format_,
texture, array_slice);
return OkStatus();
}
void DefaultTexture2DWrapper::OnError(Status status) {
if (!received_error_)
received_error_ = status;
}
void DefaultTexture2DWrapper::SetStreamHDRMetadata(
const gfx::HDRMetadata& stream_metadata) {}
void DefaultTexture2DWrapper::SetDisplayHDRMetadata(
const DXGI_HDR_METADATA_HDR10& dxgi_display_metadata) {}
DefaultTexture2DWrapper::GpuResources::GpuResources(
OnErrorCB on_error_cb,
GetCommandBufferHelperCB get_helper_cb,
const std::vector<gpu::Mailbox>& mailboxes,
const gfx::Size& size,
DXGI_FORMAT dxgi_format,
ComD3D11Texture2D texture,
size_t array_slice) {
helper_ = get_helper_cb.Run();
if (!helper_ || !helper_->MakeContextCurrent()) {
std::move(on_error_cb)
.Run(std::move(StatusCode::kMakeContextCurrentFailed));
return;
}
// Usage flags to allow the display compositor to draw from it, video to
// decode, and allow webgl/canvas access.
constexpr uint32_t usage =
gpu::SHARED_IMAGE_USAGE_VIDEO_DECODE | gpu::SHARED_IMAGE_USAGE_GLES2 |
gpu::SHARED_IMAGE_USAGE_RASTER | gpu::SHARED_IMAGE_USAGE_DISPLAY |
gpu::SHARED_IMAGE_USAGE_SCANOUT;
base::win::ScopedHandle shared_handle;
if (texture) {
D3D11_TEXTURE2D_DESC desc = {};
texture->GetDesc(&desc);
// Create shared handle for shareable output texture.
if (desc.MiscFlags & D3D11_RESOURCE_MISC_SHARED_NTHANDLE) {
Microsoft::WRL::ComPtr<IDXGIResource1> dxgi_resource;
HRESULT hr = texture.As(&dxgi_resource);
if (FAILED(hr)) {
DLOG(ERROR) << "QueryInterface for IDXGIResource failed with error "
<< std::hex << hr;
std::move(on_error_cb)
.Run(std::move(StatusCode::kCreateSharedHandleFailed));
return;
}
HANDLE handle = nullptr;
hr = dxgi_resource->CreateSharedHandle(
nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE,
nullptr, &handle);
if (FAILED(hr)) {
DLOG(ERROR) << "CreateSharedHandle failed with error " << std::hex
<< hr;
std::move(on_error_cb)
.Run(std::move(StatusCode::kCreateSharedHandleFailed));
return;
}
shared_handle.Set(handle);
}
}
auto shared_image_backings =
gpu::SharedImageBackingD3D::CreateFromVideoTexture(
mailboxes, dxgi_format, size, usage, texture, array_slice,
std::move(shared_handle));
if (shared_image_backings.empty()) {
std::move(on_error_cb).Run(std::move(StatusCode::kCreateSharedImageFailed));
return;
}
DCHECK_EQ(shared_image_backings.size(), NumPlanes(dxgi_format));
for (auto& backing : shared_image_backings)
shared_images_.push_back(helper_->Register(std::move(backing)));
}
DefaultTexture2DWrapper::GpuResources::~GpuResources() {
// Destroy shared images with a current context.
if (!helper_ || !helper_->MakeContextCurrent())
return;
shared_images_.clear();
}
} // namespace media