/* -*- 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 "RenderCompositorD3D11SWGL.h" #include "gfxConfig.h" #include "mozilla/gfx/2D.h" #include "mozilla/widget/CompositorWidget.h" #include "mozilla/layers/Effects.h" #include "mozilla/webrender/RenderD3D11TextureHost.h" #include "RenderCompositorRecordedFrame.h" #include "RenderThread.h" namespace mozilla { using namespace layers; namespace wr { extern LazyLogModule gRenderThreadLog; #define LOG(...) MOZ_LOG(gRenderThreadLog, LogLevel::Debug, (__VA_ARGS__)) RenderCompositorD3D11SWGL::UploadMode RenderCompositorD3D11SWGL::GetUploadMode() { int mode = StaticPrefs::gfx_webrender_software_d3d11_upload_mode(); switch (mode) { case 1: return Upload_Immediate; case 2: return Upload_Staging; case 3: return Upload_StagingNoBlock; case 4: return Upload_StagingPooled; default: return Upload_Staging; } } UniquePtr RenderCompositorD3D11SWGL::Create( const RefPtr& aWidget, nsACString& aError) { if (!aWidget->GetCompositorOptions().AllowSoftwareWebRenderD3D11() || !gfx::gfxConfig::IsEnabled(gfx::Feature::D3D11_COMPOSITING)) { return nullptr; } void* ctx = wr_swgl_create_context(); if (!ctx) { gfxCriticalNote << "Failed SWGL context creation for WebRender"; return nullptr; } RefPtr compositor = MakeAndAddRef(aWidget); nsCString log; if (!compositor->Initialize(&log)) { gfxCriticalNote << "Failed to initialize CompositorD3D11 for SWGL: " << log.get(); return nullptr; } return MakeUnique(compositor, aWidget, ctx); } RenderCompositorD3D11SWGL::RenderCompositorD3D11SWGL( CompositorD3D11* aCompositor, const RefPtr& aWidget, void* aContext) : RenderCompositorLayersSWGL(aCompositor, aWidget, aContext) { LOG("RenderCompositorD3D11SWGL::RenderCompositorD3D11SWGL()"); mSyncObject = GetCompositorD3D11()->GetSyncObject(); } RenderCompositorD3D11SWGL::~RenderCompositorD3D11SWGL() { LOG("RenderCompositorD3D11SWGL::~RenderCompositorD3D11SWGL()"); } bool RenderCompositorD3D11SWGL::BeginFrame() { if (!RenderCompositorLayersSWGL::BeginFrame()) { return false; } mUploadMode = GetUploadMode(); return true; } void RenderCompositorD3D11SWGL::HandleExternalImage( RenderTextureHost* aExternalImage, FrameSurface& aFrameSurface) { // We need to hold the texture source separately from the effect, // since the effect doesn't hold a strong reference. RefPtr layer; RefPtr texturedEffect; gfx::IntSize size; if (auto* host = aExternalImage->AsRenderDXGITextureHost()) { if (!host->EnsureD3D11Texture2D(GetDevice())) { return; } layer = new DataTextureSourceD3D11(GetDevice(), host->GetFormat(), host->GetD3D11Texture2D()); if (host->GetFormat() == gfx::SurfaceFormat::NV12 || host->GetFormat() == gfx::SurfaceFormat::P010 || host->GetFormat() == gfx::SurfaceFormat::P016) { const auto yuv = FromYUVRangedColorSpace(host->GetYUVColorSpace()); texturedEffect = new EffectNV12(layer, yuv.space, yuv.range, host->GetColorDepth(), aFrameSurface.mFilter); } else { MOZ_ASSERT(host->GetFormat() == gfx::SurfaceFormat::B8G8R8X8 || host->GetFormat() == gfx::SurfaceFormat::B8G8R8A8); texturedEffect = CreateTexturedEffect(host->GetFormat(), layer, aFrameSurface.mFilter, true); } size = host->GetSize(0); host->LockInternal(); } else if (auto* host = aExternalImage->AsRenderDXGIYCbCrTextureHost()) { if (!host->EnsureD3D11Texture2D(GetDevice())) { return; } layer = new DataTextureSourceD3D11(GetDevice(), gfx::SurfaceFormat::A8, host->GetD3D11Texture2D(0)); RefPtr u = new DataTextureSourceD3D11( GetDevice(), gfx::SurfaceFormat::A8, host->GetD3D11Texture2D(1)); layer->SetNextSibling(u); RefPtr v = new DataTextureSourceD3D11( GetDevice(), gfx::SurfaceFormat::A8, host->GetD3D11Texture2D(2)); u->SetNextSibling(v); const auto yuv = FromYUVRangedColorSpace(host->GetYUVColorSpace()); texturedEffect = new EffectYCbCr(layer, yuv.space, yuv.range, host->GetColorDepth(), aFrameSurface.mFilter); size = host->GetSize(0); host->LockInternal(); } gfx::Rect drawRect(0, 0, size.width, size.height); EffectChain effect; effect.mPrimaryEffect = texturedEffect; mCompositor->DrawQuad(drawRect, aFrameSurface.mClipRect, effect, 1.0, aFrameSurface.mTransform, drawRect); if (auto* host = aExternalImage->AsRenderDXGITextureHost()) { host->Unlock(); } else if (auto* host = aExternalImage->AsRenderDXGIYCbCrTextureHost()) { host->Unlock(); } } void RenderCompositorD3D11SWGL::Pause() {} bool RenderCompositorD3D11SWGL::Resume() { return true; } GLenum RenderCompositorD3D11SWGL::IsContextLost(bool aForce) { // CompositorD3D11 uses ID3D11Device for composite. The device status needs to // be checked. auto reason = GetDevice()->GetDeviceRemovedReason(); switch (reason) { case S_OK: return LOCAL_GL_NO_ERROR; case DXGI_ERROR_DEVICE_REMOVED: case DXGI_ERROR_DRIVER_INTERNAL_ERROR: NS_WARNING("Device reset due to system / different device"); return LOCAL_GL_INNOCENT_CONTEXT_RESET_ARB; case DXGI_ERROR_DEVICE_HUNG: case DXGI_ERROR_DEVICE_RESET: case DXGI_ERROR_INVALID_CALL: gfxCriticalError() << "Device reset due to WR device: " << gfx::hexa(reason); return LOCAL_GL_GUILTY_CONTEXT_RESET_ARB; default: gfxCriticalError() << "Device reset with WR device unexpected reason: " << gfx::hexa(reason); return LOCAL_GL_UNKNOWN_CONTEXT_RESET_ARB; } } UniquePtr RenderCompositorD3D11SWGL::DoCreateSurface(wr::DeviceIntSize aTileSize, bool aIsOpaque) { return MakeUnique(aTileSize, aIsOpaque); } SurfaceD3D11SWGL::SurfaceD3D11SWGL(wr::DeviceIntSize aTileSize, bool aIsOpaque) : Surface(aTileSize, aIsOpaque) {} RenderCompositorD3D11SWGL::TileD3D11::TileD3D11( layers::DataTextureSourceD3D11* aTexture, ID3D11Texture2D* aStagingTexture, gfx::DataSourceSurface* aDataSourceSurface, Surface* aOwner, RenderCompositorD3D11SWGL* aRenderCompositor) : Tile(), mTexture(aTexture), mStagingTexture(aStagingTexture), mSurface(aDataSourceSurface), mOwner(aOwner->AsSurfaceD3D11SWGL()), mRenderCompositor(aRenderCompositor) {} bool RenderCompositorD3D11SWGL::TileD3D11::Map(wr::DeviceIntRect aDirtyRect, wr::DeviceIntRect aValidRect, void** aData, int32_t* aStride) { const UploadMode uploadMode = mRenderCompositor->GetUploadMode(); const gfx::IntSize tileSize = mOwner->TileSize(); if (!IsValid()) { return false; } // Check if this tile's upload method matches what we're using for this frame, // and if not then reallocate to fix it. Do this before we copy the struct // into mCurrentTile. if (uploadMode == Upload_Immediate) { if (mStagingTexture) { MOZ_ASSERT(!mSurface); mStagingTexture = nullptr; mSurface = mRenderCompositor->CreateStagingSurface(tileSize); } } else { if (mSurface) { MOZ_ASSERT(!mStagingTexture); mSurface = nullptr; mStagingTexture = mRenderCompositor->CreateStagingTexture(tileSize); } } mRenderCompositor->mCurrentStagingTexture = mStagingTexture; mRenderCompositor->mCurrentStagingTextureIsTemp = false; if (uploadMode == Upload_Immediate) { gfx::DataSourceSurface::MappedSurface map; if (!mSurface->Map(gfx::DataSourceSurface::READ_WRITE, &map)) { return false; } *aData = map.mData + aValidRect.min.y * map.mStride + aValidRect.min.x * 4; *aStride = map.mStride; // Ensure our mapped data is accessible by writing to the beginning and end // of the dirty region. See bug 171519 uint32_t* probeData = (uint32_t*)map.mData + aDirtyRect.min.y * (map.mStride / 4) + aDirtyRect.min.x; *probeData = 0; uint32_t* probeDataEnd = (uint32_t*)map.mData + (aDirtyRect.max.y - 1) * (map.mStride / 4) + (aDirtyRect.max.x - 1); *probeDataEnd = 0; mValidRect = gfx::Rect(aValidRect.min.x, aValidRect.min.y, aValidRect.width(), aValidRect.height()); return true; } if (!mRenderCompositor->mCurrentStagingTexture) { return false; } RefPtr context; mRenderCompositor->GetDevice()->GetImmediateContext(getter_AddRefs(context)); D3D11_MAPPED_SUBRESOURCE mappedSubresource; bool shouldBlock = uploadMode == Upload_Staging; HRESULT hr = context->Map( mRenderCompositor->mCurrentStagingTexture, 0, D3D11_MAP_READ_WRITE, shouldBlock ? 0 : D3D11_MAP_FLAG_DO_NOT_WAIT, &mappedSubresource); if (hr == DXGI_ERROR_WAS_STILL_DRAWING) { // mCurrentTile is a copy of the real tile data, so we can just replace the // staging one with a temporary for this draw. The staging texture for the // real tile remains untouched, so we'll go back to using that for future // frames and discard the new one. In the future we could improve this by // having a pool of shared staging textures for all the tiles. // Mark the tile as having a temporary staging texture. mRenderCompositor->mCurrentStagingTextureIsTemp = true; // Try grabbing a texture from the staging pool and see if we can use that. if (uploadMode == Upload_StagingPooled && mOwner->mStagingPool.Length()) { mRenderCompositor->mCurrentStagingTexture = mOwner->mStagingPool.ElementAt(0); mOwner->mStagingPool.RemoveElementAt(0); hr = context->Map(mRenderCompositor->mCurrentStagingTexture, 0, D3D11_MAP_READ_WRITE, D3D11_MAP_FLAG_DO_NOT_WAIT, &mappedSubresource); // If that failed, put it back into the pool (but at the end). if (hr == DXGI_ERROR_WAS_STILL_DRAWING) { mOwner->mStagingPool.AppendElement( mRenderCompositor->mCurrentStagingTexture); } } // No staging textures, or we tried one and it was busy. Allocate a brand // new one instead. if (hr == DXGI_ERROR_WAS_STILL_DRAWING) { mRenderCompositor->mCurrentStagingTexture = mRenderCompositor->CreateStagingTexture(tileSize); if (!mRenderCompositor->mCurrentStagingTexture) { return false; } hr = context->Map(mRenderCompositor->mCurrentStagingTexture, 0, D3D11_MAP_READ_WRITE, 0, &mappedSubresource); } } if (!SUCCEEDED(hr)) { gfxCriticalError() << "Failed to map tile: " << gfx::hexa(hr); // This is only expected to fail if we hit a device reset. MOZ_RELEASE_ASSERT( mRenderCompositor->GetDevice()->GetDeviceRemovedReason() != S_OK); return false; } // aData is expected to contain a pointer to the first pixel within the valid // rect, so take the mapped resource's data (which covers the full tile size) // and offset it by the top/left of the valid rect. *aData = (uint8_t*)mappedSubresource.pData + aValidRect.min.y * mappedSubresource.RowPitch + aValidRect.min.x * 4; *aStride = mappedSubresource.RowPitch; // Ensure our mapped data is accessible by writing to the beginning and end // of the dirty region. See bug 171519 uint32_t* probeData = (uint32_t*)mappedSubresource.pData + aDirtyRect.min.y * (mappedSubresource.RowPitch / 4) + aDirtyRect.min.x; *probeData = 0; uint32_t* probeDataEnd = (uint32_t*)mappedSubresource.pData + (aDirtyRect.max.y - 1) * (mappedSubresource.RowPitch / 4) + (aDirtyRect.max.x - 1); *probeDataEnd = 0; // Store the new valid rect, so that we can composite only those pixels mValidRect = gfx::Rect(aValidRect.min.x, aValidRect.min.y, aValidRect.width(), aValidRect.height()); return true; } void RenderCompositorD3D11SWGL::TileD3D11::Unmap( const gfx::IntRect& aDirtyRect) { const UploadMode uploadMode = mRenderCompositor->GetUploadMode(); if (!IsValid()) { return; } if (mSurface) { MOZ_ASSERT(uploadMode == Upload_Immediate); mSurface->Unmap(); nsIntRegion dirty(aDirtyRect); // This uses UpdateSubresource, which blocks, so is likely implemented as a // memcpy into driver owned memory, followed by an async upload. The staging // method should avoid this extra copy, and is likely to be faster usually. // We could possible do this call on a background thread so that sw-wr can // start drawing the next tile while the memcpy is in progress. mTexture->Update(mSurface, &dirty); return; } if (!mRenderCompositor->mCurrentStagingTexture) { return; } RefPtr context; mRenderCompositor->GetDevice()->GetImmediateContext(getter_AddRefs(context)); context->Unmap(mRenderCompositor->mCurrentStagingTexture, 0); D3D11_BOX box; box.front = 0; box.back = 1; box.left = aDirtyRect.X(); box.top = aDirtyRect.Y(); box.right = aDirtyRect.XMost(); box.bottom = aDirtyRect.YMost(); context->CopySubresourceRegion( mTexture->GetD3D11Texture(), 0, aDirtyRect.x, aDirtyRect.y, 0, mRenderCompositor->mCurrentStagingTexture, 0, &box); // If we allocated a temp staging texture for this tile, and we're running // in pooled mode, then consider adding it to the pool for later. if (mRenderCompositor->mCurrentStagingTextureIsTemp && uploadMode == Upload_StagingPooled) { static const uint32_t kMaxPoolSize = 5; if (mOwner->mStagingPool.Length() < kMaxPoolSize) { mOwner->mStagingPool.AppendElement( mRenderCompositor->mCurrentStagingTexture); } } mRenderCompositor->mCurrentStagingTexture = nullptr; mRenderCompositor->mCurrentStagingTextureIsTemp = false; } bool RenderCompositorD3D11SWGL::TileD3D11::IsValid() { return !!mTexture; } already_AddRefed RenderCompositorD3D11SWGL::CreateStagingTexture(const gfx::IntSize aSize) { CD3D11_TEXTURE2D_DESC desc(DXGI_FORMAT_B8G8R8A8_UNORM, aSize.width, aSize.height, 1, 1); desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE | D3D11_CPU_ACCESS_READ; desc.Usage = D3D11_USAGE_STAGING; desc.BindFlags = 0; RefPtr cpuTexture; DebugOnly hr = GetDevice()->CreateTexture2D(&desc, nullptr, getter_AddRefs(cpuTexture)); MOZ_ASSERT(SUCCEEDED(hr)); if (!cpuTexture) { gfxCriticalNote << "Failed to create StagingTexture: " << aSize; RenderThread::Get()->HandleWebRenderError(WebRenderError::NEW_SURFACE); } return cpuTexture.forget(); } already_AddRefed RenderCompositorD3D11SWGL::CreateStagingSurface(const gfx::IntSize aSize) { return gfx::Factory::CreateDataSourceSurface(aSize, gfx::SurfaceFormat::B8G8R8A8); } UniquePtr RenderCompositorD3D11SWGL::DoCreateTile(Surface* aSurface) { MOZ_RELEASE_ASSERT(aSurface); const auto tileSize = aSurface->TileSize(); if (mUploadMode == Upload_Immediate) { RefPtr source = new DataTextureSourceD3D11(gfx::SurfaceFormat::B8G8R8A8, mCompositor, layers::TextureFlags::NO_FLAGS); RefPtr surf = CreateStagingSurface(tileSize); return MakeUnique(source, nullptr, surf, aSurface, this); } MOZ_ASSERT(mUploadMode == Upload_Staging || mUploadMode == Upload_StagingNoBlock || mUploadMode == Upload_StagingPooled); CD3D11_TEXTURE2D_DESC desc(DXGI_FORMAT_B8G8R8A8_UNORM, tileSize.width, tileSize.height, 1, 1); RefPtr texture; DebugOnly hr = GetDevice()->CreateTexture2D(&desc, nullptr, getter_AddRefs(texture)); MOZ_ASSERT(SUCCEEDED(hr)); if (!texture) { gfxCriticalNote << "Failed to allocate Texture2D: " << aSurface->TileSize(); RenderThread::Get()->HandleWebRenderError(WebRenderError::NEW_SURFACE); return MakeUnique(nullptr, nullptr, nullptr, aSurface, this); } RefPtr source = new DataTextureSourceD3D11( GetDevice(), gfx::SurfaceFormat::B8G8R8A8, texture); RefPtr cpuTexture = CreateStagingTexture(tileSize); return MakeUnique(source, cpuTexture, nullptr, aSurface, this); } bool RenderCompositorD3D11SWGL::MaybeReadback( const gfx::IntSize& aReadbackSize, const wr::ImageFormat& aReadbackFormat, const Range& aReadbackBuffer, bool* aNeedsYFlip) { MOZ_ASSERT(aReadbackFormat == wr::ImageFormat::BGRA8); auto stride = aReadbackSize.width * gfx::BytesPerPixel(gfx::SurfaceFormat::B8G8R8A8); RefPtr dt = gfx::Factory::CreateDrawTargetForData( gfx::BackendType::SKIA, &aReadbackBuffer[0], aReadbackSize, stride, gfx::SurfaceFormat::B8G8R8A8, false); if (!dt) { return false; } GetCompositorD3D11()->Readback(dt); return true; } } // namespace wr } // namespace mozilla