Bug 1575765 - Implement KHR_partial_update for webrender. r=sotaro,jgilbert

KHR_partial_update allows us to avoid rerendering the entire
backbuffer every frame, and instead only render what has changed on
the current frame, as well as the difference between the current
backbuffer and the current frontbuffer. It works similarily to
EXT_buffer_age, which we already support, with the additional
requirement that we must call eglSetDamageRegion each frame before
rendering to the backbuffer.

Modify GLContextEGL::GetBufferAge() so that it queries the age if
either EXT_buffer_age or KHR_partial_update are available. This will
now automatically be queried by webrender through the
PartialPresentCompositor trait. Add a new function to that trait,
set_buffer_damage_region(), whose RenderCompositorEGL implementation
calls eglSetDamageRegion(). Call this from composite_simple(), once
the damage rect has been calculated but before rendering to the
backbuffer.

Additionally, change both RenderCompositorEGL and
RenderCompositorOGL's implementations of
ShouldDrawPreviousPartialPresentRegions() to unconditionally return
true, rather than checking for the existence of EXT_buffer_age (or
adding a new check for KHR_partial_update). The lack of these
extensions does not mean that webrender is able to skip rendering
previous frames' damage. Rather the opposite, it means we cannot
render *only* the previous frames' damage, and must instead always
render the entire buffer.

Differential Revision: https://phabricator.services.mozilla.com/D91203
This commit is contained in:
Jamie Nicol 2020-10-02 10:23:56 +00:00
Родитель 5d407f06e0
Коммит 7adec85b80
12 изменённых файлов: 123 добавлений и 17 удалений

Просмотреть файл

@ -98,7 +98,8 @@ class GLContextEGL final : public GLContext {
EGLSurface GetEGLSurface() const { return mSurface; }
bool HasBufferAge() const;
bool HasExtBufferAge() const;
bool HasKhrPartialUpdate() const;
EGLint GetBufferAge() const;
bool BindTex2DOffscreen(GLContext* aOffscreen);

Просмотреть файл

@ -599,15 +599,19 @@ void GLContextEGL::GetWSIInfo(nsCString* const out) const {
// for the lifetime of this context.
void GLContextEGL::HoldSurface(gfxASurface* aSurf) { mThebesSurface = aSurf; }
bool GLContextEGL::HasBufferAge() const {
bool GLContextEGL::HasExtBufferAge() const {
return mEgl->IsExtensionSupported(EGLExtension::EXT_buffer_age);
}
bool GLContextEGL::HasKhrPartialUpdate() const {
return mEgl->IsExtensionSupported(EGLExtension::KHR_partial_update);
}
EGLint GLContextEGL::GetBufferAge() const {
EGLSurface surface =
mSurfaceOverride != EGL_NO_SURFACE ? mSurfaceOverride : mSurface;
if (surface && HasBufferAge()) {
if (surface && (HasExtBufferAge() || HasKhrPartialUpdate())) {
EGLint result;
mEgl->fQuerySurface(surface, LOCAL_EGL_BUFFER_AGE_EXT, &result);
return result;

Просмотреть файл

@ -76,7 +76,8 @@ static const char* sEGLExtensionNames[] = {
"EGL_MOZ_create_context_provoking_vertex_dont_care",
"EGL_EXT_swap_buffers_with_damage",
"EGL_KHR_swap_buffers_with_damage",
"EGL_EXT_buffer_age"};
"EGL_EXT_buffer_age",
"EGL_KHR_partial_update"};
PRLibrary* LoadApitraceLibrary() {
const char* path = nullptr;
@ -599,6 +600,12 @@ bool GLLibraryEGL::Init(nsACString* const out_failureId) {
END_OF_SYMBOLS};
(void)fnLoadSymbols(symbols);
}
{
const SymLoadStruct symbols[] = {
{(PRFuncPtr*)&mSymbols.fSetDamageRegion, {{"eglSetDamageRegionKHR"}}},
END_OF_SYMBOLS};
(void)fnLoadSymbols(symbols);
}
return true;
}

Просмотреть файл

@ -104,6 +104,7 @@ enum class EGLExtension {
EXT_swap_buffers_with_damage,
KHR_swap_buffers_with_damage,
EXT_buffer_age,
KHR_partial_update,
Max
};
@ -446,6 +447,12 @@ class GLLibraryEGL final {
WRAP(fSwapBuffersWithDamage(dpy, surface, rects, n_rects));
}
// EGL_KHR_partial_update
EGLBoolean fSetDamageRegion(EGLDisplay dpy, EGLSurface surface,
const EGLint* rects, EGLint n_rects) {
WRAP(fSetDamageRegion(dpy, surface, rects, n_rects));
}
#undef WRAP
#undef PROFILE_CALL
#undef BEFORE_CALL
@ -571,6 +578,10 @@ class GLLibraryEGL final {
EGLSurface surface,
const EGLint* rects,
EGLint n_rects);
// EGL_KHR_partial_update
EGLBoolean(GLAPIENTRY* fSetDamageRegion)(EGLDisplay dpy, EGLSurface surface,
const EGLint* rects,
EGLint n_rects);
EGLClientBuffer(GLAPIENTRY* fGetNativeClientBufferANDROID)(
const struct AHardwareBuffer* buffer);
} mSymbols = {};
@ -824,6 +835,13 @@ class EglDisplay final {
IsExtensionSupported(EGLExtension::KHR_swap_buffers_with_damage));
return mLib->fSwapBuffersWithDamage(mDisplay, surface, rects, n_rects);
}
// EGL_KHR_partial_update
EGLBoolean fSetDamageRegion(EGLSurface surface, const EGLint* rects,
EGLint n_rects) {
MOZ_ASSERT(IsExtensionSupported(EGLExtension::KHR_partial_update));
return mLib->fSetDamageRegion(mDisplay, surface, rects, n_rects);
}
};
} /* namespace gl */

