diff --git a/demos/gtk-demo/demo3widget.c b/demos/gtk-demo/demo3widget.c index 9f8d943d14..bc66a088e0 100644 --- a/demos/gtk-demo/demo3widget.c +++ b/demos/gtk-demo/demo3widget.c @@ -3,7 +3,8 @@ enum { - PROP_PAINTABLE = 1, + PROP_TEXTURE = 1, + PROP_FILTER, PROP_SCALE }; @@ -11,8 +12,9 @@ struct _Demo3Widget { GtkWidget parent_instance; - GdkPaintable *paintable; + GdkTexture *texture; float scale; + GskScalingFilter filter; GtkWidget *menu; }; @@ -28,6 +30,7 @@ static void demo3_widget_init (Demo3Widget *self) { self->scale = 1.f; + self->filter = GSK_SCALING_FILTER_LINEAR; gtk_widget_init_template (GTK_WIDGET (self)); } @@ -36,7 +39,7 @@ demo3_widget_dispose (GObject *object) { Demo3Widget *self = DEMO3_WIDGET (object); - g_clear_object (&self->paintable); + g_clear_object (&self->texture); gtk_widget_dispose_template (GTK_WIDGET (self), DEMO3_TYPE_WIDGET); @@ -50,12 +53,13 @@ demo3_widget_snapshot (GtkWidget *widget, Demo3Widget *self = DEMO3_WIDGET (widget); int x, y, width, height; double w, h; + GskRenderNode *node; width = gtk_widget_get_width (widget); height = gtk_widget_get_height (widget); - w = self->scale * gdk_paintable_get_intrinsic_width (self->paintable); - h = self->scale * gdk_paintable_get_intrinsic_height (self->paintable); + w = self->scale * gdk_texture_get_width (self->texture); + h = self->scale * gdk_texture_get_height (self->texture); x = MAX (0, (width - ceil (w)) / 2); y = MAX (0, (height - ceil (h)) / 2); @@ -63,7 +67,11 @@ demo3_widget_snapshot (GtkWidget *widget, gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_INIT (0, 0, width, height)); gtk_snapshot_save (snapshot); gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (x, y)); - gdk_paintable_snapshot (self->paintable, snapshot, w, h); + node = gsk_texture_scale_node_new (self->texture, + &GRAPHENE_RECT_INIT (0, 0, w, h), + self->filter); + gtk_snapshot_append_node (snapshot, node); + gsk_render_node_unref (node); gtk_snapshot_restore (snapshot); gtk_snapshot_pop (snapshot); } @@ -81,9 +89,9 @@ demo3_widget_measure (GtkWidget *widget, int size; if (orientation == GTK_ORIENTATION_HORIZONTAL) - size = gdk_paintable_get_intrinsic_width (self->paintable); + size = gdk_texture_get_width (self->texture); else - size = gdk_paintable_get_intrinsic_height (self->paintable); + size = gdk_texture_get_height (self->texture); *minimum = *natural = self->scale * size; } @@ -113,9 +121,9 @@ demo3_widget_set_property (GObject *object, switch (prop_id) { - case PROP_PAINTABLE: - g_clear_object (&self->paintable); - self->paintable = g_value_dup_object (value); + case PROP_TEXTURE: + g_clear_object (&self->texture); + self->texture = g_value_dup_object (value); gtk_widget_queue_resize (GTK_WIDGET (object)); break; @@ -124,6 +132,11 @@ demo3_widget_set_property (GObject *object, gtk_widget_queue_resize (GTK_WIDGET (object)); break; + case PROP_FILTER: + self->filter = g_value_get_enum (value); + gtk_widget_queue_resize (GTK_WIDGET (object)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -140,14 +153,18 @@ demo3_widget_get_property (GObject *object, switch (prop_id) { - case PROP_PAINTABLE: - g_value_set_object (value, self->paintable); + case PROP_TEXTURE: + g_value_set_object (value, self->texture); break; case PROP_SCALE: g_value_set_float (value, self->scale); break; + case PROP_FILTER: + g_value_set_enum (value, self->filter); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -205,16 +222,21 @@ demo3_widget_class_init (Demo3WidgetClass *class) widget_class->measure = demo3_widget_measure; widget_class->size_allocate = demo3_widget_size_allocate; - g_object_class_install_property (object_class, PROP_PAINTABLE, - g_param_spec_object ("paintable", "Paintable", "Paintable", - GDK_TYPE_PAINTABLE, + g_object_class_install_property (object_class, PROP_TEXTURE, + g_param_spec_object ("texture", NULL, NULL, + GDK_TYPE_TEXTURE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_SCALE, - g_param_spec_float ("scale", "Scale", "Scale", + g_param_spec_float ("scale", NULL, NULL, 0.0, 10.0, 1.0, G_PARAM_READWRITE)); + g_object_class_install_property (object_class, PROP_FILTER, + g_param_spec_enum ("filter", NULL, NULL, + GSK_TYPE_SCALING_FILTER, GSK_SCALING_FILTER_LINEAR, + G_PARAM_READWRITE)); + /* These are the actions that we are using in the menu */ gtk_widget_class_install_action (widget_class, "zoom.in", NULL, zoom_cb); gtk_widget_class_install_action (widget_class, "zoom.out", NULL, zoom_cb); @@ -229,16 +251,13 @@ GtkWidget * demo3_widget_new (const char *resource) { Demo3Widget *self; - GdkPixbuf *pixbuf; - GdkPaintable *paintable; + GdkTexture *texture; - pixbuf = gdk_pixbuf_new_from_resource (resource, NULL); - paintable = GDK_PAINTABLE (gdk_texture_new_for_pixbuf (pixbuf)); + texture = gdk_texture_new_from_resource (resource); - self = g_object_new (DEMO3_TYPE_WIDGET, "paintable", paintable, NULL); + self = g_object_new (DEMO3_TYPE_WIDGET, "texture", texture, NULL); - g_object_unref (pixbuf); - g_object_unref (paintable); + g_object_unref (texture); return GTK_WIDGET (self); } diff --git a/demos/gtk-demo/menu.c b/demos/gtk-demo/menu.c index b0a1ceb6a1..63610632c0 100644 --- a/demos/gtk-demo/menu.c +++ b/demos/gtk-demo/menu.c @@ -22,9 +22,11 @@ do_menu (GtkWidget *do_widget) if (!window) { GtkWidget *box; + GtkWidget *box2; GtkWidget *sw; GtkWidget *widget; GtkWidget *scale; + GtkWidget *dropdown; window = gtk_window_new (); gtk_window_set_title (GTK_WINDOW (window), "Menu"); @@ -43,10 +45,19 @@ do_menu (GtkWidget *do_widget) widget = demo3_widget_new ("/transparent/portland-rose.jpg"); gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), widget); + box2 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_append (GTK_BOX (box), box2); + scale = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, 0.01, 10.0, 0.1); gtk_range_set_value (GTK_RANGE (scale), 1.0); - gtk_box_append (GTK_BOX (box), scale); + gtk_widget_set_hexpand (scale, TRUE); + gtk_box_append (GTK_BOX (box2), scale); + dropdown = gtk_drop_down_new (G_LIST_MODEL (gtk_string_list_new ((const char *[]){ "Linear", "Nearest", "Trilinear", NULL })), NULL); + gtk_box_append (GTK_BOX (box2), dropdown); + + g_object_bind_property (dropdown, "selected", widget, "filter", G_BINDING_DEFAULT); + g_object_bind_property (gtk_range_get_adjustment (GTK_RANGE (scale)), "value", widget, "scale", G_BINDING_BIDIRECTIONAL); diff --git a/demos/node-editor/node-format.md b/demos/node-editor/node-format.md index ca9417f7d0..557a8950a5 100644 --- a/demos/node-editor/node-format.md +++ b/demos/node-editor/node-format.md @@ -297,6 +297,21 @@ The default texture is a 10x10 checkerboard with the top left and bottom right representation for this texture is `url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABmJLR0QA/wD/AP+gvaeTAAAAKUlEQVQYlWP8z3DmPwMaYGQwYUQXY0IXwAUGUCGGoxkYGBiweXAoeAYAz44F3e3U1xUAAAAASUVORK5CYII=") `. +### texture-scale + +| property | syntax | default | printed | +| -------- | ---------------- | ---------------------- | ----------- | +| bounds | `` | 50 | always | +| texture | `` | *see below* | always | +| filter | `filter` | *see below* | non-default | + +Creates a node like `gsk_texture_scale_node_new()` with the given properties. + +The default texture is a 10x10 checkerboard, just like for texture. + +The possible filter values are `linear`, `nearest` and `trilinear`, with +`linear` being the default. + ### transform | property | syntax | default | printed | diff --git a/gsk/broadway/gskbroadwayrenderer.c b/gsk/broadway/gskbroadwayrenderer.c index bfd7270f56..6fee960edf 100644 --- a/gsk/broadway/gskbroadwayrenderer.c +++ b/gsk/broadway/gskbroadwayrenderer.c @@ -250,6 +250,7 @@ collect_reused_child_nodes (GskRenderer *renderer, /* Leaf nodes */ case GSK_TEXTURE_NODE: + case GSK_TEXTURE_SCALE_NODE: case GSK_CAIRO_NODE: case GSK_COLOR_NODE: case GSK_BORDER_NODE: @@ -845,6 +846,7 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, } break; /* Fallback */ + case GSK_TEXTURE_SCALE_NODE: case GSK_TEXT_NODE: case GSK_RADIAL_GRADIENT_NODE: case GSK_REPEATING_LINEAR_GRADIENT_NODE: diff --git a/gsk/gl/gskglcommandqueue.c b/gsk/gl/gskglcommandqueue.c index 8a1f9c84b0..673958e579 100644 --- a/gsk/gl/gskglcommandqueue.c +++ b/gsk/gl/gskglcommandqueue.c @@ -1439,8 +1439,7 @@ gsk_gl_command_queue_upload_texture (GskGLCommandQueue *self, g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); g_assert (!GDK_IS_GL_TEXTURE (texture)); - g_assert (min_filter == GL_LINEAR || min_filter == GL_NEAREST); - g_assert (mag_filter == GL_LINEAR || min_filter == GL_NEAREST); + g_assert (mag_filter == GL_LINEAR || mag_filter == GL_NEAREST); width = gdk_texture_get_width (texture); height = gdk_texture_get_height (texture); @@ -1464,6 +1463,9 @@ gsk_gl_command_queue_upload_texture (GskGLCommandQueue *self, gsk_gl_command_queue_do_upload_texture (self, texture); + if (min_filter == GL_LINEAR_MIPMAP_LINEAR) + glGenerateMipmap (GL_TEXTURE_2D); + /* Restore previous texture state if any */ if (self->attachments->textures[0].id > 0) glBindTexture (self->attachments->textures[0].target, diff --git a/gsk/gl/gskgldriver.c b/gsk/gl/gskgldriver.c index 3d58ee71e0..4e38dad685 100644 --- a/gsk/gl/gskgldriver.c +++ b/gsk/gl/gskgldriver.c @@ -753,7 +753,7 @@ gsk_gl_driver_load_texture (GskGLDriver *self, { if ((t = gdk_texture_get_render_data (texture, self))) { - if (t->min_filter == min_filter && t->mag_filter == mag_filter) + if (t->min_filter == min_filter && t->mag_filter == mag_filter && t->texture_id) return t->texture_id; } @@ -1195,6 +1195,10 @@ gsk_gl_driver_create_command_queue (GskGLDriver *self, void gsk_gl_driver_add_texture_slices (GskGLDriver *self, GdkTexture *texture, + int min_filter, + int mag_filter, + guint min_cols, + guint min_rows, GskGLTextureSlice **out_slices, guint *out_n_slices) { @@ -1216,31 +1220,37 @@ gsk_gl_driver_add_texture_slices (GskGLDriver *self, /* XXX: Too much? */ max_texture_size = self->command_queue->max_texture_size / 4; - tex_width = texture->width; tex_height = texture->height; - cols = (texture->width / max_texture_size) + 1; - rows = (texture->height / max_texture_size) + 1; + + cols = MAX ((texture->width / max_texture_size) + 1, min_cols); + rows = MAX ((texture->height / max_texture_size) + 1, min_rows); + + n_slices = cols * rows; if ((t = gdk_texture_get_render_data (texture, self))) { - *out_slices = t->slices; - *out_n_slices = t->n_slices; - return; + if (t->n_slices == n_slices) + { + *out_slices = t->slices; + *out_n_slices = t->n_slices; + return; + } + + gdk_texture_clear_render_data (texture); } - n_slices = cols * rows; slices = g_new0 (GskGLTextureSlice, n_slices); memtex = gdk_memory_texture_from_texture (texture, gdk_texture_get_format (texture)); - for (guint col = 0; col < cols; col ++) + for (guint col = 0; col < cols; col++) { - int slice_width = MIN (max_texture_size, texture->width - x); + int slice_width = col + 1 < cols ? tex_width / cols : tex_width - x; - for (guint row = 0; row < rows; row ++) + for (guint row = 0; row < rows; row++) { - int slice_height = MIN (max_texture_size, texture->height - y); + int slice_height = row + 1 < rows ? tex_height / rows : tex_height - y; int slice_index = (col * rows) + row; GdkTexture *subtex; guint texture_id; @@ -1250,7 +1260,7 @@ gsk_gl_driver_add_texture_slices (GskGLDriver *self, slice_width, slice_height); texture_id = gsk_gl_command_queue_upload_texture (self->command_queue, subtex, - GL_NEAREST, GL_NEAREST); + min_filter, mag_filter); g_object_unref (subtex); slices[slice_index].rect.x = x; diff --git a/gsk/gl/gskgldriverprivate.h b/gsk/gl/gskgldriverprivate.h index accae5b3bb..2b39217919 100644 --- a/gsk/gl/gskgldriverprivate.h +++ b/gsk/gl/gskgldriverprivate.h @@ -177,6 +177,10 @@ GskGLTexture * gsk_gl_driver_mark_texture_permanent (GskGLDriver *s guint texture_id); void gsk_gl_driver_add_texture_slices (GskGLDriver *self, GdkTexture *texture, + int min_filter, + int mag_filter, + guint min_cols, + guint min_rows, GskGLTextureSlice **out_slices, guint *out_n_slices); GskGLProgram * gsk_gl_driver_lookup_shader (GskGLDriver *self, @@ -228,6 +232,10 @@ gsk_gl_driver_lookup_texture (GskGLDriver *self, static inline void gsk_gl_driver_slice_texture (GskGLDriver *self, GdkTexture *texture, + int min_filter, + int mag_filter, + guint min_cols, + guint min_rows, GskGLTextureSlice **out_slices, guint *out_n_slices) { @@ -235,12 +243,15 @@ gsk_gl_driver_slice_texture (GskGLDriver *self, if ((t = gdk_texture_get_render_data (texture, self))) { - *out_slices = t->slices; - *out_n_slices = t->n_slices; - return; + if (min_cols == 0 && min_rows == 0) + { + *out_slices = t->slices; + *out_n_slices = t->n_slices; + return; + } } - gsk_gl_driver_add_texture_slices (self, texture, out_slices, out_n_slices); + gsk_gl_driver_add_texture_slices (self, texture, min_filter, mag_filter, min_cols, min_rows, out_slices, out_n_slices); } G_END_DECLS diff --git a/gsk/gl/gskglrenderjob.c b/gsk/gl/gskglrenderjob.c index e30a1e1341..b26a691cc5 100644 --- a/gsk/gl/gskglrenderjob.c +++ b/gsk/gl/gskglrenderjob.c @@ -3443,9 +3443,13 @@ gsk_gl_render_job_visit_gl_shader_node (GskGLRenderJob *job, static void gsk_gl_render_job_upload_texture (GskGLRenderJob *job, GdkTexture *texture, + int min_filter, + int mag_filter, GskGLRenderOffscreen *offscreen) { - if (gsk_gl_texture_library_can_cache ((GskGLTextureLibrary *)job->driver->icons_library, + if (min_filter == GL_LINEAR && + mag_filter == GL_LINEAR && + gsk_gl_texture_library_can_cache ((GskGLTextureLibrary *)job->driver->icons_library, texture->width, texture->height) && !GDK_IS_GL_TEXTURE (texture)) @@ -3458,16 +3462,16 @@ gsk_gl_render_job_upload_texture (GskGLRenderJob *job, } else { - offscreen->texture_id = gsk_gl_driver_load_texture (job->driver, texture, GL_LINEAR, GL_LINEAR); + offscreen->texture_id = gsk_gl_driver_load_texture (job->driver, texture, min_filter, mag_filter); init_full_texture_region (offscreen); } } static inline void -gsk_gl_render_job_visit_texture_node (GskGLRenderJob *job, - const GskRenderNode *node) +gsk_gl_render_job_visit_texture (GskGLRenderJob *job, + GdkTexture *texture, + const graphene_rect_t *bounds) { - GdkTexture *texture = gsk_texture_node_get_texture (node); int max_texture_size = job->command_queue->max_texture_size; if G_LIKELY (texture->width <= max_texture_size && @@ -3475,7 +3479,7 @@ gsk_gl_render_job_visit_texture_node (GskGLRenderJob *job, { GskGLRenderOffscreen offscreen = {0}; - gsk_gl_render_job_upload_texture (job, texture, &offscreen); + gsk_gl_render_job_upload_texture (job, texture, GL_LINEAR, GL_LINEAR, &offscreen); g_assert (offscreen.texture_id); g_assert (offscreen.was_offscreen == FALSE); @@ -3486,21 +3490,21 @@ gsk_gl_render_job_visit_texture_node (GskGLRenderJob *job, GL_TEXTURE_2D, GL_TEXTURE0, offscreen.texture_id); - gsk_gl_render_job_draw_offscreen (job, &node->bounds, &offscreen); + gsk_gl_render_job_draw_offscreen (job, bounds, &offscreen); gsk_gl_render_job_end_draw (job); } else { - float min_x = job->offset_x + node->bounds.origin.x; - float min_y = job->offset_y + node->bounds.origin.y; - float max_x = min_x + node->bounds.size.width; - float max_y = min_y + node->bounds.size.height; + float min_x = job->offset_x + bounds->origin.x; + float min_y = job->offset_y + bounds->origin.y; + float max_x = min_x + bounds->size.width; + float max_y = min_y + bounds->size.height; float scale_x = (max_x - min_x) / texture->width; float scale_y = (max_y - min_y) / texture->height; GskGLTextureSlice *slices = NULL; guint n_slices = 0; - gsk_gl_driver_slice_texture (job->driver, texture, &slices, &n_slices); + gsk_gl_driver_slice_texture (job->driver, texture, GL_NEAREST, GL_NEAREST, 0, 0, &slices, &n_slices); g_assert (slices != NULL); g_assert (n_slices > 0); @@ -3535,6 +3539,151 @@ gsk_gl_render_job_visit_texture_node (GskGLRenderJob *job, } } +static inline void +gsk_gl_render_job_visit_texture_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + GdkTexture *texture = gsk_texture_node_get_texture (node); + const graphene_rect_t *bounds = &node->bounds; + + gsk_gl_render_job_visit_texture (job, texture, bounds); +} + +static inline void +gsk_gl_render_job_visit_texture_scale_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + GdkTexture *texture = gsk_texture_scale_node_get_texture (node); + const graphene_rect_t *bounds = &node->bounds; + GskScalingFilter scaling_filter = gsk_texture_scale_node_get_filter (node); + int min_filters[] = { GL_LINEAR, GL_NEAREST, GL_LINEAR_MIPMAP_LINEAR }; + int mag_filters[] = { GL_LINEAR, GL_NEAREST, GL_LINEAR }; + int min_filter = min_filters[scaling_filter]; + int mag_filter = mag_filters[scaling_filter]; + int max_texture_size = job->command_queue->max_texture_size; + + if (scaling_filter == GSK_SCALING_FILTER_LINEAR) + { + gsk_gl_render_job_visit_texture (job, texture, bounds); + return; + } + + if G_LIKELY (texture->width <= max_texture_size && + texture->height <= max_texture_size) + { + GskGLRenderTarget *render_target; + GskGLRenderOffscreen offscreen = {0}; + graphene_rect_t viewport; + graphene_rect_t prev_viewport; + graphene_matrix_t prev_projection; + float prev_alpha; + guint prev_fbo; + guint texture_id; + + viewport = GRAPHENE_RECT_INIT (0, 0, + bounds->size.width, + bounds->size.height); + + if (!gsk_gl_driver_create_render_target (job->driver, + (int) ceilf (viewport.size.width), + (int) ceilf (viewport.size.height), + get_target_format (job, node), + GL_LINEAR, GL_LINEAR, + &render_target)) + { + /* viewport is too big, slice the texture and try again */ + goto slice; + } + + gsk_gl_render_job_upload_texture (job, texture, min_filter, mag_filter, &offscreen); + + g_assert (offscreen.texture_id); + g_assert (offscreen.was_offscreen == FALSE); + + gsk_gl_render_job_set_viewport (job, &viewport, &prev_viewport); + gsk_gl_render_job_set_projection_from_rect (job, &viewport, &prev_projection); + gsk_gl_render_job_set_modelview (job, NULL); + prev_alpha = gsk_gl_render_job_set_alpha (job, 1.0f); + gsk_gl_render_job_push_clip (job, &GSK_ROUNDED_RECT_INIT_FROM_RECT (viewport)); + + prev_fbo = gsk_gl_command_queue_bind_framebuffer (job->command_queue, render_target->framebuffer_id); + gsk_gl_command_queue_clear (job->command_queue, 0, &viewport); + + gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit)); + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + offscreen.texture_id); + gsk_gl_render_job_draw_offscreen (job, &viewport, &offscreen); + gsk_gl_render_job_end_draw (job); + + gsk_gl_render_job_pop_clip (job); + 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); + gsk_gl_command_queue_bind_framebuffer (job->command_queue, prev_fbo); + + texture_id = gsk_gl_driver_release_render_target (job->driver, render_target, FALSE); + + gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit)); + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + texture_id); + gsk_gl_render_job_draw_offscreen_rect (job, bounds); + gsk_gl_render_job_end_draw (job); + } + else + { +slice: + float min_x = bounds->origin.x; + float min_y = bounds->origin.y; + float max_x = min_x + bounds->size.width; + float max_y = min_y + bounds->size.height; + float scale_x = (max_x - min_x) / texture->width; + float scale_y = (max_y - min_y) / texture->height; + GskGLTextureSlice *slices = NULL; + guint n_slices = 0; + GdkGLContext *context = gsk_gl_driver_get_context (job->driver); + guint rows, cols; + + /* Slice enough that neither the original texture nor the scaled texture + * exceed the texture size limit + */ + cols = (int)(MAX (bounds->size.width, texture->width) / (max_texture_size / 4)) + 1; + rows = (int)(MAX (bounds->size.height, texture->height) / (max_texture_size / 4)) + 1; + + gsk_gl_driver_slice_texture (job->driver, texture, GL_NEAREST, GL_NEAREST, cols, rows, &slices, &n_slices); + + g_assert (slices != NULL); + g_assert (n_slices > 0); + + for (guint i = 0; i < n_slices; i ++) + { + const GskGLTextureSlice *slice = &slices[i]; + float x1, x2, y1, y2; + GdkTexture *sub_texture; + GskRenderNode *sub_node; + + x1 = min_x + (scale_x * slice->rect.x); + x2 = x1 + (slice->rect.width * scale_x); + y1 = min_y + (scale_y * slice->rect.y); + y2 = y1 + (slice->rect.height * scale_y); + + sub_texture = gdk_gl_texture_new (context, slice->texture_id, slice->rect.width, slice->rect.height, NULL, NULL); + + sub_node = gsk_texture_scale_node_new (sub_texture, &GRAPHENE_RECT_INIT (x1, y1, x2 - x1, y2 - y1), scaling_filter); + + gsk_gl_render_job_visit_node (job, sub_node); + gsk_render_node_unref (sub_node); + g_object_unref (sub_texture); + } + } +} + static inline void gsk_gl_render_job_visit_repeat_node (GskGLRenderJob *job, const GskRenderNode *node) @@ -3762,6 +3911,10 @@ gsk_gl_render_job_visit_node (GskGLRenderJob *job, gsk_gl_render_job_visit_texture_node (job, node); break; + case GSK_TEXTURE_SCALE_NODE: + gsk_gl_render_job_visit_texture_scale_node (job, node); + break; + case GSK_TRANSFORM_NODE: gsk_gl_render_job_visit_transform_node (job, node); break; @@ -3808,7 +3961,7 @@ gsk_gl_render_job_visit_node_with_offscreen (GskGLRenderJob *job, offscreen->force_offscreen == FALSE) { GdkTexture *texture = gsk_texture_node_get_texture (node); - gsk_gl_render_job_upload_texture (job, texture, offscreen); + gsk_gl_render_job_upload_texture (job, texture, GL_LINEAR, GL_LINEAR, offscreen); g_assert (offscreen->was_offscreen == FALSE); return TRUE; } diff --git a/gsk/gskenums.h b/gsk/gskenums.h index 0dfeb0a0ea..0c92555cfd 100644 --- a/gsk/gskenums.h +++ b/gsk/gskenums.h @@ -35,6 +35,7 @@ * @GSK_CONIC_GRADIENT_NODE: A node drawing a conic gradient * @GSK_BORDER_NODE: A node stroking a border around an area * @GSK_TEXTURE_NODE: A node drawing a `GdkTexture` + * @GSK_TEXTURE_SCALE_NODE: A node drawing a `GdkTexture` scaled and filtered * @GSK_INSET_SHADOW_NODE: A node drawing an inset shadow * @GSK_OUTSET_SHADOW_NODE: A node drawing an outset shadow * @GSK_TRANSFORM_NODE: A node that renders its child after applying a matrix transform @@ -65,6 +66,7 @@ typedef enum { GSK_CONIC_GRADIENT_NODE, GSK_BORDER_NODE, GSK_TEXTURE_NODE, + GSK_TEXTURE_SCALE_NODE, GSK_INSET_SHADOW_NODE, GSK_OUTSET_SHADOW_NODE, GSK_TRANSFORM_NODE, diff --git a/gsk/gskrendernode.h b/gsk/gskrendernode.h index 3fa1d057c2..fb6e1f51f6 100644 --- a/gsk/gskrendernode.h +++ b/gsk/gskrendernode.h @@ -142,6 +142,7 @@ GskRenderNode * gsk_render_node_deserialize (GBytes #define GSK_TYPE_DEBUG_NODE (gsk_debug_node_get_type()) #define GSK_TYPE_COLOR_NODE (gsk_color_node_get_type()) #define GSK_TYPE_TEXTURE_NODE (gsk_texture_node_get_type()) +#define GSK_TYPE_TEXTURE_SCALE_NODE (gsk_texture_scale_node_get_type()) #define GSK_TYPE_LINEAR_GRADIENT_NODE (gsk_linear_gradient_node_get_type()) #define GSK_TYPE_REPEATING_LINEAR_GRADIENT_NODE (gsk_repeating_linear_gradient_node_get_type()) #define GSK_TYPE_RADIAL_GRADIENT_NODE (gsk_radial_gradient_node_get_type()) @@ -168,6 +169,7 @@ GskRenderNode * gsk_render_node_deserialize (GBytes typedef struct _GskDebugNode GskDebugNode; typedef struct _GskColorNode GskColorNode; typedef struct _GskTextureNode GskTextureNode; +typedef struct _GskTextureScaleNode GskTextureScaleNode; typedef struct _GskLinearGradientNode GskLinearGradientNode; typedef struct _GskRepeatingLinearGradientNode GskRepeatingLinearGradientNode; typedef struct _GskRadialGradientNode GskRadialGradientNode; @@ -217,6 +219,17 @@ GskRenderNode * gsk_texture_node_new (GdkTexture GDK_AVAILABLE_IN_ALL GdkTexture * gsk_texture_node_get_texture (const GskRenderNode *node) G_GNUC_PURE; +GDK_AVAILABLE_IN_4_10 +GType gsk_texture_scale_node_get_type (void) G_GNUC_CONST; +GDK_AVAILABLE_IN_4_10 +GskRenderNode * gsk_texture_scale_node_new (GdkTexture *texture, + const graphene_rect_t *bounds, + GskScalingFilter filter); +GDK_AVAILABLE_IN_4_10 +GdkTexture * gsk_texture_scale_node_get_texture (const GskRenderNode *node) G_GNUC_PURE; +GDK_AVAILABLE_IN_4_10 +GskScalingFilter gsk_texture_scale_node_get_filter (const GskRenderNode *node) G_GNUC_PURE; + GDK_AVAILABLE_IN_ALL GType gsk_linear_gradient_node_get_type (void) G_GNUC_CONST; GDK_AVAILABLE_IN_ALL diff --git a/gsk/gskrendernodeimpl.c b/gsk/gskrendernodeimpl.c index cb747802ba..376d32cb4e 100644 --- a/gsk/gskrendernodeimpl.c +++ b/gsk/gskrendernodeimpl.c @@ -1581,6 +1581,191 @@ gsk_texture_node_new (GdkTexture *texture, return node; } +/* }}} */ +/* {{{ GSK_TEXTURE_SCALE_NODE */ + +/** + * GskTextureScaleNode: + * + * A render node for a `GdkTexture`. + */ +struct _GskTextureScaleNode +{ + GskRenderNode render_node; + + GdkTexture *texture; + GskScalingFilter filter; +}; + +static void +gsk_texture_scale_node_finalize (GskRenderNode *node) +{ + GskTextureScaleNode *self = (GskTextureScaleNode *) node; + GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_TEXTURE_SCALE_NODE)); + + g_clear_object (&self->texture); + + parent_class->finalize (node); +} + +static void +gsk_texture_scale_node_draw (GskRenderNode *node, + cairo_t *cr) +{ + GskTextureScaleNode *self = (GskTextureScaleNode *) node; + cairo_surface_t *surface; + cairo_pattern_t *pattern; + cairo_matrix_t matrix; + cairo_filter_t filters[] = { + CAIRO_FILTER_BILINEAR, + CAIRO_FILTER_NEAREST, + CAIRO_FILTER_GOOD, + }; + cairo_t *cr2; + cairo_surface_t *surface2; + + surface2 = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + (int) ceilf (node->bounds.size.width), + (int) ceilf (node->bounds.size.height)); + cr2 = cairo_create (surface2); + + cairo_set_source_rgba (cr2, 0, 0, 0, 0); + cairo_paint (cr2); + + surface = gdk_texture_download_surface (self->texture); + pattern = cairo_pattern_create_for_surface (surface); + cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD); + + cairo_matrix_init_scale (&matrix, + gdk_texture_get_width (self->texture) / node->bounds.size.width, + gdk_texture_get_height (self->texture) / node->bounds.size.height); + cairo_pattern_set_matrix (pattern, &matrix); + cairo_pattern_set_filter (pattern, filters[self->filter]); + + cairo_set_source (cr2, pattern); + cairo_pattern_destroy (pattern); + cairo_surface_destroy (surface); + + cairo_rectangle (cr2, 0, 0, node->bounds.size.width, node->bounds.size.height); + cairo_fill (cr2); + + cairo_destroy (cr2); + + cairo_save (cr); + + pattern = cairo_pattern_create_for_surface (surface2); + cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD); + + cairo_matrix_init_identity (&matrix); + cairo_matrix_translate (&matrix, + -node->bounds.origin.x, + -node->bounds.origin.y); + cairo_pattern_set_matrix (pattern, &matrix); + cairo_set_source (cr, pattern); + cairo_pattern_destroy (pattern); + cairo_surface_destroy (surface2); + + gsk_cairo_rectangle (cr, &node->bounds); + cairo_fill (cr); + + cairo_restore (cr); +} + +static void +gsk_texture_scale_node_diff (GskRenderNode *node1, + GskRenderNode *node2, + cairo_region_t *region) +{ + GskTextureScaleNode *self1 = (GskTextureScaleNode *) node1; + GskTextureScaleNode *self2 = (GskTextureScaleNode *) node2; + + if (graphene_rect_equal (&node1->bounds, &node2->bounds) && + self1->texture == self2->texture && + self1->filter == self2->filter) + return; + + gsk_render_node_diff_impossible (node1, node2, region); +} + +/** + * gsk_texture_scale_node_get_texture: + * @node: (type GskTextureNode): a `GskRenderNode` of type %GSK_TEXTURE_SCALE_NODE + * + * Retrieves the `GdkTexture` used when creating this `GskRenderNode`. + * + * Returns: (transfer none): the `GdkTexture` + * + * Since: 4.10 + */ +GdkTexture * +gsk_texture_scale_node_get_texture (const GskRenderNode *node) +{ + const GskTextureScaleNode *self = (const GskTextureScaleNode *) node; + + return self->texture; +} + +/** + * gsk_texture_scale_node_get_filter: + * @node: (type GskTextureNode): a `GskRenderNode` of type %GSK_TEXTURE_SCALE_NODE + * + * Retrieves the `GskScalingFilter` used when creating this `GskRenderNode`. + * + * Returns: (transfer none): the `GskScalingFilter` + * + * Since: 4.10 + */ +GskScalingFilter +gsk_texture_scale_node_get_filter (const GskRenderNode *node) +{ + const GskTextureScaleNode *self = (const GskTextureScaleNode *) node; + + return self->filter; +} + +/** + * gsk_texture_scale_node_new: + * @texture: the texture to scale + * @bounds: the size of the texture to scale to + * @filter: how to scale the texture + * + * Creates a node that scales the texture to the size given by the + * bounds and the filter and then places it at the bounds' position. + * + * This node is intended for tight control over scaling applied + * to a texture, such as in image editors and requires the + * application to be aware of the whole render tree as further + * transforms may be applied that conflict with the desired effect + * of this node. + * + * Returns: (transfer full) (type GskTextureScaleNode): A new `GskRenderNode` + * + * Since: 4.10 + */ +GskRenderNode * +gsk_texture_scale_node_new (GdkTexture *texture, + const graphene_rect_t *bounds, + GskScalingFilter filter) +{ + GskTextureScaleNode *self; + GskRenderNode *node; + + g_return_val_if_fail (GDK_IS_TEXTURE (texture), NULL); + g_return_val_if_fail (bounds != NULL, NULL); + + self = gsk_render_node_alloc (GSK_TEXTURE_SCALE_NODE); + node = (GskRenderNode *) self; + node->offscreen_for_opacity = FALSE; + + self->texture = g_object_ref (texture); + graphene_rect_init_from_rect (&node->bounds, bounds); + self->filter = filter; + + node->prefers_high_depth = gdk_memory_format_prefers_high_depth (gdk_texture_get_format (texture)); + + return node; +} + /* }}} */ /* {{{ GSK_INSET_SHADOW_NODE */ @@ -5357,6 +5542,7 @@ GSK_DEFINE_RENDER_NODE_TYPE (gsk_repeating_radial_gradient_node, GSK_REPEATING_R GSK_DEFINE_RENDER_NODE_TYPE (gsk_conic_gradient_node, GSK_CONIC_GRADIENT_NODE) GSK_DEFINE_RENDER_NODE_TYPE (gsk_border_node, GSK_BORDER_NODE) GSK_DEFINE_RENDER_NODE_TYPE (gsk_texture_node, GSK_TEXTURE_NODE) +GSK_DEFINE_RENDER_NODE_TYPE (gsk_texture_scale_node, GSK_TEXTURE_SCALE_NODE) GSK_DEFINE_RENDER_NODE_TYPE (gsk_inset_shadow_node, GSK_INSET_SHADOW_NODE) GSK_DEFINE_RENDER_NODE_TYPE (gsk_outset_shadow_node, GSK_OUTSET_SHADOW_NODE) GSK_DEFINE_RENDER_NODE_TYPE (gsk_transform_node, GSK_TRANSFORM_NODE) @@ -5536,6 +5722,22 @@ gsk_render_node_init_types_once (void) gsk_render_node_types[GSK_TEXTURE_NODE] = node_type; } + { + const GskRenderNodeTypeInfo node_info = + { + GSK_TEXTURE_SCALE_NODE, + sizeof (GskTextureScaleNode), + NULL, + gsk_texture_scale_node_finalize, + gsk_texture_scale_node_draw, + NULL, + gsk_texture_scale_node_diff, + }; + + GType node_type = gsk_render_node_type_register_static (I_("GskTextureScaleNode"), &node_info); + gsk_render_node_types[GSK_TEXTURE_SCALE_NODE] = node_type; + } + { const GskRenderNodeTypeInfo node_info = { diff --git a/gsk/gskrendernodeparser.c b/gsk/gskrendernodeparser.c index 68069b477e..ee27eda5a7 100644 --- a/gsk/gskrendernodeparser.c +++ b/gsk/gskrendernodeparser.c @@ -600,6 +600,32 @@ clear_shadows (gpointer inout_shadows) g_array_set_size (*(GArray **) inout_shadows, 0); } +static const struct +{ + GskScalingFilter filter; + const char *name; +} scaling_filters[] = { + { GSK_SCALING_FILTER_LINEAR, "linear" }, + { GSK_SCALING_FILTER_NEAREST, "nearest" }, + { GSK_SCALING_FILTER_TRILINEAR, "trilinear" }, +}; + +static gboolean +parse_scaling_filter (GtkCssParser *parser, + gpointer out_filter) +{ + for (unsigned int i = 0; i < G_N_ELEMENTS (scaling_filters); i++) + { + if (gtk_css_parser_try_ident (parser, scaling_filters[i].name)) + { + *(GskScalingFilter *) out_filter = scaling_filters[i].filter; + return TRUE; + } + } + + return FALSE; +} + static const struct { GskBlendMode mode; @@ -1389,6 +1415,30 @@ parse_texture_node (GtkCssParser *parser) return node; } +static GskRenderNode * +parse_texture_scale_node (GtkCssParser *parser) +{ + graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 50, 50); + GdkTexture *texture = NULL; + GskScalingFilter filter = GSK_SCALING_FILTER_LINEAR; + const Declaration declarations[] = { + { "bounds", parse_rect, NULL, &bounds }, + { "texture", parse_texture, clear_texture, &texture }, + { "filter", parse_scaling_filter, NULL, &filter } + }; + GskRenderNode *node; + + parse_declarations (parser, declarations, G_N_ELEMENTS (declarations)); + + if (texture == NULL) + texture = create_default_texture (); + + node = gsk_texture_scale_node_new (texture, &bounds, filter); + g_object_unref (texture); + + return node; +} + static GskRenderNode * parse_cairo_node (GtkCssParser *parser) { @@ -1861,6 +1911,7 @@ parse_node (GtkCssParser *parser, { "shadow", parse_shadow_node }, { "text", parse_text_node }, { "texture", parse_texture_node }, + { "texture-scale", parse_texture_scale_node }, { "transform", parse_transform_node }, { "glshader", parse_glshader_node }, }; @@ -2757,6 +2808,73 @@ render_node_print (Printer *p, } break; + case GSK_TEXTURE_SCALE_NODE: + { + GdkTexture *texture = gsk_texture_scale_node_get_texture (node); + GskScalingFilter filter = gsk_texture_scale_node_get_filter (node); + GBytes *bytes; + + start_node (p, "texture-scale"); + append_rect_param (p, "bounds", &node->bounds); + + if (filter != GSK_SCALING_FILTER_LINEAR) + { + _indent (p); + for (unsigned int i = 0; i < G_N_ELEMENTS (scaling_filters); i++) + { + if (scaling_filters[i].filter == filter) + { + g_string_append_printf (p->str, "filter: %s;\n", scaling_filters[i].name); + break; + } + } + } + _indent (p); + + switch (gdk_texture_get_format (texture)) + { + case GDK_MEMORY_B8G8R8A8_PREMULTIPLIED: + case GDK_MEMORY_A8R8G8B8_PREMULTIPLIED: + case GDK_MEMORY_R8G8B8A8_PREMULTIPLIED: + case GDK_MEMORY_B8G8R8A8: + case GDK_MEMORY_A8R8G8B8: + case GDK_MEMORY_R8G8B8A8: + case GDK_MEMORY_A8B8G8R8: + case GDK_MEMORY_R8G8B8: + case GDK_MEMORY_B8G8R8: + case GDK_MEMORY_R16G16B16: + case GDK_MEMORY_R16G16B16A16_PREMULTIPLIED: + case GDK_MEMORY_R16G16B16A16: + bytes = gdk_texture_save_to_png_bytes (texture); + g_string_append (p->str, "texture: url(\"data:image/png;base64,"); + break; + + case GDK_MEMORY_R16G16B16_FLOAT: + case GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED: + case GDK_MEMORY_R16G16B16A16_FLOAT: + case GDK_MEMORY_R32G32B32_FLOAT: + case GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED: + case GDK_MEMORY_R32G32B32A32_FLOAT: + bytes = gdk_texture_save_to_tiff_bytes (texture); + g_string_append (p->str, "texture: url(\"data:image/tiff;base64,"); + break; + + case GDK_MEMORY_N_FORMATS: + default: + g_assert_not_reached (); + } + + b64 = base64_encode_with_linebreaks (g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes)); + append_escaping_newlines (p->str, b64); + g_free (b64); + g_string_append (p->str, "\");\n"); + end_node (p); + + g_bytes_unref (bytes); + } + break; + case GSK_TEXT_NODE: { const graphene_point_t *offset = gsk_text_node_get_offset (node); diff --git a/gsk/vulkan/gskvulkanrenderpass.c b/gsk/vulkan/gskvulkanrenderpass.c index 48127e20b9..3008c42154 100644 --- a/gsk/vulkan/gskvulkanrenderpass.c +++ b/gsk/vulkan/gskvulkanrenderpass.c @@ -440,6 +440,9 @@ gsk_vulkan_render_pass_add_node (GskVulkanRenderPass *self, g_array_append_val (self->render_ops, op); return; + case GSK_TEXTURE_SCALE_NODE: + goto fallback; + case GSK_COLOR_NODE: if (gsk_vulkan_clip_contains_rect (&constants->clip, &node->bounds)) pipeline_type = GSK_VULKAN_PIPELINE_COLOR; diff --git a/gtk/gtksnapshot.c b/gtk/gtksnapshot.c index 56edc74b9f..32686d264b 100644 --- a/gtk/gtksnapshot.c +++ b/gtk/gtksnapshot.c @@ -1918,6 +1918,10 @@ gtk_snapshot_append_cairo (GtkSnapshot *snapshot, * Creates a new render node drawing the @texture * into the given @bounds and appends it to the * current render node of @snapshot. + * + * If the texture needs to be scaled to fill @bounds, + * linear filtering is used. See [method@Gtk.Snapshot.append_scaled_texture] + * if you need other filtering, such as nearest-neighbour. */ void gtk_snapshot_append_texture (GtkSnapshot *snapshot, @@ -1939,6 +1943,44 @@ gtk_snapshot_append_texture (GtkSnapshot *snapshot, gtk_snapshot_append_node_internal (snapshot, node); } +/** + * gtk_snapshot_append_scaled_texture: + * @snapshot: a `GtkSnapshot` + * @texture: the texture to render + * @filter: the filter to use + * @bounds: the bounds for the new node + * + * Creates a new render node drawing the @texture + * into the given @bounds and appends it to the + * current render node of @snapshot. + * + * In contrast to [method@Gtk.Snapshot.append_texture], + * this function provides control about how the filter + * that is used when scaling. + * + * Since: 4.10 + */ +void +gtk_snapshot_append_scaled_texture (GtkSnapshot *snapshot, + GdkTexture *texture, + GskScalingFilter filter, + const graphene_rect_t *bounds) +{ + GskRenderNode *node; + graphene_rect_t real_bounds; + float scale_x, scale_y, dx, dy; + + g_return_if_fail (snapshot != NULL); + g_return_if_fail (GDK_IS_TEXTURE (texture)); + g_return_if_fail (bounds != NULL); + + gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy); + gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &real_bounds); + node = gsk_texture_scale_node_new (texture, &real_bounds, filter); + + gtk_snapshot_append_node_internal (snapshot, node); +} + /** * gtk_snapshot_append_color: * @snapshot: a `GtkSnapshot` diff --git a/gtk/gtksnapshot.h b/gtk/gtksnapshot.h index 38cc0271aa..66d0d879f1 100644 --- a/gtk/gtksnapshot.h +++ b/gtk/gtksnapshot.h @@ -152,6 +152,11 @@ GDK_AVAILABLE_IN_ALL void gtk_snapshot_append_texture (GtkSnapshot *snapshot, GdkTexture *texture, const graphene_rect_t *bounds); +GDK_AVAILABLE_IN_4_10 +void gtk_snapshot_append_scaled_texture (GtkSnapshot *snapshot, + GdkTexture *texture, + GskScalingFilter filter, + const graphene_rect_t *bounds); GDK_AVAILABLE_IN_ALL void gtk_snapshot_append_color (GtkSnapshot *snapshot, const GdkRGBA *color, diff --git a/gtk/inspector/recorder.c b/gtk/inspector/recorder.c index e33d058b05..53528a26cb 100644 --- a/gtk/inspector/recorder.c +++ b/gtk/inspector/recorder.c @@ -264,6 +264,7 @@ create_list_model_for_render_node (GskRenderNode *node) case GSK_CAIRO_NODE: case GSK_TEXT_NODE: case GSK_TEXTURE_NODE: + case GSK_TEXTURE_SCALE_NODE: case GSK_COLOR_NODE: case GSK_LINEAR_GRADIENT_NODE: case GSK_REPEATING_LINEAR_GRADIENT_NODE: @@ -402,6 +403,8 @@ node_type_name (GskRenderNodeType type) return "Border"; case GSK_TEXTURE_NODE: return "Texture"; + case GSK_TEXTURE_SCALE_NODE: + return "Scaled Texture"; case GSK_INSET_SHADOW_NODE: return "Inset Shadow"; case GSK_OUTSET_SHADOW_NODE: @@ -476,6 +479,11 @@ node_name (GskRenderNode *node) GdkTexture *texture = gsk_texture_node_get_texture (node); return g_strdup_printf ("%dx%d Texture", gdk_texture_get_width (texture), gdk_texture_get_height (texture)); } + case GSK_TEXTURE_SCALE_NODE: + { + GdkTexture *texture = gsk_texture_node_get_texture (node); + return g_strdup_printf ("%dx%d Texture, Filter %d", gdk_texture_get_width (texture), gdk_texture_get_height (texture), gsk_texture_scale_node_get_filter (node)); + } } } @@ -933,6 +941,18 @@ populate_render_node_properties (GListStore *store, } break; + case GSK_TEXTURE_SCALE_NODE: + { + GdkTexture *texture = g_object_ref (gsk_texture_scale_node_get_texture (node)); + GskScalingFilter filter = gsk_texture_scale_node_get_filter (node); + g_list_store_append (store, object_property_new ("Texture", "", texture)); + + tmp = g_enum_to_string (GSK_TYPE_SCALING_FILTER, filter); + add_text_row (store, "Filter", tmp); + g_free (tmp); + } + break; + case GSK_COLOR_NODE: add_color_row (store, "Color", gsk_color_node_get_color (node)); break; diff --git a/testsuite/gsk/compare/scaled-texture.node b/testsuite/gsk/compare/scaled-texture.node new file mode 100644 index 0000000000..ffcf9127cc --- /dev/null +++ b/testsuite/gsk/compare/scaled-texture.node @@ -0,0 +1,7 @@ +container { + texture-scale { + texture: url("some-10x10-image-with-content.png"); + bounds: 0 0 100 100; + filter: nearest; + } +} diff --git a/testsuite/gsk/compare/scaled-texture.png b/testsuite/gsk/compare/scaled-texture.png new file mode 100644 index 0000000000..acc16bf0dd Binary files /dev/null and b/testsuite/gsk/compare/scaled-texture.png differ diff --git a/testsuite/gsk/meson.build b/testsuite/gsk/meson.build index 1897562465..631f4127bb 100644 --- a/testsuite/gsk/meson.build +++ b/testsuite/gsk/meson.build @@ -75,6 +75,7 @@ compare_render_tests = [ 'transform-in-transform', 'transform-in-transform-in-transform', 'rounded-clip-in-clip-3d', # not really 3d, but cairo fails it + 'scaled-texture', ] # these are too sensitive to differences in the renderers