/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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 "TextureHost.h" #include "CompositableHost.h" // for CompositableHost #include "LayerScope.h" #include "mozilla/gfx/2D.h" // for DataSourceSurface, Factory #include "mozilla/gfx/gfxVars.h" #include "mozilla/ipc/Shmem.h" // for Shmem #include "mozilla/layers/AsyncImagePipelineManager.h" #include "mozilla/layers/BufferTexture.h" #include "mozilla/layers/CompositableTransactionParent.h" // for CompositableParentManager #include "mozilla/layers/CompositorBridgeParent.h" #include "mozilla/layers/Compositor.h" // for Compositor #include "mozilla/layers/ISurfaceAllocator.h" // for ISurfaceAllocator #include "mozilla/layers/ImageBridgeParent.h" // for ImageBridgeParent #include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor, etc #include "mozilla/layers/TextureHostBasic.h" #include "mozilla/layers/TextureHostOGL.h" // for TextureHostOGL #include "mozilla/layers/ImageDataSerializer.h" #include "mozilla/layers/TextureClient.h" #ifdef XP_DARWIN # include "mozilla/layers/TextureSync.h" #endif #include "mozilla/layers/GPUVideoTextureHost.h" #include "mozilla/layers/WebRenderTextureHost.h" #include "mozilla/StaticPrefs_layers.h" #include "mozilla/StaticPrefs_gfx.h" #include "mozilla/webrender/RenderBufferTextureHost.h" #include "mozilla/webrender/RenderBufferTextureHostSWGL.h" #include "mozilla/webrender/RenderExternalTextureHost.h" #include "mozilla/webrender/RenderThread.h" #include "mozilla/webrender/WebRenderAPI.h" #include "nsAString.h" #include "mozilla/RefPtr.h" // for nsRefPtr #include "nsPrintfCString.h" // for nsPrintfCString #include "mozilla/layers/PTextureParent.h" #include "mozilla/Unused.h" #include #include "../opengl/CompositorOGL.h" #include "gfxUtils.h" #include "IPDLActor.h" #ifdef MOZ_ENABLE_D3D10_LAYER # include "../d3d11/CompositorD3D11.h" #endif #ifdef MOZ_X11 # include "mozilla/layers/X11TextureHost.h" #endif #ifdef XP_MACOSX # include "../opengl/MacIOSurfaceTextureHostOGL.h" #endif #ifdef XP_WIN # include "mozilla/layers/TextureD3D11.h" # include "mozilla/layers/TextureDIB.h" #endif #if 0 # define RECYCLE_LOG(...) printf_stderr(__VA_ARGS__) #else # define RECYCLE_LOG(...) \ do { \ } while (0) #endif namespace mozilla { namespace layers { /** * TextureParent is the host-side IPDL glue between TextureClient and * TextureHost. It is an IPDL actor just like LayerParent, CompositableParent, * etc. */ class TextureParent : public ParentActor { public: TextureParent(HostIPCAllocator* aAllocator, uint64_t aSerial, const wr::MaybeExternalImageId& aExternalImageId); virtual ~TextureParent(); bool Init(const SurfaceDescriptor& aSharedData, const ReadLockDescriptor& aReadLock, const LayersBackend& aLayersBackend, const TextureFlags& aFlags); void NotifyNotUsed(uint64_t aTransactionId); mozilla::ipc::IPCResult RecvRecycleTexture( const TextureFlags& aTextureFlags) final; TextureHost* GetTextureHost() { return mTextureHost; } void Destroy() override; uint64_t GetSerial() const { return mSerial; } HostIPCAllocator* mSurfaceAllocator; RefPtr mTextureHost; // mSerial is unique in TextureClient's process. const uint64_t mSerial; wr::MaybeExternalImageId mExternalImageId; }; static bool WrapWithWebRenderTextureHost(ISurfaceAllocator* aDeallocator, LayersBackend aBackend, TextureFlags aFlags) { if ((aFlags & TextureFlags::SNAPSHOT) || (aBackend != LayersBackend::LAYERS_WR) || (!aDeallocator->UsesImageBridge() && !aDeallocator->AsCompositorBridgeParentBase())) { return false; } return true; } //////////////////////////////////////////////////////////////////////////////// PTextureParent* TextureHost::CreateIPDLActor( HostIPCAllocator* aAllocator, const SurfaceDescriptor& aSharedData, const ReadLockDescriptor& aReadLock, LayersBackend aLayersBackend, TextureFlags aFlags, uint64_t aSerial, const wr::MaybeExternalImageId& aExternalImageId) { TextureParent* actor = new TextureParent(aAllocator, aSerial, aExternalImageId); if (!actor->Init(aSharedData, aReadLock, aLayersBackend, aFlags)) { actor->ActorDestroy(ipc::IProtocol::ActorDestroyReason::FailedConstructor); delete actor; return nullptr; } return actor; } // static bool TextureHost::DestroyIPDLActor(PTextureParent* actor) { delete actor; return true; } // static bool TextureHost::SendDeleteIPDLActor(PTextureParent* actor) { return PTextureParent::Send__delete__(actor); } // static TextureHost* TextureHost::AsTextureHost(PTextureParent* actor) { if (!actor) { return nullptr; } return static_cast(actor)->mTextureHost; } // static uint64_t TextureHost::GetTextureSerial(PTextureParent* actor) { if (!actor) { return UINT64_MAX; } return static_cast(actor)->mSerial; } PTextureParent* TextureHost::GetIPDLActor() { return mActor; } void TextureHost::SetLastFwdTransactionId(uint64_t aTransactionId) { MOZ_ASSERT(mFwdTransactionId <= aTransactionId); mFwdTransactionId = aTransactionId; } already_AddRefed CreateDummyBufferTextureHost( mozilla::layers::LayersBackend aBackend, mozilla::layers::TextureFlags aFlags) { // Ensure that the host will delete the memory. aFlags &= ~TextureFlags::DEALLOCATE_CLIENT; UniquePtr textureData(BufferTextureData::Create( gfx::IntSize(1, 1), gfx::SurfaceFormat::B8G8R8A8, gfx::BackendType::SKIA, aBackend, aFlags, TextureAllocationFlags::ALLOC_DEFAULT, nullptr)); SurfaceDescriptor surfDesc; textureData->Serialize(surfDesc); const SurfaceDescriptorBuffer& bufferDesc = surfDesc.get_SurfaceDescriptorBuffer(); const MemoryOrShmem& data = bufferDesc.data(); RefPtr host = new MemoryTextureHost(reinterpret_cast(data.get_uintptr_t()), bufferDesc.desc(), aFlags); return host.forget(); } already_AddRefed TextureHost::Create( const SurfaceDescriptor& aDesc, const ReadLockDescriptor& aReadLock, ISurfaceAllocator* aDeallocator, LayersBackend aBackend, TextureFlags aFlags, wr::MaybeExternalImageId& aExternalImageId) { RefPtr result; switch (aDesc.type()) { case SurfaceDescriptor::TSurfaceDescriptorBuffer: case SurfaceDescriptor::TSurfaceDescriptorDIB: case SurfaceDescriptor::TSurfaceDescriptorFileMapping: case SurfaceDescriptor::TSurfaceDescriptorGPUVideo: result = CreateBackendIndependentTextureHost(aDesc, aDeallocator, aBackend, aFlags); break; case SurfaceDescriptor::TEGLImageDescriptor: case SurfaceDescriptor::TSurfaceTextureDescriptor: case SurfaceDescriptor::TSurfaceDescriptorAndroidHardwareBuffer: case SurfaceDescriptor::TSurfaceDescriptorSharedGLTexture: case SurfaceDescriptor::TSurfaceDescriptorDMABuf: result = CreateTextureHostOGL(aDesc, aDeallocator, aBackend, aFlags); break; case SurfaceDescriptor::TSurfaceDescriptorMacIOSurface: if (aBackend == LayersBackend::LAYERS_OPENGL || aBackend == LayersBackend::LAYERS_WR) { result = CreateTextureHostOGL(aDesc, aDeallocator, aBackend, aFlags); break; } else { result = CreateTextureHostBasic(aDesc, aDeallocator, aBackend, aFlags); break; } #ifdef MOZ_X11 case SurfaceDescriptor::TSurfaceDescriptorX11: { if (!aDeallocator->IsSameProcess()) { NS_ERROR( "A client process is trying to peek at our address space using a " "X11Texture!"); return nullptr; } const SurfaceDescriptorX11& desc = aDesc.get_SurfaceDescriptorX11(); result = MakeAndAddRef(aFlags, desc); break; } #endif #ifdef XP_WIN case SurfaceDescriptor::TSurfaceDescriptorD3D10: case SurfaceDescriptor::TSurfaceDescriptorDXGIYCbCr: result = CreateTextureHostD3D11(aDesc, aDeallocator, aBackend, aFlags); break; #endif case SurfaceDescriptor::TSurfaceDescriptorRecorded: { const SurfaceDescriptorRecorded& desc = aDesc.get_SurfaceDescriptorRecorded(); UniquePtr realDesc = aDeallocator->AsCompositorBridgeParentBase() ->LookupSurfaceDescriptorForClientTexture(desc.textureId()); if (!realDesc) { gfxCriticalNote << "Failed to get descriptor for recorded texture."; // Create a dummy to prevent any crashes due to missing IPDL actors. result = CreateDummyBufferTextureHost(aBackend, aFlags); break; } result = TextureHost::Create(*realDesc, aReadLock, aDeallocator, aBackend, aFlags, aExternalImageId); return result.forget(); } default: MOZ_CRASH("GFX: Unsupported Surface type host"); } if (!result) { gfxCriticalNote << "TextureHost creation failure type=" << aDesc.type(); } if (result && WrapWithWebRenderTextureHost(aDeallocator, aBackend, aFlags)) { MOZ_ASSERT(aExternalImageId.isSome()); result = new WebRenderTextureHost(aDesc, aFlags, result, aExternalImageId.ref()); } if (result) { result->DeserializeReadLock(aReadLock, aDeallocator); } return result.forget(); } already_AddRefed CreateBackendIndependentTextureHost( const SurfaceDescriptor& aDesc, ISurfaceAllocator* aDeallocator, LayersBackend aBackend, TextureFlags aFlags) { RefPtr result; switch (aDesc.type()) { case SurfaceDescriptor::TSurfaceDescriptorBuffer: { const SurfaceDescriptorBuffer& bufferDesc = aDesc.get_SurfaceDescriptorBuffer(); const MemoryOrShmem& data = bufferDesc.data(); switch (data.type()) { case MemoryOrShmem::TShmem: { const ipc::Shmem& shmem = data.get_Shmem(); const BufferDescriptor& desc = bufferDesc.desc(); if (!shmem.IsReadable()) { // We failed to map the shmem so we can't verify its size. This // should not be a fatal error, so just create the texture with // nothing backing it. result = new ShmemTextureHost(shmem, desc, aDeallocator, aFlags); break; } size_t bufSize = shmem.Size(); size_t reqSize = SIZE_MAX; switch (desc.type()) { case BufferDescriptor::TYCbCrDescriptor: { const YCbCrDescriptor& ycbcr = desc.get_YCbCrDescriptor(); reqSize = ImageDataSerializer::ComputeYCbCrBufferSize( ycbcr.ySize(), ycbcr.yStride(), ycbcr.cbCrSize(), ycbcr.cbCrStride(), ycbcr.yOffset(), ycbcr.cbOffset(), ycbcr.crOffset()); break; } case BufferDescriptor::TRGBDescriptor: { const RGBDescriptor& rgb = desc.get_RGBDescriptor(); reqSize = ImageDataSerializer::ComputeRGBBufferSize(rgb.size(), rgb.format()); break; } default: gfxCriticalError() << "Bad buffer host descriptor " << (int)desc.type(); MOZ_CRASH("GFX: Bad descriptor"); } if (reqSize == 0 || bufSize < reqSize) { NS_ERROR( "A client process gave a shmem too small to fit for its " "descriptor!"); return nullptr; } result = new ShmemTextureHost(shmem, desc, aDeallocator, aFlags); break; } case MemoryOrShmem::Tuintptr_t: { if (!aDeallocator->IsSameProcess()) { NS_ERROR( "A client process is trying to peek at our address space using " "a MemoryTexture!"); return nullptr; } result = new MemoryTextureHost( reinterpret_cast(data.get_uintptr_t()), bufferDesc.desc(), aFlags); break; } default: gfxCriticalError() << "Failed texture host for backend " << (int)data.type(); MOZ_CRASH("GFX: No texture host for backend"); } break; } case SurfaceDescriptor::TSurfaceDescriptorGPUVideo: { if (aDesc.get_SurfaceDescriptorGPUVideo().type() == SurfaceDescriptorGPUVideo::TSurfaceDescriptorPlugin) { MOZ_ASSERT(aDeallocator && aDeallocator->UsesImageBridge()); auto ibpBase = static_cast(aDeallocator); result = ibpBase->LookupTextureHost(aDesc.get_SurfaceDescriptorGPUVideo()); if (!result) { return nullptr; } MOZ_ASSERT(aFlags == result->GetFlags()); break; } MOZ_ASSERT(aDesc.get_SurfaceDescriptorGPUVideo().type() == SurfaceDescriptorGPUVideo::TSurfaceDescriptorRemoteDecoder); result = GPUVideoTextureHost::CreateFromDescriptor( aFlags, aDesc.get_SurfaceDescriptorGPUVideo()); break; } #ifdef XP_WIN case SurfaceDescriptor::TSurfaceDescriptorDIB: { if (!aDeallocator->IsSameProcess()) { NS_ERROR( "A client process is trying to peek at our address space using a " "DIBTexture!"); return nullptr; } result = new DIBTextureHost(aFlags, aDesc); break; } case SurfaceDescriptor::TSurfaceDescriptorFileMapping: { result = new TextureHostFileMapping(aFlags, aDesc); break; } #endif default: { NS_WARNING("No backend independent TextureHost for this descriptor type"); } } return result.forget(); } TextureHost::TextureHost(TextureFlags aFlags) : AtomicRefCountedWithFinalize("TextureHost"), mActor(nullptr), mFlags(aFlags), mCompositableCount(0), mFwdTransactionId(0), mReadLocked(false) {} TextureHost::~TextureHost() { if (mReadLocked) { // If we still have a ReadLock, unlock it. At this point we don't care about // the texture client being written into on the other side since it should // be destroyed by now. But we will hit assertions if we don't ReadUnlock // before destroying the lock itself. ReadUnlock(); MaybeNotifyUnlocked(); } } void TextureHost::Finalize() { MaybeDestroyRenderTexture(); if (!(GetFlags() & TextureFlags::DEALLOCATE_CLIENT)) { DeallocateSharedData(); DeallocateDeviceData(); } } void TextureHost::UnbindTextureSource() { if (mReadLocked) { // This TextureHost is not used anymore. Since most compositor backends are // working asynchronously under the hood a compositor could still be using // this texture, so it is generally best to wait until the end of the next // composition before calling ReadUnlock. We ask the compositor to take care // of that for us. if (mProvider) { mProvider->UnlockAfterComposition(this); } else { // GetCompositor returned null which means no compositor can be using this // texture. We can ReadUnlock right away. ReadUnlock(); MaybeNotifyUnlocked(); } } } void TextureHost::RecycleTexture(TextureFlags aFlags) { MOZ_ASSERT(GetFlags() & TextureFlags::RECYCLE); MOZ_ASSERT(aFlags & TextureFlags::RECYCLE); mFlags = aFlags; } void TextureHost::NotifyNotUsed() { if (!mActor) { return; } // Do not need to call NotifyNotUsed() if TextureHost does not have // TextureFlags::RECYCLE flag nor TextureFlags::WAIT_HOST_USAGE_END flag. if (!(GetFlags() & TextureFlags::RECYCLE) && !(GetFlags() & TextureFlags::WAIT_HOST_USAGE_END)) { return; } // The following cases do not need to defer NotifyNotUsed until next // Composite. // - TextureHost does not have Compositor. // - Compositor is BasicCompositor. // - TextureHost has intermediate buffer. // end of buffer usage. if (!mProvider || HasIntermediateBuffer() || !mProvider->NotifyNotUsedAfterComposition(this)) { static_cast(mActor)->NotifyNotUsed(mFwdTransactionId); return; } } void TextureHost::CallNotifyNotUsed() { if (!mActor) { return; } static_cast(mActor)->NotifyNotUsed(mFwdTransactionId); } void TextureHost::MaybeDestroyRenderTexture() { if (mExternalImageId.isNothing()) { // RenderTextureHost was not created return; } // When TextureHost created RenderTextureHost, delete it here. TextureHost::DestroyRenderTexture(mExternalImageId.ref()); } void TextureHost::DestroyRenderTexture( const wr::ExternalImageId& aExternalImageId) { wr::RenderThread::Get()->UnregisterExternalImage( wr::AsUint64(aExternalImageId)); } void TextureHost::EnsureRenderTexture( const wr::MaybeExternalImageId& aExternalImageId) { if (aExternalImageId.isNothing()) { // TextureHost is wrapped by GPUVideoTextureHost. if (mExternalImageId.isSome()) { // RenderTextureHost was already created. return; } mExternalImageId = Some(AsyncImagePipelineManager::GetNextExternalImageId()); } else { // TextureHost is wrapped by WebRenderTextureHost. if (aExternalImageId == mExternalImageId) { // The texture has already been created. return; } MOZ_ASSERT(mExternalImageId.isNothing()); mExternalImageId = aExternalImageId; } CreateRenderTexture(mExternalImageId.ref()); } void TextureHost::PrintInfo(std::stringstream& aStream, const char* aPrefix) { aStream << aPrefix; aStream << nsPrintfCString("%s (0x%p)", Name(), this).get(); // Note: the TextureHost needs to be locked before it is safe to call // GetSize() and GetFormat() on it. if (Lock()) { aStream << " [size=" << GetSize() << "]" << " [format=" << GetFormat() << "]"; Unlock(); } aStream << " [flags=" << mFlags << "]"; #ifdef MOZ_DUMP_PAINTING if (StaticPrefs::layers_dump_texture()) { nsAutoCString pfx(aPrefix); pfx += " "; aStream << "\n" << pfx.get() << "Surface: "; RefPtr dSurf = GetAsSurface(); if (dSurf) { aStream << gfxUtils::GetAsLZ4Base64Str(dSurf).get(); } } #endif } void TextureHost::Updated(const nsIntRegion* aRegion) { LayerScope::ContentChanged(this); UpdatedInternal(aRegion); } TextureSource::TextureSource() : mCompositableCount(0) {} TextureSource::~TextureSource() = default; BufferTextureHost::BufferTextureHost(const BufferDescriptor& aDesc, TextureFlags aFlags) : TextureHost(aFlags), mUpdateSerial(1), mLocked(false), mNeedsFullUpdate(false) { mDescriptor = aDesc; switch (mDescriptor.type()) { case BufferDescriptor::TYCbCrDescriptor: { const YCbCrDescriptor& ycbcr = mDescriptor.get_YCbCrDescriptor(); mSize = ycbcr.display().Size(); mFormat = gfx::SurfaceFormat::YUV; mHasIntermediateBuffer = ycbcr.hasIntermediateBuffer(); break; } case BufferDescriptor::TRGBDescriptor: { const RGBDescriptor& rgb = mDescriptor.get_RGBDescriptor(); mSize = rgb.size(); mFormat = rgb.format(); mHasIntermediateBuffer = rgb.hasIntermediateBuffer(); break; } default: gfxCriticalError() << "Bad buffer host descriptor " << (int)mDescriptor.type(); MOZ_CRASH("GFX: Bad descriptor"); } if (aFlags & TextureFlags::COMPONENT_ALPHA) { // One texture of a component alpha texture pair will start out all white. // This hack allows us to easily make sure that white will be uploaded. // See bug 1138934 mNeedsFullUpdate = true; } #ifdef XP_MACOSX const int kMinSize = 1024; const int kMaxSize = 4096; mUseExternalTextures = kMaxSize >= mSize.width && mSize.width >= kMinSize && kMaxSize >= mSize.height && mSize.height >= kMinSize && StaticPrefs::gfx_webrender_enable_client_storage_AtStartup(); #else mUseExternalTextures = false; #endif } BufferTextureHost::~BufferTextureHost() = default; void BufferTextureHost::UpdatedInternal(const nsIntRegion* aRegion) { ++mUpdateSerial; // If the last frame wasn't uploaded yet, and we -don't- have a partial // update, we still need to update the full surface. if (aRegion && !mNeedsFullUpdate) { mMaybeUpdatedRegion.OrWith(*aRegion); } else { mNeedsFullUpdate = true; } if (GetFlags() & TextureFlags::IMMEDIATE_UPLOAD) { DebugOnly result = MaybeUpload(!mNeedsFullUpdate ? &mMaybeUpdatedRegion : nullptr); NS_WARNING_ASSERTION(result, "Failed to upload a texture"); } } void BufferTextureHost::SetTextureSourceProvider( TextureSourceProvider* aProvider) { if (mProvider == aProvider) { return; } if (mFirstSource && mFirstSource->IsOwnedBy(this)) { mFirstSource->SetOwner(nullptr); } if (mFirstSource) { mFirstSource = nullptr; mNeedsFullUpdate = true; } mProvider = aProvider; } void BufferTextureHost::DeallocateDeviceData() { if (mFirstSource && mFirstSource->NumCompositableRefs() > 0) { // WrappingTextureSourceYCbCrBasic wraps YUV format BufferTextureHost. // When BufferTextureHost is destroyed, data of // WrappingTextureSourceYCbCrBasic becomes invalid. if (mFirstSource->AsWrappingTextureSourceYCbCrBasic() && mFirstSource->IsOwnedBy(this)) { mFirstSource->SetOwner(nullptr); mFirstSource->DeallocateDeviceData(); } return; } if (!mFirstSource || !mFirstSource->IsOwnedBy(this)) { mFirstSource = nullptr; return; } mFirstSource->SetOwner(nullptr); RefPtr it = mFirstSource; while (it) { it->DeallocateDeviceData(); it = it->GetNextSibling(); } } bool BufferTextureHost::Lock() { MOZ_ASSERT(!mLocked); if (!UploadIfNeeded()) { return false; } mLocked = !!mFirstSource; return mLocked; } void BufferTextureHost::Unlock() { MOZ_ASSERT(mLocked); mLocked = false; } void BufferTextureHost::CreateRenderTexture( const wr::ExternalImageId& aExternalImageId) { RefPtr texture; if (gfx::gfxVars::UseSoftwareWebRender()) { texture = new wr::RenderBufferTextureHostSWGL(GetBuffer(), GetBufferDescriptor()); } else if (UseExternalTextures()) { texture = new wr::RenderExternalTextureHost(GetBuffer(), GetBufferDescriptor()); } else { texture = new wr::RenderBufferTextureHost(GetBuffer(), GetBufferDescriptor()); } wr::RenderThread::Get()->RegisterExternalImage(wr::AsUint64(aExternalImageId), texture.forget()); } uint32_t BufferTextureHost::NumSubTextures() { if (GetFormat() == gfx::SurfaceFormat::YUV) { return 3; } return 1; } void BufferTextureHost::PushResourceUpdates( wr::TransactionBuilder& aResources, ResourceUpdateOp aOp, const Range& aImageKeys, const wr::ExternalImageId& aExtID) { auto method = aOp == TextureHost::ADD_IMAGE ? &wr::TransactionBuilder::AddExternalImage : &wr::TransactionBuilder::UpdateExternalImage; auto imageType = UseExternalTextures() || gfx::gfxVars::UseSoftwareWebRender() ? wr::ExternalImageType::TextureHandle( wr::ImageBufferKind::TextureRect) : wr::ExternalImageType::Buffer(); if (GetFormat() != gfx::SurfaceFormat::YUV) { MOZ_ASSERT(aImageKeys.length() == 1); wr::ImageDescriptor descriptor( GetSize(), ImageDataSerializer::ComputeRGBStride(GetFormat(), GetSize().width), GetFormat()); (aResources.*method)(aImageKeys[0], descriptor, aExtID, imageType, 0); } else { MOZ_ASSERT(aImageKeys.length() == 3); const layers::YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor(); wr::ImageDescriptor yDescriptor( desc.ySize(), desc.yStride(), SurfaceFormatForColorDepth(desc.colorDepth())); wr::ImageDescriptor cbcrDescriptor( desc.cbCrSize(), desc.cbCrStride(), SurfaceFormatForColorDepth(desc.colorDepth())); (aResources.*method)(aImageKeys[0], yDescriptor, aExtID, imageType, 0); (aResources.*method)(aImageKeys[1], cbcrDescriptor, aExtID, imageType, 1); (aResources.*method)(aImageKeys[2], cbcrDescriptor, aExtID, imageType, 2); } } void BufferTextureHost::PushDisplayItems(wr::DisplayListBuilder& aBuilder, const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip, wr::ImageRendering aFilter, const Range& aImageKeys, PushDisplayItemFlagSet aFlags) { // SWGL should always try to bypass shaders and composite directly. bool preferCompositorSurface = aFlags.contains(PushDisplayItemFlag::PREFER_COMPOSITOR_SURFACE); bool useExternalSurface = aFlags.contains(PushDisplayItemFlag::SUPPORTS_EXTERNAL_BUFFER_TEXTURES); if (GetFormat() != gfx::SurfaceFormat::YUV) { MOZ_ASSERT(aImageKeys.length() == 1); aBuilder.PushImage(aBounds, aClip, true, aFilter, aImageKeys[0], !(mFlags & TextureFlags::NON_PREMULTIPLIED), wr::ColorF{1.0f, 1.0f, 1.0f, 1.0f}, preferCompositorSurface, useExternalSurface); } else { MOZ_ASSERT(aImageKeys.length() == 3); const YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor(); aBuilder.PushYCbCrPlanarImage( aBounds, aClip, true, aImageKeys[0], aImageKeys[1], aImageKeys[2], wr::ToWrColorDepth(desc.colorDepth()), wr::ToWrYuvColorSpace(desc.yUVColorSpace()), wr::ToWrColorRange(desc.colorRange()), aFilter, preferCompositorSurface, useExternalSurface); } } void TextureHost::DeserializeReadLock(const ReadLockDescriptor& aDesc, ISurfaceAllocator* aAllocator) { if (mReadLock) { return; } mReadLock = TextureReadLock::Deserialize(aDesc, aAllocator); } void TextureHost::SetReadLocked() { if (!mReadLock) { return; } // If mReadLocked is true it means we haven't read unlocked yet and the // content side should not have been able to write into this texture and read // lock again! MOZ_ASSERT(!mReadLocked); mReadLocked = true; if (mProvider) { mProvider->MaybeUnlockBeforeNextComposition(this); } } void TextureHost::ReadUnlock() { if (mReadLock && mReadLocked) { mReadLock->ReadUnlock(); mReadLocked = false; } } bool TextureHost::NeedsYFlip() const { return bool(mFlags & TextureFlags::ORIGIN_BOTTOM_LEFT); } bool BufferTextureHost::EnsureWrappingTextureSource() { MOZ_ASSERT(!mHasIntermediateBuffer); if (mFirstSource && mFirstSource->IsOwnedBy(this)) { return true; } // We don't own it, apparently. if (mFirstSource) { mNeedsFullUpdate = true; mFirstSource = nullptr; } if (!mProvider) { return false; } if (mFormat == gfx::SurfaceFormat::YUV) { mFirstSource = mProvider->CreateDataTextureSourceAroundYCbCr(this); } else { uint8_t* data = GetBuffer(); if (!data) { return false; } RefPtr surf = gfx::Factory::CreateWrappingDataSourceSurface( data, ImageDataSerializer::ComputeRGBStride(mFormat, mSize.width), mSize, mFormat); if (!surf) { return false; } mFirstSource = mProvider->CreateDataTextureSourceAround(surf); } if (!mFirstSource) { // BasicCompositor::CreateDataTextureSourceAround never returns null // and we don't expect to take this branch if we are using another backend. // Returning false is fine but if we get into this situation it probably // means something fishy is going on, like a texture being used with // several compositor backends. NS_WARNING("Failed to use a BufferTextureHost without intermediate buffer"); return false; } mFirstSource->SetUpdateSerial(mUpdateSerial); mFirstSource->SetOwner(this); return true; } static bool IsCompatibleTextureSource(TextureSource* aTexture, const BufferDescriptor& aDescriptor, TextureSourceProvider* aProvider) { if (!aProvider) { return false; } switch (aDescriptor.type()) { case BufferDescriptor::TYCbCrDescriptor: { const YCbCrDescriptor& ycbcr = aDescriptor.get_YCbCrDescriptor(); if (!aProvider->SupportsEffect(EffectTypes::YCBCR)) { return aTexture->GetFormat() == gfx::SurfaceFormat::B8G8R8X8 && aTexture->GetSize() == ycbcr.ySize(); } if (aTexture->GetFormat() != gfx::SurfaceFormat::A8 || aTexture->GetSize() != ycbcr.ySize()) { return false; } auto cbTexture = aTexture->GetSubSource(1); if (!cbTexture || cbTexture->GetFormat() != gfx::SurfaceFormat::A8 || cbTexture->GetSize() != ycbcr.cbCrSize()) { return false; } auto crTexture = aTexture->GetSubSource(2); if (!crTexture || crTexture->GetFormat() != gfx::SurfaceFormat::A8 || crTexture->GetSize() != ycbcr.cbCrSize()) { return false; } return true; } case BufferDescriptor::TRGBDescriptor: { const RGBDescriptor& rgb = aDescriptor.get_RGBDescriptor(); return aTexture->GetFormat() == rgb.format() && aTexture->GetSize() == rgb.size(); } default: { return false; } } } void BufferTextureHost::PrepareTextureSource( CompositableTextureSourceRef& aTexture) { // Reuse WrappingTextureSourceYCbCrBasic to reduce memory consumption. if (mFormat == gfx::SurfaceFormat::YUV && !mHasIntermediateBuffer && aTexture.get() && aTexture->AsWrappingTextureSourceYCbCrBasic() && aTexture->NumCompositableRefs() <= 1 && aTexture->GetSize() == GetSize()) { aTexture->AsSourceBasic()->SetBufferTextureHost(this); aTexture->AsDataTextureSource()->SetOwner(this); mFirstSource = aTexture->AsDataTextureSource(); mNeedsFullUpdate = true; } if (!mHasIntermediateBuffer) { EnsureWrappingTextureSource(); } if (mFirstSource && mFirstSource->IsOwnedBy(this)) { // We are already attached to a TextureSource, nothing to do except tell // the compositable to use it. aTexture = mFirstSource.get(); return; } // We don't own it, apparently. if (mFirstSource) { mNeedsFullUpdate = true; mFirstSource = nullptr; } DataTextureSource* texture = aTexture.get() ? aTexture->AsDataTextureSource() : nullptr; bool compatibleFormats = texture && IsCompatibleTextureSource(texture, mDescriptor, mProvider); bool shouldCreateTexture = !compatibleFormats || texture->NumCompositableRefs() > 1 || texture->HasOwner(); if (!shouldCreateTexture) { mFirstSource = texture; mFirstSource->SetOwner(this); mNeedsFullUpdate = true; // It's possible that texture belonged to a different compositor, // so make sure we update it (and all of its siblings) to the // current one. RefPtr it = mFirstSource; while (it) { it->SetTextureSourceProvider(mProvider); it = it->GetNextSibling(); } } } bool BufferTextureHost::BindTextureSource( CompositableTextureSourceRef& aTexture) { MOZ_ASSERT(mLocked); MOZ_ASSERT(mFirstSource); aTexture = mFirstSource; return !!aTexture; } bool BufferTextureHost::AcquireTextureSource( CompositableTextureSourceRef& aTexture) { if (!UploadIfNeeded()) { return false; } aTexture = mFirstSource; return !!mFirstSource; } void BufferTextureHost::ReadUnlock() { if (mFirstSource) { mFirstSource->Sync(true); } TextureHost::ReadUnlock(); } void BufferTextureHost::MaybeNotifyUnlocked() { #ifdef XP_DARWIN auto actor = GetIPDLActor(); if (actor) { AutoTArray serials; serials.AppendElement(TextureHost::GetTextureSerial(actor)); TextureSync::SetTexturesUnlocked(actor->OtherPid(), serials); } #endif } void BufferTextureHost::UnbindTextureSource() { if (mFirstSource && mFirstSource->IsOwnedBy(this)) { mFirstSource->Unbind(); } // This texture is not used by any layer anymore. // If the texture doesn't have an intermediate buffer, it means we are // compositing synchronously on the CPU, so we don't need to wait until // the end of the next composition to ReadUnlock (which other textures do // by default). // If the texture has an intermediate buffer we don't care either because // texture uploads are also performed synchronously for BufferTextureHost. ReadUnlock(); MaybeNotifyUnlocked(); } gfx::SurfaceFormat BufferTextureHost::GetFormat() const { // mFormat is the format of the data that we share with the content process. // GetFormat, on the other hand, expects the format that we present to the // Compositor (it is used to choose the effect type). // if the compositor does not support YCbCr effects, we give it a RGBX texture // instead (see BufferTextureHost::Upload) if (mFormat == gfx::SurfaceFormat::YUV && mProvider && !mProvider->SupportsEffect(EffectTypes::YCBCR)) { return gfx::SurfaceFormat::R8G8B8X8; } return mFormat; } gfx::YUVColorSpace BufferTextureHost::GetYUVColorSpace() const { if (mFormat == gfx::SurfaceFormat::YUV) { const YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor(); return desc.yUVColorSpace(); } return gfx::YUVColorSpace::UNKNOWN; } gfx::ColorDepth BufferTextureHost::GetColorDepth() const { if (mFormat == gfx::SurfaceFormat::YUV) { const YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor(); return desc.colorDepth(); } return gfx::ColorDepth::COLOR_8; } gfx::ColorRange BufferTextureHost::GetColorRange() const { if (mFormat == gfx::SurfaceFormat::YUV) { const YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor(); return desc.colorRange(); } return TextureHost::GetColorRange(); } bool BufferTextureHost::UploadIfNeeded() { return MaybeUpload(!mNeedsFullUpdate ? &mMaybeUpdatedRegion : nullptr); } bool BufferTextureHost::MaybeUpload(nsIntRegion* aRegion) { auto serial = mFirstSource ? mFirstSource->GetUpdateSerial() : 0; if (serial == mUpdateSerial) { return true; } if (serial == 0) { // 0 means the source has no valid content aRegion = nullptr; } if (!Upload(aRegion)) { return false; } if (mHasIntermediateBuffer) { // We just did the texture upload, the content side can now freely write // into the shared buffer. ReadUnlock(); MaybeNotifyUnlocked(); } // We no longer have an invalid region. mNeedsFullUpdate = false; mMaybeUpdatedRegion.SetEmpty(); // If upload returns true we know mFirstSource is not null mFirstSource->SetUpdateSerial(mUpdateSerial); return true; } bool BufferTextureHost::Upload(nsIntRegion* aRegion) { uint8_t* buf = GetBuffer(); if (!buf) { // We don't have a buffer; a possible cause is that the IPDL actor // is already dead. This inevitably happens as IPDL actors can die // at any time, so we want to silently return in this case. // another possible cause is that IPDL failed to map the shmem when // deserializing it. return false; } if (!mProvider) { // This can happen if we send textures to a compositable that isn't yet // attached to a layer. return false; } if (!mHasIntermediateBuffer && EnsureWrappingTextureSource()) { if (!mFirstSource || !mFirstSource->IsDirectMap()) { return true; } } if (mFormat == gfx::SurfaceFormat::UNKNOWN) { NS_WARNING("BufferTextureHost: unsupported format!"); return false; } else if (mFormat == gfx::SurfaceFormat::YUV) { const YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor(); if (!mProvider->SupportsEffect(EffectTypes::YCBCR)) { RefPtr surf = ImageDataSerializer::DataSourceSurfaceFromYCbCrDescriptor( buf, mDescriptor.get_YCbCrDescriptor()); if (NS_WARN_IF(!surf)) { return false; } if (!mFirstSource) { mFirstSource = mProvider->CreateDataTextureSource( mFlags | TextureFlags::RGB_FROM_YCBCR); mFirstSource->SetOwner(this); } return mFirstSource->Update(surf, aRegion); } RefPtr srcY; RefPtr srcU; RefPtr srcV; if (!mFirstSource) { // We don't support BigImages for YCbCr compositing. srcY = mProvider->CreateDataTextureSource( mFlags | TextureFlags::DISALLOW_BIGIMAGE); srcU = mProvider->CreateDataTextureSource( mFlags | TextureFlags::DISALLOW_BIGIMAGE); srcV = mProvider->CreateDataTextureSource( mFlags | TextureFlags::DISALLOW_BIGIMAGE); mFirstSource = srcY; mFirstSource->SetOwner(this); srcY->SetNextSibling(srcU); srcU->SetNextSibling(srcV); } else { // mFormat never changes so if this was created as a YCbCr host and // already contains a source it should already have 3 sources. // BufferTextureHost only uses DataTextureSources so it is safe to assume // all 3 sources are DataTextureSource. MOZ_ASSERT(mFirstSource->GetNextSibling()); MOZ_ASSERT(mFirstSource->GetNextSibling()->GetNextSibling()); srcY = mFirstSource; srcU = mFirstSource->GetNextSibling()->AsDataTextureSource(); srcV = mFirstSource->GetNextSibling() ->GetNextSibling() ->AsDataTextureSource(); } RefPtr tempY = gfx::Factory::CreateWrappingDataSourceSurface( ImageDataSerializer::GetYChannel(buf, desc), desc.yStride(), desc.ySize(), SurfaceFormatForColorDepth(desc.colorDepth())); RefPtr tempCb = gfx::Factory::CreateWrappingDataSourceSurface( ImageDataSerializer::GetCbChannel(buf, desc), desc.cbCrStride(), desc.cbCrSize(), SurfaceFormatForColorDepth(desc.colorDepth())); RefPtr tempCr = gfx::Factory::CreateWrappingDataSourceSurface( ImageDataSerializer::GetCrChannel(buf, desc), desc.cbCrStride(), desc.cbCrSize(), SurfaceFormatForColorDepth(desc.colorDepth())); // We don't support partial updates for Y U V textures NS_ASSERTION(!aRegion, "Unsupported partial updates for YCbCr textures"); if (!tempY || !tempCb || !tempCr || !srcY->Update(tempY) || !srcU->Update(tempCb) || !srcV->Update(tempCr)) { NS_WARNING("failed to update the DataTextureSource"); return false; } } else { // non-YCbCr case nsIntRegion* regionToUpdate = aRegion; if (!mFirstSource) { mFirstSource = mProvider->CreateDataTextureSource(mFlags); mFirstSource->SetOwner(this); if (mFlags & TextureFlags::COMPONENT_ALPHA) { // Update the full region the first time for component alpha textures. regionToUpdate = nullptr; } } RefPtr surf = gfx::Factory::CreateWrappingDataSourceSurface( GetBuffer(), ImageDataSerializer::ComputeRGBStride(mFormat, mSize.width), mSize, mFormat); if (!surf) { return false; } if (!mFirstSource->Update(surf.get(), regionToUpdate)) { NS_WARNING("failed to update the DataTextureSource"); return false; } } MOZ_ASSERT(mFirstSource); return true; } already_AddRefed BufferTextureHost::GetAsSurface() { RefPtr result; if (mFormat == gfx::SurfaceFormat::UNKNOWN) { NS_WARNING("BufferTextureHost: unsupported format!"); return nullptr; } else if (mFormat == gfx::SurfaceFormat::YUV) { result = ImageDataSerializer::DataSourceSurfaceFromYCbCrDescriptor( GetBuffer(), mDescriptor.get_YCbCrDescriptor()); if (NS_WARN_IF(!result)) { return nullptr; } } else { result = gfx::Factory::CreateWrappingDataSourceSurface( GetBuffer(), ImageDataSerializer::GetRGBStride(mDescriptor.get_RGBDescriptor()), mSize, mFormat); } return result.forget(); } ShmemTextureHost::ShmemTextureHost(const ipc::Shmem& aShmem, const BufferDescriptor& aDesc, ISurfaceAllocator* aDeallocator, TextureFlags aFlags) : BufferTextureHost(aDesc, aFlags), mDeallocator(aDeallocator) { if (aShmem.IsReadable()) { mShmem = MakeUnique(aShmem); } else { // This can happen if we failed to map the shmem on this process, perhaps // because it was big and we didn't have enough contiguous address space // available, even though we did on the child process. // As a result this texture will be in an invalid state and Lock will // always fail. gfxCriticalNote << "Failed to create a valid ShmemTextureHost"; } MOZ_COUNT_CTOR(ShmemTextureHost); } ShmemTextureHost::~ShmemTextureHost() { MOZ_ASSERT(!mShmem || (mFlags & TextureFlags::DEALLOCATE_CLIENT), "Leaking our buffer"); DeallocateDeviceData(); MOZ_COUNT_DTOR(ShmemTextureHost); } void ShmemTextureHost::DeallocateSharedData() { if (mShmem) { MOZ_ASSERT(mDeallocator, "Shared memory would leak without a ISurfaceAllocator"); mDeallocator->AsShmemAllocator()->DeallocShmem(*mShmem); mShmem = nullptr; } } void ShmemTextureHost::ForgetSharedData() { if (mShmem) { mShmem = nullptr; } } void ShmemTextureHost::OnShutdown() { mShmem = nullptr; } uint8_t* ShmemTextureHost::GetBuffer() { return mShmem ? mShmem->get() : nullptr; } size_t ShmemTextureHost::GetBufferSize() { return mShmem ? mShmem->Size() : 0; } MemoryTextureHost::MemoryTextureHost(uint8_t* aBuffer, const BufferDescriptor& aDesc, TextureFlags aFlags) : BufferTextureHost(aDesc, aFlags), mBuffer(aBuffer) { MOZ_COUNT_CTOR(MemoryTextureHost); } MemoryTextureHost::~MemoryTextureHost() { MOZ_ASSERT(!mBuffer || (mFlags & TextureFlags::DEALLOCATE_CLIENT), "Leaking our buffer"); DeallocateDeviceData(); MOZ_COUNT_DTOR(MemoryTextureHost); } void MemoryTextureHost::DeallocateSharedData() { if (mBuffer) { GfxMemoryImageReporter::WillFree(mBuffer); } delete[] mBuffer; mBuffer = nullptr; } void MemoryTextureHost::ForgetSharedData() { mBuffer = nullptr; } uint8_t* MemoryTextureHost::GetBuffer() { return mBuffer; } size_t MemoryTextureHost::GetBufferSize() { // MemoryTextureHost just trusts that the buffer size is large enough to read // anything we need to. That's because MemoryTextureHost has to trust the // buffer pointer anyway, so the security model here is just that // MemoryTexture's are restricted to same-process clients. return std::numeric_limits::max(); } TextureParent::TextureParent(HostIPCAllocator* aSurfaceAllocator, uint64_t aSerial, const wr::MaybeExternalImageId& aExternalImageId) : mSurfaceAllocator(aSurfaceAllocator), mSerial(aSerial), mExternalImageId(aExternalImageId) { MOZ_COUNT_CTOR(TextureParent); } TextureParent::~TextureParent() { MOZ_COUNT_DTOR(TextureParent); } void TextureParent::NotifyNotUsed(uint64_t aTransactionId) { if (!mTextureHost) { return; } mSurfaceAllocator->NotifyNotUsed(this, aTransactionId); } bool TextureParent::Init(const SurfaceDescriptor& aSharedData, const ReadLockDescriptor& aReadLock, const LayersBackend& aBackend, const TextureFlags& aFlags) { mTextureHost = TextureHost::Create(aSharedData, aReadLock, mSurfaceAllocator, aBackend, aFlags, mExternalImageId); if (mTextureHost) { mTextureHost->mActor = this; } return !!mTextureHost; } void TextureParent::Destroy() { if (!mTextureHost) { return; } if (mTextureHost->mReadLocked) { // ReadUnlock here to make sure the ReadLock's shmem does not outlive the // protocol that created it. mTextureHost->ReadUnlock(); mTextureHost->MaybeNotifyUnlocked(); } if (mTextureHost->GetFlags() & TextureFlags::DEALLOCATE_CLIENT) { mTextureHost->ForgetSharedData(); } mTextureHost->mActor = nullptr; mTextureHost = nullptr; } void TextureHost::ReceivedDestroy(PTextureParent* aActor) { static_cast(aActor)->RecvDestroy(); } mozilla::ipc::IPCResult TextureParent::RecvRecycleTexture( const TextureFlags& aTextureFlags) { if (!mTextureHost) { return IPC_OK(); } mTextureHost->RecycleTexture(aTextureFlags); return IPC_OK(); } //////////////////////////////////////////////////////////////////////////////// } // namespace layers } // namespace mozilla