Просмотреть файл

@ -131,6 +131,12 @@ size_t wr_partial_present_compositor_get_buffer_age(const void* aCompositor) {
return compositor->GetBufferAge();
}
void wr_partial_present_compositor_set_buffer_damage_region(
void* aCompositor, const wr::DeviceIntRect* aRects, size_t aNumRects) {
RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
compositor->SetBufferDamageRegion(aRects, aNumRects);
}
/* static */
UniquePtr<RenderCompositor> RenderCompositor::Create(
RefPtr<widget::CompositorWidget>&& aWidget, nsACString& aError) {

Просмотреть файл

@ -131,6 +131,12 @@ class RenderCompositor {
// region which must be rendered in addition to the current frame's dirty
// rect.
virtual size_t GetBufferAge() const { return 0; }
// Allows webrender to specify the total region that will be rendered to this
// frame, ie the frame's dirty region and some previous frames' dirty regions,
// if applicable (calculated using the buffer age). Must be called before
// anything has been rendered to the main framebuffer.
virtual void SetBufferDamageRegion(const wr::DeviceIntRect* aRects,
size_t aNumRects) {}
// Whether the surface origin is top-left.
virtual bool SurfaceOriginIsTopLeft() { return false; }

Просмотреть файл

@ -245,9 +245,47 @@ uint32_t RenderCompositorEGL::GetMaxPartialPresentRects() {
}
bool RenderCompositorEGL::ShouldDrawPreviousPartialPresentRegions() {
return gl::GLContextEGL::Cast(gl())->HasBufferAge();
return true;
}
size_t RenderCompositorEGL::GetBufferAge() const { return mBufferAge; }
void RenderCompositorEGL::SetBufferDamageRegion(const wr::DeviceIntRect* aRects,
size_t aNumRects) {
const auto& gle = gl::GLContextEGL::Cast(gl());
const auto& egl = gle->mEgl;
if (gle->HasKhrPartialUpdate()) {
std::vector<EGLint> rects;
rects.reserve(4 * aNumRects);
const auto bufferSize = GetBufferSize();
for (size_t i = 0; i < aNumRects; i++) {
const auto left =
std::max(0, std::min(bufferSize.width, aRects[i].origin.x));
const auto top =
std::max(0, std::min(bufferSize.height, aRects[i].origin.y));
const auto right =
std::min(bufferSize.width,
std::max(0, aRects[i].origin.x + aRects[i].size.width));
const auto bottom =
std::min(bufferSize.height,
std::max(0, aRects[i].origin.y + aRects[i].size.height));
const auto width = right - left;
const auto height = bottom - top;
rects.push_back(left);
rects.push_back(bufferSize.height - bottom);
rects.push_back(width);
rects.push_back(height);
}
const auto ret =
egl->fSetDamageRegion(mEGLSurface, rects.data(), rects.size() / 4);
if (ret == LOCAL_EGL_FALSE) {
const auto err = egl->mLib->fGetError();
gfxCriticalError() << "Error in eglSetDamageRegion: " << gfx::hexa(err);
}
}
}
} // namespace mozilla::wr

Просмотреть файл

@ -43,6 +43,8 @@ class RenderCompositorEGL : public RenderCompositor {
uint32_t GetMaxPartialPresentRects() override;
bool ShouldDrawPreviousPartialPresentRegions() override;
size_t GetBufferAge() const override;
void SetBufferDamageRegion(const wr::DeviceIntRect* aRects,
size_t aNumRects) override;
ipc::FileDescriptor GetAndResetReleaseFence() override;

Просмотреть файл

@ -121,7 +121,7 @@ bool RenderCompositorOGL::UsePartialPresent() {
}
bool RenderCompositorOGL::ShouldDrawPreviousPartialPresentRegions() {
return mIsEGL && gl::GLContextEGL::Cast(gl())->HasBufferAge();
return true;
}
size_t RenderCompositorOGL::GetBufferAge() const {

Просмотреть файл

@ -1242,6 +1242,11 @@ extern "C" {
fn wr_compositor_unmap_tile(compositor: *mut c_void);
fn wr_partial_present_compositor_get_buffer_age(compositor: *const c_void) -> usize;
fn wr_partial_present_compositor_set_buffer_damage_region(
compositor: *mut c_void,
rects: *const DeviceIntRect,
n_rects: usize,
);
}
pub struct WrCompositor(*mut c_void);
@ -1362,6 +1367,12 @@ impl PartialPresentCompositor for WrPartialPresentCompositor {
fn get_buffer_age(&self) -> usize {
unsafe { wr_partial_present_compositor_get_buffer_age(self.0) }
}
fn set_buffer_damage_region(&mut self, rects: &[DeviceIntRect]) {
unsafe {
wr_partial_present_compositor_set_buffer_damage_region(self.0, rects.as_ptr(), rects.len());
}
}
}
/// Information about the underlying data buffer of a mapped tile.

Просмотреть файл

@ -1024,6 +1024,11 @@ pub trait PartialPresentCompositor {
/// draw_previous_partial_present_regions is true, to determine the
/// region which must be rendered in addition to the current frame's dirty rect.
fn get_buffer_age(&self) -> usize;
/// Allows webrender to specify the total region that will be rendered to this frame,
/// ie the frame's dirty region and some previous frames' dirty regions, if applicable
/// (calculated using the buffer age). Must be called before anything has been rendered
/// to the main framebuffer.
fn set_buffer_damage_region(&mut self, rects: &[DeviceIntRect]);
}
/// Information about an opaque surface used to occlude tiles.

Просмотреть файл

@ -5242,14 +5242,14 @@ impl Renderer {
// Work out how many dirty rects WR produced, and if that's more than
// what the device supports.
for tile in composite_state.opaque_tiles.iter().chain(composite_state.alpha_tiles.iter()) {
let dirty_rect = tile.dirty_rect.translate(tile.rect.origin.to_vector());
combined_dirty_rect = combined_dirty_rect.union(&dirty_rect);
let tile_dirty_rect = tile.dirty_rect.translate(tile.rect.origin.to_vector());
combined_dirty_rect = combined_dirty_rect.union(&tile_dirty_rect);
}
let combined_dirty_rect = combined_dirty_rect.round();
let combined_dirty_rect_i32 = combined_dirty_rect.to_i32();
// If nothing has changed, don't return any dirty rects at all (the client
// can use this as a signal to skip present completely).
// Return this frame's dirty region. If nothing has changed, don't return any dirty
// rects at all (the client can use this as a signal to skip present completely).
if !combined_dirty_rect.is_empty() {
results.dirty_rects.push(combined_dirty_rect_i32);
}
@ -5260,15 +5260,23 @@ impl Renderer {
}
// If the implementation requires manually keeping the buffer consistent,
// combine the previous frames' damage rects for tile clipping.
// (Not for the returned region though, that should be from this frame only)
// then we must combine this frame's dirty region with that of previous frames
// to determine the total_dirty_rect. The is used to determine what region we
// render to, and is what we send to the compositor as the buffer damage region
// (eg for KHR_partial_update).
let total_dirty_rect = if draw_previous_partial_present_regions {
combined_dirty_rect.union(&prev_frames_damage_rect.unwrap())
} else {
combined_dirty_rect
};
partial_present_mode = Some(PartialPresentMode::Single {
dirty_rect: if draw_previous_partial_present_regions {
combined_dirty_rect.union(&prev_frames_damage_rect.unwrap())
} else {
combined_dirty_rect
},
dirty_rect: total_dirty_rect,
});
if let Some(partial_present) = self.compositor_config.partial_present() {
partial_present.set_buffer_damage_region(&[total_dirty_rect.to_i32()]);
}
} else {
// If we don't have a valid partial present scenario, return a single
// dirty rect to the client that covers the entire framebuffer.