/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- * 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 "IMFYCbCrImage.h" #include "DeviceManagerD3D9.h" #include "mozilla/layers/TextureD3D11.h" #include "mozilla/layers/CompositableClient.h" #include "mozilla/layers/CompositableForwarder.h" #include "mozilla/gfx/DeviceManagerDx.h" #include "mozilla/gfx/Types.h" #include "mozilla/layers/TextureClient.h" #include "d3d9.h" namespace mozilla { namespace layers { IMFYCbCrImage::IMFYCbCrImage(IMFMediaBuffer* aBuffer, IMF2DBuffer* a2DBuffer) : RecyclingPlanarYCbCrImage(nullptr) , mBuffer(aBuffer) , m2DBuffer(a2DBuffer) {} IMFYCbCrImage::~IMFYCbCrImage() { if (m2DBuffer) { m2DBuffer->Unlock2D(); } else { mBuffer->Unlock(); } } struct AutoLockTexture { explicit AutoLockTexture(ID3D11Texture2D* aTexture) { aTexture->QueryInterface((IDXGIKeyedMutex**)getter_AddRefs(mMutex)); if (!mMutex) { return; } HRESULT hr = mMutex->AcquireSync(0, 10000); if (hr == WAIT_TIMEOUT) { MOZ_CRASH("GFX: IMFYCbCrImage timeout"); } if (FAILED(hr)) { NS_WARNING("Failed to lock the texture"); } } ~AutoLockTexture() { if (!mMutex) { return; } HRESULT hr = mMutex->ReleaseSync(0); if (FAILED(hr)) { NS_WARNING("Failed to unlock the texture"); } } RefPtr mMutex; }; static already_AddRefed InitTextures(IDirect3DDevice9* aDevice, const IntSize &aSize, _D3DFORMAT aFormat, RefPtr& aSurface, HANDLE& aHandle, D3DLOCKED_RECT& aLockedRect) { if (!aDevice) { return nullptr; } RefPtr result; if (FAILED(aDevice->CreateTexture(aSize.width, aSize.height, 1, 0, aFormat, D3DPOOL_DEFAULT, getter_AddRefs(result), &aHandle))) { return nullptr; } if (!result) { return nullptr; } RefPtr tmpTexture; if (FAILED(aDevice->CreateTexture(aSize.width, aSize.height, 1, 0, aFormat, D3DPOOL_SYSTEMMEM, getter_AddRefs(tmpTexture), nullptr))) { return nullptr; } if (!tmpTexture) { return nullptr; } tmpTexture->GetSurfaceLevel(0, getter_AddRefs(aSurface)); if (FAILED(aSurface->LockRect(&aLockedRect, nullptr, 0)) || !aLockedRect.pBits) { NS_WARNING("Could not lock surface"); return nullptr; } return result.forget(); } static bool FinishTextures(IDirect3DDevice9* aDevice, IDirect3DTexture9* aTexture, IDirect3DSurface9* aSurface) { if (!aDevice) { return false; } HRESULT hr = aSurface->UnlockRect(); if (FAILED(hr)) { return false; } RefPtr dstSurface; hr = aTexture->GetSurfaceLevel(0, getter_AddRefs(dstSurface)); if (FAILED(hr)) { return false; } hr = aDevice->UpdateSurface(aSurface, nullptr, dstSurface, nullptr); if (FAILED(hr)) { return false; } return true; } static bool UploadData(IDirect3DDevice9* aDevice, RefPtr& aTexture, HANDLE& aHandle, uint8_t* aSrc, const gfx::IntSize& aSrcSize, int32_t aSrcStride) { RefPtr surf; D3DLOCKED_RECT rect; aTexture = InitTextures(aDevice, aSrcSize, D3DFMT_A8, surf, aHandle, rect); if (!aTexture) { return false; } if (aSrcStride == rect.Pitch) { memcpy(rect.pBits, aSrc, rect.Pitch * aSrcSize.height); } else { for (int i = 0; i < aSrcSize.height; i++) { memcpy((uint8_t*)rect.pBits + i * rect.Pitch, aSrc + i * aSrcStride, aSrcSize.width); } } return FinishTextures(aDevice, aTexture, surf); } DXGIYCbCrTextureData* IMFYCbCrImage::GetD3D9TextureData(Data aData, gfx::IntSize aSize) { RefPtr device = DeviceManagerD3D9::GetDevice(); if (!device) { return nullptr; } RefPtr textureY; HANDLE shareHandleY = 0; if (!UploadData(device, textureY, shareHandleY, aData.mYChannel, aData.mYSize, aData.mYStride)) { return nullptr; } RefPtr textureCb; HANDLE shareHandleCb = 0; if (!UploadData(device, textureCb, shareHandleCb, aData.mCbChannel, aData.mCbCrSize, aData.mCbCrStride)) { return nullptr; } RefPtr textureCr; HANDLE shareHandleCr = 0; if (!UploadData(device, textureCr, shareHandleCr, aData.mCrChannel, aData.mCbCrSize, aData.mCbCrStride)) { return nullptr; } RefPtr query; HRESULT hr = device->CreateQuery(D3DQUERYTYPE_EVENT, getter_AddRefs(query)); hr = query->Issue(D3DISSUE_END); int iterations = 0; bool valid = false; while (iterations < 10) { HRESULT hr = query->GetData(nullptr, 0, D3DGETDATA_FLUSH); if (hr == S_FALSE) { Sleep(1); iterations++; continue; } if (hr == S_OK) { valid = true; } break; } if (!valid) { return nullptr; } return DXGIYCbCrTextureData::Create(TextureFlags::DEFAULT, textureY, textureCb, textureCr, shareHandleY, shareHandleCb, shareHandleCr, aSize, aData.mYSize, aData.mCbCrSize); } DXGIYCbCrTextureData* IMFYCbCrImage::GetD3D11TextureData(Data aData, gfx::IntSize aSize) { HRESULT hr; RefPtr mt; RefPtr device = gfx::DeviceManagerDx::Get()->GetContentDevice(); if (!device) { device = gfx::DeviceManagerDx::Get()->GetCompositorDevice(); } hr = device->QueryInterface((ID3D10Multithread**)getter_AddRefs(mt)); if (FAILED(hr)) { return nullptr; } if (!mt->GetMultithreadProtected()) { return nullptr; } if (!gfx::DeviceManagerDx::Get()->CanInitializeKeyedMutexTextures()) { return nullptr; } if (aData.mYStride < 0 || aData.mCbCrStride < 0) { // D3D11 only supports unsigned stride values. return nullptr; } CD3D11_TEXTURE2D_DESC newDesc(DXGI_FORMAT_R8_UNORM, aData.mYSize.width, aData.mYSize.height, 1, 1); if (device == gfx::DeviceManagerDx::Get()->GetCompositorDevice()) { newDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED; } else { newDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX; } RefPtr textureY; hr = device->CreateTexture2D(&newDesc, nullptr, getter_AddRefs(textureY)); NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr); newDesc.Width = aData.mCbCrSize.width; newDesc.Height = aData.mCbCrSize.height; RefPtr textureCb; hr = device->CreateTexture2D(&newDesc, nullptr, getter_AddRefs(textureCb)); NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr); RefPtr textureCr; hr = device->CreateTexture2D(&newDesc, nullptr, getter_AddRefs(textureCr)); NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr); // The documentation here seems to suggest using the immediate mode context // on more than one thread is not allowed: // https://msdn.microsoft.com/en-us/library/windows/desktop/ff476891(v=vs.85).aspx // The Debug Layer seems to imply it is though. When the ID3D10Multithread // layer is on. The Enter/Leave of the critical section shouldn't even be // required but were added for extra security. { AutoLockTexture lockY(textureY); AutoLockTexture lockCr(textureCr); AutoLockTexture lockCb(textureCb); mt->Enter(); RefPtr ctx; device->GetImmediateContext((ID3D11DeviceContext**)getter_AddRefs(ctx)); D3D11_BOX box; box.front = box.top = box.left = 0; box.back = 1; box.right = aData.mYSize.width; box.bottom = aData.mYSize.height; ctx->UpdateSubresource(textureY, 0, &box, aData.mYChannel, aData.mYStride, 0); box.right = aData.mCbCrSize.width; box.bottom = aData.mCbCrSize.height; ctx->UpdateSubresource(textureCb, 0, &box, aData.mCbChannel, aData.mCbCrStride, 0); ctx->UpdateSubresource(textureCr, 0, &box, aData.mCrChannel, aData.mCbCrStride, 0); mt->Leave(); } return DXGIYCbCrTextureData::Create(TextureFlags::DEFAULT, textureY, textureCb, textureCr, aSize, aData.mYSize, aData.mCbCrSize); } TextureClient* IMFYCbCrImage::GetD3D9TextureClient(KnowsCompositor* aForwarder) { DXGIYCbCrTextureData* textureData = GetD3D9TextureData(mData, GetSize()); if (textureData == nullptr) { return nullptr; } mTextureClient = TextureClient::CreateWithData( textureData, TextureFlags::DEFAULT, aForwarder->GetTextureForwarder() ); return mTextureClient; } TextureClient* IMFYCbCrImage::GetD3D11TextureClient(KnowsCompositor* aForwarder) { DXGIYCbCrTextureData* textureData = GetD3D11TextureData(mData, GetSize()); if (textureData == nullptr) { return nullptr; } mTextureClient = TextureClient::CreateWithData( textureData, TextureFlags::DEFAULT, aForwarder->GetTextureForwarder() ); return mTextureClient; } TextureClient* IMFYCbCrImage::GetTextureClient(KnowsCompositor* aForwarder) { if (mTextureClient) { return mTextureClient; } RefPtr device = gfx::DeviceManagerDx::Get()->GetContentDevice(); if (!device) { device = gfx::DeviceManagerDx::Get()->GetCompositorDevice(); } LayersBackend backend = aForwarder->GetCompositorBackendType(); if (!device || backend != LayersBackend::LAYERS_D3D11) { if (backend == LayersBackend::LAYERS_D3D9 || backend == LayersBackend::LAYERS_D3D11) { return GetD3D9TextureClient(aForwarder); } return nullptr; } return GetD3D11TextureClient(aForwarder); } } // namespace layers } // namespace mozilla