Compare commits

...

7 Commits

Author SHA1 Message Date
Matthias Clasen
d4dfbc1def gtk-demo: Add filters to the zoom demo 2023-02-07 19:45:07 -05:00
Matthias Clasen
fb73acc885 gtk-demo: Rewrite the zoom demo slightly
Use a texture directly instead of a paintable.
This will be used in the following commit to
introduce filters.
2023-02-07 19:45:07 -05:00
Matthias Clasen
acad0e8893 gsk: Use filters from texture nodes 2023-02-07 19:45:07 -05:00
Matthias Clasen
d869aec29f gsk: Add filters to texture nodes
Allow specifying the min and mag filters when
creating texture nodes.

Fixes: #5563
Fixes: #3524
2023-02-07 19:44:16 -05:00
Matthias Clasen
aae99a8704 gsk: Pass filters down from visit_texture_node
For now, we always pass GL_LINEAR.
2023-02-07 19:39:46 -05:00
Matthias Clasen
19ba03d0a6 gsk: Generate mipmaps when requested
If the min_filter requires it, call
glGenerateMipmap for our textures.
2023-02-07 19:39:46 -05:00
Matthias Clasen
ac59d553ce gsk: Don't limit filters too much
GL does not allow mipmapping for mag filters,
but it doesn't have a problem with it for min
filters.
2023-02-07 19:39:46 -05:00
6 changed files with 190 additions and 32 deletions

View File

