// Copyright 2016 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/dxva_picture_buffer_win.h"

#include "media/gpu/dxva_video_decode_accelerator_win.h"
#include "third_party/angle/include/EGL/egl.h"
#include "third_party/angle/include/EGL/eglext.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_fence.h"
#include "ui/gl/gl_image.h"
#include "ui/gl/gl_surface_egl.h"
#include "ui/gl/scoped_binders.h"

namespace media {

namespace {

void LogDXVAError(int line) {
  LOG(ERROR) << "Error in dxva_picture_buffer_win.cc on line " << line;
}

// These GLImage subclasses are just used to hold references to the underlying
// image content so it can be destroyed when the textures are.
class DummyGLImage : public gl::GLImage {
 public:
  DummyGLImage(const gfx::Size& size) : size_(size) {}

  // gl::GLImage implementation.
  gfx::Size GetSize() override { return size_; }
  unsigned GetInternalFormat() override { return GL_BGRA_EXT; }
  bool BindTexImage(unsigned target) override { return false; }
  void ReleaseTexImage(unsigned target) override {}
  bool CopyTexImage(unsigned target) override { return false; }
  bool CopyTexSubImage(unsigned target,
                       const gfx::Point& offset,
                       const gfx::Rect& rect) override {
    return false;
  }
  bool ScheduleOverlayPlane(gfx::AcceleratedWidget widget,
                            int z_order,
                            gfx::OverlayTransform transform,
                            const gfx::Rect& bounds_rect,
                            const gfx::RectF& crop_rect) override {
    return false;
  }
  void Flush() override {}
  void OnMemoryDump(base::trace_event::ProcessMemoryDump* pmd,
                    uint64_t process_tracing_id,
                    const std::string& dump_name) override {}

 protected:
  ~DummyGLImage() override {}

 private:
  gfx::Size size_;
};

class GLImagePbuffer : public DummyGLImage {
 public:
  GLImagePbuffer(const gfx::Size& size, EGLSurface surface)
      : DummyGLImage(size), surface_(surface) {}

 private:
  ~GLImagePbuffer() override {
    EGLDisplay egl_display = gl::GLSurfaceEGL::GetHardwareDisplay();

    eglReleaseTexImage(egl_display, surface_, EGL_BACK_BUFFER);

    eglDestroySurface(egl_display, surface_);
  }

  EGLSurface surface_;
};

class GLImageEGLStream : public DummyGLImage {
 public:
  GLImageEGLStream(const gfx::Size& size, EGLStreamKHR stream)
      : DummyGLImage(size), stream_(stream) {}

 private:
  ~GLImageEGLStream() override {
    EGLDisplay egl_display = gl::GLSurfaceEGL::GetHardwareDisplay();
    eglDestroyStreamKHR(egl_display, stream_);
  }

