1 | /* |
2 | * Copyright © 2016 Endless |
3 | * 2018 Benjamin Otte |
4 | * |
5 | * This library is free software; you can redistribute it and/or |
6 | * modify it under the terms of the GNU Lesser General Public |
7 | * License as published by the Free Software Foundation; either |
8 | * version 2.1 of the License, or (at your option) any later version. |
9 | * |
10 | * This library is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | * Lesser General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU Lesser General Public |
16 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
17 | * |
18 | * Authors: Benjamin Otte <otte@gnome.org> |
19 | */ |
20 | |
21 | #include "config.h" |
22 | |
23 | #include "gskcairorenderer.h" |
24 | |
25 | #include "gskdebugprivate.h" |
26 | #include "gskrendererprivate.h" |
27 | #include "gskrendernodeprivate.h" |
28 | #include "gdk/gdktextureprivate.h" |
29 | |
30 | #ifdef G_ENABLE_DEBUG |
31 | typedef struct { |
32 | GQuark cpu_time; |
33 | GQuark gpu_time; |
34 | } ProfileTimers; |
35 | #endif |
36 | |
37 | struct _GskCairoRenderer |
38 | { |
39 | GskRenderer parent_instance; |
40 | |
41 | GdkCairoContext *cairo_context; |
42 | |
43 | #ifdef G_ENABLE_DEBUG |
44 | ProfileTimers profile_timers; |
45 | #endif |
46 | }; |
47 | |
48 | struct _GskCairoRendererClass |
49 | { |
50 | GskRendererClass parent_class; |
51 | }; |
52 | |
53 | G_DEFINE_TYPE (GskCairoRenderer, gsk_cairo_renderer, GSK_TYPE_RENDERER) |
54 | |
55 | static gboolean |
56 | gsk_cairo_renderer_realize (GskRenderer *renderer, |
57 | GdkSurface *surface, |
58 | GError **error) |
59 | { |
60 | GskCairoRenderer *self = GSK_CAIRO_RENDERER (renderer); |
61 | |
62 | if (surface) |
63 | self->cairo_context = gdk_surface_create_cairo_context (surface); |
64 | |
65 | return TRUE; |
66 | } |
67 | |
68 | static void |
69 | gsk_cairo_renderer_unrealize (GskRenderer *renderer) |
70 | { |
71 | GskCairoRenderer *self = GSK_CAIRO_RENDERER (renderer); |
72 | |
73 | g_clear_object (&self->cairo_context); |
74 | } |
75 | |
76 | static void |
77 | gsk_cairo_renderer_do_render (GskRenderer *renderer, |
78 | cairo_t *cr, |
79 | GskRenderNode *root) |
80 | { |
81 | #ifdef G_ENABLE_DEBUG |
82 | GskCairoRenderer *self = GSK_CAIRO_RENDERER (renderer); |
83 | GskProfiler *profiler; |
84 | gint64 cpu_time; |
85 | #endif |
86 | |
87 | #ifdef G_ENABLE_DEBUG |
88 | profiler = gsk_renderer_get_profiler (renderer); |
89 | gsk_profiler_timer_begin (profiler, timer_id: self->profile_timers.cpu_time); |
90 | #endif |
91 | |
92 | gsk_render_node_draw (node: root, cr); |
93 | |
94 | #ifdef G_ENABLE_DEBUG |
95 | cpu_time = gsk_profiler_timer_end (profiler, timer_id: self->profile_timers.cpu_time); |
96 | gsk_profiler_timer_set (profiler, timer_id: self->profile_timers.cpu_time, value: cpu_time); |
97 | |
98 | gsk_profiler_push_samples (profiler); |
99 | #endif |
100 | } |
101 | |
102 | static GdkTexture * |
103 | gsk_cairo_renderer_render_texture (GskRenderer *renderer, |
104 | GskRenderNode *root, |
105 | const graphene_rect_t *viewport) |
106 | { |
107 | GdkTexture *texture; |
108 | cairo_surface_t *surface; |
109 | cairo_t *cr; |
110 | int width, height; |
111 | /* limit from cairo's source code */ |
112 | #define MAX_IMAGE_SIZE 32767 |
113 | |
114 | width = ceil (x: viewport->size.width); |
115 | height = ceil (x: viewport->size.height); |
116 | if (width > MAX_IMAGE_SIZE || height > MAX_IMAGE_SIZE) |
117 | { |
118 | gsize x, y, size, stride; |
119 | GBytes *bytes; |
120 | guchar *data; |
121 | |
122 | stride = width * 4; |
123 | size = stride * height; |
124 | data = g_malloc_n (n_blocks: stride, n_block_bytes: height); |
125 | |
126 | for (y = 0; y < height; y += MAX_IMAGE_SIZE) |
127 | { |
128 | for (x = 0; x < width; x += MAX_IMAGE_SIZE) |
129 | { |
130 | texture = gsk_cairo_renderer_render_texture (renderer, root, |
131 | viewport: &GRAPHENE_RECT_INIT (x, y, |
132 | MIN (MAX_IMAGE_SIZE, viewport->size.width - x), |
133 | MIN (MAX_IMAGE_SIZE, viewport->size.height - y))); |
134 | gdk_texture_download (texture, |
135 | data: data + stride * y + x * 4, |
136 | stride); |
137 | g_object_unref (object: texture); |
138 | } |
139 | } |
140 | |
141 | bytes = g_bytes_new_take (data, size); |
142 | texture = gdk_memory_texture_new (width, height, GDK_MEMORY_DEFAULT, bytes, stride); |
143 | g_bytes_unref (bytes); |
144 | return texture; |
145 | } |
146 | |
147 | surface = cairo_image_surface_create (format: CAIRO_FORMAT_ARGB32, width, height); |
148 | cr = cairo_create (target: surface); |
149 | |
150 | cairo_translate (cr, tx: - viewport->origin.x, ty: - viewport->origin.y); |
151 | |
152 | gsk_cairo_renderer_do_render (renderer, cr, root); |
153 | |
154 | cairo_destroy (cr); |
155 | |
156 | texture = gdk_texture_new_for_surface (surface); |
157 | cairo_surface_destroy (surface); |
158 | |
159 | return texture; |
160 | } |
161 | |
162 | static void |
163 | gsk_cairo_renderer_render (GskRenderer *renderer, |
164 | GskRenderNode *root, |
165 | const cairo_region_t *region) |
166 | { |
167 | GskCairoRenderer *self = GSK_CAIRO_RENDERER (renderer); |
168 | cairo_t *cr; |
169 | |
170 | gdk_draw_context_begin_frame (GDK_DRAW_CONTEXT (self->cairo_context), |
171 | region); |
172 | cr = gdk_cairo_context_cairo_create (self: self->cairo_context); |
173 | |
174 | g_return_if_fail (cr != NULL); |
175 | |
176 | #ifdef G_ENABLE_DEBUG |
177 | if (GSK_RENDERER_DEBUG_CHECK (renderer, GEOMETRY)) |
178 | { |
179 | GdkSurface *surface = gsk_renderer_get_surface (renderer); |
180 | |
181 | cairo_save (cr); |
182 | cairo_set_operator (cr, op: CAIRO_OPERATOR_OVER); |
183 | cairo_rectangle (cr, |
184 | x: 0, y: 0, |
185 | width: gdk_surface_get_width (surface), height: gdk_surface_get_height (surface)); |
186 | cairo_set_source_rgba (cr, red: 0, green: 0, blue: 0.85, alpha: 0.5); |
187 | cairo_stroke (cr); |
188 | cairo_restore (cr); |
189 | } |
190 | #endif |
191 | |
192 | gsk_cairo_renderer_do_render (renderer, cr, root); |
193 | |
194 | cairo_destroy (cr); |
195 | |
196 | gdk_draw_context_end_frame (GDK_DRAW_CONTEXT (self->cairo_context)); |
197 | } |
198 | |
199 | static void |
200 | gsk_cairo_renderer_class_init (GskCairoRendererClass *klass) |
201 | { |
202 | GskRendererClass *renderer_class = GSK_RENDERER_CLASS (klass); |
203 | |
204 | renderer_class->realize = gsk_cairo_renderer_realize; |
205 | renderer_class->unrealize = gsk_cairo_renderer_unrealize; |
206 | renderer_class->render = gsk_cairo_renderer_render; |
207 | renderer_class->render_texture = gsk_cairo_renderer_render_texture; |
208 | } |
209 | |
210 | static void |
211 | gsk_cairo_renderer_init (GskCairoRenderer *self) |
212 | { |
213 | #ifdef G_ENABLE_DEBUG |
214 | GskProfiler *profiler = gsk_renderer_get_profiler (GSK_RENDERER (self)); |
215 | |
216 | self->profile_timers.cpu_time = gsk_profiler_add_timer (profiler, timer_name: "cpu-time" , description: "CPU time" , FALSE, TRUE); |
217 | #endif |
218 | } |
219 | |
220 | /** |
221 | * gsk_cairo_renderer_new: |
222 | * |
223 | * Creates a new Cairo renderer. |
224 | * |
225 | * The Cairo renderer is the fallback renderer drawing in ways similar |
226 | * to how GTK 3 drew its content. Its primary use is as comparison tool. |
227 | * |
228 | * The Cairo renderer is incomplete. It cannot render 3D transformed |
229 | * content and will instead render an error marker. Its usage should be |
230 | * avoided. |
231 | * |
232 | * Returns: a new Cairo renderer. |
233 | **/ |
234 | GskRenderer * |
235 | gsk_cairo_renderer_new (void) |
236 | { |
237 | return g_object_new (GSK_TYPE_CAIRO_RENDERER, NULL); |
238 | } |
239 | |