// // Copyright (c) 2015 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. // // DXGISwapChainWindowSurfaceWGL.cpp: WGL implementation of egl::Surface for windows using a DXGI // swapchain. #include "libANGLE/renderer/gl/wgl/DXGISwapChainWindowSurfaceWGL.h" #include "libANGLE/formatutils.h" #include "libANGLE/renderer/gl/FramebufferGL.h" #include "libANGLE/renderer/gl/TextureGL.h" #include "libANGLE/renderer/gl/RendererGL.h" #include "libANGLE/renderer/gl/StateManagerGL.h" #include "libANGLE/renderer/gl/wgl/DisplayWGL.h" #include "libANGLE/renderer/gl/wgl/FunctionsWGL.h" #include namespace rx { DXGISwapChainWindowSurfaceWGL::DXGISwapChainWindowSurfaceWGL(const egl::SurfaceState &state, RendererGL *renderer, EGLNativeWindowType window, ID3D11Device *device, HANDLE deviceHandle, HGLRC wglContext, HDC deviceContext, const FunctionsGL *functionsGL, const FunctionsWGL *functionsWGL, EGLint orientation) : SurfaceGL(state, renderer), mWindow(window), mStateManager(renderer->getStateManager()), mWorkarounds(renderer->getWorkarounds()), mRenderer(renderer), mFunctionsGL(functionsGL), mFunctionsWGL(functionsWGL), mDevice(device), mDeviceHandle(deviceHandle), mWGLDevice(deviceContext), mWGLContext(wglContext), mSwapChainFormat(DXGI_FORMAT_UNKNOWN), mSwapChainFlags(0), mDepthBufferFormat(GL_NONE), mFirstSwap(true), mSwapChain(nullptr), mSwapChain1(nullptr), mColorRenderbufferID(0), mRenderbufferBufferHandle(nullptr), mDepthRenderbufferID(0), mFramebufferID(0), mTextureID(0), mTextureHandle(nullptr), mWidth(0), mHeight(0), mSwapInterval(1), mOrientation(orientation) { } DXGISwapChainWindowSurfaceWGL::~DXGISwapChainWindowSurfaceWGL() { if (mRenderbufferBufferHandle != nullptr) { mFunctionsWGL->dxUnlockObjectsNV(mDeviceHandle, 1, &mRenderbufferBufferHandle); mFunctionsWGL->dxUnregisterObjectNV(mDeviceHandle, mRenderbufferBufferHandle); } if (mColorRenderbufferID != 0) { mStateManager->deleteRenderbuffer(mColorRenderbufferID); mColorRenderbufferID = 0; } if (mDepthRenderbufferID != 0) { mStateManager->deleteRenderbuffer(mDepthRenderbufferID); mDepthRenderbufferID = 0; } SafeRelease(mSwapChain); SafeRelease(mSwapChain1); } egl::Error DXGISwapChainWindowSurfaceWGL::initialize() { if (mOrientation != EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE) { // TODO(geofflang): Support the orientation extensions fully. Currently only inverting Y is // supported. To support all orientations, an intermediate framebuffer will be needed with // a blit before swap. return egl::Error(EGL_BAD_ATTRIBUTE, "DXGISwapChainWindowSurfaceWGL requires an orientation of " "EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE."); } RECT rect; if (!GetClientRect(mWindow, &rect)) { return egl::Error(EGL_BAD_NATIVE_WINDOW, "Failed to query the window size."); } mWidth = rect.right - rect.left; mHeight = rect.bottom - rect.top; mSwapChainFormat = DXGI_FORMAT_R8G8B8A8_UNORM; mSwapChainFlags = 0; mDepthBufferFormat = GL_DEPTH24_STENCIL8; mFunctionsGL->genFramebuffers(1, &mFramebufferID); mFunctionsGL->genRenderbuffers(1, &mColorRenderbufferID); mFunctionsGL->genRenderbuffers(1, &mDepthRenderbufferID); return createSwapChain(); } egl::Error DXGISwapChainWindowSurfaceWGL::makeCurrent() { if (!mFunctionsWGL->makeCurrent(mWGLDevice, mWGLContext)) { // TODO: What error type here? return egl::Error(EGL_CONTEXT_LOST, "Failed to make the WGL context current."); } return egl::Error(EGL_SUCCESS); } egl::Error DXGISwapChainWindowSurfaceWGL::swap() { mFunctionsGL->flush(); egl::Error error = setObjectsLocked(false); if (error.isError()) { return error; } HRESULT result = mSwapChain->Present(mSwapInterval, 0); mFirstSwap = false; error = setObjectsLocked(true); if (error.isError()) { return error; } if (FAILED(result)) { return egl::Error(EGL_BAD_ALLOC, "Failed to present swap chain, result: 0x%X", result); } return checkForResize(); } egl::Error DXGISwapChainWindowSurfaceWGL::postSubBuffer(EGLint x, EGLint y, EGLint width, EGLint height) { ASSERT(mSwapChain1 != nullptr); mFunctionsGL->flush(); egl::Error error = setObjectsLocked(false); if (error.isError()) { return error; } HRESULT result = S_OK; if (mFirstSwap) { result = mSwapChain1->Present(mSwapInterval, 0); mFirstSwap = false; } else { RECT rect = {static_cast(x), static_cast(mHeight - y - height), static_cast(x + width), static_cast(mHeight - y)}; DXGI_PRESENT_PARAMETERS params = {1, &rect, nullptr, nullptr}; result = mSwapChain1->Present1(mSwapInterval, 0, ¶ms); } error = setObjectsLocked(true); if (error.isError()) { return error; } if (FAILED(result)) { return egl::Error(EGL_BAD_ALLOC, "Failed to present swap chain, result: 0x%X", result); } return checkForResize(); } egl::Error DXGISwapChainWindowSurfaceWGL::querySurfacePointerANGLE(EGLint attribute, void **value) { UNREACHABLE(); return egl::Error(EGL_SUCCESS); } egl::Error DXGISwapChainWindowSurfaceWGL::bindTexImage(gl::Texture *texture, EGLint buffer) { ASSERT(mTextureHandle == nullptr); const TextureGL *textureGL = GetImplAs(texture); GLuint textureID = textureGL->getTextureID(); ID3D11Texture2D *colorBuffer = nullptr; HRESULT result = mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast(&colorBuffer)); if (FAILED(result)) { return egl::Error(EGL_BAD_ALLOC, "Failed to query texture from swap chain, result: 0x%X", result); } mTextureHandle = mFunctionsWGL->dxRegisterObjectNV(mDeviceHandle, colorBuffer, textureID, GL_TEXTURE_2D, WGL_ACCESS_READ_WRITE_NV); SafeRelease(colorBuffer); if (mTextureHandle == nullptr) { return egl::Error(EGL_BAD_ALLOC, "Failed to register D3D object, error: 0x%08x.", HRESULT_CODE(GetLastError())); } if (!mFunctionsWGL->dxLockObjectsNV(mDeviceHandle, 1, &mTextureHandle)) { mFunctionsWGL->dxUnregisterObjectNV(mDeviceHandle, mTextureHandle); mTextureHandle = nullptr; return egl::Error(EGL_BAD_ALLOC, "Failed to lock D3D object, error: 0x%08x.", HRESULT_CODE(GetLastError())); } mTextureID = textureID; return egl::Error(EGL_SUCCESS); } egl::Error DXGISwapChainWindowSurfaceWGL::releaseTexImage(EGLint buffer) { ASSERT(mTextureHandle != nullptr); if (!mFunctionsWGL->dxUnlockObjectsNV(mDeviceHandle, 1, &mTextureHandle)) { return egl::Error(EGL_BAD_ALLOC, "Failed to unlock D3D object, error: 0x%08x.", HRESULT_CODE(GetLastError())); } if (!mFunctionsWGL->dxUnregisterObjectNV(mDeviceHandle, mTextureHandle)) { return egl::Error(EGL_BAD_ALLOC, "Failed to unregister D3D object, error: 0x%08x.", HRESULT_CODE(GetLastError())); } mTextureID = 0; mTextureHandle = nullptr; return egl::Error(EGL_SUCCESS); } void DXGISwapChainWindowSurfaceWGL::setSwapInterval(EGLint interval) { mSwapInterval = interval; } EGLint DXGISwapChainWindowSurfaceWGL::getWidth() const { return static_cast(mWidth); } EGLint DXGISwapChainWindowSurfaceWGL::getHeight() const { return static_cast(mHeight); } EGLint DXGISwapChainWindowSurfaceWGL::isPostSubBufferSupported() const { return mSwapChain1 != nullptr; } EGLint DXGISwapChainWindowSurfaceWGL::getSwapBehavior() const { return EGL_BUFFER_DESTROYED; } FramebufferImpl *DXGISwapChainWindowSurfaceWGL::createDefaultFramebuffer( const gl::FramebufferState &data) { return new FramebufferGL(mFramebufferID, data, mFunctionsGL, mWorkarounds, mRenderer->getBlitter(), mStateManager); } egl::Error DXGISwapChainWindowSurfaceWGL::setObjectsLocked(bool locked) { if (mRenderbufferBufferHandle == nullptr) { ASSERT(mTextureHandle == nullptr); return egl::Error(EGL_SUCCESS); } HANDLE resources[] = { mRenderbufferBufferHandle, mTextureHandle, }; GLint count = (mTextureHandle != nullptr) ? 2 : 1; if (locked) { if (!mFunctionsWGL->dxLockObjectsNV(mDeviceHandle, count, resources)) { return egl::Error(EGL_BAD_ALLOC, "Failed to lock object, error: 0x%08x.", HRESULT_CODE(GetLastError())); } } else { if (!mFunctionsWGL->dxUnlockObjectsNV(mDeviceHandle, count, resources)) { return egl::Error(EGL_BAD_ALLOC, "Failed to lock object, error: 0x%08x.", HRESULT_CODE(GetLastError())); } } return egl::Error(EGL_SUCCESS); } egl::Error DXGISwapChainWindowSurfaceWGL::checkForResize() { RECT rect; if (!GetClientRect(mWindow, &rect)) { return egl::Error(EGL_BAD_NATIVE_WINDOW, "Failed to query the window size."); } size_t newWidth = rect.right - rect.left; size_t newHeight = rect.bottom - rect.top; if (newWidth != mWidth || newHeight != mHeight) { mWidth = newWidth; mHeight = newHeight; // TODO(geofflang): Handle resize by resizing the swap chain instead of re-creating it. egl::Error error = createSwapChain(); if (error.isError()) { return error; } } return egl::Error(EGL_SUCCESS); } static IDXGIFactory *GetDXGIFactoryFromDevice(ID3D11Device *device) { IDXGIDevice *dxgiDevice = nullptr; HRESULT result = device->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast(&dxgiDevice)); if (FAILED(result)) { return nullptr; } IDXGIAdapter *dxgiAdapter = nullptr; result = dxgiDevice->GetParent(__uuidof(IDXGIAdapter), reinterpret_cast(&dxgiAdapter)); SafeRelease(dxgiDevice); if (FAILED(result)) { return nullptr; } IDXGIFactory *dxgiFactory = nullptr; result = dxgiAdapter->GetParent(__uuidof(IDXGIFactory), reinterpret_cast(&dxgiFactory)); SafeRelease(dxgiAdapter); if (FAILED(result)) { return nullptr; } return dxgiFactory; } egl::Error DXGISwapChainWindowSurfaceWGL::createSwapChain() { egl::Error error = setObjectsLocked(false); if (error.isError()) { return error; } if (mRenderbufferBufferHandle) { mFunctionsWGL->dxUnregisterObjectNV(mDeviceHandle, mRenderbufferBufferHandle); mRenderbufferBufferHandle = nullptr; } // If this surface is bound to a texture, unregister it. bool hadBoundSurface = (mTextureHandle != nullptr); if (hadBoundSurface) { mFunctionsWGL->dxUnregisterObjectNV(mDeviceHandle, mTextureHandle); mTextureHandle = nullptr; } IDXGIFactory *dxgiFactory = GetDXGIFactoryFromDevice(mDevice); if (dxgiFactory == nullptr) { return egl::Error(EGL_BAD_NATIVE_WINDOW, "Failed to query the DXGIFactory."); } IDXGIFactory2 *dxgiFactory2 = nullptr; HRESULT result = dxgiFactory->QueryInterface(__uuidof(IDXGIFactory2), reinterpret_cast(&dxgiFactory2)); if (SUCCEEDED(result)) { ASSERT(dxgiFactory2 != nullptr); DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {0}; swapChainDesc.BufferCount = 1; swapChainDesc.Format = mSwapChainFormat; swapChainDesc.Width = static_cast(mWidth); swapChainDesc.Height = static_cast(mHeight); swapChainDesc.Format = mSwapChainFormat; swapChainDesc.Stereo = FALSE; swapChainDesc.SampleDesc.Count = 1; swapChainDesc.SampleDesc.Quality = 0; swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT | DXGI_USAGE_BACK_BUFFER; swapChainDesc.BufferCount = 1; swapChainDesc.Scaling = DXGI_SCALING_STRETCH; swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_SEQUENTIAL; swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; swapChainDesc.Flags = mSwapChainFlags; result = dxgiFactory2->CreateSwapChainForHwnd(mDevice, mWindow, &swapChainDesc, nullptr, nullptr, &mSwapChain1); SafeRelease(dxgiFactory2); SafeRelease(dxgiFactory); if (FAILED(result)) { return egl::Error(EGL_BAD_ALLOC, "Failed to create swap chain for window, result: 0x%X", result); } mSwapChain = mSwapChain1; mSwapChain->AddRef(); } else { DXGI_SWAP_CHAIN_DESC swapChainDesc = {}; swapChainDesc.BufferCount = 1; swapChainDesc.BufferDesc.Format = mSwapChainFormat; swapChainDesc.BufferDesc.Width = static_cast(mWidth); swapChainDesc.BufferDesc.Height = static_cast(mHeight); swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; swapChainDesc.BufferDesc.RefreshRate.Numerator = 0; swapChainDesc.BufferDesc.RefreshRate.Denominator = 1; swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT | DXGI_USAGE_BACK_BUFFER; swapChainDesc.Flags = mSwapChainFlags; swapChainDesc.OutputWindow = mWindow; swapChainDesc.SampleDesc.Count = 1; swapChainDesc.SampleDesc.Quality = 0; swapChainDesc.Windowed = TRUE; swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; result = dxgiFactory->CreateSwapChain(mDevice, &swapChainDesc, &mSwapChain); SafeRelease(dxgiFactory); if (FAILED(result)) { return egl::Error(EGL_BAD_ALLOC, "Failed to create swap chain for window, result: 0x%X", result); } } ID3D11Texture2D *colorBuffer = nullptr; result = mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast(&colorBuffer)); if (FAILED(result)) { return egl::Error(EGL_BAD_ALLOC, "Failed to query texture from swap chain, result: 0x%X", result); } mFunctionsGL->genRenderbuffers(1, &mColorRenderbufferID); mStateManager->bindRenderbuffer(GL_RENDERBUFFER, mColorRenderbufferID); mRenderbufferBufferHandle = mFunctionsWGL->dxRegisterObjectNV(mDeviceHandle, colorBuffer, mColorRenderbufferID, GL_RENDERBUFFER, WGL_ACCESS_READ_WRITE_NV); SafeRelease(colorBuffer); if (mRenderbufferBufferHandle == nullptr) { return egl::Error(EGL_BAD_ALLOC, "Failed to register D3D object, error: 0x%X.", HRESULT_CODE(GetLastError())); } // Rebind the surface to the texture if needed. if (hadBoundSurface) { mTextureHandle = mFunctionsWGL->dxRegisterObjectNV(mDeviceHandle, colorBuffer, mTextureID, GL_TEXTURE_2D, WGL_ACCESS_READ_WRITE_NV); if (mTextureHandle == nullptr) { return egl::Error(EGL_BAD_ALLOC, "Failed to register D3D object, error: 0x%X.", HRESULT_CODE(GetLastError())); } } error = setObjectsLocked(true); if (error.isError()) { return error; } ASSERT(mFramebufferID != 0); mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID); mFunctionsGL->framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, mColorRenderbufferID); if (mDepthBufferFormat != GL_NONE) { ASSERT(mDepthRenderbufferID != 0); mStateManager->bindRenderbuffer(GL_RENDERBUFFER, mDepthRenderbufferID); mFunctionsGL->renderbufferStorage(GL_RENDERBUFFER, mDepthBufferFormat, static_cast(mWidth), static_cast(mHeight)); const gl::InternalFormat &depthStencilFormatInfo = gl::GetInternalFormatInfo(mDepthBufferFormat); if (depthStencilFormatInfo.depthBits > 0) { mFunctionsGL->framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, mDepthRenderbufferID); } if (depthStencilFormatInfo.stencilBits > 0) { mFunctionsGL->framebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, mDepthRenderbufferID); } } mFirstSwap = true; return egl::Error(EGL_SUCCESS); } } // namespace rx