Line data Source code
1 : /*
2 : * GPAC - Multimedia Framework C SDK
3 : *
4 : * Authors: Jean Le Feuvre
5 : * Copyright (c) Telecom ParisTech 2000-2020
6 : * All rights reserved
7 : *
8 : * This file is part of GPAC / Scene Compositor sub-project
9 : *
10 : * GPAC is free software; you can redistribute it and/or modify
11 : * it under the terms of the GNU Lesser General Public License as published by
12 : * the Free Software Foundation; either version 2, or (at your option)
13 : * any later version.
14 : *
15 : * GPAC is distributed in the hope that it will be useful,
16 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 : * GNU Lesser General Public License for more details.
19 : *
20 : * You should have received a copy of the GNU Lesser General Public
21 : * License along with this library; see the file COPYING. If not, write to
22 : * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23 : *
24 : */
25 :
26 :
27 :
28 : #include "visual_manager.h"
29 : #include "nodes_stacks.h"
30 : #include <gpac/options.h>
31 : #include "texturing.h"
32 :
33 : #include "gl_inc.h"
34 :
35 :
36 : #ifndef GPAC_DISABLE_3D
37 1769 : void compositor_2d_hybgl_clear_surface(GF_VisualManager *visual, GF_IRect *rc, u32 BackColor, u32 is_offscreen_clear)
38 : {
39 : SFColor rgb;
40 1769 : Fixed alpha = INT2FIX( GF_COL_A(BackColor) )/255;
41 1769 : if (!visual->is_attached) return;
42 :
43 1769 : if (!BackColor && !visual->offscreen && !is_offscreen_clear) {
44 571 : if ( !(visual->compositor->init_flags & GF_TERM_WINDOW_TRANSPARENT)) {
45 571 : BackColor = visual->compositor->back_color & 0x00FFFFFF;
46 : }
47 : }
48 1769 : if (is_offscreen_clear) {
49 996 : gf_evg_surface_clear(visual->raster_surface, rc, BackColor);
50 : //if we clear the canvas with non-0 alpha, remember the area cleared in case we have to erase it later (overlapping bitmap)
51 : //if we clear dirty area of the canvas, remember the area to force gl flush
52 996 : if (GF_COL_A(BackColor) || (is_offscreen_clear==2))
53 : {
54 592 : ra_union_rect(&visual->hybgl_drawn, rc);
55 : }
56 : } else {
57 773 : rgb.red = INT2FIX( GF_COL_R(BackColor) ) / 255;
58 773 : rgb.green = INT2FIX( GF_COL_G(BackColor) )/255;
59 773 : rgb.blue = INT2FIX( GF_COL_B(BackColor) )/255;
60 773 : visual_3d_clear(visual, rgb , alpha);
61 : }
62 : }
63 :
64 :
65 1963 : void compositor_2d_hybgl_flush_video(GF_Compositor *compositor, GF_IRect *area)
66 : {
67 : GF_TraverseState a_tr_state;
68 :
69 : //check if texture data has changed - if so, mark texture to be updated
70 1963 : if (compositor->traverse_state->immediate_draw) {
71 : //nothing drawn, nothing to do
72 157 : if (!compositor->visual->hybgl_drawn.count) {
73 11 : return;
74 : }
75 : //nodes where drawn on canvas, push texture
76 146 : gf_sc_texture_set_data(compositor->hybgl_txh);
77 : } else {
78 : //nodes where drawn on canvas, push texture
79 1806 : if (compositor->visual->hybgl_drawn.count) {
80 : //we could actually use texSubImage here, but we would need either to copy the data before pushing it or use current stride which will result in pushing too much data ...
81 1735 : gf_sc_texture_set_data(compositor->hybgl_txh);
82 : }
83 : }
84 : //if no object drawn since the last flush, no need to draw the texture
85 1952 : if (!compositor->visual->nb_objects_on_canvas_since_last_ogl_flush)
86 : goto exit;
87 :
88 : memset(&a_tr_state, 0, sizeof(GF_TraverseState));
89 1774 : a_tr_state.color_mat.identity = 1;
90 1774 : a_tr_state.visual = compositor->visual;
91 1774 : a_tr_state.camera = &compositor->visual->camera;
92 1774 : gf_mx_init(a_tr_state.model_matrix);
93 :
94 1774 : visual_3d_set_state(compositor->visual, V3D_STATE_LIGHT, GF_FALSE);
95 1774 : visual_3d_enable_antialias(compositor->visual, GF_FALSE);
96 1774 : gf_sc_texture_set_blend_mode(compositor->hybgl_txh, TX_MODULATE);
97 : //visual_3d_set_material_2d_argb(compositor->visual, 0xFFFFFFFF);
98 1774 : compositor->visual->has_material_2d = GF_FALSE;
99 1774 : a_tr_state.mesh_num_textures = gf_sc_texture_enable(compositor->hybgl_txh, NULL);
100 1774 : if (a_tr_state.mesh_num_textures ) {
101 1774 : if (area) {
102 : u32 i;
103 : Fixed umin, umax, vmin, vmax;
104 : SFVec2f size, orig;
105 740 : size.x = INT2FIX(area->width);
106 740 : size.y = INT2FIX(area->height);
107 :
108 740 : orig.x = INT2FIX(area->x);
109 740 : orig.y = INT2FIX(area->y);
110 740 : mesh_new_rectangle(compositor->hybgl_mesh, size, &orig, GF_TRUE);
111 :
112 740 : orig.x = INT2FIX(area->x) + INT2FIX(compositor->vp_width)/2;
113 740 : orig.y = INT2FIX(compositor->vp_height)/2 - INT2FIX(area->y) + INT2FIX(area->height);
114 : //set our coor texture so that we update only the right part of the canvas
115 740 : umin = gf_divfix(orig.x, INT2FIX(compositor->vp_width));
116 740 : umax = gf_divfix(orig.x+size.x, INT2FIX(compositor->vp_width));
117 740 : vmin = gf_divfix(orig.y-size.y, INT2FIX(compositor->vp_height));
118 740 : vmax = gf_divfix(orig.y, INT2FIX(compositor->vp_height));
119 :
120 3700 : for (i=0; i<compositor->hybgl_mesh->v_count; i++) {
121 2960 : if (compositor->hybgl_mesh->vertices[i].texcoords.x == FIX_ONE) {
122 1480 : compositor->hybgl_mesh->vertices[i].texcoords.x = umax;
123 : } else {
124 1480 : compositor->hybgl_mesh->vertices[i].texcoords.x = umin;
125 : }
126 2960 : if (compositor->hybgl_mesh->vertices[i].texcoords.y == FIX_ONE) {
127 1480 : compositor->hybgl_mesh->vertices[i].texcoords.y = vmax;
128 : } else {
129 1480 : compositor->hybgl_mesh->vertices[i].texcoords.y = vmin;
130 : }
131 : }
132 : }
133 :
134 1774 : visual_3d_mesh_paint(&a_tr_state, compositor->hybgl_mesh);
135 1774 : gf_sc_texture_disable(compositor->hybgl_txh);
136 :
137 1774 : if (area) {
138 : SFVec2f size;
139 740 : size.x = INT2FIX(compositor->vp_width);
140 740 : size.y = INT2FIX(compositor->vp_height);
141 740 : mesh_new_rectangle(compositor->hybgl_mesh, size, NULL, GF_TRUE);
142 : }
143 : }
144 :
145 3164 : exit:
146 : //in immediate mode always clear the canvas
147 1952 : if (compositor->traverse_state->immediate_draw) {
148 146 : compositor->visual->hybgl_drawn.count = 0;
149 : //partial flush, reset
150 146 : if (area)
151 0 : memset(compositor->hybgl_txh->data, 0, compositor->hybgl_txh->stride*compositor->hybgl_txh->height);
152 : }
153 : //complete flush (end of frame)
154 1806 : else if (!area) {
155 915 : compositor->visual->hybgl_drawn.count = 0;
156 : }
157 1952 : compositor->visual->nb_objects_on_canvas_since_last_ogl_flush = 0;
158 : }
159 :
160 697 : Bool compositor_2d_hybgl_draw_bitmap(GF_VisualManager *visual, GF_TraverseState *tr_state, DrawableContext *ctx)
161 : {
162 : GF_Node *txtrans = NULL;
163 : //for anything but background use regular routines
164 697 : if (!(ctx->flags & CTX_IS_BACKGROUND)) return GF_FALSE;
165 :
166 : #ifndef GPAC_DISABLE_VRML
167 464 : if (tr_state->appear ) txtrans = ((M_Appearance *)tr_state->appear)->textureTransform;
168 : #endif
169 :
170 : /*ignore texture transform for bitmap*/
171 464 : tr_state->mesh_num_textures = gf_sc_texture_enable(ctx->aspect.fill_texture, txtrans);
172 :
173 464 : if (tr_state->mesh_num_textures) {
174 : SFVec2f size, orig;
175 442 : size.x = ctx->bi->unclip.width;
176 442 : size.y = ctx->bi->unclip.height;
177 442 : orig.x = ctx->bi->unclip.x ;
178 442 : orig.y = ctx->bi->unclip.y;
179 442 : mesh_new_rectangle(visual->compositor->hybgl_mesh_background, size, &orig, GF_FALSE);
180 :
181 442 : visual_3d_mesh_paint(tr_state, visual->compositor->hybgl_mesh_background);
182 442 : gf_sc_texture_disable(ctx->aspect.fill_texture);
183 442 : tr_state->mesh_num_textures = 0;
184 : }
185 : return GF_TRUE;
186 : }
187 : #endif
188 :
189 : #ifndef GPAC_DISABLE_3D
190 605 : void compositor_2d_reset_gl_auto(GF_Compositor *compositor)
191 : {
192 605 : if (compositor->hybgl_txh) {
193 17 : if (compositor->hybgl_txh->data) {
194 17 : gf_free(compositor->hybgl_txh->data);
195 17 : compositor->hybgl_txh->data = NULL;
196 : }
197 17 : if (compositor->hybgl_txh->tx_io)
198 17 : gf_sc_texture_release(compositor->hybgl_txh);
199 :
200 17 : gf_free(compositor->hybgl_txh);
201 17 : compositor->hybgl_txh = NULL;
202 : }
203 605 : if (compositor->hybgl_mesh) {
204 17 : mesh_free(compositor->hybgl_mesh);
205 17 : compositor->hybgl_mesh = NULL;
206 : }
207 605 : if (compositor->hybgl_mesh_background) {
208 17 : mesh_free(compositor->hybgl_mesh_background);
209 17 : compositor->hybgl_mesh_background = NULL;
210 : }
211 605 : }
212 :
213 1062 : static GF_Err compositor_2d_setup_opengl(GF_VisualManager *visual)
214 : {
215 1062 : GF_Compositor *compositor = visual->compositor;
216 1062 : visual->is_attached = GF_TRUE;
217 :
218 1062 : visual_3d_setup(visual);
219 1062 : visual->compositor->traverse_state->camera = &visual->camera;
220 :
221 :
222 1062 : glViewport(0, 0, compositor->vp_width, compositor->vp_height);
223 :
224 1062 : visual->camera.vp.x = visual->camera.vp.y = 0;
225 1062 : visual->camera.vp.width = visual->camera.width = INT2FIX(compositor->vp_width);
226 1062 : visual->camera.vp.height = visual->camera.height = INT2FIX(compositor->vp_height);
227 1062 : visual->camera.up.y = FIX_ONE;
228 1062 : visual->camera.end_zoom = FIX_ONE;
229 1062 : visual->camera.position.z = INT2FIX(1000);
230 1062 : visual->camera.flags = CAM_IS_DIRTY;
231 :
232 1062 : camera_update(&visual->camera, NULL, visual->compositor->hybrid_opengl ? GF_TRUE : visual->center_coords);
233 :
234 1062 : visual_3d_projection_matrix_modified(visual);
235 1062 : return GF_OK;
236 : }
237 : #endif
238 :
239 : #ifndef GPAC_DISABLE_3D
240 :
241 1062 : static GF_Err c2d_video_access_hybrid_opengl(GF_VisualManager *visual)
242 : {
243 : GF_Err e;
244 1062 : GF_Compositor *compositor = visual->compositor;
245 :
246 1062 : if (!compositor->hybgl_txh) {
247 18 : GF_SAFEALLOC(compositor->hybgl_txh, GF_TextureHandler);
248 18 : if (!compositor->hybgl_txh) return GF_IO_ERR;
249 18 : compositor->hybgl_txh->compositor = compositor;
250 : }
251 :
252 1062 : if ((compositor->hybgl_txh->width != compositor->vp_width) || (compositor->hybgl_txh->height != compositor->vp_height)) {
253 : SFVec2f size;
254 20 : compositor->hybgl_txh->data = (char*)gf_realloc(compositor->hybgl_txh->data, 4*compositor->vp_width*compositor->vp_height);
255 :
256 20 : if (compositor->hybgl_txh->tx_io)
257 2 : gf_sc_texture_release(compositor->hybgl_txh);
258 :
259 20 : compositor->hybgl_txh->width = compositor->vp_width;
260 20 : compositor->hybgl_txh->height = compositor->vp_height;
261 20 : compositor->hybgl_txh->stride = 4*compositor->vp_width;
262 20 : compositor->hybgl_txh->pixelformat = GF_PIXEL_RGBA;
263 20 : compositor->hybgl_txh->transparent = GF_TRUE;
264 20 : compositor->hybgl_txh->flags = GF_SR_TEXTURE_PRIVATE_MEDIA | GF_SR_TEXTURE_NO_GL_FLIP;
265 :
266 20 : memset(compositor->hybgl_txh->data, 0, 4*compositor->hybgl_txh->width*compositor->hybgl_txh->height);
267 20 : gf_sc_texture_allocate(compositor->hybgl_txh);
268 20 : gf_sc_texture_set_data(compositor->hybgl_txh);
269 :
270 20 : if (!compositor->hybgl_mesh)
271 18 : compositor->hybgl_mesh = new_mesh();
272 :
273 20 : if (!compositor->hybgl_mesh_background)
274 18 : compositor->hybgl_mesh_background = new_mesh();
275 :
276 20 : size.x = INT2FIX(compositor->vp_width);
277 20 : size.y = INT2FIX(compositor->vp_height);
278 20 : mesh_new_rectangle(compositor->hybgl_mesh, size, NULL, GF_TRUE);
279 20 : mesh_new_rectangle(compositor->hybgl_mesh_background, size, NULL, GF_FALSE);
280 : }
281 1062 : if (!compositor->hybgl_txh->data) return GF_IO_ERR;
282 :
283 1062 : if (visual->compositor->traverse_state->immediate_draw)
284 126 : memset(compositor->hybgl_txh->data, 0, 4*compositor->hybgl_txh->width*compositor->hybgl_txh->height);
285 :
286 1062 : e = gf_evg_surface_attach_to_buffer(visual->raster_surface, compositor->hybgl_txh->data,
287 : compositor->hybgl_txh->width,
288 : compositor->hybgl_txh->height,
289 : 0,
290 1062 : compositor->hybgl_txh->width * 4,
291 : (GF_PixelFormat) GF_PIXEL_RGBA);
292 1062 : if (e) return e;
293 1062 : e = compositor_2d_setup_opengl(visual);
294 1062 : if (e) return e;
295 1062 : visual->ClearSurface = compositor_2d_hybgl_clear_surface;
296 1062 : visual->DrawBitmap = compositor_2d_hybgl_draw_bitmap;
297 1062 : return GF_OK;
298 : }
299 : #endif
300 :
301 13170 : static GF_Err c2d_get_video_access_normal(GF_VisualManager *visual)
302 : {
303 : GF_Err e;
304 13170 : GF_Compositor *compositor = visual->compositor;
305 :
306 13170 : compositor->hw_locked = GF_FALSE;
307 :
308 13170 : e = compositor->video_out->LockBackBuffer(compositor->video_out, &compositor->hw_surface, GF_TRUE);
309 13170 : if (e==GF_OK) {
310 13170 : compositor->hw_locked = GF_TRUE;
311 :
312 13170 : e = gf_evg_surface_attach_to_buffer(visual->raster_surface, compositor->hw_surface.video_buffer,
313 : compositor->hw_surface.width,
314 : compositor->hw_surface.height,
315 : compositor->hw_surface.pitch_x,
316 : compositor->hw_surface.pitch_y,
317 13170 : (GF_PixelFormat) compositor->hw_surface.pixel_format);
318 13170 : if (!e) {
319 13170 : visual->is_attached = GF_TRUE;
320 13170 : GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor2D] Video surface memory attached to raster - w=%d h=%d pitch_x=%d pitch_y=%d\n", compositor->hw_surface.width, compositor->hw_surface.height, compositor->hw_surface.pitch_x, compositor->hw_surface.pitch_y));
321 : return GF_OK;
322 : }
323 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor2D] Cannot attach video surface memory to raster for pixel format %s: %s\n", gf_pixel_fmt_name(compositor->hw_surface.pixel_format), gf_error_to_string(e) ));
324 0 : compositor->last_error = e;
325 0 : compositor->video_out->LockBackBuffer(compositor->video_out, &compositor->hw_surface, GF_FALSE);
326 : }
327 0 : compositor->hw_locked = GF_FALSE;
328 0 : visual->is_attached = GF_FALSE;
329 : /*if using BlitTexture, return OK to still be able to blit images*/
330 0 : if (compositor->video_out->BlitTexture) e = GF_OK;
331 : return e;
332 : }
333 :
334 21062 : GF_Err compositor_2d_get_video_access(GF_VisualManager *visual)
335 : {
336 21062 : if (!visual->raster_surface) return GF_BAD_PARAM;
337 :
338 : #ifndef GPAC_DISABLE_3D
339 21062 : if (visual->compositor->hybrid_opengl) {
340 1062 : return c2d_video_access_hybrid_opengl(visual);
341 : }
342 : #endif
343 : //do nothing until asked to really attach
344 : return GF_OK;
345 : }
346 :
347 759129 : Bool compositor_2d_check_attached(GF_VisualManager *visual)
348 : {
349 759129 : if (!visual->is_attached) {
350 13170 : c2d_get_video_access_normal(visual);
351 : }
352 :
353 759129 : return visual->is_attached;
354 : }
355 :
356 2057 : void compositor_2d_clear_surface(GF_VisualManager *visual, GF_IRect *rc, u32 BackColor, u32 offscreen_clear)
357 : {
358 : //visual not attached on main (direct video) visual, use texture bliting
359 2057 : if (!visual->is_attached && visual->compositor->video_out->Blit && (visual->compositor->video_out->hw_caps & GF_VIDEO_HW_HAS_RGB)) {
360 : char data[12];
361 : GF_Err e;
362 : GF_VideoSurface video_src;
363 : GF_Window src_wnd, dst_wnd;
364 :
365 0 : if (!BackColor && !visual->offscreen) {
366 0 : if ( !(visual->compositor->init_flags & GF_TERM_WINDOW_TRANSPARENT)) {
367 0 : BackColor = visual->compositor->back_color;
368 : }
369 : }
370 :
371 0 : data[0] = data[3] = data[6] = data[9] = GF_COL_R(BackColor);
372 0 : data[1] = data[4] = data[7] = data[10] = GF_COL_G(BackColor);
373 0 : data[2] = data[5] = data[8] = data[11] = GF_COL_B(BackColor);
374 :
375 : memset(&video_src, 0, sizeof(GF_VideoSurface));
376 0 : video_src.height = 2;
377 0 : video_src.width = 2;
378 : video_src.pitch_x = 0;
379 0 : video_src.pitch_y = 6;
380 0 : video_src.pixel_format = GF_PIXEL_RGB;
381 0 : video_src.video_buffer = data;
382 :
383 0 : src_wnd.x = src_wnd.y = 0;
384 0 : src_wnd.w = src_wnd.h = 1;
385 0 : if (rc) {
386 0 : if (visual->center_coords) {
387 0 : dst_wnd.x = rc->x + visual->width/2;
388 0 : dst_wnd.y = visual->height/2 - rc->y;
389 : } else {
390 0 : dst_wnd.x = rc->x;
391 0 : dst_wnd.y = rc->y - visual->height/2;
392 : }
393 0 : dst_wnd.w = rc->width;
394 0 : dst_wnd.h = rc->height;
395 : } else {
396 0 : dst_wnd.x = dst_wnd.y = 0;
397 0 : dst_wnd.w = visual->width;
398 0 : dst_wnd.h = visual->height;
399 : }
400 :
401 0 : e = visual->compositor->video_out->Blit(visual->compositor->video_out, &video_src, &src_wnd, &dst_wnd, 0);
402 0 : if (e==GF_OK) return;
403 : }
404 :
405 2057 : visual_2d_clear_surface(visual, rc, BackColor, offscreen_clear);
406 : }
407 :
408 :
409 :
410 21062 : void compositor_2d_release_video_access(GF_VisualManager *visual)
411 : {
412 21062 : GF_Compositor *compositor = visual->compositor;
413 21062 : if (visual->is_attached) {
414 14232 : visual->is_attached = GF_FALSE;
415 : }
416 :
417 : #ifndef GPAC_DISABLE_3D
418 21062 : if (compositor->hybrid_opengl) {
419 1062 : compositor_2d_hybgl_flush_video(compositor, NULL);
420 1062 : return;
421 : }
422 : #endif //GPAC_DISABLE_3D
423 :
424 20000 : if (compositor->hw_context) {
425 0 : compositor->video_out->LockOSContext(compositor->video_out, GF_FALSE);
426 0 : compositor->hw_context = NULL;
427 20000 : } else if (compositor->hw_locked) {
428 13170 : compositor->video_out->LockBackBuffer(compositor->video_out, &compositor->hw_surface, GF_FALSE);
429 13170 : compositor->hw_locked = GF_FALSE;
430 : }
431 : }
432 :
433 23676 : static void store_blit_times(GF_TextureHandler *txh, u32 push_time)
434 : {
435 : #ifndef GPAC_DISABLE_LOG
436 : u32 ck;
437 : #endif
438 :
439 23676 : push_time = gf_sys_clock() - push_time;
440 23676 : txh->nb_frames ++;
441 23676 : txh->upload_time += push_time;
442 :
443 : #ifndef GPAC_DISABLE_LOG
444 23676 : gf_mo_get_object_time(txh->stream, &ck);
445 23676 : if (ck>txh->last_frame_time) {
446 2987 : GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor2D] Bliting frame (CTS %d) %d ms too late\n", txh->last_frame_time, ck - txh->last_frame_time ));
447 : }
448 :
449 23676 : GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[2D Blitter] At %u Blit texture (CTS %u) %d ms after due date - blit in %d ms - average push time %d ms\n", ck, txh->last_frame_time, ck - txh->last_frame_time, push_time, txh->upload_time / txh->nb_frames));
450 : #endif
451 23676 : }
452 :
453 23875 : Bool compositor_texture_rectangles(GF_VisualManager *visual, GF_TextureHandler *txh, GF_IRect *clip, GF_Rect *unclip, GF_Window *src, GF_Window *dst, Bool *disable_blit, Bool *has_scale)
454 : {
455 : Fixed w_scale, h_scale, tmp;
456 : u32 output_width, output_height;
457 23875 : GF_IRect clipped_final = *clip;
458 23875 : GF_Rect final = *unclip;
459 : Bool use_blit;
460 :
461 23875 : src->w = src->h = 0;
462 23875 : dst->w = dst->h = 0;
463 23875 : if (disable_blit) *disable_blit = GF_FALSE;
464 23875 : if (has_scale) *has_scale = GF_FALSE;
465 :
466 23875 : if (final.width<=0 || final.height <=0) return GF_FALSE;
467 23875 : if (txh->width==0 || txh->height==0) return GF_FALSE;
468 :
469 23875 : w_scale = final.width / txh->width;
470 23875 : h_scale = final.height / txh->height;
471 :
472 23875 : if ((w_scale != FIX_ONE) || (h_scale!=FIX_ONE)) {
473 23706 : if (has_scale) *has_scale = GF_TRUE;
474 : }
475 :
476 23875 : if (visual->offscreen) {
477 0 : output_width = visual->width;
478 0 : output_height = visual->height;
479 : } else {
480 : /*use entire video surface for un-centering coord system*/
481 23875 : output_width = visual->compositor->vp_width;
482 23875 : output_height = visual->compositor->vp_height;
483 : }
484 : /*take care of pixel rounding for odd width/height and make sure we strictly draw in the clipped bounds*/
485 23875 : if (visual->center_coords) {
486 23532 : clipped_final.x += output_width / 2;
487 23532 : final.x += INT2FIX( output_width / 2 );
488 :
489 23532 : clipped_final.y = output_height/ 2 - clipped_final.y;
490 23532 : final.y = INT2FIX( output_height / 2) - final.y;
491 :
492 : } else {
493 343 : final.y -= final.height;
494 343 : clipped_final.y -= clipped_final.height;
495 : }
496 :
497 : /*make sure we lie in the final rect (this is needed for directdraw mode)*/
498 23875 : if (clipped_final.x<0) {
499 0 : clipped_final.width += clipped_final.x;
500 : clipped_final.x = 0;
501 0 : if (clipped_final.width <= 0) return GF_FALSE;
502 : }
503 23875 : if (clipped_final.y<0) {
504 0 : clipped_final.height += clipped_final.y;
505 : clipped_final.y = 0;
506 0 : if (clipped_final.height <= 0) return GF_FALSE;
507 : }
508 23875 : if (clipped_final.x + clipped_final.width > (s32) output_width) {
509 0 : clipped_final.width = output_width - clipped_final.x;
510 : clipped_final.x = output_width - clipped_final.width;
511 : }
512 23875 : if (clipped_final.y + clipped_final.height > (s32) output_height) {
513 0 : clipped_final.height = output_height - clipped_final.y;
514 : clipped_final.y = output_height - clipped_final.height;
515 : }
516 : /*needed in direct drawing since clipping is not performed*/
517 23875 : if (clipped_final.width<=0 || clipped_final.height <=0)
518 : return GF_FALSE;
519 :
520 23875 : if (clipped_final.width-1>= FIX2INT(final.width) ) clipped_final.width = FIX2INT(final.width);
521 23875 : if (clipped_final.height-1>= FIX2INT(final.height) ) clipped_final.height = FIX2INT(final.height);
522 :
523 : /*set dest window*/
524 23875 : dst->x = (u32) clipped_final.x;
525 23875 : dst->y = (u32) clipped_final.y;
526 23875 : dst->w = (u32) clipped_final.width;
527 23875 : dst->h = (u32) clipped_final.height;
528 :
529 23875 : if (!dst->w || !dst->h) return GF_FALSE;
530 :
531 : #ifdef GPAC_FIXED_POINT
532 : #define ROUND_FIX(_v) \
533 : _v = FIX2INT(tmp);
534 : #define CEILING(_v) \
535 : _v = FIX2INT(tmp); \
536 : if (INT2FIX(_v)!=tmp) _v++;
537 : #else
538 : #define ROUND_FIX(_v) \
539 : _v = FIX2INT(tmp); \
540 : tmp -= INT2FIX(_v); \
541 : if (tmp>99*FIX_ONE/100) { _v++; tmp = 0; } \
542 : if (ABS(tmp) > FIX_EPSILON) use_blit = 0;
543 : #define CEILING(_v) \
544 : _v = FIX2INT(tmp); \
545 : tmp -= INT2FIX(_v); \
546 : if (tmp>0) { _v++; tmp = 0; } \
547 : if (ABS(tmp) > FIX_EPSILON) use_blit = 0;
548 : #endif
549 :
550 : use_blit = GF_TRUE;
551 :
552 23875 : if (txh->data && !txh->size && (txh->width==2) && (txh->height==2) ) {
553 16789 : src->x = src->y = 0;
554 16789 : src->w = 1;
555 16789 : src->h = 1;
556 : } else {
557 : /*compute SRC window*/
558 7086 : tmp = gf_divfix(INT2FIX(clipped_final.x) - final.x, w_scale);
559 7086 : if (tmp<0) tmp=0;
560 7086 : CEILING(src->x);
561 :
562 7086 : tmp = gf_divfix(INT2FIX(clipped_final.y) - final.y, h_scale);
563 7086 : if (tmp<0) tmp=0;
564 7086 : CEILING(src->y);
565 :
566 7086 : tmp = gf_divfix(INT2FIX(clip->width), w_scale);
567 7086 : ROUND_FIX(src->w);
568 :
569 7086 : tmp = gf_divfix(INT2FIX(clip->height), h_scale);
570 7086 : ROUND_FIX(src->h);
571 :
572 :
573 7086 : if (src->w>txh->width) src->w=txh->width;
574 7086 : if (src->h>txh->height) src->h=txh->height;
575 :
576 7086 : if (!src->w || !src->h) return GF_FALSE;
577 :
578 : /*make sure we lie in src bounds*/
579 7086 : if (src->x + src->w>txh->width) src->w = txh->width - src->x;
580 7086 : if (src->y + src->h>txh->height) src->h = txh->height - src->y;
581 : }
582 : #undef ROUND_FIX
583 :
584 23875 : if (disable_blit) *disable_blit = use_blit ? GF_FALSE : GF_TRUE;
585 : return GF_TRUE;
586 : }
587 :
588 23875 : static Bool compositor_2d_draw_bitmap_ex(GF_VisualManager *visual, GF_TextureHandler *txh, DrawableContext *ctx, GF_IRect *clip, GF_Rect *unclip, u8 alpha, GF_TraverseState *tr_state, Bool force_soft_blt)
589 : {
590 : GF_VideoSurface video_src;
591 : GF_Err e;
592 : Bool use_soft_stretch, use_blit, flush_video, is_attached, has_scale;
593 : u32 overlay_type;
594 : GF_Window src_wnd, dst_wnd;
595 : u32 output_width, output_height, hw_caps;
596 :
597 :
598 23875 : if (!txh->data) return GF_TRUE;
599 :
600 23875 : if (!visual->compositor->has_size_info && !(visual->compositor->msg_type & GF_SR_CFG_OVERRIDE_SIZE)
601 : && (visual->compositor->override_size_flags & 1)
602 356 : && !(visual->compositor->override_size_flags & 2)
603 : ) {
604 0 : if ( (visual->compositor->scene_width < txh->width)
605 0 : || (visual->compositor->scene_height < txh->height)) {
606 0 : visual->compositor->scene_width = txh->width;
607 0 : visual->compositor->scene_height = txh->height;
608 0 : visual->compositor->msg_type |= GF_SR_CFG_OVERRIDE_SIZE;
609 0 : return GF_TRUE;
610 : }
611 : }
612 :
613 23875 : if (!compositor_texture_rectangles(visual, txh, clip, unclip, &src_wnd, &dst_wnd, &use_blit, &has_scale)) return GF_TRUE;
614 :
615 : //blitter is disabled
616 23875 : if (txh->flags & GF_SR_TEXTURE_DISABLE_BLIT) return GF_FALSE;
617 :
618 : /*can we use hardware blitter ?*/
619 23792 : hw_caps = visual->compositor->video_out->hw_caps;
620 : overlay_type = 0;
621 : flush_video = GF_FALSE;
622 : use_soft_stretch = GF_TRUE;
623 :
624 23792 : output_width = visual->compositor->vp_width;
625 23792 : output_height = visual->compositor->vp_height;
626 :
627 23792 : if (visual->compositor->disable_hardware_blit) force_soft_blt = GF_TRUE;
628 :
629 23792 : if (!force_soft_blt) {
630 :
631 : /*avoid partial redraw that don't come close to src pixels with the bliter, this leads to ugly artefacts -
632 : fall back to rasterizer*/
633 : // if (!(ctx->flags & CTX_TEXTURE_DIRTY) && !use_blit && (src_wnd.x || src_wnd.y) )
634 : // return 0;
635 :
636 23792 : switch (txh->pixelformat) {
637 19732 : case GF_PIXEL_RGB:
638 : case GF_PIXEL_BGR:
639 : case GF_PIXEL_RGBS:
640 : case GF_PIXEL_RGBD:
641 : // case GF_PIXEL_RGB_555:
642 : // case GF_PIXEL_RGB_565:
643 19732 : if ((alpha==0xFF) && (hw_caps & GF_VIDEO_HW_HAS_RGB)) {
644 : use_soft_stretch = GF_FALSE;
645 : }
646 147 : else if ((alpha!=0xFF) && (hw_caps & GF_VIDEO_HW_HAS_RGBA)) {
647 : use_soft_stretch = GF_FALSE;
648 : }
649 : break;
650 626 : case GF_PIXEL_ARGB:
651 : case GF_PIXEL_RGBA:
652 : case GF_PIXEL_RGBAS:
653 : case GF_PIXEL_RGBDS:
654 626 : if (hw_caps & GF_VIDEO_HW_HAS_RGBA)
655 : use_soft_stretch = GF_FALSE;
656 : break;
657 3434 : default:
658 3434 : if (gf_pixel_fmt_is_yuv(txh->pixelformat)) {
659 3413 : if (hw_caps & GF_VIDEO_HW_HAS_YUV) use_soft_stretch = GF_FALSE;
660 3413 : else if (hw_caps & GF_VIDEO_HW_HAS_YUV_OVERLAY) overlay_type = 1;
661 : }
662 : break;
663 : }
664 : /*disable based on settings*/
665 23792 : if (!visual->compositor->yuvhw
666 23792 : || (ctx->col_mat || !visual->compositor->video_out->Blit)
667 : ) {
668 : use_soft_stretch = GF_TRUE;
669 : overlay_type = 0;
670 : }
671 23792 : if (!visual->compositor->blitp && ((src_wnd.w!=txh->width) || (src_wnd.h!=txh->height) )) {
672 : use_soft_stretch = GF_TRUE;
673 : }
674 :
675 : /*disable HW color keying - not compatible with MPEG-4 MaterialKey*/
676 23792 : if (tr_state->col_key) {
677 : use_soft_stretch = GF_TRUE;
678 : overlay_type = 0;
679 : }
680 :
681 23671 : if (overlay_type) {
682 : /*no more than one overlay is supported at the current time*/
683 0 : if (visual->overlays) {
684 0 : ctx->drawable->flags &= ~DRAWABLE_IS_OVERLAY;
685 : overlay_type = 0;
686 : }
687 : /*direct draw or not last context: we must queue the overlay*/
688 0 : else if (tr_state->immediate_draw || (ctx->next && ctx->next->drawable)) {
689 : overlay_type = 2;
690 : }
691 : /*OK we can overlay this video - if full display, don't flush*/
692 : if (overlay_type==1) {
693 0 : if (dst_wnd.w==visual->compositor->display_width) flush_video = GF_FALSE;
694 0 : else if (dst_wnd.h==visual->compositor->display_height) flush_video = GF_FALSE;
695 0 : else flush_video = visual->has_modif;
696 : }
697 : /*if no color keying, we cannot queue the overlay*/
698 0 : else if (!visual->compositor->video_out->overlay_color_key) {
699 : overlay_type = 0;
700 : }
701 : }
702 : }
703 :
704 23792 : if (has_scale && !(hw_caps & GF_VIDEO_HW_HAS_STRETCH) && !overlay_type) {
705 : use_soft_stretch = GF_TRUE;
706 : }
707 :
708 : memset(&video_src, 0, sizeof(GF_VideoSurface));
709 23792 : video_src.height = txh->height;
710 23792 : video_src.width = txh->width;
711 : video_src.pitch_x = 0;
712 23792 : if (!txh->stride)
713 0 : gf_pixel_get_size_info(txh->pixelformat, txh->width, txh->height, NULL, &txh->stride, NULL, NULL, NULL);
714 23792 : video_src.pitch_y = txh->stride;
715 23792 : video_src.pixel_format = txh->pixelformat;
716 : #ifdef GF_SR_USE_DEPTH
717 23792 : if (txh->pixelformat==GF_PIXEL_YUVD) video_src.pixel_format = GF_PIXEL_YUV;
718 : #endif
719 23792 : video_src.video_buffer = txh->data;
720 23792 : if (txh->frame_ifce) {
721 : u32 stride;
722 0 : if (!txh->frame_ifce->get_plane) return GF_FALSE;
723 :
724 0 : e = txh->frame_ifce->get_plane(txh->frame_ifce, 0, (const u8 **) &video_src.video_buffer, &video_src.pitch_y);
725 0 : if (e) return GF_FALSE;
726 0 : txh->frame_ifce->get_plane(txh->frame_ifce, 1, (const u8 **) video_src.u_ptr, &stride);
727 0 : txh->frame_ifce->get_plane(txh->frame_ifce, 2, (const u8 **) video_src.v_ptr, &stride);
728 : }
729 23792 : video_src.global_alpha = alpha;
730 :
731 : //overlay queing
732 23792 : if (overlay_type==2) {
733 : GF_IRect o_rc;
734 : GF_OverlayStack *ol, *first;
735 :
736 : /*queue overlay in order*/
737 0 : GF_SAFEALLOC(ol, GF_OverlayStack);
738 0 : if (!ol) {
739 : return GF_FALSE;
740 : }
741 0 : ol->ctx = ctx;
742 0 : ol->dst = dst_wnd;
743 0 : ol->src = src_wnd;
744 0 : first = visual->overlays;
745 0 : if (first) {
746 0 : while (first->next) first = first->next;
747 0 : first->next = ol;
748 : } else {
749 0 : visual->overlays = ol;
750 : }
751 :
752 0 : if (visual->center_coords) {
753 0 : o_rc.x = dst_wnd.x - output_width/2;
754 0 : o_rc.y = output_height/2- dst_wnd.y;
755 : } else {
756 0 : o_rc.x = dst_wnd.x;
757 0 : o_rc.y = dst_wnd.y + dst_wnd.h;
758 : }
759 :
760 0 : o_rc.width = dst_wnd.w;
761 0 : o_rc.height = dst_wnd.h;
762 0 : visual->ClearSurface(visual, &o_rc, visual->compositor->video_out->overlay_color_key, GF_FALSE);
763 0 : visual->has_overlays = GF_TRUE;
764 : /*mark drawable as overlay*/
765 0 : ctx->drawable->flags |= DRAWABLE_IS_OVERLAY;
766 :
767 : /*prevents this context from being removed in direct draw mode by requesting a new one
768 : but not allocating it*/
769 0 : if (tr_state->immediate_draw)
770 0 : visual_2d_get_drawable_context(visual);
771 :
772 : return GF_TRUE;
773 : }
774 :
775 : //will pause clock if first HW load
776 23792 : gf_sc_texture_check_pause_on_first_load(txh, GF_TRUE);
777 :
778 23792 : if (overlay_type) {
779 : u32 push_time;
780 :
781 : /*top level overlay*/
782 0 : if (flush_video) {
783 : GF_Window rc;
784 0 : rc.x = rc.y = 0;
785 0 : rc.w = visual->compositor->display_width;
786 0 : rc.h = visual->compositor->display_height;
787 :
788 0 : visual_2d_release_raster(visual);
789 0 : visual->compositor->video_out->Flush(visual->compositor->video_out, &rc);
790 0 : visual_2d_init_raster(visual);
791 : }
792 0 : visual->compositor->skip_flush = 1;
793 :
794 0 : push_time = gf_sys_clock();
795 0 : e = visual->compositor->video_out->Blit(visual->compositor->video_out, &video_src, &src_wnd, &dst_wnd, 1);
796 :
797 0 : if (!e) {
798 0 : store_blit_times(txh, push_time);
799 : /*mark drawable as overlay*/
800 0 : ctx->drawable->flags |= DRAWABLE_IS_OVERLAY;
801 0 : visual->has_overlays = GF_TRUE;
802 :
803 : //will resume clock if first HW load
804 0 : gf_sc_texture_check_pause_on_first_load(txh, GF_FALSE);
805 0 : return GF_TRUE;
806 : }
807 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor2D] Error during overlay blit - trying with soft one\n"));
808 0 : visual->compositor->skip_flush = GF_FALSE;
809 : }
810 :
811 : /*most graphic cards can't perform bliting on locked surface - force unlock by releasing the hardware*/
812 23792 : is_attached = visual->is_attached;
813 23792 : if (is_attached) visual_2d_release_raster(visual);
814 :
815 23792 : if (!use_soft_stretch) {
816 0 : u32 push_time = gf_sys_clock();
817 0 : e = visual->compositor->video_out->Blit(visual->compositor->video_out, &video_src, &src_wnd, &dst_wnd, 0);
818 :
819 : /*HW pb, try soft*/
820 0 : if (e) {
821 : use_soft_stretch = GF_TRUE;
822 0 : if (visual->compositor->video_memory==1) {
823 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Compositor2D] Error during hardware blit - will use soft one\n"));
824 0 : visual->compositor->video_memory = 2;
825 : }
826 : /*force a reconfigure of video output*/
827 0 : else if (visual->compositor->video_memory!=2) {
828 0 : GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor2D] Reconfiguring video output to use video memory\n"));
829 0 : visual->compositor->request_video_memory = GF_TRUE;
830 0 : visual->compositor->root_visual_setup = GF_FALSE;
831 0 : gf_sc_next_frame_state(visual->compositor, GF_SC_DRAW_FRAME);
832 : }
833 : } else {
834 0 : store_blit_times(txh, push_time);
835 : }
836 : }
837 :
838 : //will resume clock if first HW load
839 23792 : gf_sc_texture_check_pause_on_first_load(txh, GF_FALSE);
840 :
841 23792 : if (use_soft_stretch) {
842 : GF_VideoSurface backbuffer;
843 23792 : if (!visual->compositor->softblt) {
844 116 : if (is_attached) visual_2d_init_raster(visual);
845 116 : txh->flags |= GF_SR_TEXTURE_DISABLE_BLIT;
846 345 : return GF_FALSE;
847 : }
848 :
849 23676 : e = visual->compositor->video_out->LockBackBuffer(visual->compositor->video_out, &backbuffer, GF_TRUE);
850 23676 : if (!e) {
851 23676 : u32 push_time = gf_sys_clock();
852 23676 : e = gf_stretch_bits(&backbuffer, &video_src, &dst_wnd, &src_wnd, alpha, GF_FALSE, tr_state->col_key, ctx->col_mat);
853 23676 : store_blit_times(txh, push_time);
854 23676 : visual->compositor->video_out->LockBackBuffer(visual->compositor->video_out, &backbuffer, GF_FALSE);
855 23676 : if (e) {
856 113 : if (e != GF_NOT_SUPPORTED) {
857 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Compositor2D] Cannot soft blit surface (error %s) - will try using software rasterizer\n", gf_error_to_string(e) ));
858 0 : visual->compositor->last_error = e;
859 : }
860 113 : if (is_attached) visual_2d_init_raster(visual);
861 113 : txh->flags |= GF_SR_TEXTURE_DISABLE_BLIT;
862 113 : return GF_FALSE;
863 : }
864 : } else {
865 0 : if (e != GF_NOT_SUPPORTED) {
866 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor2D] Cannot lock back buffer - Error %s\n", gf_error_to_string(e) ));
867 0 : visual->compositor->last_error = e;
868 : }
869 0 : if (is_attached) visual_2d_init_raster(visual);
870 : return GF_FALSE;
871 : }
872 23563 : if (!visual->compositor->video_memory) {
873 0 : GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor2D] Reconfiguring video output to use video memory\n"));
874 0 : visual->compositor->video_memory = 1;
875 0 : visual->compositor->root_visual_setup = GF_FALSE;
876 0 : gf_sc_next_frame_state(visual->compositor, GF_SC_DRAW_FRAME);
877 : }
878 : }
879 23563 : visual->has_modif = GF_TRUE;
880 23563 : if (is_attached) visual_2d_init_raster(visual);
881 : return GF_TRUE;
882 : }
883 :
884 :
885 :
886 21925 : Bool compositor_2d_draw_bitmap(GF_VisualManager *visual, GF_TraverseState *tr_state, DrawableContext *ctx)
887 : {
888 : u8 alpha = 0xFF;
889 :
890 21925 : if (!ctx->aspect.fill_texture) return GF_TRUE;
891 21925 : if ((ctx->aspect.fill_texture == visual->compositor->passthrough_txh) && visual->compositor->passthrough_inplace)
892 : return GF_TRUE;
893 : /*check if texture is ready - if not pretend we drew it*/
894 21896 : if (!ctx->aspect.fill_texture->data) return GF_TRUE;
895 21870 : if (ctx->transform.m[0]<0) return GF_FALSE;
896 : /*check if the <0 value is due to a flip in he scene description or
897 : due to bifs<->svg... context switching*/
898 21870 : if (ctx->transform.m[4]<0) {
899 11 : if (!(ctx->flags & CTX_FLIPED_COORDS)) return GF_FALSE;
900 : } else {
901 21859 : if (ctx->flags & CTX_FLIPED_COORDS) return GF_FALSE;
902 : }
903 21859 : if (ctx->transform.m[1] || ctx->transform.m[3]) return GF_FALSE;
904 : #ifndef GPAC_DISABLE_VRML
905 21599 : if ((ctx->flags & CTX_HAS_APPEARANCE) && ctx->appear && ((M_Appearance*)ctx->appear)->textureTransform)
906 : return GF_FALSE;
907 : #endif
908 :
909 20108 : alpha = GF_COL_A(ctx->aspect.fill_color);
910 : /*THIS IS A HACK, will not work when setting filled=0, transparency and XLineProps*/
911 20108 : if (!alpha) alpha = GF_COL_A(ctx->aspect.line_color);
912 :
913 20108 : if (!alpha) return GF_TRUE;
914 :
915 20108 : switch (ctx->aspect.fill_texture->pixelformat) {
916 0 : case GF_PIXEL_YUVD:
917 : case GF_PIXEL_RGBD:
918 : case GF_PIXEL_RGBDS:
919 : #ifndef GPAC_DISABLE_3D
920 : /*using OpenGL to render depth images*/
921 0 : if (visual->compositor->depth_gl_type) {
922 0 : gf_sc_set_option(visual->compositor, GF_OPT_USE_OPENGL, 2);
923 0 : return GF_TRUE;
924 : }
925 : #endif
926 : //fallthrought
927 : default:
928 : break;
929 : }
930 :
931 : /*direct drawing, no clippers */
932 20108 : if (tr_state->immediate_draw) {
933 254 : if (visual->compositor->video_out->BlitTexture) {
934 0 : if (! visual->compositor->video_out->BlitTexture(visual->compositor->video_out, ctx->aspect.fill_texture, &ctx->transform, &ctx->bi->clip, alpha, tr_state->col_key
935 : #ifdef GF_SR_USE_DEPTH
936 : , ctx->depth_offset, ctx->depth_gain
937 : #else
938 : , 0, 0
939 : #endif
940 : ))
941 : return GF_FALSE;
942 : } else {
943 254 : if (!compositor_2d_draw_bitmap_ex(visual, ctx->aspect.fill_texture, ctx, &ctx->bi->clip, &ctx->bi->unclip, alpha, tr_state, GF_FALSE))
944 : return GF_FALSE;
945 : }
946 : }
947 : /*draw bitmap for all dirty rects*/
948 : else {
949 : u32 i;
950 : GF_IRect clip;
951 26780 : for (i=0; i<tr_state->visual->to_redraw.count; i++) {
952 : /*there's an opaque region above, don't draw*/
953 : #ifdef TRACK_OPAQUE_REGIONS
954 : if (tr_state->visual->draw_node_index < tr_state->visual->to_redraw.list[i].opaque_node_index) continue;
955 : #endif
956 27014 : clip = ctx->bi->clip;
957 27014 : gf_irect_intersect(&clip, &tr_state->visual->to_redraw.list[i].rect);
958 27014 : if (clip.width && clip.height) {
959 23621 : if (visual->compositor->video_out->BlitTexture) {
960 0 : if (!visual->compositor->video_out->BlitTexture(visual->compositor->video_out, ctx->aspect.fill_texture, &ctx->transform, &ctx->bi->clip, alpha, tr_state->col_key
961 : #ifdef GF_SR_USE_DEPTH
962 : , ctx->depth_offset, ctx->depth_gain
963 : #else
964 : , 0, 0
965 : #endif
966 : ))
967 234 : return GF_FALSE;
968 23621 : } else if (!compositor_2d_draw_bitmap_ex(visual, ctx->aspect.fill_texture, ctx, &clip, &ctx->bi->unclip, alpha, tr_state, GF_FALSE)) {
969 : return GF_FALSE;
970 : }
971 : }
972 : }
973 : }
974 19796 : ctx->aspect.fill_texture->flags |= GF_SR_TEXTURE_USED;
975 19796 : return GF_TRUE;
976 : }
977 :
978 :
979 584 : GF_Err compositor_2d_set_aspect_ratio(GF_Compositor *compositor)
980 : {
981 : u32 old_vp_width, old_vp_height;
982 : Bool changed = GF_FALSE;
983 : Double ratio;
984 : GF_Event evt;
985 : GF_Err e;
986 : Fixed scaleX, scaleY;
987 :
988 584 : compositor->output_width = compositor->scene_width;
989 584 : compositor->output_height = compositor->scene_height;
990 584 : compositor->vp_x = compositor->vp_y = 0;
991 : scaleX = scaleY = FIX_ONE;
992 :
993 584 : old_vp_width = compositor->vp_width;
994 584 : old_vp_height = compositor->vp_height;
995 :
996 : /*force complete clean*/
997 584 : compositor->traverse_state->invalidate_all = GF_TRUE;
998 :
999 584 : if (!compositor->has_size_info && !(compositor->override_size_flags & 2) ) {
1000 28 : compositor->output_width = compositor->display_width;
1001 28 : compositor->output_height = compositor->display_height;
1002 28 : compositor->vp_width = compositor->visual->width = compositor->output_width;
1003 28 : compositor->vp_height = compositor->visual->height = compositor->output_height;
1004 : } else {
1005 556 : if (compositor->rotate_mode % 2) {
1006 0 : compositor->vp_height = compositor->display_width;
1007 0 : compositor->vp_width = compositor->display_height;
1008 : } else {
1009 556 : compositor->vp_width = compositor->display_width;
1010 556 : compositor->vp_height = compositor->display_height;
1011 : }
1012 :
1013 556 : switch (compositor->aspect_ratio) {
1014 : case GF_ASPECT_RATIO_FILL_SCREEN:
1015 : break;
1016 0 : case GF_ASPECT_RATIO_16_9:
1017 0 : compositor->vp_width = compositor->display_width;
1018 0 : compositor->vp_height = 9 * compositor->display_width / 16;
1019 0 : if (compositor->vp_height>compositor->display_height) {
1020 0 : compositor->vp_height = compositor->display_height;
1021 0 : compositor->vp_width = 16 * compositor->display_height / 9;
1022 : }
1023 : break;
1024 0 : case GF_ASPECT_RATIO_4_3:
1025 0 : compositor->vp_width = compositor->display_width;
1026 0 : compositor->vp_height = 3 * compositor->display_width / 4;
1027 0 : if (compositor->vp_height>compositor->display_height) {
1028 0 : compositor->vp_height = compositor->display_height;
1029 0 : compositor->vp_width = 4 * compositor->display_height / 3;
1030 : }
1031 : break;
1032 556 : default:
1033 556 : ratio = compositor->scene_height;
1034 556 : ratio /= compositor->scene_width;
1035 556 : if (compositor->vp_width * ratio > compositor->vp_height) {
1036 21 : compositor->vp_width = compositor->vp_height * compositor->scene_width;
1037 21 : compositor->vp_width /= compositor->scene_height;
1038 : }
1039 : else {
1040 535 : compositor->vp_height = compositor->vp_width * compositor->scene_height;
1041 535 : compositor->vp_height /= compositor->scene_width;
1042 : }
1043 : break;
1044 : }
1045 556 : compositor->vp_x = (compositor->display_width - compositor->vp_width) / 2;
1046 556 : compositor->vp_y = (compositor->display_height - compositor->vp_height) / 2;
1047 :
1048 556 : scaleX = gf_divfix(INT2FIX(compositor->vp_width), INT2FIX(compositor->scene_width));
1049 556 : if (!scaleX) scaleX = FIX_ONE;
1050 556 : scaleY = gf_divfix(INT2FIX(compositor->vp_height), INT2FIX(compositor->scene_height));
1051 556 : if (!scaleY) scaleY = FIX_ONE;
1052 :
1053 556 : if (!compositor->sz) {
1054 0 : compositor->output_width = compositor->scene_width;
1055 0 : compositor->output_height = compositor->scene_height;
1056 0 : compositor->vp_width = FIX2INT(gf_divfix(INT2FIX(compositor->display_width), scaleX));
1057 0 : compositor->vp_height = FIX2INT(gf_divfix(INT2FIX(compositor->display_height), scaleY));
1058 :
1059 0 : compositor->vp_x = (compositor->vp_width - compositor->output_width) / 2;
1060 0 : compositor->vp_y = (compositor->vp_height - compositor->output_height) / 2;
1061 :
1062 : scaleX = scaleY = FIX_ONE;
1063 : } else {
1064 556 : compositor->output_width = compositor->display_width;
1065 556 : compositor->output_height = compositor->display_height;
1066 556 : compositor->vp_width = compositor->display_width;
1067 556 : compositor->vp_height = compositor->display_height;
1068 : }
1069 556 : compositor->visual->width = compositor->output_width;
1070 556 : compositor->visual->height = compositor->output_height;
1071 : }
1072 :
1073 : /*resize hardware surface*/
1074 : memset(&evt, 0, sizeof(GF_Event));
1075 584 : evt.type = GF_EVENT_VIDEO_SETUP;
1076 584 : evt.setup.width = compositor->vp_width;
1077 584 : evt.setup.height = compositor->vp_height;
1078 : evt.setup.use_opengl = GF_FALSE;
1079 584 : evt.setup.disable_vsync = compositor->bench_mode ? GF_TRUE : GF_FALSE;
1080 : /*copy over settings*/
1081 584 : evt.setup.system_memory = compositor->video_memory ? GF_FALSE : GF_TRUE;
1082 584 : if (compositor->request_video_memory) evt.setup.system_memory = GF_FALSE;
1083 584 : compositor->request_video_memory = GF_FALSE;
1084 :
1085 : #ifndef GPAC_DISABLE_3D
1086 584 : if (compositor->hybrid_opengl) {
1087 21 : evt.setup.use_opengl = GF_TRUE;
1088 21 : evt.setup.system_memory = GF_FALSE;
1089 21 : evt.setup.back_buffer = GF_TRUE;
1090 : }
1091 : #endif
1092 :
1093 584 : if (compositor->was_system_memory != evt.setup.system_memory) changed = GF_TRUE;
1094 90 : else if (old_vp_width != compositor->vp_width) changed = GF_TRUE;
1095 88 : else if (old_vp_height != compositor->vp_height) changed = GF_TRUE;
1096 87 : else if (compositor->is_opengl != evt.setup.use_opengl) changed = GF_TRUE;
1097 :
1098 :
1099 : if (changed) {
1100 497 : GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor2D] Reconfiguring display size %d x %d - opengl %s - use %s memory\n", evt.setup.width, evt.setup.height,
1101 : evt.setup.use_opengl ? "yes" : "no", evt.setup.system_memory ? "systems" : "video"
1102 : ));
1103 :
1104 497 : e = compositor->video_out->ProcessEvent(compositor->video_out, &evt);
1105 497 : if (e) {
1106 : #ifndef GPAC_DISABLE_3D
1107 0 : if (!compositor->hybrid_opengl) {
1108 0 : compositor->hybrid_opengl = GF_TRUE;
1109 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor2D] Failed to configure 2D output (%s) - retrying in OpenGL mode\n", gf_error_to_string(e) ));
1110 0 : return compositor_2d_set_aspect_ratio(compositor);
1111 : }
1112 : #endif
1113 0 : compositor->video_setup_failed = GF_TRUE;
1114 : memset(&evt, 0, sizeof(GF_Event));
1115 0 : evt.type = GF_EVENT_QUIT;
1116 0 : evt.message.error = e;
1117 0 : evt.message.message = "Cannot setup video output";
1118 0 : gf_sc_send_event(compositor, &evt);
1119 0 : return e;
1120 : }
1121 :
1122 497 : compositor->is_opengl = evt.setup.use_opengl;
1123 497 : compositor->was_system_memory = evt.setup.system_memory;
1124 :
1125 497 : if (evt.setup.use_opengl) {
1126 21 : gf_opengl_init();
1127 : }
1128 : }
1129 584 : if (compositor->has_size_info) {
1130 556 : compositor->traverse_state->vp_size.x = INT2FIX(compositor->scene_width);
1131 556 : compositor->traverse_state->vp_size.y = INT2FIX(compositor->scene_height);
1132 : } else {
1133 28 : compositor->traverse_state->vp_size.x = INT2FIX(compositor->output_width);
1134 28 : compositor->traverse_state->vp_size.y = INT2FIX(compositor->output_height);
1135 : }
1136 584 : GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor2D] Reconfigured display size %d x %d done\n", evt.setup.width, evt.setup.height));
1137 :
1138 : /*set scale factor*/
1139 584 : compositor_set_ar_scale(compositor, scaleX, scaleY);
1140 584 : return GF_OK;
1141 : }
1142 :
1143 682 : void compositor_send_resize_event(GF_Compositor *compositor, GF_SceneGraph *subscene, Fixed old_z, Fixed old_tx, Fixed old_ty, Bool is_resize)
1144 : {
1145 : #ifndef GPAC_DISABLE_SVG
1146 : GF_DOM_Event evt;
1147 : u32 i;
1148 682 : GF_SceneGraph *scene = (subscene ? subscene : compositor->scene);
1149 682 : GF_Node *root = gf_sg_get_root_node(scene);
1150 : /*if root node is not DOM, sent a resize event (for VRML/BIFS). Otherwise this must be handled
1151 : by the composition code of the node*/
1152 682 : if (!root || (gf_node_get_tag(root) > GF_NODE_RANGE_LAST_VRML) )
1153 28 : return;
1154 :
1155 : memset(&evt, 0, sizeof(GF_DOM_Event));
1156 654 : evt.prev_scale = compositor->scale_x*old_z;
1157 654 : evt.new_scale = compositor->scale_x*compositor->zoom;
1158 654 : evt.bubbles = 1;
1159 :
1160 654 : if (is_resize) {
1161 641 : evt.type = GF_EVENT_RESIZE;
1162 641 : if (subscene == NULL) {
1163 641 : evt.screen_rect.width = INT2FIX(compositor->display_width);
1164 641 : evt.screen_rect.height = INT2FIX(compositor->display_height);
1165 : } else {
1166 : u32 w, h;
1167 0 : gf_sg_get_scene_size_info(scene, &w, &h);
1168 0 : evt.screen_rect.width = INT2FIX(w);
1169 0 : evt.screen_rect.height = INT2FIX(h);
1170 : }
1171 13 : } else if (evt.prev_scale == evt.new_scale) {
1172 : /*cannot get params for scroll events*/
1173 13 : evt.type = GF_EVENT_SCROLL;
1174 : } else {
1175 0 : evt.screen_rect.x = INT2FIX(compositor->vp_x);
1176 0 : evt.screen_rect.y = INT2FIX(compositor->vp_y);
1177 0 : evt.screen_rect.width = INT2FIX(compositor->output_width);
1178 0 : evt.screen_rect.height = INT2FIX(compositor->output_height);
1179 0 : evt.prev_translate.x = old_tx;
1180 0 : evt.prev_translate.y = old_ty;
1181 0 : evt.new_translate.x = compositor->trans_x;
1182 0 : evt.new_translate.y = compositor->trans_y;
1183 0 : evt.type = GF_EVENT_ZOOM;
1184 0 : evt.bubbles = 0;
1185 : }
1186 654 : gf_dom_event_fire(gf_sg_get_root_node(scene), &evt);
1187 :
1188 654 : i=0;
1189 1308 : while ((scene = (GF_SceneGraph*)gf_list_enum(compositor->extra_scenes, &i))) {
1190 0 : gf_dom_event_fire(gf_sg_get_root_node(scene), &evt);
1191 : }
1192 :
1193 : #endif
1194 : }
1195 3064 : void compositor_2d_set_user_transform(GF_Compositor *compositor, Fixed zoom, Fixed tx, Fixed ty, Bool is_resize)
1196 : {
1197 : Fixed ratio;
1198 : Fixed old_tx, old_ty, old_z;
1199 :
1200 3064 : gf_sc_lock(compositor, GF_TRUE);
1201 : old_tx = tx;
1202 : old_ty = ty;
1203 3064 : old_z = compositor->zoom;
1204 :
1205 3064 : if (zoom <= 0) zoom = FIX_ONE/1000;
1206 3064 : compositor->trans_x = tx;
1207 3064 : compositor->trans_y = ty;
1208 :
1209 3064 : if (zoom != compositor->zoom) {
1210 0 : ratio = gf_divfix(zoom, compositor->zoom);
1211 0 : compositor->trans_x = gf_mulfix(compositor->trans_x, ratio);
1212 0 : compositor->trans_y = gf_mulfix(compositor->trans_y, ratio);
1213 0 : compositor->zoom = zoom;
1214 0 : compositor->zoom_changed = GF_TRUE;
1215 :
1216 : /*recenter visual*/
1217 0 : if (!compositor->visual->center_coords) {
1218 : Fixed c_x, c_y, nc_x, nc_y;
1219 0 : c_x = INT2FIX(compositor->display_width/2);
1220 0 : c_y = INT2FIX(compositor->display_height/2);
1221 0 : nc_x = gf_mulfix(c_x, ratio);
1222 0 : nc_y = gf_mulfix(c_y, ratio);
1223 0 : compositor->trans_x -= (nc_x-c_x);
1224 0 : compositor->trans_y -= (nc_y-c_y);
1225 : }
1226 : }
1227 6128 : gf_mx2d_init(compositor->traverse_state->transform);
1228 :
1229 3064 : switch (compositor->rotate_mode) {
1230 0 : case 1:
1231 0 : gf_mx2d_add_rotation(&compositor->traverse_state->transform, 0, 0, -GF_PI/2);
1232 0 : break;
1233 0 : case 2:
1234 0 : gf_mx2d_add_scale(&compositor->traverse_state->transform, -1, -1);
1235 0 : break;
1236 0 : case 3:
1237 0 : gf_mx2d_add_rotation(&compositor->traverse_state->transform, 0, 0, GF_PI/2);
1238 0 : break;
1239 : }
1240 :
1241 3064 : gf_mx2d_add_scale(&compositor->traverse_state->transform, gf_mulfix(compositor->zoom,compositor->scale_x), gf_mulfix(compositor->zoom,compositor->scale_y));
1242 :
1243 3064 : gf_mx2d_add_translation(&compositor->traverse_state->transform, compositor->trans_x, compositor->trans_y);
1244 3064 : if (compositor->rotation) gf_mx2d_add_rotation(&compositor->traverse_state->transform, 0, 0, compositor->rotation);
1245 :
1246 3064 : if (!compositor->visual->center_coords) {
1247 84 : gf_mx2d_add_translation(&compositor->traverse_state->transform, INT2FIX(compositor->vp_x), INT2FIX(compositor->vp_y));
1248 : }
1249 :
1250 3064 : GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor2D] Changing Zoom (%g) and Pan (%g %g)\n", FIX2FLT(compositor->zoom), FIX2FLT(compositor->trans_x) , FIX2FLT(compositor->trans_y)));
1251 :
1252 :
1253 3064 : gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME);
1254 3064 : compositor->traverse_state->invalidate_all = GF_TRUE;
1255 :
1256 : /*for zoom&pan, send the event right away. For resize/scroll, wait for the frame to be drawn before sending it
1257 : otherwise viewport info of SVG nodes won't be correct*/
1258 3064 : if (!is_resize) compositor_send_resize_event(compositor, NULL, old_z, old_tx, old_ty, GF_FALSE);
1259 3064 : gf_sc_lock(compositor, GF_FALSE);
1260 3064 : }
1261 :
1262 :
1263 :
1264 1962 : GF_Rect compositor_2d_update_clipper(GF_TraverseState *tr_state, GF_Rect this_clip, Bool *need_restore, GF_Rect *original, Bool for_layer)
1265 : {
1266 : GF_Rect clip, orig;
1267 1962 : if (for_layer) {
1268 805 : orig = tr_state->layer_clipper;
1269 805 : *need_restore = tr_state->has_layer_clip;
1270 : } else {
1271 1157 : orig = tr_state->clipper;
1272 1157 : *need_restore = tr_state->has_clip;
1273 : }
1274 1962 : *original = orig;
1275 :
1276 1962 : clip = this_clip;
1277 1962 : if (*need_restore) {
1278 : #ifndef GPAC_DISABLE_3D
1279 159 : if (tr_state->visual->type_3d) {
1280 : GF_Matrix mx;
1281 157 : gf_mx_copy(mx, tr_state->model_matrix);
1282 157 : gf_mx_inverse(&mx);
1283 157 : gf_mx_apply_rect(&mx, &orig);
1284 157 : gf_mx_apply_rect(&tr_state->layer_matrix, &orig);
1285 : } else
1286 : #endif
1287 : {
1288 : GF_Matrix2D mx2d;
1289 2 : gf_mx2d_copy(mx2d, tr_state->transform);
1290 2 : gf_mx2d_inverse(&mx2d);
1291 2 : gf_mx2d_apply_rect(&mx2d, &orig);
1292 : }
1293 :
1294 159 : if (clip.x < orig.x) {
1295 151 : clip.width -= (orig.x - clip.x);
1296 : clip.x = orig.x;
1297 : }
1298 159 : if (clip.x + clip.width > orig.x + orig.width) {
1299 0 : clip.width = orig.x + orig.width - clip.x;
1300 : }
1301 159 : if (clip.y > orig.y) {
1302 0 : clip.height -= (clip.y - orig.y);
1303 : clip.y = orig.y;
1304 : }
1305 159 : if (clip.y - clip.height < orig.y - orig.height) {
1306 0 : clip.height = clip.y - orig.y + orig.height;
1307 : }
1308 : }
1309 1962 : if (for_layer) {
1310 805 : tr_state->layer_clipper = clip;
1311 805 : tr_state->has_layer_clip = GF_TRUE;
1312 : #ifndef GPAC_DISABLE_3D
1313 805 : if (tr_state->visual->type_3d) {
1314 805 : gf_mx_copy(tr_state->layer_matrix, tr_state->model_matrix);
1315 : }
1316 : #endif
1317 : } else {
1318 1157 : tr_state->clipper = clip;
1319 : #ifndef GPAC_DISABLE_3D
1320 1157 : if (tr_state->visual->type_3d) {
1321 : /*retranslate to world coords*/
1322 300 : gf_mx_apply_rect(&tr_state->model_matrix, &tr_state->clipper);
1323 : /*if 2D, also update with user zoom and translation*/
1324 300 : if (!tr_state->camera->is_3D)
1325 300 : gf_mx_apply_rect(&tr_state->camera->modelview, &tr_state->clipper);
1326 : } else
1327 : #endif
1328 :
1329 857 : gf_mx2d_apply_rect(&tr_state->transform, &tr_state->clipper);
1330 :
1331 1157 : tr_state->has_clip = GF_TRUE;
1332 : }
1333 1962 : return clip;
1334 : }
1335 :
1336 :
1337 : /*overlay management*/
1338 117729 : Bool visual_2d_overlaps_overlay(GF_VisualManager *visual, DrawableContext *ctx, GF_TraverseState *tr_state)
1339 : {
1340 : u32 res = 0;
1341 : GF_OverlayStack *ol;
1342 117729 : GF_Compositor *compositor = visual->compositor;
1343 117729 : if (compositor->visual != visual) return GF_FALSE;
1344 :
1345 116157 : ol = visual->overlays;
1346 232314 : while (ol) {
1347 : u32 i;
1348 : GF_IRect clip;
1349 0 : if (ctx == ol->ctx) {
1350 0 : ol = ol->next;
1351 0 : continue;
1352 : }
1353 0 : clip = ctx->bi->clip;
1354 0 : if (!ol->ra.count && !gf_irect_overlaps(&ol->ctx->bi->clip, &clip)) {
1355 0 : ol = ol->next;
1356 0 : continue;
1357 : }
1358 : /*check previsously drawn areas*/
1359 0 : for (i=0; i<ol->ra.count; i++) {
1360 : /*we have drawn something here, don't draw*/
1361 0 : if (gf_irect_inside(&ol->ra.list[i].rect, &clip))
1362 : break;
1363 : }
1364 0 : res++;
1365 0 : if (i<ol->ra.count) {
1366 0 : ol = ol->next;
1367 0 : continue;
1368 : }
1369 :
1370 : /*note that we add the entire cliper, not the intersection with the overlay one. This is a simple way
1371 : to handle the case where Drawble2 overlaps Drawable1 overlaps Overlay but Drawable2 doesn't overlaps Overlay
1372 : by adding the entire drawable cliper, we will postpone drawing of all interconnected regions touching the overlay*/
1373 0 : ra_union_rect(&ol->ra, &clip);
1374 0 : ol = ol->next;
1375 : }
1376 116157 : return res ? GF_TRUE : GF_FALSE;
1377 : }
1378 :
1379 15811 : void visual_2d_flush_overlay_areas(GF_VisualManager *visual, GF_TraverseState *tr_state)
1380 : {
1381 : DrawableContext *ctx;
1382 : GF_OverlayStack *ol;
1383 15811 : GF_Compositor *compositor = visual->compositor;
1384 15811 : if (compositor->visual != visual) return;
1385 :
1386 : /*draw all overlays*/
1387 14819 : tr_state->traversing_mode = TRAVERSE_DRAW_2D;
1388 14819 : ol = visual->overlays;
1389 29638 : while (ol) {
1390 : u32 i;
1391 : Bool needs_draw = GF_TRUE;
1392 : GF_IRect the_clip, vid_clip;
1393 :
1394 0 : ra_refresh(&ol->ra);
1395 :
1396 0 : for (i=0; i<ol->ra.count; i++) {
1397 0 : the_clip = ol->ra.list[i].rect;
1398 :
1399 : /*draw all objects above this overlay*/
1400 0 : ctx = ol->ctx->next;
1401 0 : while (ctx && ctx->drawable) {
1402 0 : if (gf_irect_overlaps(&ctx->bi->clip, &the_clip)) {
1403 0 : GF_IRect prev_clip = ctx->bi->clip;
1404 :
1405 0 : if (needs_draw) {
1406 : /*if first object above is not transparent and completely covers the overlay skip video redraw*/
1407 0 : if ((ctx->flags & CTX_IS_TRANSPARENT) || !gf_irect_inside(&prev_clip, &the_clip)) {
1408 0 : vid_clip = ol->ra.list[i].rect;
1409 0 : gf_irect_intersect(&vid_clip, &ol->ctx->bi->clip);
1410 0 : compositor_2d_draw_bitmap_ex(visual, ol->ctx->aspect.fill_texture, ol->ctx, &vid_clip, &ol->ctx->bi->unclip, 0xFF, tr_state, GF_TRUE);
1411 : }
1412 : needs_draw = GF_FALSE;
1413 : }
1414 0 : gf_irect_intersect(&ctx->bi->clip, &the_clip);
1415 0 : tr_state->ctx = ctx;
1416 :
1417 0 : if (ctx->drawable->flags & DRAWABLE_USE_TRAVERSE_DRAW) {
1418 0 : gf_node_traverse(ctx->drawable->node, tr_state);
1419 : } else {
1420 0 : drawable_draw(ctx->drawable, tr_state);
1421 : }
1422 0 : ctx->bi->clip = prev_clip;
1423 : }
1424 0 : ctx = ctx->next;
1425 : }
1426 : }
1427 0 : ol = ol->next;
1428 : }
1429 : }
1430 :
1431 26387 : void visual_2d_draw_overlays(GF_VisualManager *visual)
1432 : {
1433 : GF_Err e;
1434 : GF_TextureHandler *txh;
1435 : GF_VideoSurface video_src;
1436 :
1437 0 : while (1) {
1438 26387 : GF_OverlayStack *ol = visual->overlays;
1439 52774 : if (!ol) return;
1440 0 : visual->overlays = ol->next;
1441 :
1442 0 : txh = ol->ctx->aspect.fill_texture;
1443 : memset(&video_src, 0, sizeof(GF_VideoSurface));
1444 0 : video_src.height = txh->height;
1445 0 : video_src.width = txh->width;
1446 : video_src.pitch_x = 0;
1447 0 : video_src.pitch_y = txh->stride;
1448 0 : video_src.pixel_format = txh->pixelformat;
1449 0 : video_src.video_buffer = txh->data;
1450 :
1451 0 : e = visual->compositor->video_out->Blit(visual->compositor->video_out, &video_src, &ol->src, &ol->dst, 2);
1452 0 : if (e) GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Visual2D] Error %s during overlay update\n", gf_error_to_string(e) ));
1453 :
1454 0 : ra_del(&ol->ra);
1455 0 : gf_free(ol);
1456 : }
1457 : }
1458 :
1459 605 : void compositor_2d_init_callbacks(GF_Compositor *compositor)
1460 : {
1461 605 : compositor->visual->DrawBitmap = compositor_2d_draw_bitmap;
1462 605 : }
|