blob: 80859fc6cfa12bf8a2d2c342674567613e60fc8c [file] [log] [blame]
// Copyright 2018 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "starboard/shared/win32/hardware_decode_target_internal.h"
#include "starboard/common/log.h"
#include "starboard/configuration.h"
#include "starboard/memory.h"
#include "starboard/shared/win32/error_utils.h"
#include "third_party/angle/include/EGL/egl.h"
#include "third_party/angle/include/EGL/eglext.h"
#include "third_party/angle/include/GLES2/gl2.h"
#include "third_party/angle/include/GLES2/gl2ext.h"
namespace {
using Microsoft::WRL::ComPtr;
// {3C3A43AB-C69B-46C9-AA8D-B0CFFCD4596D}
const GUID kCobaltNv12BindChroma = {
0x3c3a43ab,
0xc69b,
0x46c9,
{0xaa, 0x8d, 0xb0, 0xcf, 0xfc, 0xd4, 0x59, 0x6d}};
ComPtr<ID3D11Texture2D> AllocateTexture(const ComPtr<ID3D11Device>& d3d_device,
SbDecodeTargetFormat format,
int width,
int height) {
ComPtr<ID3D11Texture2D> texture;
D3D11_TEXTURE2D_DESC texture_desc = {};
texture_desc.Width = width;
texture_desc.Height = height;
texture_desc.MipLevels = 1;
texture_desc.ArraySize = 1;
switch (format) {
case kSbDecodeTargetFormat2PlaneYUVNV12:
texture_desc.Format = DXGI_FORMAT_NV12;
break;
case kSbDecodeTargetFormat1PlaneRGBA:
texture_desc.Format = DXGI_FORMAT_R10G10B10A2_UNORM;
break;
default:
SB_NOTREACHED();
}
texture_desc.SampleDesc.Count = 1;
texture_desc.SampleDesc.Quality = 0;
texture_desc.Usage = D3D11_USAGE_DEFAULT;
texture_desc.BindFlags =
D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
CheckResult(d3d_device->CreateTexture2D(&texture_desc, nullptr,
texture.GetAddressOf()));
return texture;
}
void UpdateTexture(
const ComPtr<ID3D11Texture2D>& texture,
const ComPtr<ID3D11VideoDevice1>& video_device,
const ComPtr<ID3D11VideoContext>& video_context,
const ComPtr<ID3D11VideoProcessorEnumerator>& video_enumerator,
const ComPtr<ID3D11VideoProcessor>& video_processor,
const ComPtr<IMFSample>& video_sample,
const RECT& video_area) {
ComPtr<IMFMediaBuffer> media_buffer;
CheckResult(video_sample->GetBufferByIndex(0, media_buffer.GetAddressOf()));
ComPtr<IMFDXGIBuffer> dxgi_buffer;
CheckResult(media_buffer.As(&dxgi_buffer));
ComPtr<ID3D11Texture2D> input_texture;
CheckResult(dxgi_buffer->GetResource(IID_PPV_ARGS(&input_texture)));
// The VideoProcessor needs to know what subset of the decoded
// frame contains active pixels that should be displayed to the user.
video_context->VideoProcessorSetStreamSourceRect(video_processor.Get(), 0,
TRUE, &video_area);
D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC input_desc = {};
input_desc.ViewDimension = D3D11_VPIV_DIMENSION_TEXTURE2D;
input_desc.Texture2D.MipSlice = 0;
dxgi_buffer->GetSubresourceIndex(&input_desc.Texture2D.ArraySlice);
ComPtr<ID3D11VideoProcessorInputView> input_view;
CheckResult(video_device->CreateVideoProcessorInputView(
input_texture.Get(), video_enumerator.Get(), &input_desc,
input_view.GetAddressOf()));
D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC output_desc = {};
output_desc.ViewDimension = D3D11_VPOV_DIMENSION_TEXTURE2D;
output_desc.Texture2D.MipSlice = 0;
ComPtr<ID3D11VideoProcessorOutputView> output_view;
CheckResult(video_device->CreateVideoProcessorOutputView(
texture.Get(), video_enumerator.Get(), &output_desc,
output_view.GetAddressOf()));
// We have a single video stream, which is enabled for display.
D3D11_VIDEO_PROCESSOR_STREAM stream_info = {};
stream_info.Enable = TRUE;
stream_info.pInputSurface = input_view.Get();
CheckResult(video_context->VideoProcessorBlt(
video_processor.Get(), output_view.Get(), 0, 1, &stream_info));
}
} // namespace
HardwareDecodeTargetPrivate::HardwareDecodeTargetPrivate(
const ComPtr<ID3D11Device>& d3d_device,
const ComPtr<ID3D11VideoDevice1>& video_device,
const ComPtr<ID3D11VideoContext>& video_context,
const ComPtr<ID3D11VideoProcessorEnumerator>& video_enumerator,
const ComPtr<ID3D11VideoProcessor>& video_processor,
const ComPtr<IMFSample>& video_sample,
const RECT& video_area,
bool texture_RGBA)
: texture_RGBA_(texture_RGBA) {
memset(&info, 0, sizeof(info));
info.is_opaque = true;
info.width = video_area.right;
info.height = video_area.bottom;
if (texture_RGBA_) {
info.format = kSbDecodeTargetFormat1PlaneRGBA;
} else {
info.format = kSbDecodeTargetFormat2PlaneYUVNV12;
}
d3d_texture =
AllocateTexture(d3d_device, info.format, info.width, info.height);
if (video_sample) {
UpdateTexture(d3d_texture, video_device, video_context, video_enumerator,
video_processor, video_sample, video_area);
}
if (texture_RGBA_) {
InitTextureRGBA();
} else {
InitTextureYUV();
}
}
void HardwareDecodeTargetPrivate::InitTextureYUV() {
SbDecodeTargetInfoPlane* planeY = &(info.planes[kSbDecodeTargetPlaneY]);
SbDecodeTargetInfoPlane* planeUV = &(info.planes[kSbDecodeTargetPlaneUV]);
planeY->width = info.width;
planeY->height = info.height;
planeY->content_region.left = 0;
planeY->content_region.top = info.height;
planeY->content_region.right = info.width;
planeY->content_region.bottom = 0;
planeUV->width = info.width / 2;
planeUV->height = info.height / 2;
planeUV->content_region.left = planeY->content_region.left / 2;
planeUV->content_region.top = planeY->content_region.top / 2;
planeUV->content_region.right = planeY->content_region.right / 2;
planeUV->content_region.bottom = planeY->content_region.bottom / 2;
EGLint luma_texture_attributes[] = {EGL_WIDTH,
static_cast<EGLint>(info.width),
EGL_HEIGHT,
static_cast<EGLint>(info.height),
EGL_TEXTURE_TARGET,
EGL_TEXTURE_2D,
EGL_TEXTURE_FORMAT,
EGL_TEXTURE_RGBA,
EGL_NONE};
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
EGLConfig config;
EGLint attribute_list[] = {EGL_SURFACE_TYPE, // this must be first
EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
EGL_RED_SIZE,
8,
EGL_GREEN_SIZE,
8,
EGL_BLUE_SIZE,
8,
EGL_ALPHA_SIZE,
8,
EGL_BIND_TO_TEXTURE_RGBA,
EGL_TRUE,
EGL_RENDERABLE_TYPE,
EGL_OPENGL_ES2_BIT,
EGL_NONE};
EGLint num_configs;
bool ok = eglChooseConfig(display, attribute_list, &config, 1, &num_configs);
SB_DCHECK(ok);
SB_DCHECK(num_configs == 1);
GLuint gl_textures[2] = {0};
glGenTextures(2, gl_textures);
SB_DCHECK(glGetError() == GL_NO_ERROR);
// This tells ANGLE that the texture it creates should draw
// the luma channel on R8.
HRESULT hr = d3d_texture->SetPrivateData(kCobaltNv12BindChroma, 0, nullptr);
SB_DCHECK(SUCCEEDED(hr));
surface[0] = eglCreatePbufferFromClientBuffer(display, EGL_D3D_TEXTURE_ANGLE,
d3d_texture.Get(), config,
luma_texture_attributes);
SB_DCHECK(surface[0] != EGL_NO_SURFACE);
glBindTexture(GL_TEXTURE_2D, gl_textures[0]);
SB_DCHECK(glGetError() == GL_NO_ERROR);
ok = eglBindTexImage(display, surface[0], EGL_BACK_BUFFER);
SB_DCHECK(ok);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
planeY->texture = gl_textures[0];
planeY->gl_texture_target = GL_TEXTURE_2D;
planeY->gl_texture_format = GL_RED_EXT;
// This tells ANGLE that the texture it creates should draw
// the chroma channel on R8G8.
bool bind_chroma = true;
hr = d3d_texture->SetPrivateData(kCobaltNv12BindChroma, 1, &bind_chroma);
SB_DCHECK(SUCCEEDED(hr));
EGLint chroma_texture_attributes[] = {EGL_WIDTH,
static_cast<EGLint>(info.width) / 2,
EGL_HEIGHT,
static_cast<EGLint>(info.height) / 2,
EGL_TEXTURE_TARGET,
EGL_TEXTURE_2D,
EGL_TEXTURE_FORMAT,
EGL_TEXTURE_RGBA,
EGL_NONE};
surface[1] = eglCreatePbufferFromClientBuffer(display, EGL_D3D_TEXTURE_ANGLE,
d3d_texture.Get(), config,
chroma_texture_attributes);
SB_DCHECK(surface[1] != EGL_NO_SURFACE);
glBindTexture(GL_TEXTURE_2D, gl_textures[1]);
SB_DCHECK(glGetError() == GL_NO_ERROR);
ok = eglBindTexImage(display, surface[1], EGL_BACK_BUFFER);
SB_DCHECK(ok);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
planeUV->texture = gl_textures[1];
planeUV->gl_texture_target = GL_TEXTURE_2D;
planeUV->gl_texture_format = GL_RG_EXT;
}
void HardwareDecodeTargetPrivate::InitTextureRGBA() {
SbDecodeTargetInfoPlane* planeRGBA = &(info.planes[kSbDecodeTargetPlaneRGBA]);
planeRGBA->width = info.width;
planeRGBA->height = info.height;
planeRGBA->content_region.left = 0;
planeRGBA->content_region.top = info.height;
planeRGBA->content_region.right = info.width;
planeRGBA->content_region.bottom = 0;
EGLint RGBA_texture_attributes[] = {EGL_WIDTH,
static_cast<EGLint>(info.width),
EGL_HEIGHT,
static_cast<EGLint>(info.height),
EGL_TEXTURE_TARGET,
EGL_TEXTURE_2D,
EGL_TEXTURE_FORMAT,
EGL_TEXTURE_RGBA,
EGL_NONE};
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
EGLConfig config;
EGLint attribute_list[] = {EGL_SURFACE_TYPE, // this must be first
EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
EGL_RED_SIZE,
10,
EGL_GREEN_SIZE,
10,
EGL_BLUE_SIZE,
10,
EGL_ALPHA_SIZE,
2,
EGL_BIND_TO_TEXTURE_RGBA,
EGL_TRUE,
EGL_RENDERABLE_TYPE,
EGL_OPENGL_ES2_BIT,
EGL_NONE};
EGLint num_configs;
bool ok = eglChooseConfig(display, attribute_list, &config, 1, &num_configs);
SB_DCHECK(ok);
SB_DCHECK(num_configs == 1);
GLuint gl_textures[2] = {0};
glGenTextures(2, gl_textures);
SB_DCHECK(glGetError() == GL_NO_ERROR);
surface[0] = eglCreatePbufferFromClientBuffer(display, EGL_D3D_TEXTURE_ANGLE,
d3d_texture.Get(), config,
RGBA_texture_attributes);
SB_DCHECK(surface[0] != EGL_NO_SURFACE);
glBindTexture(GL_TEXTURE_2D, gl_textures[0]);
SB_DCHECK(glGetError() == GL_NO_ERROR);
ok = eglBindTexImage(display, surface[0], EGL_BACK_BUFFER);
SB_DCHECK(ok);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
planeRGBA->texture = gl_textures[0];
planeRGBA->gl_texture_target = GL_TEXTURE_2D;
planeRGBA->gl_texture_format = GL_RED_EXT;
}
HardwareDecodeTargetPrivate::~HardwareDecodeTargetPrivate() {
if (!texture_RGBA_) {
glDeleteTextures(1, &(info.planes[kSbDecodeTargetPlaneY].texture));
glDeleteTextures(1, &(info.planes[kSbDecodeTargetPlaneUV].texture));
}
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglReleaseTexImage(display, surface[0], EGL_BACK_BUFFER);
eglDestroySurface(display, surface[0]);
if (!texture_RGBA_) {
eglReleaseTexImage(display, surface[1], EGL_BACK_BUFFER);
eglDestroySurface(display, surface[1]);
}
}
bool HardwareDecodeTargetPrivate::Update(
const ComPtr<ID3D11Device>& d3d_device,
const ComPtr<ID3D11VideoDevice1>& video_device,
const ComPtr<ID3D11VideoContext>& video_context,
const ComPtr<ID3D11VideoProcessorEnumerator>& video_enumerator,
const ComPtr<ID3D11VideoProcessor>& video_processor,
const ComPtr<IMFSample>& video_sample,
const RECT& video_area,
bool texture_RGBA) {
// Only allow updating if this is the only reference. Otherwise the update
// may change something that's currently being used.
if (SbAtomicNoBarrier_Load(&refcount) > 1) {
return false;
}
// The decode target info must be compatible. The resolution should match
// exactly, otherwise the shader may sample invalid texels along the
// texture border.
if ((texture_RGBA ^ (info.format != kSbDecodeTargetFormat2PlaneYUVNV12)) ||
(texture_RGBA ^ (info.format == kSbDecodeTargetFormat1PlaneRGBA)) ||
info.is_opaque != true || info.width != video_area.right ||
info.height != video_area.bottom) {
return false;
}
UpdateTexture(d3d_texture, video_device, video_context, video_enumerator,
video_processor, video_sample, video_area);
return true;
}