From e0cc7b5d861232cbaa20d97101093a9e61f40edc Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Wed, 2 Sep 2020 18:01:06 -0400 Subject: [PATCH] Check for pixel-alignedness for interpolation When rendering to an offscreen because of transforms, check if transforming the bounds of the node results in a non-axis-aligned quad. If it doesn't, we want GL_NEAREST interpolation to get sharp edges. Otherwise, we use GL_LINEAR to get better results for things that are actually transformed. --- gsk/gl/gskgldriver.c | 4 +- gsk/gl/gskgldriverprivate.h | 2 + gsk/gl/gskglrenderer.c | 88 +++++++++++++++++++++++++++++-------- 3 files changed, 75 insertions(+), 19 deletions(-) diff --git a/gsk/gl/gskgldriver.c b/gsk/gl/gskgldriver.c index 8cd32d72ab..08e19a980f 100644 --- a/gsk/gl/gskgldriver.c +++ b/gsk/gl/gskgldriver.c @@ -594,6 +594,8 @@ void gsk_gl_driver_create_render_target (GskGLDriver *self, int width, int height, + int min_filter, + int mag_filter, int *out_texture_id, int *out_render_target_id) { @@ -604,7 +606,7 @@ gsk_gl_driver_create_render_target (GskGLDriver *self, texture = create_texture (self, width, height); gsk_gl_driver_bind_source_texture (self, texture->texture_id); - gsk_gl_driver_init_texture_empty (self, texture->texture_id, GL_NEAREST, GL_NEAREST); + gsk_gl_driver_init_texture_empty (self, texture->texture_id, min_filter, mag_filter); glGenFramebuffers (1, &fbo_id); glBindFramebuffer (GL_FRAMEBUFFER, fbo_id); diff --git a/gsk/gl/gskgldriverprivate.h b/gsk/gl/gskgldriverprivate.h index 83d0c46af9..22c5ef8cb3 100644 --- a/gsk/gl/gskgldriverprivate.h +++ b/gsk/gl/gskgldriverprivate.h @@ -45,6 +45,8 @@ int gsk_gl_driver_create_texture (GskGLDriver *driver void gsk_gl_driver_create_render_target (GskGLDriver *driver, int width, int height, + int min_filter, + int mag_filter, int *out_texture_id, int *out_render_target_id); void gsk_gl_driver_mark_texture_permanent (GskGLDriver *self, diff --git a/gsk/gl/gskglrenderer.c b/gsk/gl/gskglrenderer.c index 47ec75e21f..27d1c48cbd 100644 --- a/gsk/gl/gskglrenderer.c +++ b/gsk/gl/gskglrenderer.c @@ -71,6 +71,7 @@ typedef enum RESET_OPACITY = 1 << 2, DUMP_FRAMEBUFFER = 1 << 3, NO_CACHE_PLZ = 1 << 5, + LINEAR_FILTER = 1 << 6, } OffscreenFlags; static inline void @@ -1000,6 +1001,38 @@ render_texture_node (GskGLRenderer *self, } } +/* Returns TRUE is applying transform to bounds + * yields an axis-aligned rectangle + */ +static gboolean +result_is_axis_aligned (GskTransform *transform, + const graphene_rect_t *bounds) +{ + graphene_matrix_t m; + graphene_quad_t q; + graphene_rect_t b; + graphene_point_t b1, b2; + const graphene_point_t *p; + int i; + + gsk_transform_to_matrix (transform, &m); + gsk_matrix_transform_rect (&m, bounds, &q); + graphene_quad_bounds (&q, &b); + graphene_rect_get_top_left (&b, &b1); + graphene_rect_get_bottom_right (&b, &b2); + + for (i = 0; i < 4; i++) + { + p = graphene_quad_get_point (&q, i); + if (fabs (p->x - b1.x) > FLT_EPSILON && fabs (p->x - b2.x) > FLT_EPSILON) + return FALSE; + if (fabs (p->y - b1.y) > FLT_EPSILON && fabs (p->y - b2.y) > FLT_EPSILON) + return FALSE; + } + + return TRUE; +} + static inline void render_transform_node (GskGLRenderer *self, GskRenderNode *node, @@ -1051,27 +1084,33 @@ render_transform_node (GskGLRenderer *self, } else { + int filter_flag = 0; + + if (!result_is_axis_aligned (node_transform, &child->bounds)) + filter_flag = LINEAR_FILTER; + if (add_offscreen_ops (self, builder, &child->bounds, child, ®ion, &is_offscreen, - RESET_CLIP | RESET_OPACITY)) - { - /* For non-trivial transforms, we draw everything on a texture and then - * draw the texture transformed. */ - /* TODO: We should compute a modelview containing only the "non-trivial" - * part (e.g. the rotation) and use that. We want to keep the scale - * for the texture. - */ - ops_push_modelview (builder, node_transform); - ops_set_texture (builder, region.texture_id); - ops_set_program (builder, &self->programs->blit_program); + RESET_CLIP | RESET_OPACITY | filter_flag)) + { + /* For non-trivial transforms, we draw everything on a texture and then + * draw the texture transformed. */ + /* TODO: We should compute a modelview containing only the "non-trivial" + * part (e.g. the rotation) and use that. We want to keep the scale + * for the texture. + */ + ops_push_modelview (builder, node_transform); + ops_set_texture (builder, region.texture_id); + ops_set_program (builder, &self->programs->blit_program); - load_vertex_data_with_region (ops_draw (builder, NULL), - child, builder, - ®ion, - is_offscreen); - ops_pop_modelview (builder); + load_vertex_data_with_region (ops_draw (builder, NULL), + child, builder, + ®ion, + is_offscreen); + ops_pop_modelview (builder); + } } } break; @@ -1457,10 +1496,12 @@ blur_texture (GskGLRenderer *self, gsk_gl_driver_create_render_target (self->gl_driver, texture_to_blur_width, texture_to_blur_height, + GL_NEAREST, GL_NEAREST, &pass1_texture_id, &pass1_render_target); gsk_gl_driver_create_render_target (self->gl_driver, texture_to_blur_width, texture_to_blur_height, + GL_NEAREST, GL_NEAREST, &pass2_texture_id, &pass2_render_target); graphene_matrix_init_ortho (&item_proj, @@ -1702,6 +1743,7 @@ render_inset_shadow_node (GskGLRenderer *self, gsk_gl_driver_create_render_target (self->gl_driver, texture_width, texture_height, + GL_NEAREST, GL_NEAREST, &texture_id, &render_target); graphene_matrix_init_ortho (&item_proj, @@ -1875,7 +1917,9 @@ render_outset_shadow_node (GskGLRenderer *self, graphene_rect_t prev_viewport; graphene_matrix_t item_proj; - gsk_gl_driver_create_render_target (self->gl_driver, texture_width, texture_height, + gsk_gl_driver_create_render_target (self->gl_driver, + texture_width, texture_height, + GL_NEAREST, GL_NEAREST, &texture_id, &render_target); if (gdk_gl_context_has_debug (self->gl_context)) { @@ -3355,6 +3399,7 @@ add_offscreen_ops (GskGLRenderer *self, float prev_opacity = 1.0; int texture_id = 0; int max_texture_size; + int filter; if (node_is_invisible (child_node)) { @@ -3407,7 +3452,14 @@ add_offscreen_ops (GskGLRenderer *self, width = ceilf (width * scale); height = ceilf (height * scale); - gsk_gl_driver_create_render_target (self->gl_driver, width, height, &texture_id, &render_target); + if (flags & LINEAR_FILTER) + filter = GL_LINEAR; + else + filter = GL_NEAREST; + gsk_gl_driver_create_render_target (self->gl_driver, + width, height, + filter, filter, + &texture_id, &render_target); if (gdk_gl_context_has_debug (self->gl_context)) { gdk_gl_context_label_object_printf (self->gl_context, GL_TEXTURE, texture_id,