blob: 3ed48637dde77eb5292572566fddd0a1174f1082 [file] [log] [blame]
// Copyright 2018 Google Inc. 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/uwp/decode_target_internal.h"
#include "starboard/configuration.h"
#include "starboard/log.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;
using starboard::shared::win32::CheckResult;
// {3C3A43AB-C69B-46C9-AA8D-B0CFFCD4596D}
const GUID kCobaltNv12BindChroma = {
0x3c3a43ab,
0xc69b,
0x46c9,
{0xaa, 0x8d, 0xb0, 0xcf, 0xfc, 0xd4, 0x59, 0x6d}};
// {C62BF18D-B5EE-46B1-9C31-F61BD8AE3B0D}
const GUID kCobaltDxgiBuffer = {
0Xc62bf18d,
0Xb5ee,
0X46b1,
{0X9c, 0X31, 0Xf6, 0X1b, 0Xd8, 0Xae, 0X3b, 0X0d}};
ComPtr<ID3D11Texture2D> CreateEmptyTexture(
const ComPtr<ID3D11Device>& d3d_device,
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;
texture_desc.Format = DXGI_FORMAT_NV12;
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));
}
ComPtr<ID3D11Texture2D> CreateVPXTexture(const ComPtr<ID3D11Device>& d3d_device,
const vpx_image_t* img,
BYTE* frame_buf_nv12) {
ComPtr<ID3D11Texture2D> texture;
D3D11_TEXTURE2D_DESC texture_desc = {};
texture_desc.Width = img->w;
texture_desc.Height = img->h;
texture_desc.MipLevels = 1;
texture_desc.ArraySize = 1;
texture_desc.Format = DXGI_FORMAT_NV12;
texture_desc.SampleDesc.Count = 1;
texture_desc.SampleDesc.Quality = 0;
texture_desc.Usage = D3D11_USAGE_DEFAULT;
texture_desc.BindFlags = D3D11_BIND_DECODER | D3D11_BIND_SHADER_RESOURCE;
BYTE* pData = frame_buf_nv12;
SbMemoryCopy(pData, img->planes[VPX_PLANE_Y], img->w * img->h);
pData += texture_desc.Width * texture_desc.Height;
unsigned int j = 0;
for (unsigned int i = 0; i < (img->w * img->h / 4); i++) {
pData[j++] = img->planes[VPX_PLANE_U][i];
pData[j++] = img->planes[VPX_PLANE_V][i];
}
D3D11_SUBRESOURCE_DATA tSData;
tSData.pSysMem = frame_buf_nv12;
tSData.SysMemPitch = img->w;
tSData.SysMemSlicePitch = img->w * img->h + (img->w * img->h / 2);
CheckResult(d3d_device->CreateTexture2D(&texture_desc, &tSData,
texture.GetAddressOf()));
return texture;
}
} // namespace
void SbDecodeTargetPrivate::CreateGLSurfacesNV12(
const ComPtr<ID3D11Texture2D>& texture,
int display_width,
int display_height) {
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 = display_height;
planeY->content_region.right = display_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 = texture->SetPrivateData(kCobaltNv12BindChroma, 0, nullptr);
SB_DCHECK(SUCCEEDED(hr));
surface[0] = eglCreatePbufferFromClientBuffer(display, EGL_D3D_TEXTURE_ANGLE,
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 = 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,
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;
hr = texture->SetPrivateData(kCobaltDxgiBuffer, 0, nullptr);
SB_DCHECK(SUCCEEDED(hr));
}
SbDecodeTargetPrivate::SbDecodeTargetPrivate(
const ComPtr<ID3D11Device>& d3d_device,
const vpx_image_t* img,
BYTE* frame_buf_nv12)
: refcount(1), d3d_device_(d3d_device) {
SbMemorySet(&info, 0, sizeof(info));
info.format = kSbDecodeTargetFormat2PlaneYUVNV12;
info.is_opaque = true;
info.width = img->w;
info.height = img->h;
d3d_texture = CreateVPXTexture(d3d_device, img, frame_buf_nv12);
CreateGLSurfacesNV12(d3d_texture, img->d_w, img->d_h);
}
SbDecodeTargetPrivate::SbDecodeTargetPrivate(
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)
: refcount(1) {
SbMemorySet(&info, 0, sizeof(info));
info.format = kSbDecodeTargetFormat2PlaneYUVNV12;
info.is_opaque = true;
info.width = video_area.right;
info.height = video_area.bottom;
d3d_texture = CreateEmptyTexture(d3d_device, info.width, info.height);
if (video_sample) {
UpdateTexture(d3d_texture, video_device, video_context, video_enumerator,
video_processor, video_sample, video_area);
}
CreateGLSurfacesNV12(d3d_texture, info.width, info.height);
}
SbDecodeTargetPrivate::~SbDecodeTargetPrivate() {
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]);
eglReleaseTexImage(display, surface[1], EGL_BACK_BUFFER);
eglDestroySurface(display, surface[1]);
}
bool SbDecodeTargetPrivate::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) {
// 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 (info.format != kSbDecodeTargetFormat2PlaneYUVNV12 ||
info.is_opaque != true || info.width != video_area.right ||
info.height != video_area.bottom) {
return false;
}
SB_UNREFERENCED_PARAMETER(d3d_device);
UpdateTexture(d3d_texture, video_device, video_context, video_enumerator,
video_processor, video_sample, video_area);
return true;
}
void SbDecodeTargetPrivate::AddRef() {
SbAtomicBarrier_Increment(&refcount, 1);
}
void SbDecodeTargetPrivate::Release() {
int new_count = SbAtomicBarrier_Increment(&refcount, -1);
SB_DCHECK(new_count >= 0);
if (new_count == 0) {
delete this;
}
}
void SbDecodeTargetRelease(SbDecodeTarget decode_target) {
if (SbDecodeTargetIsValid(decode_target)) {
decode_target->Release();
}
}
SbDecodeTargetFormat SbDecodeTargetGetFormat(SbDecodeTarget decode_target) {
// Note that kSbDecodeTargetFormat2PlaneYUVNV12 represents DXGI_FORMAT_NV12.
SB_DCHECK(kSbDecodeTargetFormat2PlaneYUVNV12 == decode_target->info.format);
return decode_target->info.format;
}
bool SbDecodeTargetGetInfo(SbDecodeTarget decode_target,
SbDecodeTargetInfo* out_info) {
if (!out_info || !SbMemoryIsZero(out_info, sizeof(*out_info))) {
SB_DCHECK(false) << "out_info must be zeroed out.";
return false;
}
SbMemoryCopy(out_info, &decode_target->info, sizeof(*out_info));
return true;
}