  EGLStreamKHR stream_;
};

}  // namespace

#define RETURN_ON_FAILURE(result, log, ret) \
  do {                                      \
    if (!(result)) {                        \
      DLOG(ERROR) << log;                   \
      LogDXVAError(__LINE__);               \
      return ret;                           \
    }                                       \
  } while (0)

#define RETURN_ON_HR_FAILURE(result, log, ret) \
  RETURN_ON_FAILURE(SUCCEEDED(result),         \
                    log << ", HRESULT: 0x" << std::hex << result, ret);

enum {
  // The keyed mutex should always be released before the other thread
  // attempts to acquire it, so AcquireSync should always return immediately.
  kAcquireSyncWaitMs = 0,
};

// static
linked_ptr<DXVAPictureBuffer> DXVAPictureBuffer::Create(
    const DXVAVideoDecodeAccelerator& decoder,
    const PictureBuffer& buffer,
    EGLConfig egl_config) {
  if (decoder.share_nv12_textures_) {
    linked_ptr<EGLStreamPictureBuffer> picture_buffer(
        new EGLStreamPictureBuffer(buffer));
    if (!picture_buffer->Initialize())
      return linked_ptr<DXVAPictureBuffer>(nullptr);

    return picture_buffer;
  }
  if (decoder.copy_nv12_textures_) {
    linked_ptr<EGLStreamCopyPictureBuffer> picture_buffer(
        new EGLStreamCopyPictureBuffer(buffer));
    if (!picture_buffer->Initialize(decoder))
      return linked_ptr<DXVAPictureBuffer>(nullptr);

    return picture_buffer;
  }
  linked_ptr<PbufferPictureBuffer> picture_buffer(
      new PbufferPictureBuffer(buffer));

  if (!picture_buffer->Initialize(decoder, egl_config))
    return linked_ptr<DXVAPictureBuffer>(nullptr);

  return picture_buffer;
}

DXVAPictureBuffer::~DXVAPictureBuffer() {}

void DXVAPictureBuffer::ResetReuseFence() {
  NOTREACHED();
}

bool DXVAPictureBuffer::CopyOutputSampleDataToPictureBuffer(
    DXVAVideoDecodeAccelerator* decoder,
    IDirect3DSurface9* dest_surface,
    ID3D11Texture2D* dx11_texture,
    int input_buffer_id) {
  NOTREACHED();
  return false;
}

void DXVAPictureBuffer::set_bound() {
  DCHECK_EQ(UNUSED, state_);
  state_ = BOUND;
}

gl::GLFence* DXVAPictureBuffer::reuse_fence() {
  return nullptr;
}

bool DXVAPictureBuffer::CopySurfaceComplete(IDirect3DSurface9* src_surface,
                                            IDirect3DSurface9* dest_surface) {
  NOTREACHED();
  return false;
}

DXVAPictureBuffer::DXVAPictureBuffer(const PictureBuffer& buffer)
    : picture_buffer_(buffer) {}

bool DXVAPictureBuffer::BindSampleToTexture(
    base::win::ScopedComPtr<IMFSample> sample) {
  NOTREACHED();
  return false;
}

bool PbufferPictureBuffer::Initialize(const DXVAVideoDecodeAccelerator& decoder,
                                      EGLConfig egl_config) {
  RETURN_ON_FAILURE(!picture_buffer_.service_texture_ids().empty(),
                    "No service texture ids provided", false);

  EGLDisplay egl_display = gl::GLSurfaceEGL::GetHardwareDisplay();
  EGLint use_rgb = 1;
  eglGetConfigAttrib(egl_display, egl_config, EGL_BIND_TO_TEXTURE_RGB,
                     &use_rgb);

  if (!InitializeTexture(decoder, !!use_rgb))
    return false;

  EGLint attrib_list[] = {EGL_WIDTH,
                          size().width(),
                          EGL_HEIGHT,
                          size().height(),
                          EGL_TEXTURE_FORMAT,
                          use_rgb ? EGL_TEXTURE_RGB : EGL_TEXTURE_RGBA,
                          EGL_TEXTURE_TARGET,
                          EGL_TEXTURE_2D,
                          EGL_NONE};

  decoding_surface_ = eglCreatePbufferFromClientBuffer(
      egl_display, EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE, texture_share_handle_,
      egl_config, attrib_list);
  RETURN_ON_FAILURE(decoding_surface_, "Failed to create surface", false);
  gl_image_ = make_scoped_refptr(new GLImagePbuffer(size(), decoding_surface_));
  if (decoder.d3d11_device_ && decoder.use_keyed_mutex_) {
    void* keyed_mutex = nullptr;
    EGLBoolean ret =
        eglQuerySurfacePointerANGLE(egl_display, decoding_surface_,
                                    EGL_DXGI_KEYED_MUTEX_ANGLE, &keyed_mutex);
    RETURN_ON_FAILURE(keyed_mutex && ret == EGL_TRUE,
                      "Failed to query ANGLE keyed mutex", false);
    egl_keyed_mutex_ = base::win::ScopedComPtr<IDXGIKeyedMutex>(
        static_cast<IDXGIKeyedMutex*>(keyed_mutex));
  }
  use_rgb_ = !!use_rgb;
  return true;
}

bool PbufferPictureBuffer::InitializeTexture(
    const DXVAVideoDecodeAccelerator& decoder,
    bool use_rgb) {
  DCHECK(!texture_share_handle_);
  if (decoder.d3d11_device_) {
    D3D11_TEXTURE2D_DESC desc;
    desc.Width = picture_buffer_.size().width();
    desc.Height = picture_buffer_.size().height();
    desc.MipLevels = 1;
    desc.ArraySize = 1;
    desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    desc.SampleDesc.Count = 1;
    desc.SampleDesc.Quality = 0;
    desc.Usage = D3D11_USAGE_DEFAULT;
    desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
    desc.CPUAccessFlags = 0;
    desc.MiscFlags = decoder.use_keyed_mutex_
                         ? D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX
                         : D3D11_RESOURCE_MISC_SHARED;

    HRESULT hr = decoder.d3d11_device_->CreateTexture2D(
        &desc, nullptr, dx11_decoding_texture_.Receive());
    RETURN_ON_HR_FAILURE(hr, "Failed to create texture", false);
    if (decoder.use_keyed_mutex_) {
      hr = dx11_keyed_mutex_.QueryFrom(dx11_decoding_texture_.get());
      RETURN_ON_HR_FAILURE(hr, "Failed to get keyed mutex", false);
    }

    base::win::ScopedComPtr<IDXGIResource> resource;
    hr = resource.QueryFrom(dx11_decoding_texture_.get());
    DCHECK(SUCCEEDED(hr));
    hr = resource->GetSharedHandle(&texture_share_handle_);
    RETURN_ON_FAILURE(SUCCEEDED(hr) && texture_share_handle_,
                      "Failed to query shared handle", false);

  } else {
    HRESULT hr = E_FAIL;
    hr = decoder.d3d9_device_ex_->CreateTexture(
        picture_buffer_.size().width(), picture_buffer_.size().height(), 1,
        D3DUSAGE_RENDERTARGET, use_rgb ? D3DFMT_X8R8G8B8 : D3DFMT_A8R8G8B8,
        D3DPOOL_DEFAULT, decoding_texture_.Receive(), &texture_share_handle_);
    RETURN_ON_HR_FAILURE(hr, "Failed to create texture", false);
    RETURN_ON_FAILURE(texture_share_handle_, "Failed to query shared handle",
                      false);
  }
  return true;
}

void PbufferPictureBuffer::ResetReuseFence() {
  DCHECK_EQ(IN_CLIENT, state_);
  if (!reuse_fence_ || !reuse_fence_->ResetSupported())
    reuse_fence_.reset(gl::GLFence::Create());
  else
    reuse_fence_->ResetState();
  state_ = WAITING_TO_REUSE;
}

bool PbufferPictureBuffer::CopyOutputSampleDataToPictureBuffer(
    DXVAVideoDecodeAccelerator* decoder,
    IDirect3DSurface9* dest_surface,
    ID3D11Texture2D* dx11_texture,
    int input_buffer_id) {
  DCHECK_EQ(BOUND, state_);
  state_ = COPYING;
  DCHECK(dest_surface || dx11_texture);
  if (dx11_texture) {
    // Grab a reference on the decoder texture. This reference will be released
    // when we receive a notification that the copy was completed or when the
    // DXVAPictureBuffer instance is destroyed.
    decoder_dx11_texture_ = dx11_texture;
    decoder->CopyTexture(dx11_texture, dx11_decoding_texture_.get(),
                         dx11_keyed_mutex_, keyed_mutex_value_, id(),
                         input_buffer_id);
    return true;
  }
  D3DSURFACE_DESC surface_desc;
  HRESULT hr = dest_surface->GetDesc(&surface_desc);
  RETURN_ON_HR_FAILURE(hr, "Failed to get surface description", false);

  D3DSURFACE_DESC texture_desc;
  decoding_texture_->GetLevelDesc(0, &texture_desc);

  if (texture_desc.Width != surface_desc.Width ||
      texture_desc.Height != surface_desc.Height) {
    NOTREACHED() << "Decode surface of different dimension than texture";
    return false;
  }

  hr = decoder->d3d9_->CheckDeviceFormatConversion(
      D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, surface_desc.Format,
      use_rgb_ ? D3DFMT_X8R8G8B8 : D3DFMT_A8R8G8B8);
  RETURN_ON_HR_FAILURE(hr, "Device does not support format converision", false);

  // The same picture buffer can be reused for a different frame. Release the
  // target surface and the decoder references here.
  target_surface_.Release();
  decoder_surface_.Release();

  // Grab a reference on the decoder surface and the target surface. These
  // references will be released when we receive a notification that the
  // copy was completed or when the DXVAPictureBuffer instance is destroyed.
  // We hold references here as it is easier to manage their lifetimes.
  hr = decoding_texture_->GetSurfaceLevel(0, target_surface_.Receive());
  RETURN_ON_HR_FAILURE(hr, "Failed to get surface from texture", false);

  decoder_surface_ = dest_surface;

  decoder->CopySurface(decoder_surface_.get(), target_surface_.get(), id(),
                       input_buffer_id);
  return true;
}

gl::GLFence* PbufferPictureBuffer::reuse_fence() {
  return reuse_fence_.get();
}

bool PbufferPictureBuffer::CopySurfaceComplete(
    IDirect3DSurface9* src_surface,
    IDirect3DSurface9* dest_surface) {
  DCHECK_EQ(COPYING, state_);
  state_ = IN_CLIENT;

  GLint current_texture = 0;
  glGetIntegerv(GL_TEXTURE_BINDING_2D, &current_texture);

  glBindTexture(GL_TEXTURE_2D, picture_buffer_.service_texture_ids()[0]);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

  if (src_surface && dest_surface) {
    DCHECK_EQ(src_surface, decoder_surface_.get());
    DCHECK_EQ(dest_surface, target_surface_.get());
    decoder_surface_.Release();
    target_surface_.Release();
  } else {
    DCHECK(decoder_dx11_texture_.get());
    decoder_dx11_texture_.Release();
  }
  if (egl_keyed_mutex_) {
    keyed_mutex_value_++;
    HRESULT result =
        egl_keyed_mutex_->AcquireSync(keyed_mutex_value_, kAcquireSyncWaitMs);
    RETURN_ON_FAILURE(result == S_OK, "Could not acquire sync mutex", false);
  }

  EGLDisplay egl_display = gl::GLSurfaceEGL::GetHardwareDisplay();
  eglBindTexImage(egl_display, decoding_surface_, EGL_BACK_BUFFER);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glBindTexture(GL_TEXTURE_2D, current_texture);
  return true;
}

PbufferPictureBuffer::PbufferPictureBuffer(const PictureBuffer& buffer)
    : DXVAPictureBuffer(buffer),
      decoding_surface_(NULL),
      texture_share_handle_(nullptr),
      keyed_mutex_value_(0),
      use_rgb_(true) {}

PbufferPictureBuffer::~PbufferPictureBuffer() {
  // decoding_surface_ will be deleted by gl_image_.
}

bool PbufferPictureBuffer::ReusePictureBuffer() {
  DCHECK_NE(UNUSED, state_);
  DCHECK(decoding_surface_);
  EGLDisplay egl_display = gl::GLSurfaceEGL::GetHardwareDisplay();
  eglReleaseTexImage(egl_display, decoding_surface_, EGL_BACK_BUFFER);

  decoder_surface_.Release();
  target_surface_.Release();
  decoder_dx11_texture_.Release();
  state_ = UNUSED;
  if (egl_keyed_mutex_) {
    HRESULT hr = egl_keyed_mutex_->ReleaseSync(++keyed_mutex_value_);
    RETURN_ON_FAILURE(hr == S_OK, "Could not release sync mutex", false);
  }
  return true;
}

EGLStreamPictureBuffer::EGLStreamPictureBuffer(const PictureBuffer& buffer)
    : DXVAPictureBuffer(buffer), stream_(nullptr) {}

EGLStreamPictureBuffer::~EGLStreamPictureBuffer() {
  // stream_ will be deleted by gl_image_.
}

bool EGLStreamPictureBuffer::Initialize() {
  RETURN_ON_FAILURE(picture_buffer_.service_texture_ids().size() >= 2,
                    "Not enough texture ids provided", false);

  EGLDisplay egl_display = gl::GLSurfaceEGL::GetHardwareDisplay();
  const EGLint stream_attributes[] = {
      EGL_CONSUMER_LATENCY_USEC_KHR,
      0,
      EGL_CONSUMER_ACQUIRE_TIMEOUT_USEC_KHR,
      0,
      EGL_NONE,
  };
  stream_ = eglCreateStreamKHR(egl_display, stream_attributes);
  RETURN_ON_FAILURE(!!stream_, "Could not create stream", false);
  gl_image_ = make_scoped_refptr(new GLImageEGLStream(size(), stream_));
  gl::ScopedActiveTexture texture0(GL_TEXTURE0);
  gl::ScopedTextureBinder texture0_binder(
      GL_TEXTURE_EXTERNAL_OES, picture_buffer_.service_texture_ids()[0]);
  gl::ScopedActiveTexture texture1(GL_TEXTURE1);
  gl::ScopedTextureBinder texture1_binder(
      GL_TEXTURE_EXTERNAL_OES, picture_buffer_.service_texture_ids()[1]);

  EGLAttrib consumer_attributes[] = {
      EGL_COLOR_BUFFER_TYPE,
      EGL_YUV_BUFFER_EXT,
      EGL_YUV_NUMBER_OF_PLANES_EXT,
      2,
      EGL_YUV_PLANE0_TEXTURE_UNIT_NV,
      0,
      EGL_YUV_PLANE1_TEXTURE_UNIT_NV,
      1,
      EGL_NONE,
  };
  EGLBoolean result = eglStreamConsumerGLTextureExternalAttribsNV(
      egl_display, stream_, consumer_attributes);
  RETURN_ON_FAILURE(result, "Could not set stream consumer", false);

  EGLAttrib producer_attributes[] = {
      EGL_NONE,
  };

  result = eglCreateStreamProducerD3DTextureNV12ANGLE(egl_display, stream_,
                                                      producer_attributes);
  RETURN_ON_FAILURE(result, "Could not create stream producer", false);
  return true;
}

bool EGLStreamPictureBuffer::ReusePictureBuffer() {
  DCHECK_NE(UNUSED, state_);
  EGLDisplay egl_display = gl::GLSurfaceEGL::GetHardwareDisplay();

  if (stream_) {
    EGLBoolean result = eglStreamConsumerReleaseKHR(egl_display, stream_);
    RETURN_ON_FAILURE(result, "Could not release stream", false);
  }
  if (current_d3d_sample_) {
    dx11_decoding_texture_.Release();
    current_d3d_sample_.Release();
  }
  state_ = UNUSED;
  return true;
}

bool EGLStreamPictureBuffer::BindSampleToTexture(
    base::win::ScopedComPtr<IMFSample> sample) {
  DCHECK_EQ(BOUND, state_);
  state_ = IN_CLIENT;

  current_d3d_sample_ = sample;
  EGLDisplay egl_display = gl::GLSurfaceEGL::GetHardwareDisplay();

  base::win::ScopedComPtr<IMFMediaBuffer> output_buffer;
  HRESULT hr =
      current_d3d_sample_->GetBufferByIndex(0, output_buffer.Receive());
  RETURN_ON_HR_FAILURE(hr, "Failed to get buffer from output sample", false);

  base::win::ScopedComPtr<IMFDXGIBuffer> dxgi_buffer;
  hr = dxgi_buffer.QueryFrom(output_buffer.get());
  RETURN_ON_HR_FAILURE(hr, "Failed to get DXGIBuffer from output sample",
                       false);
  hr = dxgi_buffer->GetResource(IID_PPV_ARGS(dx11_decoding_texture_.Receive()));
  RETURN_ON_HR_FAILURE(hr, "Failed to get texture from output sample", false);
  UINT subresource;
  dxgi_buffer->GetSubresourceIndex(&subresource);

  EGLAttrib frame_attributes[] = {
      EGL_D3D_TEXTURE_SUBRESOURCE_ID_ANGLE, subresource, EGL_NONE,
  };

  EGLBoolean result = eglStreamPostD3DTextureNV12ANGLE(
      egl_display, stream_, static_cast<void*>(dx11_decoding_texture_.get()),
      frame_attributes);
  RETURN_ON_FAILURE(result, "Could not post texture", false);
  result = eglStreamConsumerAcquireKHR(egl_display, stream_);
  RETURN_ON_FAILURE(result, "Could not post acquire stream", false);
  return true;
}

EGLStreamCopyPictureBuffer::EGLStreamCopyPictureBuffer(
    const PictureBuffer& buffer)
    : DXVAPictureBuffer(buffer), stream_(nullptr) {}

EGLStreamCopyPictureBuffer::~EGLStreamCopyPictureBuffer() {
  // stream_ will be deleted by gl_image_.
}

bool EGLStreamCopyPictureBuffer::Initialize(
    const DXVAVideoDecodeAccelerator& decoder) {
  RETURN_ON_FAILURE(picture_buffer_.service_texture_ids().size() >= 2,
                    "Not enough texture ids provided", false);

  EGLDisplay egl_display = gl::GLSurfaceEGL::GetHardwareDisplay();
  const EGLint stream_attributes[] = {
      EGL_CONSUMER_LATENCY_USEC_KHR,
      0,
      EGL_CONSUMER_ACQUIRE_TIMEOUT_USEC_KHR,
      0,
      EGL_NONE,
  };
  stream_ = eglCreateStreamKHR(egl_display, stream_attributes);
  RETURN_ON_FAILURE(!!stream_, "Could not create stream", false);
  gl_image_ = make_scoped_refptr(new GLImageEGLStream(size(), stream_));
  gl::ScopedActiveTexture texture0(GL_TEXTURE0);
  gl::ScopedTextureBinder texture0_binder(
      GL_TEXTURE_EXTERNAL_OES, picture_buffer_.service_texture_ids()[0]);
  gl::ScopedActiveTexture texture1(GL_TEXTURE1);
  gl::ScopedTextureBinder texture1_binder(
      GL_TEXTURE_EXTERNAL_OES, picture_buffer_.service_texture_ids()[1]);

  EGLAttrib consumer_attributes[] = {
      EGL_COLOR_BUFFER_TYPE,
      EGL_YUV_BUFFER_EXT,
      EGL_YUV_NUMBER_OF_PLANES_EXT,
      2,
      EGL_YUV_PLANE0_TEXTURE_UNIT_NV,
      0,
      EGL_YUV_PLANE1_TEXTURE_UNIT_NV,
      1,
      EGL_NONE,
  };
  EGLBoolean result = eglStreamConsumerGLTextureExternalAttribsNV(
      egl_display, stream_, consumer_attributes);
  RETURN_ON_FAILURE(result, "Could not set stream consumer", false);

  EGLAttrib producer_attributes[] = {
      EGL_NONE,
  };

  result = eglCreateStreamProducerD3DTextureNV12ANGLE(egl_display, stream_,
                                                      producer_attributes);
  RETURN_ON_FAILURE(result, "Could not create stream producer", false);

  DCHECK(decoder.use_keyed_mutex_);
  D3D11_TEXTURE2D_DESC desc;
  desc.Width = picture_buffer_.size().width();
  desc.Height = picture_buffer_.size().height();
  desc.MipLevels = 1;
  desc.ArraySize = 1;
  desc.Format = DXGI_FORMAT_NV12;
  desc.SampleDesc.Count = 1;
  desc.SampleDesc.Quality = 0;
  desc.Usage = D3D11_USAGE_DEFAULT;
  desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
  desc.CPUAccessFlags = 0;
  desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;

  HRESULT hr = decoder.d3d11_device_->CreateTexture2D(
      &desc, nullptr, decoder_copy_texture_.Receive());
  RETURN_ON_HR_FAILURE(hr, "Failed to create texture", false);
  DCHECK(decoder.use_keyed_mutex_);
  hr = dx11_keyed_mutex_.QueryFrom(decoder_copy_texture_.get());
  RETURN_ON_HR_FAILURE(hr, "Failed to get keyed mutex", false);

  base::win::ScopedComPtr<IDXGIResource> resource;
  hr = resource.QueryFrom(decoder_copy_texture_.get());
  DCHECK(SUCCEEDED(hr));
  hr = resource->GetSharedHandle(&texture_share_handle_);
  RETURN_ON_FAILURE(SUCCEEDED(hr) && texture_share_handle_,
                    "Failed to query shared handle", false);

  hr = decoder.angle_device_->OpenSharedResource(
      texture_share_handle_, IID_PPV_ARGS(angle_copy_texture_.Receive()));
  RETURN_ON_HR_FAILURE(hr, "Failed to open shared resource", false);
  hr = egl_keyed_mutex_.QueryFrom(angle_copy_texture_.get());
  RETURN_ON_HR_FAILURE(hr, "Failed to get ANGLE mutex", false);
  return true;
}

bool EGLStreamCopyPictureBuffer::CopyOutputSampleDataToPictureBuffer(
    DXVAVideoDecodeAccelerator* decoder,
    IDirect3DSurface9* dest_surface,
    ID3D11Texture2D* dx11_texture,
    int input_buffer_id) {
  DCHECK_EQ(BOUND, state_);
  state_ = COPYING;
  DCHECK(dx11_texture);
  // Grab a reference on the decoder texture. This reference will be released
  // when we receive a notification that the copy was completed or when the
  // DXVAPictureBuffer instance is destroyed.
  dx11_decoding_texture_ = dx11_texture;
  decoder->CopyTexture(dx11_texture, decoder_copy_texture_.get(),
                       dx11_keyed_mutex_, keyed_mutex_value_, id(),
                       input_buffer_id);
  // The texture copy will acquire the current keyed mutex value and release
  // with the value + 1.
  keyed_mutex_value_++;
  return true;
}

bool EGLStreamCopyPictureBuffer::CopySurfaceComplete(
    IDirect3DSurface9* src_surface,
    IDirect3DSurface9* dest_surface) {
  DCHECK(!src_surface);
  DCHECK(!dest_surface);
  DCHECK_EQ(COPYING, state_);
  state_ = IN_CLIENT;

  dx11_decoding_texture_.Release();

  HRESULT hr =
      egl_keyed_mutex_->AcquireSync(keyed_mutex_value_, kAcquireSyncWaitMs);
  RETURN_ON_FAILURE(hr == S_OK, "Could not acquire sync mutex", false);

  EGLAttrib frame_attributes[] = {
      EGL_D3D_TEXTURE_SUBRESOURCE_ID_ANGLE, 0, EGL_NONE,
  };

  EGLDisplay egl_display = gl::GLSurfaceEGL::GetHardwareDisplay();

  EGLBoolean result = eglStreamPostD3DTextureNV12ANGLE(
      egl_display, stream_, static_cast<void*>(angle_copy_texture_.get()),
      frame_attributes);
  RETURN_ON_FAILURE(result, "Could not post stream", false);
  result = eglStreamConsumerAcquireKHR(egl_display, stream_);
  RETURN_ON_FAILURE(result, "Could not post acquire stream", false);

  return true;
}

bool EGLStreamCopyPictureBuffer::ReusePictureBuffer() {
  DCHECK_NE(UNUSED, state_);
  EGLDisplay egl_display = gl::GLSurfaceEGL::GetHardwareDisplay();

  if (state_ == IN_CLIENT) {
    HRESULT hr = egl_keyed_mutex_->ReleaseSync(++keyed_mutex_value_);
    RETURN_ON_FAILURE(hr == S_OK, "Could not release sync mutex", false);
  }
  state_ = UNUSED;

  if (stream_) {
    EGLBoolean result = eglStreamConsumerReleaseKHR(egl_display, stream_);
    RETURN_ON_FAILURE(result, "Could not release stream", false);
  }
  return true;
}

}  // namespace media
