Bug 1607352 - Support DirectComposition virtual surface API. r=sotaro

Adds an #ifdef to the DCLayerTree implementation that allows
selecting whether to use the virtual surface API (enabled by
default) or the regular DC surface API.

For now, this is a compile-time switch. As a follow up to this,
we will support both options at runtime (for example, using the
regular surface API for surfaces that have holes or translucency).

Differential Revision: https://phabricator.services.mozilla.com/D58870

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Glenn Watson 2020-01-07 20:42:58 +00:00
parent 5f422b375c
commit 74722337a2
3 changed files with 218 additions and 74 deletions

View File

@ -50,11 +50,15 @@ DCLayerTree::DCLayerTree(gl::GLContext* aGL, EGLConfig aEGLConfig,
mDevice(aDevice),
mCompositionDevice(aCompositionDevice),
mDebugCounter(false),
mDebugVisualRedrawRegions(false) {}
mDebugVisualRedrawRegions(false),
mEGLImage(EGL_NO_IMAGE),
mColorRBO(0) {}
DCLayerTree::~DCLayerTree() {
const auto gl = GetGLContext();
DestroyEGLSurface();
// Delete any cached FBO objects
for (auto it = mFrameBuffers.begin(); it != mFrameBuffers.end(); ++it) {
gl->fDeleteRenderbuffers(1, &it->depthRboId);
@ -210,23 +214,34 @@ void DCLayerTree::CompositorEndFrame() {
void DCLayerTree::Bind(wr::NativeTileId aId, wr::DeviceIntPoint* aOffset,
uint32_t* aFboId, wr::DeviceIntRect aDirtyRect) {
auto surface = GetSurface(aId.surface_id);
auto layer = surface->GetLayer(aId.x, aId.y);
wr::DeviceIntPoint targetOffset{0, 0};
*aFboId = layer->CreateEGLSurfaceForCompositionSurface(aDirtyRect, aOffset);
mCurrentId = Some(aId);
#ifdef USE_VIRTUAL_SURFACES
wr::DeviceIntSize tileSize = surface->GetTileSize();
RefPtr<IDCompositionSurface> compositionSurface =
surface->GetCompositionSurface();
targetOffset.x = VIRTUAL_OFFSET + tileSize.width * aId.x;
targetOffset.y = VIRTUAL_OFFSET + tileSize.height * aId.y;
#else
auto layer = surface->GetLayer(aId.x, aId.y);
RefPtr<IDCompositionSurface> compositionSurface =
layer->GetCompositionSurface();
#endif
*aFboId = CreateEGLSurfaceForCompositionSurface(
aDirtyRect, aOffset, compositionSurface, targetOffset);
mCurrentSurface = Some(compositionSurface);
}
void DCLayerTree::Unbind() {
if (mCurrentId.isNothing()) {
if (mCurrentSurface.isNothing()) {
return;
}
const auto id = mCurrentId.ref();
auto surface = GetSurface(id.surface_id);
auto layer = surface->GetLayer(id.x, id.y);
RefPtr<IDCompositionSurface> surface = mCurrentSurface.ref();
surface->EndDraw();
layer->EndDraw();
mCurrentId = Nothing();
mCurrentSurface = Nothing();
}
void DCLayerTree::CreateSurface(wr::NativeSurfaceId aId,
@ -274,6 +289,13 @@ void DCLayerTree::AddSurface(wr::NativeSurfaceId aId,
const auto layer = it->second.get();
const auto visual = layer->GetVisual();
#ifdef USE_VIRTUAL_SURFACES
layer->UpdateAllocatedRect();
aPosition.x -= VIRTUAL_OFFSET;
aPosition.y -= VIRTUAL_OFFSET;
#endif
// Place the visual - this changes frame to frame based on scroll position
// of the slice.
visual->SetOffsetX(aPosition.x);
@ -336,7 +358,10 @@ GLuint DCLayerTree::GetOrCreateFbo(int aWidth, int aHeight) {
DCSurface::DCSurface(wr::DeviceIntSize aTileSize, bool aIsOpaque,
DCLayerTree* aDCLayerTree)
: mDCLayerTree(aDCLayerTree), mTileSize(aTileSize), mIsOpaque(aIsOpaque) {}
: mDCLayerTree(aDCLayerTree),
mTileSize(aTileSize),
mIsOpaque(aIsOpaque),
mAllocatedRectDirty(true) {}
DCSurface::~DCSurface() {}
@ -349,6 +374,20 @@ bool DCSurface::Initialize() {
return false;
}
#ifdef USE_VIRTUAL_SURFACES
DXGI_ALPHA_MODE alpha_mode =
mIsOpaque ? DXGI_ALPHA_MODE_IGNORE : DXGI_ALPHA_MODE_PREMULTIPLIED;
hr = dCompDevice->CreateVirtualSurface(VIRTUAL_OFFSET * 2, VIRTUAL_OFFSET * 2,
DXGI_FORMAT_B8G8R8A8_UNORM, alpha_mode,
getter_AddRefs(mVirtualSurface));
MOZ_ASSERT(SUCCEEDED(hr));
// Bind the surface memory to this visual
hr = mVisual->SetContent(mVirtualSurface);
MOZ_ASSERT(SUCCEEDED(hr));
#endif
return true;
}
@ -362,17 +401,53 @@ void DCSurface::CreateTile(int aX, int aY) {
return;
}
#ifdef USE_VIRTUAL_SURFACES
mAllocatedRectDirty = true;
#else
mVisual->AddVisual(layer->GetVisual(), FALSE, NULL);
#endif
mDCLayers[key] = std::move(layer);
}
void DCSurface::DestroyTile(int aX, int aY) {
TileKey key(aX, aY);
#ifdef USE_VIRTUAL_SURFACES
mAllocatedRectDirty = true;
#else
auto layer = GetLayer(aX, aY);
mVisual->RemoveVisual(layer->GetVisual());
#endif
mDCLayers.erase(key);
}
#ifdef USE_VIRTUAL_SURFACES
void DCSurface::UpdateAllocatedRect() {
if (mAllocatedRectDirty) {
RECT rect = {1000000, 1000000, -1000000, -1000000};
for (auto it = mDCLayers.begin(); it != mDCLayers.end(); ++it) {
int x = it->first.mX;
int y = it->first.mY;
rect.left = std::min((int)rect.left, x * mTileSize.width);
rect.right = std::max((int)rect.right, (x + 1) * mTileSize.width);
rect.top = std::min((int)rect.top, y * mTileSize.height);
rect.bottom = std::max((int)rect.bottom, (y + 1) * mTileSize.height);
}
rect.left += VIRTUAL_OFFSET;
rect.top += VIRTUAL_OFFSET;
rect.bottom += VIRTUAL_OFFSET;
rect.right += VIRTUAL_OFFSET;
mVirtualSurface->Trim(&rect, 1);
mAllocatedRectDirty = false;
}
}
#endif
DCLayer* DCSurface::GetLayer(int aX, int aY) const {
TileKey key(aX, aY);
auto layer_it = mDCLayers.find(key);
@ -380,18 +455,17 @@ DCLayer* DCSurface::GetLayer(int aX, int aY) const {
return layer_it->second.get();
}
DCLayer::DCLayer(DCLayerTree* aDCLayerTree)
: mDCLayerTree(aDCLayerTree), mEGLImage(EGL_NO_IMAGE), mColorRBO(0) {}
DCLayer::DCLayer(DCLayerTree* aDCLayerTree) : mDCLayerTree(aDCLayerTree) {}
DCLayer::~DCLayer() { DestroyEGLSurface(); }
DCLayer::~DCLayer() {}
bool DCLayer::Initialize(int aX, int aY, wr::DeviceIntSize aSize,
bool aIsOpaque) {
if (aSize.width <= 0 || aSize.height <= 0) {
return false;
}
mBufferSize = LayoutDeviceIntSize(aSize.width, aSize.height);
#ifndef USE_VIRTUAL_SURFACES
HRESULT hr;
const auto dCompDevice = mDCLayerTree->GetCompositionDevice();
hr = dCompDevice->CreateVisual(getter_AddRefs(mVisual));
@ -415,10 +489,12 @@ bool DCLayer::Initialize(int aX, int aY, wr::DeviceIntSize aSize,
// Scroll offsets get applied to the parent visual only.
mVisual->SetOffsetX(aX * aSize.width);
mVisual->SetOffsetY(aY * aSize.height);
#endif
return true;
}
#ifndef USE_VIRTUAL_SURFACES
RefPtr<IDCompositionSurface> DCLayer::CreateCompositionSurface(
wr::DeviceIntSize aSize, bool aIsOpaque) {
HRESULT hr;
@ -437,45 +513,38 @@ RefPtr<IDCompositionSurface> DCLayer::CreateCompositionSurface(
}
return compositionSurface;
}
#endif
GLuint DCLayer::CreateEGLSurfaceForCompositionSurface(
wr::DeviceIntRect aDirtyRect, wr::DeviceIntPoint* aOffset) {
MOZ_ASSERT(mCompositionSurface.get());
GLuint DCLayerTree::CreateEGLSurfaceForCompositionSurface(
wr::DeviceIntRect aDirtyRect, wr::DeviceIntPoint* aOffset,
RefPtr<IDCompositionSurface> aCompositionSurface,
wr::DeviceIntPoint aSurfaceOffset) {
MOZ_ASSERT(aCompositionSurface.get());
HRESULT hr;
const auto gl = mDCLayerTree->GetGLContext();
const auto gl = GetGLContext();
RefPtr<ID3D11Texture2D> backBuf;
POINT offset;
LayoutDeviceIntRect dirtyRect(aDirtyRect.origin.x, aDirtyRect.origin.y,
aDirtyRect.size.width, aDirtyRect.size.height);
RECT update_rect;
update_rect.left = dirtyRect.X();
update_rect.top = dirtyRect.Y();
update_rect.right = dirtyRect.XMost();
update_rect.bottom = dirtyRect.YMost();
update_rect.left = aSurfaceOffset.x + aDirtyRect.origin.x;
update_rect.top = aSurfaceOffset.y + aDirtyRect.origin.y;
update_rect.right = update_rect.left + aDirtyRect.size.width;
update_rect.bottom = update_rect.top + aDirtyRect.size.height;
RECT* rect = &update_rect;
if (StaticPrefs::gfx_webrender_compositor_max_update_rects_AtStartup() <= 0) {
// Update entire surface
rect = nullptr;
}
hr = mCompositionSurface->BeginDraw(rect, __uuidof(ID3D11Texture2D),
hr = aCompositionSurface->BeginDraw(&update_rect, __uuidof(ID3D11Texture2D),
(void**)getter_AddRefs(backBuf), &offset);
if (FAILED(hr)) {
gfxCriticalNote << "DCompositionSurface::BeginDraw failed: "
<< gfx::hexa(hr) << "dirtyRect: " << dirtyRect;
<< gfx::hexa(hr);
return false;
}
// DC includes the origin of the dirty / update rect in the draw offset,
// undo that here since WR expects it to be an absolute offset.
offset.x -= dirtyRect.X();
offset.y -= dirtyRect.Y();
offset.x -= aDirtyRect.origin.x;
offset.y -= aDirtyRect.origin.y;
// Texture size could be diffrent from mBufferSize.
D3D11_TEXTURE2D_DESC desc;
backBuf->GetDesc(&desc);
@ -500,7 +569,7 @@ GLuint DCLayer::CreateEGLSurfaceForCompositionSurface(
gl->fEGLImageTargetRenderbufferStorage(LOCAL_GL_RENDERBUFFER, mEGLImage);
// Get or create an FBO for the specified dimensions
GLuint fboId = mDCLayerTree->GetOrCreateFbo(desc.Width, desc.Height);
GLuint fboId = GetOrCreateFbo(desc.Width, desc.Height);
// Attach the new renderbuffer to the FBO
gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, fboId);
@ -518,8 +587,8 @@ GLuint DCLayer::CreateEGLSurfaceForCompositionSurface(
return fboId;
}
void DCLayer::DestroyEGLSurface() {
const auto gl = mDCLayerTree->GetGLContext();
void DCLayerTree::DestroyEGLSurface() {
const auto gl = GetGLContext();
if (mColorRBO) {
gl->fDeleteRenderbuffers(1, &mColorRBO);
@ -534,15 +603,5 @@ void DCLayer::DestroyEGLSurface() {
}
}
void DCLayer::EndDraw() {
MOZ_ASSERT(mCompositionSurface.get());
if (!mCompositionSurface) {
return;
}
mCompositionSurface->EndDraw();
DestroyEGLSurface();
}
} // namespace wr
} // namespace mozilla

View File

@ -23,6 +23,7 @@ struct IDCompositionSurface;
struct IDCompositionTarget;
struct IDCompositionVisual2;
struct IDXGISwapChain1;
struct IDCompositionVirtualSurface;
namespace mozilla {
@ -32,6 +33,15 @@ class GLContext;
namespace wr {
#define USE_VIRTUAL_SURFACES
// DirectComposition virtual surfaces are zero based, but WR picture cache
// bounds can potentially have a negative origin. Shift all the picture cache
// coordinates by a large fixed amount, such that we don't need to re-create
// the surface if the picture cache origin becomes negative due to adding more
// tiles to the above / left.
#define VIRTUAL_OFFSET 512 * 1024
class DCLayer;
class DCSurface;
@ -81,6 +91,11 @@ class DCLayerTree {
bool Initialize(HWND aHwnd);
bool MaybeUpdateDebugCounter();
bool MaybeUpdateDebugVisualRedrawRegions();
void DestroyEGLSurface();
GLuint CreateEGLSurfaceForCompositionSurface(
wr::DeviceIntRect aDirtyRect, wr::DeviceIntPoint* aOffset,
RefPtr<IDCompositionSurface> aCompositionSurface,
wr::DeviceIntPoint aSurfaceOffset);
RefPtr<gl::GLContext> mGL;
EGLConfig mEGLConfig;
@ -95,7 +110,15 @@ class DCLayerTree {
bool mDebugCounter;
bool mDebugVisualRedrawRegions;
Maybe<wr::NativeTileId> mCurrentId;
Maybe<RefPtr<IDCompositionSurface>> mCurrentSurface;
// The EGL image that is bound to the D3D texture provided by
// DirectComposition.
EGLImage mEGLImage;
// The GL render buffer ID that maps the EGLImage to an RBO for attaching to
// an FBO.
GLuint mColorRBO;
struct SurfaceIdHashFn {
std::size_t operator()(const wr::NativeSurfaceId& aId) const {
@ -153,6 +176,16 @@ class DCSurface {
int32_t mY;
};
#ifdef USE_VIRTUAL_SURFACES
wr::DeviceIntSize GetTileSize() const { return mTileSize; }
IDCompositionVirtualSurface* GetCompositionSurface() const {
return mVirtualSurface;
}
void UpdateAllocatedRect();
#endif
protected:
DCLayerTree* mDCLayerTree;
@ -170,7 +203,12 @@ class DCSurface {
wr::DeviceIntSize mTileSize;
bool mIsOpaque;
bool mAllocatedRectDirty;
std::unordered_map<TileKey, UniquePtr<DCLayer>, TileKeyHashFn> mDCLayers;
#ifdef USE_VIRTUAL_SURFACES
RefPtr<IDCompositionVirtualSurface> mVirtualSurface;
#endif
};
/**
@ -182,10 +220,8 @@ class DCLayer {
explicit DCLayer(DCLayerTree* aDCLayerTree);
~DCLayer();
bool Initialize(int aX, int aY, wr::DeviceIntSize aSize, bool aIsOpaque);
GLuint CreateEGLSurfaceForCompositionSurface(wr::DeviceIntRect aDirtyRect,
wr::DeviceIntPoint* aOffset);
void EndDraw();
#ifndef USE_VIRTUAL_SURFACES
IDCompositionSurface* GetCompositionSurface() const {
return mCompositionSurface;
}
@ -194,23 +230,12 @@ class DCLayer {
protected:
RefPtr<IDCompositionSurface> CreateCompositionSurface(wr::DeviceIntSize aSize,
bool aIsOpaque);
void DestroyEGLSurface();
DCLayerTree* mDCLayerTree;
RefPtr<IDCompositionSurface> mCompositionSurface;
// The EGL image that is bound to the D3D texture provided by
// DirectComposition.
EGLImage mEGLImage;
// The GL render buffer ID that maps the EGLImage to an RBO for attaching to
// an FBO.
GLuint mColorRBO;
LayoutDeviceIntSize mBufferSize;
RefPtr<IDCompositionVisual2> mVisual;
#endif
DCLayerTree* mDCLayerTree;
};
static inline bool operator==(const DCSurface::TileKey& a0,

View File

@ -27,6 +27,9 @@
#define NUM_QUERIES 2
#define USE_VIRTUAL_SURFACES
#define VIRTUAL_OFFSET 512 * 1024
enum SyncMode {
None = 0,
Swap = 1,
@ -37,10 +40,12 @@ enum SyncMode {
// The OS compositor representation of a picture cache tile.
struct Tile {
#ifndef USE_VIRTUAL_SURFACES
// Represents the underlying DirectComposition surface texture that gets drawn into.
IDCompositionSurface *pSurface;
// Represents the node in the visual tree that defines the properties of this tile (clip, position etc).
IDCompositionVisual2 *pVisual;
#endif
};
struct TileKey {
@ -66,6 +71,9 @@ struct Surface {
bool is_opaque;
std::unordered_map<TileKey, Tile, TileKeyHasher> tiles;
IDCompositionVisual2 *pVisual;
#ifdef USE_VIRTUAL_SURFACES
IDCompositionVirtualSurface *pVirtualSurface;
#endif
};
struct CachedFrameBuffer {
@ -216,7 +224,8 @@ extern "C" {
name = L"example-compositor (Simple)";
}
window->hWnd = CreateWindow(
window->hWnd = CreateWindowEx(
WS_EX_NOREDIRECTIONBITMAP,
CLASS_NAME,
name,
WS_OVERLAPPEDWINDOW,
@ -379,10 +388,12 @@ extern "C" {
for (auto surface_it=window->surfaces.begin() ; surface_it != window->surfaces.end() ; ++surface_it) {
Surface &surface = surface_it->second;
#ifndef USE_VIRTUAL_SURFACES
for (auto tile_it=surface.tiles.begin() ; tile_it != surface.tiles.end() ; ++tile_it) {
tile_it->second.pSurface->Release();
tile_it->second.pVisual->Release();
}
#endif
surface.pVisual->Release();
}
@ -480,6 +491,23 @@ extern "C" {
HRESULT hr = window->pDCompDevice->CreateVisual(&surface.pVisual);
assert(SUCCEEDED(hr));
#ifdef USE_VIRTUAL_SURFACES
DXGI_ALPHA_MODE alpha_mode = surface.is_opaque ? DXGI_ALPHA_MODE_IGNORE : DXGI_ALPHA_MODE_PREMULTIPLIED;
hr = window->pDCompDevice->CreateVirtualSurface(
VIRTUAL_OFFSET * 2,
VIRTUAL_OFFSET * 2,
DXGI_FORMAT_B8G8R8A8_UNORM,
alpha_mode,
&surface.pVirtualSurface
);
assert(SUCCEEDED(hr));
// Bind the surface memory to this visual
hr = surface.pVisual->SetContent(surface.pVirtualSurface);
assert(SUCCEEDED(hr));
#endif
window->surfaces[id] = surface;
}
@ -497,6 +525,7 @@ extern "C" {
Tile tile;
#ifndef USE_VIRTUAL_SURFACES
// Create the video memory surface.
DXGI_ALPHA_MODE alpha_mode = surface.is_opaque ? DXGI_ALPHA_MODE_IGNORE : DXGI_ALPHA_MODE_PREMULTIPLIED;
HRESULT hr = window->pDCompDevice->CreateSurface(
@ -527,6 +556,7 @@ extern "C" {
FALSE,
NULL
);
#endif
surface.tiles[key] = tile;
}
@ -544,10 +574,12 @@ extern "C" {
assert(surface.tiles.count(key) == 1);
Tile &tile = surface.tiles[key];
#ifndef USE_VIRTUAL_SURFACES
surface.pVisual->RemoveVisual(tile.pVisual);
tile.pVisual->Release();
tile.pSurface->Release();
#endif
surface.tiles.erase(key);
}
@ -561,11 +593,15 @@ extern "C" {
window->pRoot->RemoveVisual(surface.pVisual);
#ifdef USE_VIRTUAL_SURFACES
surface.pVirtualSurface->Release();
#else
// Release the video memory and visual in the tree
for (auto tile_it=surface.tiles.begin() ; tile_it != surface.tiles.end() ; ++tile_it) {
tile_it->second.pSurface->Release();
tile_it->second.pVisual->Release();
}
#endif
surface.pVisual->Release();
window->surfaces.erase(id);
@ -591,9 +627,6 @@ extern "C" {
assert(surface.tiles.count(key) == 1);
Tile &tile = surface.tiles[key];
// Store the current surface for unbinding later
window->pCurrentSurface = tile.pSurface;
// Inform DC that we want to draw on this surface. DC uses texture
// atlases when the tiles are small. It returns an offset where the
// client code must draw into this surface when this happens.
@ -605,17 +638,40 @@ extern "C" {
POINT offset;
D3D11_TEXTURE2D_DESC desc;
ID3D11Texture2D *pTexture;
HRESULT hr = tile.pSurface->BeginDraw(
HRESULT hr;
// Store the current surface for unbinding later
#ifdef USE_VIRTUAL_SURFACES
LONG tile_offset_x = VIRTUAL_OFFSET + tile_x * surface.tile_width;
LONG tile_offset_y = VIRTUAL_OFFSET + tile_y * surface.tile_height;
update_rect.left += tile_offset_x;
update_rect.top += tile_offset_y;
update_rect.right += tile_offset_x;
update_rect.bottom += tile_offset_y;
hr = surface.pVirtualSurface->BeginDraw(
&update_rect,
__uuidof(ID3D11Texture2D),
(void **) &pTexture,
&offset
);
window->pCurrentSurface = surface.pVirtualSurface;
#else
hr = tile.pSurface->BeginDraw(
&update_rect,
__uuidof(ID3D11Texture2D),
(void **) &pTexture,
&offset
);
window->pCurrentSurface = tile.pSurface;
#endif
// DC includes the origin of the dirty / update rect in the draw offset,
// undo that here since WR expects it to be an absolute offset.
assert(SUCCEEDED(hr));
offset.x -= dirty_x0;
offset.y -= dirty_y0;
assert(SUCCEEDED(hr));
pTexture->GetDesc(&desc);
*x_offset = offset.x;
*y_offset = offset.y;
@ -690,6 +746,10 @@ extern "C" {
// of the slice.
float offset_x = (float) (x + window->client_rect.left);
float offset_y = (float) (y + window->client_rect.top);
#ifdef USE_VIRTUAL_SURFACES
offset_x -= VIRTUAL_OFFSET;
offset_y -= VIRTUAL_OFFSET;
#endif
surface.pVisual->SetOffsetX(offset_x);
surface.pVisual->SetOffsetY(offset_y);