mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-11-08 17:50:10 +00:00
gsk: gsk_render_node_set_transform() => GskTransformNode
Instead of having a setter for the transform, have a GskTransformNode. Most of the oprations that GTK does do not require a transform, so it doesn't make sense to have it as a primary attribute. Also, changing the transform requires updating the uniforms of the GL renderer, so we're happy if we can avoid that.
This commit is contained in:
parent
19753062c4
commit
67fb129ed7
@ -28,7 +28,6 @@ gsk_render_node_ref
|
||||
gsk_render_node_unref
|
||||
GskRenderNodeType
|
||||
gsk_render_node_get_node_type
|
||||
gsk_render_node_set_transform
|
||||
gsk_render_node_set_opacity
|
||||
GskBlendMode
|
||||
gsk_render_node_set_blend_mode
|
||||
@ -42,6 +41,8 @@ gsk_container_node_new
|
||||
gsk_container_node_append_child
|
||||
gsk_container_node_get_n_children
|
||||
gsk_container_node_get_child
|
||||
gsk_transform_node_new
|
||||
gsk_transform_node_get_child
|
||||
<SUBSECTION Standard>
|
||||
GSK_IS_RENDER_NODE
|
||||
GSK_RENDER_NODE
|
||||
|
@ -52,24 +52,10 @@ gsk_cairo_renderer_render_node (GskCairoRenderer *self,
|
||||
cairo_t *cr)
|
||||
{
|
||||
gboolean pop_group = FALSE;
|
||||
graphene_matrix_t mat;
|
||||
cairo_matrix_t ctm;
|
||||
graphene_rect_t frame;
|
||||
|
||||
cairo_save (cr);
|
||||
|
||||
gsk_render_node_get_transform (node, &mat);
|
||||
if (graphene_matrix_to_2d (&mat, &ctm.xx, &ctm.yx, &ctm.xy, &ctm.yy, &ctm.x0, &ctm.y0))
|
||||
{
|
||||
GSK_NOTE (CAIRO, g_print ("CTM = { .xx = %g, .yx = %g, .xy = %g, .yy = %g, .x0 = %g, .y0 = %g }\n",
|
||||
ctm.xx, ctm.yx,
|
||||
ctm.xy, ctm.yy,
|
||||
ctm.x0, ctm.y0));
|
||||
cairo_transform (cr, &ctm);
|
||||
}
|
||||
else
|
||||
g_critical ("Invalid non-affine transformation for node %p", node);
|
||||
|
||||
gsk_render_node_get_bounds (node, &frame);
|
||||
GSK_NOTE (CAIRO, g_print ("CLIP = { .x = %g, .y = %g, .width = %g, .height = %g }\n",
|
||||
frame.origin.x, frame.origin.y,
|
||||
@ -131,6 +117,27 @@ gsk_cairo_renderer_render_node (GskCairoRenderer *self,
|
||||
cairo_paint (cr);
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_TRANSFORM_NODE:
|
||||
{
|
||||
graphene_matrix_t mat;
|
||||
cairo_matrix_t ctm;
|
||||
|
||||
gsk_transform_node_get_transform (node, &mat);
|
||||
if (graphene_matrix_to_2d (&mat, &ctm.xx, &ctm.yx, &ctm.xy, &ctm.yy, &ctm.x0, &ctm.y0))
|
||||
{
|
||||
GSK_NOTE (CAIRO, g_print ("CTM = { .xx = %g, .yx = %g, .xy = %g, .yy = %g, .x0 = %g, .y0 = %g }\n",
|
||||
ctm.xx, ctm.yx,
|
||||
ctm.xy, ctm.yy,
|
||||
ctm.x0, ctm.y0));
|
||||
cairo_transform (cr, &ctm);
|
||||
}
|
||||
else
|
||||
g_critical ("Invalid non-affine transformation for node %p", node);
|
||||
|
||||
gsk_cairo_renderer_render_node (self, gsk_transform_node_get_child (node), cr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (GSK_RENDER_MODE_CHECK (GEOMETRY))
|
||||
|
@ -28,6 +28,8 @@
|
||||
* @GSK_CONTAINER_NODE: A node containing a stack of children
|
||||
* @GSK_CAIRO_NODE: A node drawing a #cairo_surface_t
|
||||
* @GSK_TEXTURE_NODE: A node drawing a #GskTexture
|
||||
* @GSK_TRANSFORM_NODE: A node that renders its child after applying a
|
||||
* matrix transform
|
||||
*
|
||||
* The type of a node determines what the node is rendering.
|
||||
*
|
||||
@ -37,7 +39,8 @@ typedef enum {
|
||||
GSK_NOT_A_RENDER_NODE = 0,
|
||||
GSK_CONTAINER_NODE,
|
||||
GSK_CAIRO_NODE,
|
||||
GSK_TEXTURE_NODE
|
||||
GSK_TEXTURE_NODE,
|
||||
GSK_TRANSFORM_NODE
|
||||
} GskRenderNodeType;
|
||||
|
||||
/**
|
||||
|
@ -603,13 +603,12 @@ render_node_needs_render_target (GskRenderNode *node)
|
||||
static void
|
||||
gsk_gl_renderer_add_render_item (GskGLRenderer *self,
|
||||
const graphene_matrix_t *projection,
|
||||
const graphene_matrix_t *parent_modelview,
|
||||
const graphene_matrix_t *modelview,
|
||||
GArray *render_items,
|
||||
GskRenderNode *node,
|
||||
RenderItem *parent)
|
||||
{
|
||||
graphene_rect_t viewport;
|
||||
graphene_matrix_t mv, transform;
|
||||
graphene_rect_t bounds;
|
||||
RenderItem item;
|
||||
RenderItem *ritem = NULL;
|
||||
@ -645,10 +644,8 @@ gsk_gl_renderer_add_render_item (GskGLRenderer *self,
|
||||
item.max.z = 0.f;
|
||||
|
||||
/* The location of the item, in normalized world coordinates */
|
||||
gsk_render_node_get_transform (node, &transform);
|
||||
graphene_matrix_multiply (&transform, parent_modelview, &mv);
|
||||
graphene_matrix_multiply (&mv, &self->mvp, &item.mvp);
|
||||
item.z = project_item (projection, &mv);
|
||||
graphene_matrix_multiply (modelview, &self->mvp, &item.mvp);
|
||||
item.z = project_item (projection, modelview);
|
||||
|
||||
item.opacity = gsk_render_node_get_opacity (node);
|
||||
|
||||
@ -743,11 +740,25 @@ gsk_gl_renderer_add_render_item (GskGLRenderer *self,
|
||||
for (i = 0; i < gsk_container_node_get_n_children (node); i++)
|
||||
{
|
||||
GskRenderNode *child = gsk_container_node_get_child (node, i);
|
||||
gsk_gl_renderer_add_render_item (self, projection, &mv, render_items, child, ritem);
|
||||
gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, ritem);
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
case GSK_TRANSFORM_NODE:
|
||||
{
|
||||
graphene_matrix_t transform, transformed_mv;
|
||||
|
||||
gsk_transform_node_get_transform (node, &transform);
|
||||
graphene_matrix_multiply (&transform, modelview, &transformed_mv);
|
||||
gsk_gl_renderer_add_render_item (self,
|
||||
projection, &transformed_mv,
|
||||
render_items,
|
||||
gsk_transform_node_get_child (node),
|
||||
ritem);
|
||||
}
|
||||
return;
|
||||
|
||||
case GSK_NOT_A_RENDER_NODE:
|
||||
default:
|
||||
return;
|
||||
|
@ -97,8 +97,6 @@ gsk_render_node_new (const GskRenderNodeClass *node_class)
|
||||
|
||||
self->ref_count = 1;
|
||||
|
||||
graphene_matrix_init_identity (&self->transform);
|
||||
|
||||
self->opacity = 1.0;
|
||||
|
||||
self->min_filter = GSK_SCALING_FILTER_NEAREST;
|
||||
@ -187,47 +185,6 @@ gsk_render_node_get_bounds (GskRenderNode *node,
|
||||
node->node_class->get_bounds (node, bounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_render_node_set_transform:
|
||||
* @node: a #GskRenderNode
|
||||
* @transform: (nullable): a transformation matrix
|
||||
*
|
||||
* Sets the transformation matrix used when rendering the @node.
|
||||
*
|
||||
* Since: 3.90
|
||||
*/
|
||||
void
|
||||
gsk_render_node_set_transform (GskRenderNode *node,
|
||||
const graphene_matrix_t *transform)
|
||||
{
|
||||
g_return_if_fail (GSK_IS_RENDER_NODE (node));
|
||||
g_return_if_fail (node->is_mutable);
|
||||
|
||||
if (transform == NULL)
|
||||
graphene_matrix_init_identity (&node->transform);
|
||||
else
|
||||
graphene_matrix_init_from_matrix (&node->transform, transform);
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_render_node_get_transform:
|
||||
* @node: a #GskRenderNode
|
||||
* @mv: (out caller-allocates): return location for the transform matrix
|
||||
*
|
||||
* Retrieves the transform matrix set using gsk_render_node_set_transform().
|
||||
*
|
||||
* Since: 3.90
|
||||
*/
|
||||
void
|
||||
gsk_render_node_get_transform (GskRenderNode *node,
|
||||
graphene_matrix_t *mv)
|
||||
{
|
||||
g_return_if_fail (GSK_IS_RENDER_NODE (node));
|
||||
g_return_if_fail (mv != NULL);
|
||||
|
||||
graphene_matrix_init_from_matrix (mv, &node->transform);
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_render_node_set_opacity:
|
||||
* @node: a #GskRenderNode
|
||||
|
@ -66,8 +66,11 @@ GskRenderNode * gsk_container_node_get_child (GskRenderNode
|
||||
guint idx);
|
||||
|
||||
GDK_AVAILABLE_IN_3_90
|
||||
void gsk_render_node_set_transform (GskRenderNode *node,
|
||||
GskRenderNode * gsk_transform_node_new (GskRenderNode *child,
|
||||
const graphene_matrix_t *transform);
|
||||
GDK_AVAILABLE_IN_3_90
|
||||
GskRenderNode * gsk_transform_node_get_child (GskRenderNode *node);
|
||||
|
||||
GDK_AVAILABLE_IN_3_90
|
||||
void gsk_render_node_set_opacity (GskRenderNode *node,
|
||||
double opacity);
|
||||
|
@ -424,3 +424,112 @@ gsk_container_node_get_child (GskRenderNode *node,
|
||||
return g_ptr_array_index (container->children, idx);
|
||||
}
|
||||
|
||||
/*** GSK_TRANSFORM_NODE ***/
|
||||
|
||||
typedef struct _GskTransformNode GskTransformNode;
|
||||
|
||||
struct _GskTransformNode
|
||||
{
|
||||
GskRenderNode render_node;
|
||||
|
||||
GskRenderNode *child;
|
||||
graphene_matrix_t transform;
|
||||
};
|
||||
|
||||
static void
|
||||
gsk_transform_node_finalize (GskRenderNode *node)
|
||||
{
|
||||
GskTransformNode *self = (GskTransformNode *) node;
|
||||
|
||||
gsk_render_node_unref (self->child);
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_transform_node_make_immutable (GskRenderNode *node)
|
||||
{
|
||||
GskTransformNode *self = (GskTransformNode *) node;
|
||||
|
||||
gsk_render_node_make_immutable (self->child);
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_transform_node_get_bounds (GskRenderNode *node,
|
||||
graphene_rect_t *bounds)
|
||||
{
|
||||
GskTransformNode *self = (GskTransformNode *) node;
|
||||
graphene_rect_t child_bounds;
|
||||
|
||||
gsk_render_node_get_bounds (self->child, &child_bounds);
|
||||
|
||||
graphene_matrix_transform_bounds (&self->transform,
|
||||
&child_bounds,
|
||||
bounds);
|
||||
}
|
||||
|
||||
static const GskRenderNodeClass GSK_TRANSFORM_NODE_CLASS = {
|
||||
GSK_TRANSFORM_NODE,
|
||||
sizeof (GskTransformNode),
|
||||
"GskTransformNode",
|
||||
gsk_transform_node_finalize,
|
||||
gsk_transform_node_make_immutable,
|
||||
gsk_transform_node_get_bounds
|
||||
};
|
||||
|
||||
/**
|
||||
* gsk_transform_node_new:
|
||||
* @child: The node to transform
|
||||
* @transform: The transform to apply
|
||||
*
|
||||
* Creates a #GskRenderNode that will transform the given @child
|
||||
* with the given @transform.
|
||||
*
|
||||
* Returns: A new #GskRenderNode
|
||||
*
|
||||
* Since: 3.90
|
||||
*/
|
||||
GskRenderNode *
|
||||
gsk_transform_node_new (GskRenderNode *child,
|
||||
const graphene_matrix_t *transform)
|
||||
{
|
||||
GskTransformNode *self;
|
||||
|
||||
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
||||
g_return_val_if_fail (transform != NULL, NULL);
|
||||
|
||||
self = (GskTransformNode *) gsk_render_node_new (&GSK_TRANSFORM_NODE_CLASS);
|
||||
|
||||
self->child = gsk_render_node_ref (child);
|
||||
graphene_matrix_init_from_matrix (&self->transform, transform);
|
||||
|
||||
return &self->render_node;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_transform_node_get_child:
|
||||
* @node: a transform @GskRenderNode
|
||||
*
|
||||
* Gets the child node that is getting transformed by the given @node.
|
||||
*
|
||||
* Returns: (transfer none): The child that is getting transformed
|
||||
**/
|
||||
GskRenderNode *
|
||||
gsk_transform_node_get_child (GskRenderNode *node)
|
||||
{
|
||||
GskTransformNode *self = (GskTransformNode *) node;
|
||||
|
||||
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_TRANSFORM_NODE), NULL);
|
||||
|
||||
return self->child;
|
||||
}
|
||||
|
||||
void
|
||||
gsk_transform_node_get_transform (GskRenderNode *node,
|
||||
graphene_matrix_t *transform)
|
||||
{
|
||||
GskTransformNode *self = (GskTransformNode *) node;
|
||||
|
||||
g_return_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_TRANSFORM_NODE));
|
||||
|
||||
graphene_matrix_init_from_matrix (transform, &self->transform);
|
||||
}
|
||||
|
||||
|
@ -29,9 +29,6 @@ struct _GskRenderNode
|
||||
GskScalingFilter min_filter;
|
||||
GskScalingFilter mag_filter;
|
||||
|
||||
/* Transformations applied to the node */
|
||||
graphene_matrix_t transform;
|
||||
|
||||
/* Bit fields; leave at the end */
|
||||
gboolean is_mutable : 1;
|
||||
};
|
||||
@ -53,14 +50,14 @@ void gsk_render_node_make_immutable (GskRenderNode *node);
|
||||
|
||||
void gsk_render_node_get_bounds (GskRenderNode *node,
|
||||
graphene_rect_t *frame);
|
||||
void gsk_render_node_get_transform (GskRenderNode *node,
|
||||
graphene_matrix_t *mv);
|
||||
double gsk_render_node_get_opacity (GskRenderNode *node);
|
||||
|
||||
cairo_surface_t *gsk_cairo_node_get_surface (GskRenderNode *node);
|
||||
|
||||
GskTexture *gsk_texture_node_get_texture (GskRenderNode *node);
|
||||
|
||||
void gsk_transform_node_get_transform (GskRenderNode *node, graphene_matrix_t *transform);
|
||||
|
||||
GskBlendMode gsk_render_node_get_blend_mode (GskRenderNode *node);
|
||||
|
||||
G_END_DECLS
|
||||
|
@ -233,7 +233,7 @@ gsk_vulkan_render_add_node (GskVulkanRender *self,
|
||||
|
||||
self->render_passes = g_slist_prepend (self->render_passes, pass);
|
||||
|
||||
gsk_vulkan_render_pass_add_node (pass, self, node);
|
||||
gsk_vulkan_render_pass_add (pass, self, &self->mvp, node);
|
||||
}
|
||||
|
||||
void
|
||||
@ -466,7 +466,7 @@ gsk_vulkan_render_draw (GskVulkanRender *self,
|
||||
|
||||
for (l = self->render_passes; l; l = l->next)
|
||||
{
|
||||
gsk_vulkan_render_pass_draw (l->data, self, &self->mvp, self->pipeline, self->command_buffer);
|
||||
gsk_vulkan_render_pass_draw (l->data, self, self->pipeline, self->command_buffer);
|
||||
}
|
||||
|
||||
vkCmdEndRenderPass (self->command_buffer);
|
||||
|
@ -12,7 +12,8 @@ typedef struct _GskVulkanRenderOp GskVulkanRenderOp;
|
||||
typedef enum {
|
||||
GSK_VULKAN_OP_FALLBACK,
|
||||
GSK_VULKAN_OP_SURFACE,
|
||||
GSK_VULKAN_OP_TEXTURE
|
||||
GSK_VULKAN_OP_TEXTURE,
|
||||
GSK_VULKAN_OP_BIND_MVP
|
||||
} GskVulkanOpType;
|
||||
|
||||
struct _GskVulkanRenderOp
|
||||
@ -20,6 +21,7 @@ struct _GskVulkanRenderOp
|
||||
GskVulkanOpType type;
|
||||
GskRenderNode *node; /* node that's the source of this op */
|
||||
GskVulkanImage *source; /* source image to render */
|
||||
graphene_matrix_t mvp; /* new mvp to set */
|
||||
gsize vertex_offset; /* offset into vertex buffer */
|
||||
gsize vertex_count; /* number of vertices */
|
||||
gsize descriptor_set_index; /* index into descriptor sets array for the right descriptor set to bind */
|
||||
@ -54,9 +56,10 @@ gsk_vulkan_render_pass_free (GskVulkanRenderPass *self)
|
||||
}
|
||||
|
||||
void
|
||||
gsk_vulkan_render_pass_add_node (GskVulkanRenderPass *self,
|
||||
GskVulkanRender *render,
|
||||
GskRenderNode *node)
|
||||
gsk_vulkan_render_pass_add_node (GskVulkanRenderPass *self,
|
||||
GskVulkanRender *render,
|
||||
const graphene_matrix_t *mvp,
|
||||
GskRenderNode *node)
|
||||
{
|
||||
GskVulkanRenderOp op = {
|
||||
.type = GSK_VULKAN_OP_FALLBACK,
|
||||
@ -89,10 +92,24 @@ gsk_vulkan_render_pass_add_node (GskVulkanRenderPass *self,
|
||||
|
||||
for (i = 0; i < gsk_container_node_get_n_children (node); i++)
|
||||
{
|
||||
gsk_vulkan_render_pass_add_node (self, render, gsk_container_node_get_child (node, i));
|
||||
gsk_vulkan_render_pass_add_node (self, render, mvp, gsk_container_node_get_child (node, i));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GSK_TRANSFORM_NODE:
|
||||
{
|
||||
graphene_matrix_t transform;
|
||||
|
||||
op.type = GSK_VULKAN_OP_BIND_MVP;
|
||||
gsk_transform_node_get_transform (node, &transform);
|
||||
graphene_matrix_multiply (&transform, mvp, &op.mvp);
|
||||
g_array_append_val (self->render_ops, op);
|
||||
gsk_vulkan_render_pass_add_node (self, render, &op.mvp, gsk_transform_node_get_child (node));
|
||||
graphene_matrix_init_from_matrix (&op.mvp, mvp);
|
||||
g_array_append_val (self->render_ops, op);
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return;
|
||||
@ -101,6 +118,22 @@ fallback:
|
||||
g_array_append_val (self->render_ops, op);
|
||||
}
|
||||
|
||||
void
|
||||
gsk_vulkan_render_pass_add (GskVulkanRenderPass *self,
|
||||
GskVulkanRender *render,
|
||||
const graphene_matrix_t *mvp,
|
||||
GskRenderNode *node)
|
||||
{
|
||||
GskVulkanRenderOp op = {
|
||||
.type = GSK_VULKAN_OP_BIND_MVP,
|
||||
.mvp = *mvp
|
||||
};
|
||||
|
||||
g_array_append_val (self->render_ops, op);
|
||||
|
||||
gsk_vulkan_render_pass_add_node (self, render, mvp, node);
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_vulkan_render_pass_upload_fallback (GskVulkanRenderPass *self,
|
||||
GskVulkanRenderOp *op,
|
||||
@ -182,6 +215,7 @@ gsk_vulkan_render_pass_upload (GskVulkanRenderPass *self,
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
case GSK_VULKAN_OP_BIND_MVP:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -237,6 +271,9 @@ gsk_vulkan_render_pass_collect_vertices (GskVulkanRenderPass *self,
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
case GSK_VULKAN_OP_BIND_MVP:
|
||||
op->vertex_offset = 0;
|
||||
op->vertex_count = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -268,6 +305,7 @@ gsk_vulkan_render_pass_reserve_descriptor_sets (GskVulkanRenderPass *self,
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
case GSK_VULKAN_OP_BIND_MVP:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -276,12 +314,10 @@ gsk_vulkan_render_pass_reserve_descriptor_sets (GskVulkanRenderPass *self,
|
||||
void
|
||||
gsk_vulkan_render_pass_draw (GskVulkanRenderPass *self,
|
||||
GskVulkanRender *render,
|
||||
const graphene_matrix_t *parent_mvp,
|
||||
GskVulkanPipeline *pipeline,
|
||||
VkCommandBuffer command_buffer)
|
||||
{
|
||||
GskVulkanRenderOp *op;
|
||||
graphene_matrix_t transform, mvp;
|
||||
float float_matrix[16];
|
||||
guint i;
|
||||
|
||||
@ -289,30 +325,41 @@ gsk_vulkan_render_pass_draw (GskVulkanRenderPass *self,
|
||||
{
|
||||
op = &g_array_index (self->render_ops, GskVulkanRenderOp, i);
|
||||
|
||||
vkCmdBindDescriptorSets (command_buffer,
|
||||
VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
gsk_vulkan_pipeline_get_pipeline_layout (pipeline),
|
||||
0,
|
||||
1,
|
||||
(VkDescriptorSet[1]) {
|
||||
gsk_vulkan_render_get_descriptor_set (render, op->descriptor_set_index)
|
||||
},
|
||||
0,
|
||||
NULL);
|
||||
switch (op->type)
|
||||
{
|
||||
case GSK_VULKAN_OP_FALLBACK:
|
||||
case GSK_VULKAN_OP_SURFACE:
|
||||
case GSK_VULKAN_OP_TEXTURE:
|
||||
vkCmdBindDescriptorSets (command_buffer,
|
||||
VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
gsk_vulkan_pipeline_get_pipeline_layout (pipeline),
|
||||
0,
|
||||
1,
|
||||
(VkDescriptorSet[1]) {
|
||||
gsk_vulkan_render_get_descriptor_set (render, op->descriptor_set_index)
|
||||
},
|
||||
0,
|
||||
NULL);
|
||||
|
||||
gsk_render_node_get_transform (op->node, &transform);
|
||||
graphene_matrix_multiply (&transform, parent_mvp, &mvp);
|
||||
graphene_matrix_to_float (&mvp, float_matrix);
|
||||
vkCmdDraw (command_buffer,
|
||||
op->vertex_count, 1,
|
||||
op->vertex_offset, 0);
|
||||
break;
|
||||
|
||||
vkCmdPushConstants (command_buffer,
|
||||
gsk_vulkan_pipeline_get_pipeline_layout (pipeline),
|
||||
VK_SHADER_STAGE_VERTEX_BIT,
|
||||
0,
|
||||
sizeof (float_matrix),
|
||||
&float_matrix);
|
||||
case GSK_VULKAN_OP_BIND_MVP:
|
||||
graphene_matrix_to_float (&op->mvp, float_matrix);
|
||||
vkCmdPushConstants (command_buffer,
|
||||
gsk_vulkan_pipeline_get_pipeline_layout (pipeline),
|
||||
VK_SHADER_STAGE_VERTEX_BIT,
|
||||
0,
|
||||
sizeof (float_matrix),
|
||||
&float_matrix);
|
||||
|
||||
vkCmdDraw (command_buffer,
|
||||
op->vertex_count, 1,
|
||||
op->vertex_offset, 0);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,8 +13,9 @@ typedef struct _GskVulkanRenderPass GskVulkanRenderPass;
|
||||
GskVulkanRenderPass * gsk_vulkan_render_pass_new (GdkVulkanContext *context);
|
||||
void gsk_vulkan_render_pass_free (GskVulkanRenderPass *self);
|
||||
|
||||
void gsk_vulkan_render_pass_add_node (GskVulkanRenderPass *self,
|
||||
void gsk_vulkan_render_pass_add (GskVulkanRenderPass *self,
|
||||
GskVulkanRender *render,
|
||||
const graphene_matrix_t*mvp,
|
||||
GskRenderNode *node);
|
||||
|
||||
void gsk_vulkan_render_pass_upload (GskVulkanRenderPass *self,
|
||||
@ -31,7 +32,6 @@ void gsk_vulkan_render_pass_reserve_descriptor_sets (GskVulk
|
||||
GskVulkanRender *render);
|
||||
void gsk_vulkan_render_pass_draw (GskVulkanRenderPass *self,
|
||||
GskVulkanRender *render,
|
||||
const graphene_matrix_t*root_mvp,
|
||||
GskVulkanPipeline *pipeline,
|
||||
VkCommandBuffer command_buffer);
|
||||
|
||||
|
@ -96,9 +96,9 @@ gtk_css_style_snapshot_icon (GtkCssStyle *style,
|
||||
GtkCssImageBuiltinType builtin_type)
|
||||
{
|
||||
const GtkCssValue *shadows, *transform;
|
||||
graphene_matrix_t transform_matrix, m1, m2, m3, saved_matrix;
|
||||
GtkCssImage *image;
|
||||
static gboolean shadow_warning;
|
||||
graphene_matrix_t transform_matrix;
|
||||
GtkCssImage *image;
|
||||
|
||||
g_return_if_fail (GTK_IS_CSS_STYLE (style));
|
||||
g_return_if_fail (snapshot != NULL);
|
||||
@ -113,23 +113,39 @@ gtk_css_style_snapshot_icon (GtkCssStyle *style,
|
||||
if (!gtk_css_transform_value_get_matrix (transform, &transform_matrix))
|
||||
return;
|
||||
|
||||
graphene_matrix_init_from_matrix (&saved_matrix, gtk_snapshot_get_transform (snapshot));
|
||||
|
||||
/* XXX: Implement -gtk-icon-transform-origin instead of hardcoding "50% 50%" here */
|
||||
graphene_matrix_init_translate (&m1, &GRAPHENE_POINT3D_INIT(width / 2.0, height / 2.0, 0));
|
||||
graphene_matrix_multiply (&transform_matrix, &m1, &m3);
|
||||
graphene_matrix_init_translate (&m2, &GRAPHENE_POINT3D_INIT(- width / 2.0, - height / 2.0, 0));
|
||||
graphene_matrix_multiply (&m2, &m3, &m1);
|
||||
gtk_snapshot_transform (snapshot, &m1);
|
||||
|
||||
if (!_gtk_css_shadows_value_is_none (shadows) && !shadow_warning)
|
||||
{
|
||||
g_warning ("Painting shadows not implemented for textures yet.");
|
||||
shadow_warning = TRUE;
|
||||
}
|
||||
gtk_css_image_builtin_snapshot (image, snapshot, width, height, builtin_type);
|
||||
|
||||
gtk_snapshot_set_transform (snapshot, &saved_matrix);
|
||||
if (graphene_matrix_is_identity (&transform_matrix))
|
||||
{
|
||||
gtk_css_image_builtin_snapshot (image, snapshot, width, height, builtin_type);
|
||||
}
|
||||
else
|
||||
{
|
||||
graphene_matrix_t m1, m2, m3;
|
||||
GskRenderNode *transform_node, *container_node;
|
||||
double offset_x, offset_y;
|
||||
|
||||
gtk_snapshot_get_offset (snapshot, &offset_x, &offset_y);
|
||||
/* XXX: Implement -gtk-icon-transform-origin instead of hardcoding "50% 50%" here */
|
||||
graphene_matrix_init_translate (&m1, &GRAPHENE_POINT3D_INIT(offset_x + width / 2.0, offset_y + height / 2.0, 0));
|
||||
graphene_matrix_multiply (&transform_matrix, &m1, &m3);
|
||||
graphene_matrix_init_translate (&m2, &GRAPHENE_POINT3D_INIT(- width / 2.0, - height / 2.0, 0));
|
||||
graphene_matrix_multiply (&m2, &m3, &m1);
|
||||
|
||||
container_node = gsk_container_node_new ();
|
||||
gsk_render_node_set_name (container_node, "CSS Icon Transform Container");
|
||||
transform_node = gsk_transform_node_new (container_node, &m1);
|
||||
gsk_render_node_set_name (transform_node, "CSS Icon Transform");
|
||||
gtk_snapshot_append_node (snapshot, transform_node);
|
||||
|
||||
gtk_snapshot_push_node (snapshot, container_node);
|
||||
gtk_css_image_builtin_snapshot (image, snapshot, width, height, builtin_type);
|
||||
gtk_snapshot_pop (snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
@ -253,9 +269,9 @@ gtk_css_style_snapshot_icon_texture (GtkCssStyle *style,
|
||||
double texture_scale)
|
||||
{
|
||||
const GtkCssValue *shadows, *transform;
|
||||
graphene_matrix_t transform_matrix, translate, matrix, saved_matrix;
|
||||
graphene_matrix_t transform_matrix;
|
||||
graphene_rect_t bounds;
|
||||
GskRenderNode *node;
|
||||
GskRenderNode *icon_node, *transform_node;
|
||||
double width, height;
|
||||
static gboolean shadow_warning;
|
||||
|
||||
@ -272,26 +288,47 @@ gtk_css_style_snapshot_icon_texture (GtkCssStyle *style,
|
||||
if (!gtk_css_transform_value_get_matrix (transform, &transform_matrix))
|
||||
return;
|
||||
|
||||
graphene_matrix_init_from_matrix (&saved_matrix, gtk_snapshot_get_transform (snapshot));
|
||||
|
||||
/* XXX: Implement -gtk-icon-transform-origin instead of hardcoding "50% 50%" here */
|
||||
graphene_matrix_init_translate (&translate, &GRAPHENE_POINT3D_INIT(width / 2.0, height / 2.0, 0));
|
||||
graphene_matrix_multiply (&transform_matrix, &translate, &matrix);
|
||||
graphene_matrix_translate (&matrix, &GRAPHENE_POINT3D_INIT(- width / 2.0, - height / 2.0, 0));
|
||||
graphene_matrix_scale (&matrix, 1.0 / texture_scale, 1.0 / texture_scale, 1);
|
||||
gtk_snapshot_transform (snapshot, &matrix);
|
||||
|
||||
graphene_rect_init (&bounds, 0, 0, gsk_texture_get_width (texture), gsk_texture_get_height (texture));
|
||||
|
||||
node = gsk_texture_node_new (texture, &bounds);
|
||||
gsk_render_node_set_name (node, "Icon");
|
||||
gtk_snapshot_append_node (snapshot, node);
|
||||
if (!_gtk_css_shadows_value_is_none (shadows) && !shadow_warning)
|
||||
{
|
||||
g_warning ("Painting shadows not implemented for textures yet.");
|
||||
shadow_warning = TRUE;
|
||||
}
|
||||
gsk_render_node_unref (node);
|
||||
|
||||
gtk_snapshot_set_transform (snapshot, &saved_matrix);
|
||||
if (graphene_matrix_is_identity (&transform_matrix))
|
||||
{
|
||||
double offset_x, offset_y;
|
||||
|
||||
gtk_snapshot_get_offset (snapshot, &offset_x, &offset_y);
|
||||
graphene_rect_init (&bounds,
|
||||
offset_x, offset_y,
|
||||
gsk_texture_get_width (texture) / texture_scale,
|
||||
gsk_texture_get_height (texture) / texture_scale);
|
||||
icon_node = gsk_texture_node_new (texture, &bounds);
|
||||
gsk_render_node_set_name (icon_node, "Icon");
|
||||
|
||||
gtk_snapshot_append_node (snapshot, icon_node);
|
||||
}
|
||||
else
|
||||
{
|
||||
graphene_matrix_t translate, matrix;
|
||||
double offset_x, offset_y;
|
||||
|
||||
gtk_snapshot_get_offset (snapshot, &offset_x, &offset_y);
|
||||
/* XXX: Implement -gtk-icon-transform-origin instead of hardcoding "50% 50%" here */
|
||||
graphene_matrix_init_translate (&translate, &GRAPHENE_POINT3D_INIT(offset_x + width / 2.0, offset_y + height / 2.0, 0));
|
||||
graphene_matrix_multiply (&transform_matrix, &translate, &matrix);
|
||||
graphene_matrix_translate (&matrix, &GRAPHENE_POINT3D_INIT(- width / 2.0, - height / 2.0, 0));
|
||||
graphene_matrix_scale (&matrix, 1.0 / texture_scale, 1.0 / texture_scale, 1);
|
||||
|
||||
graphene_rect_init (&bounds, 0, 0, gsk_texture_get_width (texture), gsk_texture_get_height (texture));
|
||||
icon_node = gsk_texture_node_new (texture, &bounds);
|
||||
gsk_render_node_set_name (icon_node, "Icon");
|
||||
|
||||
transform_node = gsk_transform_node_new (icon_node, &matrix);
|
||||
gsk_render_node_set_name (transform_node, "Icon Transform");
|
||||
gtk_snapshot_append_node (snapshot, transform_node);
|
||||
|
||||
gsk_render_node_unref (icon_node);
|
||||
gsk_render_node_unref (transform_node);
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@
|
||||
* transformations.
|
||||
*
|
||||
* The node at the top of the stack is the the one that gtk_snapshot_append_node()
|
||||
* operates on. Use the gtk_snapshot_push_() and gtk_snapshot_pop() functions to
|
||||
* operates on. Use the gtk_snapshot_push() and gtk_snapshot_pop() functions to
|
||||
* change the current node.
|
||||
*
|
||||
* The only way to obtain a #GtkSnapshot object is as an argument to
|
||||
@ -58,7 +58,6 @@ gtk_snapshot_state_new (GtkSnapshotState *parent,
|
||||
|
||||
state->node = node;
|
||||
state->parent = parent;
|
||||
graphene_matrix_init_identity (&state->transform);
|
||||
|
||||
return state;
|
||||
}
|
||||
@ -69,33 +68,6 @@ gtk_snapshot_state_free (GtkSnapshotState *state)
|
||||
g_slice_free (GtkSnapshotState, state);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_snapshot_state_set_transform (GtkSnapshotState *state,
|
||||
const graphene_matrix_t *transform)
|
||||
{
|
||||
graphene_matrix_init_from_matrix (&state->transform, transform);
|
||||
|
||||
state->world_is_valid = FALSE;
|
||||
}
|
||||
|
||||
static const graphene_matrix_t *
|
||||
gtk_snapshot_state_get_world_transform (GtkSnapshotState *state)
|
||||
{
|
||||
if (!state->world_is_valid)
|
||||
{
|
||||
if (state->parent)
|
||||
graphene_matrix_multiply (gtk_snapshot_state_get_world_transform (state->parent),
|
||||
&state->transform,
|
||||
&state->world_transform);
|
||||
else
|
||||
graphene_matrix_init_from_matrix (&state->world_transform, &state->transform);
|
||||
|
||||
state->world_is_valid = TRUE;
|
||||
}
|
||||
|
||||
return &state->world_transform;
|
||||
}
|
||||
|
||||
void
|
||||
gtk_snapshot_init (GtkSnapshot *snapshot,
|
||||
GskRenderer *renderer,
|
||||
@ -147,8 +119,8 @@ gtk_snapshot_finish (GtkSnapshot *snapshot)
|
||||
* @snapshot: a #GtkSnapshot
|
||||
* @node: the render node to push
|
||||
*
|
||||
* Appends @node to the current render node of @snapshot,
|
||||
* and makes @node the new current render node.
|
||||
* Makes @node the new current render node. You are responsible for adding
|
||||
* @node to the snapshot.
|
||||
*
|
||||
* Since: 3.90
|
||||
*/
|
||||
@ -158,8 +130,6 @@ gtk_snapshot_push_node (GtkSnapshot *snapshot,
|
||||
{
|
||||
g_return_if_fail (gsk_render_node_get_node_type (node) == GSK_CONTAINER_NODE);
|
||||
|
||||
gtk_snapshot_append_node (snapshot, node);
|
||||
|
||||
snapshot->state = gtk_snapshot_state_new (snapshot->state, node);
|
||||
}
|
||||
|
||||
@ -198,6 +168,7 @@ gtk_snapshot_push (GtkSnapshot *snapshot,
|
||||
g_free (str);
|
||||
}
|
||||
|
||||
gtk_snapshot_append_node (snapshot, node);
|
||||
gtk_snapshot_push_node (snapshot, node);
|
||||
gsk_render_node_unref (node);
|
||||
}
|
||||
@ -245,45 +216,6 @@ gtk_snapshot_get_renderer (const GtkSnapshot *snapshot)
|
||||
return snapshot->renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_snapshot_set_transform:
|
||||
* @snapshot: a #GtkSnapshot
|
||||
* @transform: a transformation matrix
|
||||
*
|
||||
* Replaces the current transformation with the given @transform.
|
||||
*
|
||||
* Since: 3.90
|
||||
*/
|
||||
void
|
||||
gtk_snapshot_set_transform (GtkSnapshot *snapshot,
|
||||
const graphene_matrix_t *transform)
|
||||
{
|
||||
g_return_if_fail (snapshot->state != NULL);
|
||||
|
||||
gtk_snapshot_state_set_transform (snapshot->state, transform);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_snapshot_transform:
|
||||
* @snapshot: a #GtkSnapshot
|
||||
* @transform: a transformation matrix
|
||||
*
|
||||
* Appends @transform to the current transformation.
|
||||
*
|
||||
* Since: 3.90
|
||||
*/
|
||||
void
|
||||
gtk_snapshot_transform (GtkSnapshot *snapshot,
|
||||
const graphene_matrix_t *transform)
|
||||
{
|
||||
graphene_matrix_t result;
|
||||
|
||||
g_return_if_fail (snapshot->state != NULL);
|
||||
|
||||
graphene_matrix_multiply (transform, &snapshot->state->transform, &result);
|
||||
gtk_snapshot_state_set_transform (snapshot->state, &result);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_snapshot_translate_2d:
|
||||
* @snapshot: a $GtkSnapshot
|
||||
@ -299,12 +231,37 @@ gtk_snapshot_translate_2d (GtkSnapshot *snapshot,
|
||||
int x,
|
||||
int y)
|
||||
{
|
||||
graphene_matrix_t transform;
|
||||
graphene_point3d_t point;
|
||||
snapshot->state->translate_x += x;
|
||||
snapshot->state->translate_y += y;
|
||||
}
|
||||
|
||||
graphene_point3d_init (&point, x, y, 0);
|
||||
graphene_matrix_init_translate (&transform, &point);
|
||||
gtk_snapshot_transform (snapshot, &transform);
|
||||
/**
|
||||
* gtk_snapshot_get_offset:
|
||||
* @snapshot: a #GtkSnapshot
|
||||
* @x: (out allow-none): return location for x offset
|
||||
* @y: (out allow-none): return location for y offset
|
||||
*
|
||||
* Queries the offset managed by @snapshot. This offset is the
|
||||
* accumulated sum of calls to gtk_snapshot_translate_2d().
|
||||
*
|
||||
* Use this offset to determine how to offset nodes that you
|
||||
* manually add to the snapshot using
|
||||
* gtk_snapshot_append_node().
|
||||
*
|
||||
* Note that other functions that add nodes for you, such as
|
||||
* gtk_snapshot_append_cairo_node() will add this offset for
|
||||
* you.
|
||||
**/
|
||||
void
|
||||
gtk_snapshot_get_offset (GtkSnapshot *snapshot,
|
||||
double *x,
|
||||
double *y)
|
||||
{
|
||||
if (x)
|
||||
*x = snapshot->state->translate_x;
|
||||
|
||||
if (y)
|
||||
*y = snapshot->state->translate_y;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -327,7 +284,6 @@ gtk_snapshot_append_node (GtkSnapshot *snapshot,
|
||||
if (snapshot->state)
|
||||
{
|
||||
gsk_container_node_append_child (snapshot->state->node, node);
|
||||
gsk_render_node_set_transform (node, &snapshot->state->transform);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -357,11 +313,14 @@ gtk_snapshot_append_cairo_node (GtkSnapshot *snapshot,
|
||||
...)
|
||||
{
|
||||
GskRenderNode *node;
|
||||
graphene_rect_t real_bounds;
|
||||
cairo_t *cr;
|
||||
|
||||
g_return_val_if_fail (snapshot != NULL, NULL);
|
||||
g_return_val_if_fail (bounds != NULL, NULL);
|
||||
|
||||
node = gsk_cairo_node_new (bounds);
|
||||
graphene_rect_offset_r (bounds, snapshot->state->translate_x, snapshot->state->translate_y, &real_bounds);
|
||||
node = gsk_cairo_node_new (&real_bounds);
|
||||
|
||||
if (name)
|
||||
{
|
||||
@ -380,7 +339,11 @@ gtk_snapshot_append_cairo_node (GtkSnapshot *snapshot,
|
||||
gtk_snapshot_append_node (snapshot, node);
|
||||
gsk_render_node_unref (node);
|
||||
|
||||
return gsk_cairo_node_get_draw_context (node, snapshot->renderer);
|
||||
cr = gsk_cairo_node_get_draw_context (node, snapshot->renderer);
|
||||
|
||||
cairo_translate (cr, snapshot->state->translate_x, snapshot->state->translate_y);
|
||||
|
||||
return cr;
|
||||
}
|
||||
|
||||
static void
|
||||
@ -408,22 +371,11 @@ gboolean
|
||||
gtk_snapshot_clips_rect (GtkSnapshot *snapshot,
|
||||
const graphene_rect_t *bounds)
|
||||
{
|
||||
graphene_rect_t offset_bounds;
|
||||
cairo_rectangle_int_t rect;
|
||||
|
||||
if (snapshot->state)
|
||||
{
|
||||
const graphene_matrix_t *world;
|
||||
graphene_rect_t transformed;
|
||||
|
||||
world = gtk_snapshot_state_get_world_transform (snapshot->state);
|
||||
|
||||
graphene_matrix_transform_bounds (world, bounds, &transformed);
|
||||
rectangle_init_from_graphene (&rect, &transformed);
|
||||
}
|
||||
else
|
||||
{
|
||||
rectangle_init_from_graphene (&rect, bounds);
|
||||
}
|
||||
graphene_rect_offset_r (bounds, snapshot->state->translate_x, snapshot->state->translate_y, &offset_bounds);
|
||||
rectangle_init_from_graphene (&rect, &offset_bounds);
|
||||
|
||||
return cairo_region_contains_rectangle (snapshot->clip_region, &rect) == CAIRO_REGION_OVERLAP_OUT;
|
||||
}
|
||||
|
@ -49,16 +49,14 @@ void gtk_snapshot_push_node (GtkSnapshot
|
||||
GDK_AVAILABLE_IN_3_90
|
||||
void gtk_snapshot_pop (GtkSnapshot *snapshot);
|
||||
|
||||
GDK_AVAILABLE_IN_3_90
|
||||
void gtk_snapshot_set_transform (GtkSnapshot *snapshot,
|
||||
const graphene_matrix_t *transform);
|
||||
GDK_AVAILABLE_IN_3_90
|
||||
void gtk_snapshot_transform (GtkSnapshot *snapshot,
|
||||
const graphene_matrix_t *transform);
|
||||
GDK_AVAILABLE_IN_3_90
|
||||
void gtk_snapshot_translate_2d (GtkSnapshot *snapshot,
|
||||
int x,
|
||||
int y);
|
||||
GDK_AVAILABLE_IN_3_90
|
||||
void gtk_snapshot_get_offset (GtkSnapshot *snapshot,
|
||||
double *x,
|
||||
double *y);
|
||||
|
||||
GDK_AVAILABLE_IN_3_90
|
||||
void gtk_snapshot_append_node (GtkSnapshot *snapshot,
|
||||
|
@ -29,9 +29,8 @@ struct _GtkSnapshotState {
|
||||
|
||||
GskRenderNode *node;
|
||||
|
||||
graphene_matrix_t transform;
|
||||
graphene_matrix_t world_transform;
|
||||
guint world_is_valid : 1;
|
||||
double translate_x;
|
||||
double translate_y;
|
||||
};
|
||||
|
||||
struct _GtkSnapshot {
|
||||
@ -49,12 +48,6 @@ void gtk_snapshot_init (GtkSnapshot *state,
|
||||
...) G_GNUC_PRINTF (4, 5);
|
||||
GskRenderNode * gtk_snapshot_finish (GtkSnapshot *state);
|
||||
|
||||
static inline const graphene_matrix_t *
|
||||
gtk_snapshot_get_transform (const GtkSnapshot *snapshot)
|
||||
{
|
||||
return &snapshot->state->transform;
|
||||
}
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GTK_SNAPSHOT_PRIVATE_H__ */
|
||||
|
@ -525,6 +525,10 @@ append_node (GtkTreeModelRenderNode *nodemodel,
|
||||
/* no children */
|
||||
break;
|
||||
|
||||
case GSK_TRANSFORM_NODE:
|
||||
append_node (nodemodel, gsk_transform_node_get_child (node), priv->nodes->len - 1);
|
||||
break;
|
||||
|
||||
case GSK_CONTAINER_NODE:
|
||||
{
|
||||
gint elt_index;
|
||||
|
@ -132,36 +132,13 @@ static void
|
||||
populate_render_node_properties (GtkListStore *store,
|
||||
GskRenderNode *node)
|
||||
{
|
||||
graphene_matrix_t m;
|
||||
graphene_rect_t bounds;
|
||||
GString *s;
|
||||
int i;
|
||||
char *tmp;
|
||||
GEnumClass *class;
|
||||
|
||||
gtk_list_store_clear (store);
|
||||
|
||||
gsk_render_node_get_transform (node, &m);
|
||||
|
||||
s = g_string_new ("");
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
g_string_append (s, "\n");
|
||||
g_string_append_printf (s, "| %+.6f %+.6f %+.6f %+.6f |",
|
||||
graphene_matrix_get_value (&m, i, 0),
|
||||
graphene_matrix_get_value (&m, i, 1),
|
||||
graphene_matrix_get_value (&m, i, 2),
|
||||
graphene_matrix_get_value (&m, i, 3));
|
||||
}
|
||||
|
||||
gtk_list_store_insert_with_values (store, NULL, -1,
|
||||
0, "Transform",
|
||||
1, s->str,
|
||||
-1);
|
||||
|
||||
g_string_free (s, TRUE);
|
||||
|
||||
gsk_render_node_get_bounds (node, &bounds);
|
||||
|
||||
tmp = g_strdup_printf ("%.6f x %.6f + %.6f + %.6f",
|
||||
|
Loading…
Reference in New Issue
Block a user