/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 4; -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "SharedSurfaceD3D11Interop.h" #include #include #include "GLContext.h" #include "WGLLibrary.h" #include "nsPrintfCString.h" #include "mozilla/gfx/DeviceManagerDx.h" #include "mozilla/StaticPrefs_webgl.h" namespace mozilla { namespace gl { /* Sample Code for WGL_NV_DX_interop2: Example: Render to Direct3D 11 backbuffer with openGL: // create D3D11 device, context and swap chain. ID3D11Device *device; ID3D11DeviceContext *devCtx; IDXGISwapChain *swapChain; DXGI_SWAP_CHAIN_DESC scd; hr = D3D11CreateDeviceAndSwapChain( NULL, // pAdapter D3D_DRIVER_TYPE_HARDWARE, // DriverType NULL, // Software 0, // Flags (Do not set // D3D11_CREATE_DEVICE_SINGLETHREADED) NULL, // pFeatureLevels 0, // FeatureLevels D3D11_SDK_VERSION, // SDKVersion &scd, // pSwapChainDesc &swapChain, // ppSwapChain &device, // ppDevice NULL, // pFeatureLevel &devCtx); // ppImmediateContext // Fetch the swapchain backbuffer ID3D11Texture2D *dxColorbuffer; swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID *)&dxColorbuffer); // Create depth stencil texture ID3D11Texture2D *dxDepthBuffer; D3D11_TEXTURE2D_DESC depthDesc; depthDesc.Usage = D3D11_USAGE_DEFAULT; // Create Views ID3D11RenderTargetView *colorBufferView; D3D11_RENDER_TARGET_VIEW_DESC rtd; device->CreateRenderTargetView(dxColorbuffer, &rtd, &colorBufferView); ID3D11DepthStencilView *depthBufferView; D3D11_DEPTH_STENCIL_VIEW_DESC dsd; device->CreateDepthStencilView(dxDepthBuffer, &dsd, &depthBufferView); // Attach back buffer and depth texture to redertarget for the device. devCtx->OMSetRenderTargets(1, &colorBufferView, depthBufferView); // Register D3D11 device with GL HANDLE gl_handleD3D; gl_handleD3D = wglDXOpenDeviceNV(device); // register the Direct3D color and depth/stencil buffers as // renderbuffers in opengl GLuint gl_names[2]; HANDLE gl_handles[2]; glGenRenderbuffers(2, gl_names); gl_handles[0] = wglDXRegisterObjectNV(gl_handleD3D, dxColorBuffer, gl_names[0], GL_RENDERBUFFER, WGL_ACCESS_READ_WRITE_NV); gl_handles[1] = wglDXRegisterObjectNV(gl_handleD3D, dxDepthBuffer, gl_names[1], GL_RENDERBUFFER, WGL_ACCESS_READ_WRITE_NV); // attach the Direct3D buffers to an FBO glBindFramebuffer(GL_FRAMEBUFFER, fbo); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, gl_names[0]); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, gl_names[1]); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, gl_names[1]); while (!done) { // lock the render targets for GL access wglDXLockObjectsNVX(gl_handleD3D, 2, gl_handles); // unlock the render targets wglDXUnlockObjectsNVX(gl_handleD3D, 2, gl_handles); } */ //////////////////////////////////////////////////////////////////////////////// // DXInterop2Device class ScopedContextState final { ID3D11DeviceContext1* const mD3DContext; RefPtr mOldContextState; public: ScopedContextState(ID3D11DeviceContext1* d3dContext, ID3DDeviceContextState* newContextState) : mD3DContext(d3dContext), mOldContextState(nullptr) { if (!mD3DContext) return; mD3DContext->SwapDeviceContextState(newContextState, getter_AddRefs(mOldContextState)); } ~ScopedContextState() { if (!mD3DContext) return; mD3DContext->SwapDeviceContextState(mOldContextState, nullptr); } }; class DXInterop2Device : public RefCounted { public: MOZ_DECLARE_REFCOUNTED_TYPENAME(DXInterop2Device) WGLLibrary* const mWGL; const RefPtr mD3D; // Only needed for lifetime guarantee. const HANDLE mInteropDevice; GLContext* const mGL; // AMD workaround. const RefPtr mD3DContext; const RefPtr mContextState; static already_AddRefed Open(WGLLibrary* wgl, GLContext* gl) { MOZ_ASSERT(wgl->HasDXInterop2()); const RefPtr d3d = gfx::DeviceManagerDx::Get()->GetContentDevice(); if (!d3d) { gfxCriticalNote << "DXInterop2Device::Open: Failed to create D3D11 device."; return nullptr; } if (!gl->MakeCurrent()) return nullptr; RefPtr d3dContext; RefPtr contextState; if (gl->WorkAroundDriverBugs() && gl->Vendor() == GLVendor::ATI) { // AMD calls ID3D10Device::Flush, so we need to be in ID3D10Device mode. RefPtr d3d11_1; auto hr = d3d->QueryInterface(__uuidof(ID3D11Device1), getter_AddRefs(d3d11_1)); if (!SUCCEEDED(hr)) return nullptr; d3d11_1->GetImmediateContext1(getter_AddRefs(d3dContext)); MOZ_ASSERT(d3dContext); const D3D_FEATURE_LEVEL featureLevel10_0 = D3D_FEATURE_LEVEL_10_0; hr = d3d11_1->CreateDeviceContextState( 0, &featureLevel10_0, 1, D3D11_SDK_VERSION, __uuidof(ID3D10Device), nullptr, getter_AddRefs(contextState)); if (!SUCCEEDED(hr)) return nullptr; } const auto interopDevice = wgl->mSymbols.fDXOpenDeviceNV(d3d); if (!interopDevice) { gfxCriticalNote << "DXInterop2Device::Open: DXOpenDevice failed."; return nullptr; } return MakeAndAddRef(wgl, d3d, interopDevice, gl, d3dContext, contextState); } DXInterop2Device(WGLLibrary* wgl, ID3D11Device* d3d, HANDLE interopDevice, GLContext* gl, ID3D11DeviceContext1* d3dContext, ID3DDeviceContextState* contextState) : mWGL(wgl), mD3D(d3d), mInteropDevice(interopDevice), mGL(gl), mD3DContext(d3dContext), mContextState(contextState) {} ~DXInterop2Device() { const auto isCurrent = mGL->MakeCurrent(); if (mWGL->mSymbols.fDXCloseDeviceNV(mInteropDevice)) return; if (isCurrent) { // That shouldn't have failed. const uint32_t error = GetLastError(); const nsPrintfCString errorMessage( "wglDXCloseDevice(0x%p) failed:" " GetLastError(): %u\n", mInteropDevice, error); gfxCriticalError() << errorMessage.BeginReading(); } } HANDLE RegisterObject(void* d3dObject, GLuint name, GLenum type, GLenum access) const { if (!mGL->MakeCurrent()) return nullptr; const ScopedContextState autoCS(mD3DContext, mContextState); const auto ret = mWGL->mSymbols.fDXRegisterObjectNV( mInteropDevice, d3dObject, name, type, access); if (ret) return ret; const uint32_t error = GetLastError(); const nsPrintfCString errorMessage( "wglDXRegisterObject(0x%p, 0x%p, %u, 0x%04x," " 0x%04x) failed: GetLastError(): %u\n", mInteropDevice, d3dObject, name, type, access, error); gfxCriticalNote << errorMessage.BeginReading(); return nullptr; } bool UnregisterObject(HANDLE lockHandle) const { const auto isCurrent = mGL->MakeCurrent(); const ScopedContextState autoCS(mD3DContext, mContextState); if (mWGL->mSymbols.fDXUnregisterObjectNV(mInteropDevice, lockHandle)) return true; if (!isCurrent) { // That shouldn't have failed. const uint32_t error = GetLastError(); const nsPrintfCString errorMessage( "wglDXUnregisterObject(0x%p, 0x%p) failed:" " GetLastError(): %u\n", mInteropDevice, lockHandle, error); gfxCriticalError() << errorMessage.BeginReading(); } return false; } bool LockObject(HANDLE lockHandle) const { MOZ_ASSERT(mGL->IsCurrent()); if (mWGL->mSymbols.fDXLockObjectsNV(mInteropDevice, 1, &lockHandle)) return true; if (!mGL->MakeCurrent()) return false; gfxCriticalNote << "wglDXLockObjects called without mGL being current." << " Retrying after MakeCurrent."; if (mWGL->mSymbols.fDXLockObjectsNV(mInteropDevice, 1, &lockHandle)) return true; const uint32_t error = GetLastError(); const nsPrintfCString errorMessage( "wglDXLockObjects(0x%p, 1, {0x%p}) failed:" " GetLastError(): %u\n", mInteropDevice, lockHandle, error); gfxCriticalError() << errorMessage.BeginReading(); return false; } bool UnlockObject(HANDLE lockHandle) const { MOZ_ASSERT(mGL->IsCurrent()); if (mWGL->mSymbols.fDXUnlockObjectsNV(mInteropDevice, 1, &lockHandle)) return true; if (!mGL->MakeCurrent()) return false; gfxCriticalNote << "wglDXUnlockObjects called without mGL being current." << " Retrying after MakeCurrent."; if (mWGL->mSymbols.fDXUnlockObjectsNV(mInteropDevice, 1, &lockHandle)) return true; const uint32_t error = GetLastError(); const nsPrintfCString errorMessage( "wglDXUnlockObjects(0x%p, 1, {0x%p}) failed:" " GetLastError(): %u\n", mInteropDevice, lockHandle, error); gfxCriticalError() << errorMessage.BeginReading(); return false; } }; //////////////////////////////////////////////////////////////////////////////// // Shared Surface /*static*/ UniquePtr SharedSurface_D3D11Interop::Create( DXInterop2Device* interop, GLContext* gl, const gfx::IntSize& size, bool hasAlpha) { const auto& d3d = interop->mD3D; // Create a texture in case we need to readback. DXGI_FORMAT format = hasAlpha ? DXGI_FORMAT_B8G8R8A8_UNORM : DXGI_FORMAT_B8G8R8X8_UNORM; CD3D11_TEXTURE2D_DESC desc(format, size.width, size.height, 1, 1); desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX; RefPtr texD3D; auto hr = d3d->CreateTexture2D(&desc, nullptr, getter_AddRefs(texD3D)); if (FAILED(hr)) { NS_WARNING("Failed to create texture for CanvasLayer!"); return nullptr; } RefPtr texDXGI; hr = texD3D->QueryInterface(__uuidof(IDXGIResource), getter_AddRefs(texDXGI)); if (FAILED(hr)) { NS_WARNING("Failed to open texture for sharing!"); return nullptr; } HANDLE dxgiHandle; texDXGI->GetSharedHandle(&dxgiHandle); //// if (!gl->MakeCurrent()) { NS_WARNING("MakeCurrent failed."); return nullptr; } GLuint interopRB = 0; gl->fGenRenderbuffers(1, &interopRB); const auto lockHandle = interop->RegisterObject(texD3D, interopRB, LOCAL_GL_RENDERBUFFER, LOCAL_WGL_ACCESS_WRITE_DISCARD_NV); if (!lockHandle) { NS_WARNING("Failed to register D3D object with WGL."); gl->fDeleteRenderbuffers(1, &interopRB); return nullptr; } //// GLuint prodTex = 0; GLuint interopFB = 0; { GLint samples = 0; { const ScopedBindRenderbuffer bindRB(gl, interopRB); gl->fGetRenderbufferParameteriv(LOCAL_GL_RENDERBUFFER, LOCAL_GL_RENDERBUFFER_SAMPLES, &samples); } if (samples > 0) { // Intel // Intel's dx_interop GL-side textures have SAMPLES=1, likely because // that's what the D3DTextures technically have. However, SAMPLES=1 is // very different from SAMPLES=0 in GL. For more, see // https://bugzilla.mozilla.org/show_bug.cgi?id=1325835 // Our ShSurf tex or rb must be single-sampled. gl->fGenTextures(1, &prodTex); const ScopedBindTexture bindTex(gl, prodTex); gl->TexParams_SetClampNoMips(); const GLenum format = (hasAlpha ? LOCAL_GL_RGBA : LOCAL_GL_RGB); const ScopedBindPBO nullPBO(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER); gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, format, size.width, size.height, 0, format, LOCAL_GL_UNSIGNED_BYTE, nullptr); gl->fGenFramebuffers(1, &interopFB); ScopedBindFramebuffer bindFB(gl, interopFB); gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_RENDERBUFFER, interopRB); MOZ_ASSERT(gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER) == LOCAL_GL_FRAMEBUFFER_COMPLETE); } } //// typedef SharedSurface_D3D11Interop ptrT; UniquePtr ret(new ptrT(gl, size, hasAlpha, prodTex, interopFB, interopRB, interop, lockHandle, texD3D, dxgiHandle)); return ret; } SharedSurface_D3D11Interop::SharedSurface_D3D11Interop( GLContext* gl, const gfx::IntSize& size, bool hasAlpha, GLuint prodTex, GLuint interopFB, GLuint interopRB, DXInterop2Device* interop, HANDLE lockHandle, ID3D11Texture2D* texD3D, HANDLE dxgiHandle) : SharedSurface( SharedSurfaceType::DXGLInterop2, prodTex ? AttachmentType::GLTexture : AttachmentType::GLRenderbuffer, gl, size, hasAlpha, true), mProdTex(prodTex), mInteropFB(interopFB), mInteropRB(interopRB), mInterop(interop), mLockHandle(lockHandle), mTexD3D(texD3D), mDXGIHandle(dxgiHandle), mNeedsFinish(StaticPrefs::webgl_dxgl_needs_finish()), mLockedForGL(false) { MOZ_ASSERT(bool(mProdTex) == bool(mInteropFB)); } SharedSurface_D3D11Interop::~SharedSurface_D3D11Interop() { MOZ_ASSERT(!IsProducerAcquired()); if (!mGL || !mGL->MakeCurrent()) return; if (!mInterop->UnregisterObject(mLockHandle)) { NS_WARNING("Failed to release mLockHandle, possibly leaking it."); } mGL->fDeleteTextures(1, &mProdTex); mGL->fDeleteFramebuffers(1, &mInteropFB); mGL->fDeleteRenderbuffers(1, &mInteropRB); } void SharedSurface_D3D11Interop::ProducerAcquireImpl() { MOZ_ASSERT(!mLockedForGL); // Now we have the mutex, we can lock for GL. MOZ_ALWAYS_TRUE(mInterop->LockObject(mLockHandle)); mLockedForGL = true; } void SharedSurface_D3D11Interop::ProducerReleaseImpl() { MOZ_ASSERT(mLockedForGL); if (mProdTex) { const ScopedBindFramebuffer bindFB(mGL, mInteropFB); mGL->BlitHelper()->DrawBlitTextureToFramebuffer(mProdTex, mSize, mSize); } if (mNeedsFinish) { mGL->fFinish(); } else { // We probably don't even need this. mGL->fFlush(); } MOZ_ALWAYS_TRUE(mInterop->UnlockObject(mLockHandle)); mLockedForGL = false; } bool SharedSurface_D3D11Interop::ToSurfaceDescriptor( layers::SurfaceDescriptor* const out_descriptor) { const auto format = (mHasAlpha ? gfx::SurfaceFormat::B8G8R8A8 : gfx::SurfaceFormat::B8G8R8X8); *out_descriptor = layers::SurfaceDescriptorD3D10( WindowsHandle(mDXGIHandle), format, mSize, gfx::YUVColorSpace::UNKNOWN, gfx::ColorRange::FULL); return true; } ////////////////////////////////////////////////////////////////////////////////////////// // Factory /*static*/ UniquePtr SurfaceFactory_D3D11Interop::Create( GLContext* gl, const SurfaceCaps& caps, layers::LayersIPCChannel* allocator, const layers::TextureFlags& flags) { WGLLibrary* wgl = &sWGLLib; if (!wgl || !wgl->HasDXInterop2()) return nullptr; const RefPtr interop = DXInterop2Device::Open(wgl, gl); if (!interop) { NS_WARNING("Failed to open D3D device for use by WGL."); return nullptr; } typedef SurfaceFactory_D3D11Interop ptrT; UniquePtr ret(new ptrT(gl, caps, allocator, flags, interop)); return ret; } SurfaceFactory_D3D11Interop::SurfaceFactory_D3D11Interop( GLContext* gl, const SurfaceCaps& caps, layers::LayersIPCChannel* allocator, const layers::TextureFlags& flags, DXInterop2Device* interop) : SurfaceFactory(SharedSurfaceType::DXGLInterop2, gl, caps, allocator, flags), mInterop(interop) {} SurfaceFactory_D3D11Interop::~SurfaceFactory_D3D11Interop() {} } // namespace gl } // namespace mozilla