@@ -3,7 +3,9 @@
enum
{
PROP_PAINTABLE = 1,
PROP_TEXTURE = 1,
PROP_MIN_FILTER,
PROP_MAG_FILTER,
PROP_SCALE
};
@@ -11,8 +13,10 @@ struct _Demo3Widget
{
GtkWidget parent_instance;
GdkPaintable *paintable;
GdkTexture *texture;
float scale;
GskScalingFilter min_filter;
GskScalingFilter mag_filter;
GtkWidget *menu;
};
@@ -28,6 +32,8 @@ static void
demo3_widget_init (Demo3Widget *self)
{
self->scale = 1.f;
self->min_filter = GSK_SCALING_FILTER_NEAREST;
self->mag_filter = GSK_SCALING_FILTER_NEAREST;
gtk_widget_init_template (GTK_WIDGET (self));
}
@@ -36,7 +42,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 +56,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 +70,12 @@ 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_node_new_with_filters (self->texture,
&GRAPHENE_RECT_INIT (0, 0, w, h),
self->min_filter,
self->mag_filter);
gtk_snapshot_append_node (snapshot, node);
gsk_render_node_unref (node);
gtk_snapshot_restore (snapshot);
gtk_snapshot_pop (snapshot);
}
@@ -81,9 +93,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 +125,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 +136,16 @@ demo3_widget_set_property (GObject *object,
gtk_widget_queue_resize (GTK_WIDGET (object));
break;
case PROP_MIN_FILTER:
self->min_filter = g_value_get_enum (value);
gtk_widget_queue_resize (GTK_WIDGET (object));
break;
case PROP_MAG_FILTER:
self->mag_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 +162,22 @@ 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_MIN_FILTER:
g_value_set_enum (value, self->min_filter);
break;
case PROP_MAG_FILTER:
g_value_set_enum (value, self->mag_filter);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -205,16 +235,26 @@ 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_MIN_FILTER,
g_param_spec_enum ("min-filter", NULL, NULL,
GSK_TYPE_SCALING_FILTER, GSK_SCALING_FILTER_LINEAR,
G_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_MAG_FILTER,
g_param_spec_enum ("mag-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 +269,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);
}

View File

@@ -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,20 @@ 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, "min-filter", G_BINDING_DEFAULT);
g_object_bind_property (dropdown, "selected", widget, "mag-filter", G_BINDING_DEFAULT);
g_object_bind_property (gtk_range_get_adjustment (GTK_RANGE (scale)), "value",
widget, "scale",
G_BINDING_BIDIRECTIONAL);

View File

@@ -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,

View File

@@ -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,7 +3462,7 @@ 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);
}
}
@@ -3468,6 +3472,10 @@ gsk_gl_render_job_visit_texture_node (GskGLRenderJob *job,
const GskRenderNode *node)
{
GdkTexture *texture = gsk_texture_node_get_texture (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[gsk_texture_node_get_min_filter (node)];
int mag_filter = mag_filters[gsk_texture_node_get_mag_filter (node)];
int max_texture_size = job->command_queue->max_texture_size;
if G_LIKELY (texture->width <= max_texture_size &&
@@ -3475,7 +3483,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, min_filter, mag_filter, &offscreen);
g_assert (offscreen.texture_id);
g_assert (offscreen.was_offscreen == FALSE);
@@ -3808,7 +3816,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;
}

View File

@@ -214,8 +214,18 @@ GType gsk_texture_node_get_type (void) G_GNUC_CO
GDK_AVAILABLE_IN_ALL
GskRenderNode * gsk_texture_node_new (GdkTexture *texture,
const graphene_rect_t *bounds);
GDK_AVAILABLE_IN_4_10
GskRenderNode * gsk_texture_node_new_with_filters (GdkTexture *texture,
const graphene_rect_t *bounds,
GskScalingFilter min_filter,
GskScalingFilter mag_filter);
GDK_AVAILABLE_IN_ALL
GdkTexture * gsk_texture_node_get_texture (const GskRenderNode *node) G_GNUC_PURE;
GDK_AVAILABLE_IN_4_10
GskScalingFilter gsk_texture_node_get_min_filter (const GskRenderNode *node);
GDK_AVAILABLE_IN_4_10
GskScalingFilter gsk_texture_node_get_mag_filter (const GskRenderNode *node);
GDK_AVAILABLE_IN_ALL
GType gsk_linear_gradient_node_get_type (void) G_GNUC_CONST;

View File

@@ -1471,6 +1471,9 @@ struct _GskTextureNode
GskRenderNode render_node;
GdkTexture *texture;
GskScalingFilter min_filter;
GskScalingFilter mag_filter;
};
static void
@@ -1492,6 +1495,11 @@ gsk_texture_node_draw (GskRenderNode *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,
};
surface = gdk_texture_download_surface (self->texture);
pattern = cairo_pattern_create_for_surface (surface);
@@ -1504,6 +1512,10 @@ gsk_texture_node_draw (GskRenderNode *node,
-node->bounds.origin.x,
-node->bounds.origin.y);
cairo_pattern_set_matrix (pattern, &matrix);
if (gdk_texture_get_width (self->texture) > node->bounds.size.width)
cairo_pattern_set_filter (pattern, filters[self->min_filter]);
else
cairo_pattern_set_filter (pattern, filters[self->mag_filter]);
cairo_set_source (cr, pattern);
cairo_pattern_destroy (pattern);
@@ -1522,7 +1534,9 @@ gsk_texture_node_diff (GskRenderNode *node1,
GskTextureNode *self2 = (GskTextureNode *) node2;
if (graphene_rect_equal (&node1->bounds, &node2->bounds) &&
self1->texture == self2->texture)
self1->texture == self2->texture &&
self1->min_filter ==self2->min_filter &&
self1->mag_filter ==self2->mag_filter)
return;
gsk_render_node_diff_impossible (node1, node2, region);
@@ -1544,6 +1558,38 @@ gsk_texture_node_get_texture (const GskRenderNode *node)
return self->texture;
}
/**
* gsk_texture_node_get_min_filter:
* @node: (type GskTextureNode): a `GskRenderNode` of type %GSK_TEXTURE_NODE
*
* Retrieves the filter to apply when scaling the texture down.
*
* Since: 4.10
*/
GskScalingFilter
gsk_texture_node_get_min_filter (const GskRenderNode *node)
{
const GskTextureNode *self = (const GskTextureNode *) node;
return self->min_filter;
}
/**
* gsk_texture_node_get_mag_filter:
* @node: (type GskTextureNode): a `GskRenderNode` of type %GSK_TEXTURE_NODE
*
* Retrieves the filter to apply when scaling the texture up.
*
* Since: 4.10
*/
GskScalingFilter
gsk_texture_node_get_mag_filter (const GskRenderNode *node)
{
const GskTextureNode *self = (const GskTextureNode *) node;
return self->mag_filter;
}
/**
* gsk_texture_node_new:
* @texture: the `GdkTexture`
@@ -1569,6 +1615,49 @@ gsk_texture_node_new (GdkTexture *texture,
node->offscreen_for_opacity = FALSE;
self->texture = g_object_ref (texture);
self->min_filter = GSK_SCALING_FILTER_LINEAR;
self->mag_filter = GSK_SCALING_FILTER_LINEAR;
graphene_rect_init_from_rect (&node->bounds, bounds);
node->prefers_high_depth = gdk_memory_format_prefers_high_depth (gdk_texture_get_format (texture));
return node;
}
/**
* gsk_texture_node_new_with_filters:
* @texture: the `GdkTexture`
* @bounds: the rectangle to render the texture into
* @min_filter: filter to apply when scaling down
* @mag_filter: filter to apply when scaling up
*
* Creates a `GskRenderNode` that will render the given
* @texture into the area given by @bounds, using the
* given filters when required.
*
* Returns: (transfer full) (type GskTextureNode): A new `GskRenderNode`
*
* Since: 4.10
*/
GskRenderNode *
gsk_texture_node_new_with_filters (GdkTexture *texture,
const graphene_rect_t *bounds,
GskScalingFilter min_filter,
GskScalingFilter mag_filter)
{
GskTextureNode *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_NODE);
node = (GskRenderNode *) self;
node->offscreen_for_opacity = FALSE;
self->texture = g_object_ref (texture);
self->min_filter = min_filter;
self->mag_filter = mag_filter;
graphene_rect_init_from_rect (&node->bounds, bounds);
node->prefers_high_depth = gdk_memory_format_prefers_high_depth (gdk_texture_get_format (texture));