This is one of several patchset s to improve fb deferred I/O and DRM's use of it. The patches fix several problems with the current helpers and add some easy improvements.
Deferred I/O tracks dirty pages of the screen buffer, from which DRM calculates a bounding box of the damaged area. Patches 1 and 2 resolve bugs in the computation. Specifically, it makes the damage handling work with overallocation and panning enabled.
Patches 3 to 5 add an easy performance improvement. Instead of damaging the whole viewport after a write operation, only damage the written area. For large writes the area consists of multiple scanlines, for small writes the area might consist of only a few pixels on a single scanline. Write operations now also work correctly with overallocation and panning enabled.
v2: * rename memory-to-clip helper (Javier)
Thomas Zimmermann (5): drm/fb-helper: Fix clip rectangle height drm/fb-helper: Fix vertical damage clipping drm/fb-helper: Calculate damaged area in separate helper drm/fb-helper: Clip damage area to written memory range drm/fb-helper: Clip damage area horizontally
drivers/gpu/drm/drm_fb_helper.c | 69 ++++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 15 deletions(-)
Computing the clip rectangle is prone to off-by-one errors when writes happen near the end of a memory page. Point the end of the memory area to the first trailing byte, so that (end - start) returns the area's length.
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Reviewed-by: Javier Martinez Canillas javierm@redhat.com --- drivers/gpu/drm/drm_fb_helper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index f15127a32f7a..a37fb4a980c8 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -699,7 +699,7 @@ void drm_fb_helper_deferred_io(struct fb_info *info, max = 0; list_for_each_entry(page, pagelist, lru) { start = page->index << PAGE_SHIFT; - end = start + PAGE_SIZE - 1; + end = start + PAGE_SIZE; min = min(min, start); max = max(max, end); }
Don't clip the damage rectangle against the viewport. This only works if the viewport is located at the beginning of the video memory and the video memory doesn't extend the screen (i.e., if there's no overallocation).
Fbdev emulation transfers data from write operations into a possible shadow buffer, then into a GEM buffer object, and finally via graphics driver onto the screen.
If callers write outside the currently visible area, clipping the damage rectangle against the viewport will loose these updates in the shadow buffer and the fbdev's buffer object will contain stale data. Panning the viewport to the stale area of the buffer will display obsolete data.
Instead, mark all written areas as damaged, so that the damage handler updates the buffer object from the shadow buffer for all such areas. The graphics driver's later has the option of clipping the damaged area against the viewport when updating the screen from the buffer object.
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Reviewed-by: Javier Martinez Canillas javierm@redhat.com --- drivers/gpu/drm/drm_fb_helper.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index a37fb4a980c8..87c47093c3a2 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -706,8 +706,7 @@ void drm_fb_helper_deferred_io(struct fb_info *info,
if (min < max) { y1 = min / info->fix.line_length; - y2 = min_t(u32, DIV_ROUND_UP(max, info->fix.line_length), - info->var.yres); + y2 = DIV_ROUND_UP(max, info->fix.line_length); drm_fb_helper_damage(info, 0, y1, info->var.xres, y2 - y1); } }
Add drm_fb_helper_memory_range_to_clip(), a helper function that accepts an linear range of video memory and converts it into a rectangle. The computed rectangle describes the damaged area in terms of scanlines and pixels per scanline.
While at it, make the code more readable by using struct drm_rect and related helpers.
The code was previously part of the deferred I/O helpers, but is also useful for damage handling of regular write operations. Update the deferred I/O code to use the new function.
v2: * rename helper (Javier)
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Reviewed-by: Javier Martinez Canillas javierm@redhat.com --- drivers/gpu/drm/drm_fb_helper.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index 87c47093c3a2..3d6d0b1464e7 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -680,6 +680,19 @@ static void drm_fb_helper_damage(struct fb_info *info, u32 x, u32 y, schedule_work(&helper->damage_work); }
+/* Convert memory region into area of scanlines and pixels per scanline */ +static void drm_fb_helper_memory_range_to_clip(struct fb_info *info, off_t off, size_t len, + struct drm_rect *clip) +{ + off_t end = off + len; + u32 x1 = 0; + u32 y1 = off / info->fix.line_length; + u32 x2 = info->var.xres; + u32 y2 = DIV_ROUND_UP(end, info->fix.line_length); + + drm_rect_init(clip, x1, y1, x2 - x1, y2 - y1); +} + /** * drm_fb_helper_deferred_io() - fbdev deferred_io callback function * @info: fb_info struct pointer @@ -693,7 +706,7 @@ void drm_fb_helper_deferred_io(struct fb_info *info, { unsigned long start, end, min, max; struct page *page; - u32 y1, y2; + struct drm_rect damage_area;
min = ULONG_MAX; max = 0; @@ -703,12 +716,13 @@ void drm_fb_helper_deferred_io(struct fb_info *info, min = min(min, start); max = max(max, end); } + if (min >= max) + return;
- if (min < max) { - y1 = min / info->fix.line_length; - y2 = DIV_ROUND_UP(max, info->fix.line_length); - drm_fb_helper_damage(info, 0, y1, info->var.xres, y2 - y1); - } + drm_fb_helper_memory_range_to_clip(info, min, max - min, &damage_area); + drm_fb_helper_damage(info, damage_area.x1, damage_area.y1, + drm_rect_width(&damage_area), + drm_rect_height(&damage_area)); } EXPORT_SYMBOL(drm_fb_helper_deferred_io);
Write helpers used to mark the complete screen as dirty. This is wasteful for writes that only change a small portion of the screen. Fix the problem by computing the damaged area from the written memory range and perform damage handling accordingly.
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Reviewed-by: Javier Martinez Canillas javierm@redhat.com --- drivers/gpu/drm/drm_fb_helper.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index 3d6d0b1464e7..4d5410dd96c5 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -754,11 +754,18 @@ EXPORT_SYMBOL(drm_fb_helper_sys_read); ssize_t drm_fb_helper_sys_write(struct fb_info *info, const char __user *buf, size_t count, loff_t *ppos) { + loff_t pos = *ppos; ssize_t ret; + struct drm_rect damage_area;
ret = fb_sys_write(info, buf, count, ppos); - if (ret > 0) - drm_fb_helper_damage(info, 0, 0, info->var.xres, info->var.yres); + if (ret <= 0) + return ret; + + drm_fb_helper_memory_range_to_clip(info, pos, ret, &damage_area); + drm_fb_helper_damage(info, damage_area.x1, damage_area.y1, + drm_rect_width(&damage_area), + drm_rect_height(&damage_area));
return ret; } @@ -2237,6 +2244,7 @@ static ssize_t drm_fbdev_fb_write(struct fb_info *info, const char __user *buf, loff_t pos = *ppos; size_t total_size; ssize_t ret; + struct drm_rect damage_area; int err = 0;
if (info->screen_size) @@ -2265,13 +2273,19 @@ static ssize_t drm_fbdev_fb_write(struct fb_info *info, const char __user *buf, else ret = fb_write_screen_buffer(info, buf, count, pos);
- if (ret > 0) - *ppos += ret; + if (ret < 0) + return ret; /* return last error, if any */ + else if (!ret) + return err; /* return previous error, if any */
- if (ret > 0) - drm_fb_helper_damage(info, 0, 0, info->var.xres_virtual, info->var.yres_virtual); + *ppos += ret;
- return ret ? ret : err; + drm_fb_helper_memory_range_to_clip(info, pos, ret, &damage_area); + drm_fb_helper_damage(info, damage_area.x1, damage_area.y1, + drm_rect_width(&damage_area), + drm_rect_height(&damage_area)); + + return ret; }
static void drm_fbdev_fb_fillrect(struct fb_info *info,
Clip the damage area horizontally if only a single scanline has been changed. This is helpful to reduce the memcpy overhead for small writes.
Signed-off-by: Thomas Zimmermann tzimmermann@suse.de Reviewed-by: Javier Martinez Canillas javierm@redhat.com --- drivers/gpu/drm/drm_fb_helper.c | 12 ++++++++++++ 1 file changed, 12 insertions(+)
diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index 4d5410dd96c5..e05a7bd6fb12 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -690,6 +690,18 @@ static void drm_fb_helper_memory_range_to_clip(struct fb_info *info, off_t off, u32 x2 = info->var.xres; u32 y2 = DIV_ROUND_UP(end, info->fix.line_length);
+ if ((y2 - y1) == 1) { + /* + * We've only written to a single scanline. Try to reduce + * the number of horizontal pixels that need an update. + */ + off_t bit_off = (off % info->fix.line_length) * 8; + off_t bit_end = (end % info->fix.line_length) * 8; + + x1 = bit_off / info->var.bits_per_pixel; + x2 = DIV_ROUND_UP(bit_end, info->var.bits_per_pixel); + } + drm_rect_init(clip, x1, y1, x2 - x1, y2 - y1); }
dri-devel@lists.freedesktop.org