blob: 41993b384cf83f53d6374c4599eb684153452d6a [file] [log] [blame]
//
// Copyright 2014 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// SurfaceD3D.cpp: D3D implementation of an EGL surface
#include <Mfobjects.h>
#include "libANGLE/renderer/d3d/SurfaceD3D.h"
#include "libANGLE/Context.h"
#include "libANGLE/Display.h"
#include "libANGLE/Surface.h"
#include "libANGLE/renderer/Format.h"
#include "libANGLE/renderer/d3d/DisplayD3D.h"
#include "libANGLE/renderer/d3d/RenderTargetD3D.h"
#include "libANGLE/renderer/d3d/RendererD3D.h"
#include "libANGLE/renderer/d3d/SwapChainD3D.h"
#include "libANGLE/renderer/d3d/d3d11/formatutils11.h"
#include <EGL/eglext.h>
#include <tchar.h>
#include <algorithm>
// A key for ID3D11DeviceChild. The value should be a bool.
// When set and true, indicates that the NV12 texture passed
// in eglCreatePbufferFromClientBuffer EGL_D3D_TEXTURE_ANGLE should
// draw it's chroma component when a ShaderResourceView is created for it.
// If absent or false, the luma component is drawn.
//
// The value is fetched from ID3D11DeviceChild and stored before
// eglCreatePbufferFromClientBuffer returns.
//
// {3C3A43AB-C69B-46C9-AA8D-B0CFFCD4596D}
static const GUID kCobaltNv12BindChroma = {0x3c3a43ab,
0xc69b,
0x46c9,
{0xaa, 0x8d, 0xb0, 0xcf, 0xfc, 0xd4, 0x59, 0x6d}};
namespace rx
{
SurfaceD3D::SurfaceD3D(const egl::SurfaceState &state,
RendererD3D *renderer,
egl::Display *display,
EGLNativeWindowType window,
EGLenum buftype,
EGLClientBuffer clientBuffer,
const egl::AttributeMap &attribs)
: SurfaceImpl(state),
mRenderer(renderer),
mDisplay(display),
mFixedSize(window == nullptr || attribs.get(EGL_FIXED_SIZE_ANGLE, EGL_FALSE) == EGL_TRUE),
mFixedWidth(0),
mFixedHeight(0),
mOrientation(static_cast<EGLint>(attribs.get(EGL_SURFACE_ORIENTATION_ANGLE, 0))),
mRenderTargetFormat(state.config->renderTargetFormat),
mDepthStencilFormat(state.config->depthStencilFormat),
mColorFormat(nullptr),
mSwapChain(nullptr),
mSwapIntervalDirty(true),
mNativeWindow(renderer->createNativeWindow(window, state.config, attribs)),
mWidth(static_cast<EGLint>(attribs.get(EGL_WIDTH, 0))),
mHeight(static_cast<EGLint>(attribs.get(EGL_HEIGHT, 0))),
mSwapInterval(1),
mShareHandle(0),
mD3DTexture(nullptr),
mBuftype(buftype),
mBindChroma(false)
{
if (window != nullptr && !mFixedSize)
{
mWidth = -1;
mHeight = -1;
}
if (mFixedSize)
{
mFixedWidth = mWidth;
mFixedHeight = mHeight;
}
switch (buftype)
{
case EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE:
mShareHandle = static_cast<HANDLE>(clientBuffer);
break;
case EGL_D3D_TEXTURE_ANGLE:
{
mD3DTexture = static_cast<IUnknown *>(clientBuffer);
ASSERT(mD3DTexture != nullptr);
mD3DTexture->AddRef();
break;
}
default:
break;
}
}
SurfaceD3D::~SurfaceD3D()
{
releaseSwapChain();
SafeDelete(mNativeWindow);
SafeRelease(mD3DTexture);
}
void SurfaceD3D::releaseSwapChain()
{
SafeDelete(mSwapChain);
}
egl::Error SurfaceD3D::initialize(const egl::Display *display)
{
if (mNativeWindow->getNativeWindow())
{
if (!mNativeWindow->initialize())
{
return egl::EglBadSurface();
}
}
if (mBuftype == EGL_D3D_TEXTURE_ANGLE)
{
UINT out;
HRESULT hr = static_cast<ID3D11DeviceChild *>(mD3DTexture)
->GetPrivateData(kCobaltNv12BindChroma, &out, nullptr);
mBindChroma = (SUCCEEDED(hr)) && (out != 0);
ANGLE_TRY(mRenderer->getD3DTextureInfo(mState.config, mD3DTexture, mState.attributes,
&mFixedWidth, &mFixedHeight, nullptr, nullptr,
&mColorFormat));
if (mState.attributes.contains(EGL_GL_COLORSPACE))
{
if (mColorFormat->id != angle::FormatID::R8G8B8A8_TYPELESS &&
mColorFormat->id != angle::FormatID::B8G8R8A8_TYPELESS)
{
return egl::EglBadMatch()
<< "EGL_GL_COLORSPACE may only be specified for TYPELESS textures";
}
}
if (mColorFormat->id == angle::FormatID::R8G8B8A8_TYPELESS)
{
EGLAttrib colorspace =
mState.attributes.get(EGL_GL_COLORSPACE, EGL_GL_COLORSPACE_LINEAR);
if (colorspace == EGL_GL_COLORSPACE_SRGB)
{
mColorFormat = &angle::Format::Get(angle::FormatID::R8G8B8A8_TYPELESS_SRGB);
}
}
if (mColorFormat->id == angle::FormatID::B8G8R8A8_TYPELESS)
{
EGLAttrib colorspace =
mState.attributes.get(EGL_GL_COLORSPACE, EGL_GL_COLORSPACE_LINEAR);
if (colorspace == EGL_GL_COLORSPACE_SRGB)
{
mColorFormat = &angle::Format::Get(angle::FormatID::B8G8R8A8_TYPELESS_SRGB);
}
}
mRenderTargetFormat = mColorFormat->fboImplementationInternalFormat;
ID3D11Texture2D *d3Texture = static_cast<ID3D11Texture2D *>(mD3DTexture);
D3D11_TEXTURE2D_DESC texture_desc;
d3Texture->GetDesc(&texture_desc);
if (texture_desc.Format == DXGI_FORMAT_NV12)
{
// NV12 textures cannot be rendered to,
// so don't proceed to making a swap chain.
mWidth = mFixedWidth;
mHeight = mFixedHeight;
return egl::NoError();
}
}
ANGLE_TRY(resetSwapChain(display));
return egl::NoError();
}
FramebufferImpl *SurfaceD3D::createDefaultFramebuffer(const gl::Context *context,
const gl::FramebufferState &data)
{
return mRenderer->createDefaultFramebuffer(data);
}
egl::Error SurfaceD3D::bindTexImage(const gl::Context *, gl::Texture *, EGLint)
{
return egl::NoError();
}
egl::Error SurfaceD3D::releaseTexImage(const gl::Context *, EGLint)
{
return egl::NoError();
}
egl::Error SurfaceD3D::getSyncValues(EGLuint64KHR *ust, EGLuint64KHR *msc, EGLuint64KHR *sbc)
{
if (!mState.directComposition)
{
return egl::EglBadSurface()
<< "getSyncValues: surface requires Direct Composition to be enabled";
}
return mSwapChain->getSyncValues(ust, msc, sbc);
}
egl::Error SurfaceD3D::resetSwapChain(const egl::Display *display)
{
ASSERT(!mSwapChain);
int width;
int height;
if (!mFixedSize)
{
RECT windowRect;
if (!mNativeWindow->getClientRect(&windowRect))
{
ASSERT(false);
return egl::EglBadSurface() << "Could not retrieve the window dimensions";
}
width = windowRect.right - windowRect.left;
height = windowRect.bottom - windowRect.top;
}
else
{
// non-window surface - size is determined at creation
width = mFixedWidth;
height = mFixedHeight;
}
mSwapChain =
mRenderer->createSwapChain(mNativeWindow, mShareHandle, mD3DTexture, mRenderTargetFormat,
mDepthStencilFormat, mOrientation, mState.config->samples);
if (!mSwapChain)
{
return egl::EglBadAlloc();
}
// This is a bit risky to pass the proxy context here, but it can happen at almost any time.
DisplayD3D *displayD3D = GetImplAs<DisplayD3D>(display);
egl::Error error = resetSwapChain(displayD3D, width, height);
if (error.isError())
{
SafeDelete(mSwapChain);
return error;
}
return egl::NoError();
}
egl::Error SurfaceD3D::resizeSwapChain(DisplayD3D *displayD3D,
int backbufferWidth,
int backbufferHeight)
{
ASSERT(backbufferWidth >= 0 && backbufferHeight >= 0);
ASSERT(mSwapChain);
EGLint status =
mSwapChain->resize(displayD3D, std::max(1, backbufferWidth), std::max(1, backbufferHeight));
if (status == EGL_CONTEXT_LOST)
{
mDisplay->notifyDeviceLost();
return egl::Error(status);
}
else if (status != EGL_SUCCESS)
{
return egl::Error(status);
}
mWidth = backbufferWidth;
mHeight = backbufferHeight;
return egl::NoError();
}
egl::Error SurfaceD3D::resetSwapChain(DisplayD3D *displayD3D,
int backbufferWidth,
int backbufferHeight)
{
ASSERT(backbufferWidth >= 0 && backbufferHeight >= 0);
ASSERT(mSwapChain);
EGLint status = mSwapChain->reset(displayD3D, std::max(1, backbufferWidth),
std::max(1, backbufferHeight), mSwapInterval);
if (status == EGL_CONTEXT_LOST)
{
mRenderer->notifyDeviceLost();
return egl::Error(status);
}
else if (status != EGL_SUCCESS)
{
return egl::Error(status);
}
mWidth = backbufferWidth;
mHeight = backbufferHeight;
mSwapIntervalDirty = false;
return egl::NoError();
}
egl::Error SurfaceD3D::swapRect(DisplayD3D *displayD3D,
EGLint x,
EGLint y,
EGLint width,
EGLint height)
{
if (!mSwapChain)
{
return egl::NoError();
}
if (x + width > mWidth)
{
width = mWidth - x;
}
if (y + height > mHeight)
{
height = mHeight - y;
}
if (width != 0 && height != 0)
{
EGLint status = mSwapChain->swapRect(displayD3D, x, y, width, height);
if (status == EGL_CONTEXT_LOST)
{
mRenderer->notifyDeviceLost();
return egl::Error(status);
}
else if (status != EGL_SUCCESS)
{
return egl::Error(status);
}
}
ANGLE_TRY(checkForOutOfDateSwapChain(displayD3D));
return egl::NoError();
}
egl::Error SurfaceD3D::checkForOutOfDateSwapChain(DisplayD3D *displayD3D)
{
RECT client;
int clientWidth = getWidth();
int clientHeight = getHeight();
bool sizeDirty = false;
if (!mFixedSize && !mNativeWindow->isIconic())
{
// The window is automatically resized to 150x22 when it's minimized, but the swapchain
// shouldn't be resized because that's not a useful size to render to.
if (!mNativeWindow->getClientRect(&client))
{
UNREACHABLE();
return egl::NoError();
}
// Grow the buffer now, if the window has grown. We need to grow now to avoid losing
// information.
clientWidth = client.right - client.left;
clientHeight = client.bottom - client.top;
sizeDirty = clientWidth != getWidth() || clientHeight != getHeight();
}
else if (mFixedSize)
{
clientWidth = mFixedWidth;
clientHeight = mFixedHeight;
sizeDirty = mFixedWidth != getWidth() || mFixedHeight != getHeight();
}
if (mSwapIntervalDirty)
{
ANGLE_TRY(resetSwapChain(displayD3D, clientWidth, clientHeight));
}
else if (sizeDirty)
{
ANGLE_TRY(resizeSwapChain(displayD3D, clientWidth, clientHeight));
}
return egl::NoError();
}
egl::Error SurfaceD3D::swap(const gl::Context *context)
{
DisplayD3D *displayD3D = GetImplAs<DisplayD3D>(context->getDisplay());
return swapRect(displayD3D, 0, 0, mWidth, mHeight);
}
egl::Error SurfaceD3D::postSubBuffer(const gl::Context *context,
EGLint x,
EGLint y,
EGLint width,
EGLint height)
{
DisplayD3D *displayD3D = GetImplAs<DisplayD3D>(context->getDisplay());
return swapRect(displayD3D, x, y, width, height);
}
rx::SwapChainD3D *SurfaceD3D::getSwapChain() const
{
return mSwapChain;
}
void SurfaceD3D::setSwapInterval(EGLint interval)
{
if (mSwapInterval == interval)
{
return;
}
mSwapInterval = interval;
mSwapIntervalDirty = true;
}
void SurfaceD3D::setFixedWidth(EGLint width)
{
mFixedWidth = width;
}
void SurfaceD3D::setFixedHeight(EGLint height)
{
mFixedHeight = height;
}
EGLint SurfaceD3D::getWidth() const
{
return mWidth;
}
EGLint SurfaceD3D::getHeight() const
{
return mHeight;
}
EGLint SurfaceD3D::isPostSubBufferSupported() const
{
// post sub buffer is always possible on D3D surfaces
return EGL_TRUE;
}
EGLint SurfaceD3D::getSwapBehavior() const
{
return EGL_BUFFER_PRESERVED;
}
egl::Error SurfaceD3D::querySurfacePointerANGLE(EGLint attribute, void **value)
{
if (attribute == EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE)
{
*value = mSwapChain->getShareHandle();
}
else if (attribute == EGL_DXGI_KEYED_MUTEX_ANGLE)
{
*value = mSwapChain->getKeyedMutex();
}
else
UNREACHABLE();
return egl::NoError();
}
const angle::Format *SurfaceD3D::getD3DTextureColorFormat() const
{
return mColorFormat;
}
angle::Result SurfaceD3D::getAttachmentRenderTarget(const gl::Context *context,
GLenum binding,
const gl::ImageIndex &imageIndex,
GLsizei samples,
FramebufferAttachmentRenderTarget **rtOut)
{
if (binding == GL_BACK)
{
*rtOut = mSwapChain->getColorRenderTarget();
}
else
{
*rtOut = mSwapChain->getDepthStencilRenderTarget();
}
return angle::Result::Continue;
}
angle::Result SurfaceD3D::initializeContents(const gl::Context *context,
const gl::ImageIndex &imageIndex)
{
if (mState.config->renderTargetFormat != GL_NONE)
{
ANGLE_TRY(mRenderer->initRenderTarget(context, mSwapChain->getColorRenderTarget()));
}
if (mState.config->depthStencilFormat != GL_NONE)
{
ANGLE_TRY(mRenderer->initRenderTarget(context, mSwapChain->getDepthStencilRenderTarget()));
}
return angle::Result::Continue;
}
WindowSurfaceD3D::WindowSurfaceD3D(const egl::SurfaceState &state,
RendererD3D *renderer,
egl::Display *display,
EGLNativeWindowType window,
const egl::AttributeMap &attribs)
: SurfaceD3D(state, renderer, display, window, 0, static_cast<EGLClientBuffer>(0), attribs)
{}
WindowSurfaceD3D::~WindowSurfaceD3D() {}
PbufferSurfaceD3D::PbufferSurfaceD3D(const egl::SurfaceState &state,
RendererD3D *renderer,
egl::Display *display,
EGLenum buftype,
EGLClientBuffer clientBuffer,
const egl::AttributeMap &attribs)
: SurfaceD3D(state,
renderer,
display,
static_cast<EGLNativeWindowType>(0),
buftype,
clientBuffer,
attribs)
{}
PbufferSurfaceD3D::~PbufferSurfaceD3D() {}
} // namespace rx