From 85a6517d65d20ed873aac2e7c9078e6af7673d3c Mon Sep 17 00:00:00 2001 From: Sebastian Keller Date: Thu, 31 Mar 2022 16:44:34 +0200 Subject: [PATCH] gsk/gl: Always align offscreen rendering with the pixel grid This fixes two issues with the offscreen rendering code for nodes with bounds not aligned with the pixel grid: 1.) When drawing to an offscreen buffer the size of the offscreen buffer was rounded up, but then later when used as texture the vertices correspond to the original bounds with the unrounded size. This could then result in the offscreen texture being drawn onscreen at a slightly smaller size, which then lead to it being visually shifted and blurry. This is fixed by adjusting the u/v coordinates to ignore the padding region in the offscreen texture that got added by the size increase from rounding. 2.) The viewport used when rendering to the offscreen buffer was not aligned with the pixel grid for nodes at coordinates not aligned with the pixel grid. Then because the content of the offscreen buffer is not aligned with the pixel grid and later when used as textures sampling from it will result in interpolated values for an onscreen pixel. This could also result in shifting and blurriness, especially for nested offscreen rendering at different offsets. This is fixed by adding similar padding at the beginning of the texture and also adjusting the u/v coordinates to ignore this region. Fixes: https://gitlab.gnome.org/GNOME/gtk/-/issues/3833 --- gsk/gl/gskglrenderjob.c | 140 +++++++++++++++++++++++++--------------- 1 file changed, 89 insertions(+), 51 deletions(-) diff --git a/gsk/gl/gskglrenderjob.c b/gsk/gl/gskglrenderjob.c index 30c6cd7a41..a2f6e83a51 100644 --- a/gsk/gl/gskglrenderjob.c +++ b/gsk/gl/gskglrenderjob.c @@ -3871,7 +3871,6 @@ gsk_gl_render_job_visit_node_with_offscreen (GskGLRenderJob *job, filter = offscreen->linear_filter ? GL_LINEAR : GL_NEAREST; - /* Check if we've already cached the drawn texture. */ key.pointer = node; key.pointer_is_child = TRUE; /* Don't conflict with the child using the cache too */ key.parent_rect = *offscreen->bounds; @@ -3879,61 +3878,111 @@ gsk_gl_render_job_visit_node_with_offscreen (GskGLRenderJob *job, key.scale_y = job->scale_y; key.filter = filter; - cached_id = gsk_gl_driver_lookup_texture (job->driver, &key); + float offset_x = job->offset_x; + float offset_y = job->offset_y; + gboolean flipped_x = job->scale_x < 0; + gboolean flipped_y = job->scale_y < 0; + graphene_rect_t viewport; - if (cached_id != 0) + if (flipped_x || flipped_y) { - offscreen->texture_id = cached_id; - init_full_texture_region (offscreen); - /* We didn't render it offscreen, but hand out an offscreen texture id */ - offscreen->was_offscreen = TRUE; - return TRUE; + GskTransform *transform = gsk_transform_scale (NULL, + flipped_x ? -1 : 1, + flipped_y ? -1 : 1); + gsk_gl_render_job_push_modelview (job, transform); } - float scaled_width; - float scaled_height; - float downscale_x = 1; - float downscale_y = 1; + gsk_gl_render_job_transform_bounds (job, offscreen->bounds, &viewport); - g_assert (job->command_queue->max_texture_size > 0); + float aligned_x = floorf (viewport.origin.x); + float padding_left = viewport.origin.x - aligned_x; + float aligned_width = ceilf (viewport.size.width + padding_left); + float padding_right = aligned_width - viewport.size.width - padding_left; + + float aligned_y = floorf (viewport.origin.y); + float padding_top = viewport.origin.y - aligned_y; + float aligned_height = ceilf (viewport.size.height + padding_top); + float padding_bottom = aligned_height - viewport.size.height - padding_top; /* Tweak the scale factor so that the required texture doesn't * exceed the max texture limit. This will render with a lower * resolution, but this is better than clipping. */ - { - int max_texture_size = job->command_queue->max_texture_size; - scaled_width = ceilf (offscreen->bounds->size.width * fabs (job->scale_x)); - if (scaled_width > max_texture_size) - { - downscale_x = (float)max_texture_size / scaled_width; - scaled_width = max_texture_size; - } - if (job->scale_x < 0) - downscale_x = -downscale_x; + g_assert (job->command_queue->max_texture_size > 0); - scaled_height = ceilf (offscreen->bounds->size.height * fabs (job->scale_y)); - if (scaled_height > max_texture_size) - { - downscale_y = (float)max_texture_size / scaled_height; - scaled_height = max_texture_size; - } - if (job->scale_y < 0) - downscale_y = -downscale_y; - } + float downscale_x = 1; + float downscale_y = 1; + int texture_width; + int texture_height; + int max_texture_size = job->command_queue->max_texture_size; + + if (aligned_width > max_texture_size) + downscale_x = (float)max_texture_size / viewport.size.width; + + if (aligned_height > max_texture_size) + downscale_y = (float)max_texture_size / viewport.size.height; + + if (downscale_x != 1 || downscale_y != 1) + { + GskTransform *transform = gsk_transform_scale (NULL, downscale_x, downscale_y); + gsk_gl_render_job_push_modelview (job, transform); + gsk_gl_render_job_transform_bounds (job, offscreen->bounds, &viewport); + } + + if (downscale_x == 1) + { + viewport.origin.x = aligned_x; + viewport.size.width = aligned_width; + offscreen->area.x = padding_left / aligned_width; + offscreen->area.x2 = 1.0f - (padding_right / aligned_width); + texture_width = aligned_width; + } + else + { + offscreen->area.x = 0; + offscreen->area.x2 = 1; + texture_width = max_texture_size; + } + + if (downscale_y == 1) + { + viewport.origin.y = aligned_y; + viewport.size.height = aligned_height; + offscreen->area.y = padding_bottom / aligned_height; + offscreen->area.y2 = 1.0f - padding_top / aligned_height; + texture_height = aligned_height; + } + else + { + offscreen->area.y = 0; + offscreen->area.y2 = 1; + texture_height = max_texture_size; + } + + /* Check if we've already cached the drawn texture. */ + cached_id = gsk_gl_driver_lookup_texture (job->driver, &key); + + if (cached_id != 0) + { + if (downscale_x != 1 || downscale_y != 1) + gsk_gl_render_job_pop_modelview (job); + if (flipped_x || flipped_y) + gsk_gl_render_job_pop_modelview (job); + offscreen->texture_id = cached_id; + /* We didn't render it offscreen, but hand out an offscreen texture id */ + offscreen->was_offscreen = TRUE; + return TRUE; + } GskGLRenderTarget *render_target; graphene_matrix_t prev_projection; graphene_rect_t prev_viewport; - graphene_rect_t viewport; - float offset_x = job->offset_x; - float offset_y = job->offset_y; float prev_alpha; guint prev_fbo; if (!gsk_gl_driver_create_render_target (job->driver, - scaled_width, scaled_height, + texture_width, texture_height, get_target_format (job, node), filter, filter, &render_target)) @@ -3955,19 +4004,6 @@ gsk_gl_render_job_visit_node_with_offscreen (GskGLRenderJob *job, render_target->framebuffer_id); } - if (downscale_x != 1 || downscale_y != 1) - { - GskTransform *transform = gsk_transform_scale (NULL, downscale_x, downscale_y); - gsk_gl_render_job_push_modelview (job, transform); - gsk_transform_unref (transform); - } - - gsk_gl_render_job_transform_bounds (job, offscreen->bounds, &viewport); - /* Code above will scale the size with the scale we use in the render ops, - * but for the viewport size, we need our own size limited by the texture size */ - viewport.size.width = scaled_width; - viewport.size.height = scaled_height; - gsk_gl_render_job_set_viewport (job, &viewport, &prev_viewport); gsk_gl_render_job_set_projection_from_rect (job, &job->viewport, &prev_projection); prev_alpha = gsk_gl_render_job_set_alpha (job, 1.0f); @@ -3985,6 +4021,10 @@ gsk_gl_render_job_visit_node_with_offscreen (GskGLRenderJob *job, if (downscale_x != 1 || downscale_y != 1) gsk_gl_render_job_pop_modelview (job); + + if (flipped_x || flipped_y) + gsk_gl_render_job_pop_modelview (job); + gsk_gl_render_job_set_viewport (job, &prev_viewport, NULL); gsk_gl_render_job_set_projection (job, &prev_projection); gsk_gl_render_job_set_alpha (job, prev_alpha); @@ -3998,8 +4038,6 @@ gsk_gl_render_job_visit_node_with_offscreen (GskGLRenderJob *job, render_target, FALSE); - init_full_texture_region (offscreen); - if (!offscreen->do_not_cache) gsk_gl_driver_cache_texture (job->driver, &key, offscreen->texture_id);