Line data Source code
1 : /*
2 : * GPAC - Multimedia Framework C SDK
3 : *
4 : * Authors: Jean Le Feuvre
5 : * Copyright (c) Telecom ParisTech 2006-2012
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 : #include "offscreen_cache.h"
27 :
28 : #include "visual_manager.h"
29 : #include "mpeg4_grouping.h"
30 : #include "texturing.h"
31 :
32 : #define NUM_STATS_FRAMES 2
33 : #define MIN_OBJECTS_IN_CACHE 2
34 :
35 :
36 : //#define CACHE_DEBUG_ALPHA
37 : //#define CACHE_DEBUG_CENTER
38 :
39 561 : void group_cache_draw(GroupCache *cache, GF_TraverseState *tr_state)
40 : {
41 561 : GF_TextureHandler *old_txh = tr_state->ctx->aspect.fill_texture;
42 : /*switch the texture to our offscreen cache*/
43 561 : tr_state->ctx->aspect.fill_texture = &cache->txh;
44 :
45 :
46 : #if !defined( GPAC_DISABLE_3D) && !defined(GPAC_DISABLE_VRML)
47 561 : if (tr_state->traversing_mode == TRAVERSE_DRAW_3D) {
48 6 : if (!cache->drawable->mesh) {
49 6 : cache->drawable->mesh = new_mesh();
50 : }
51 6 : mesh_from_path(cache->drawable->mesh, cache->drawable->path);
52 6 : visual_3d_draw_2d_with_aspect(cache->drawable, tr_state, &tr_state->ctx->aspect);
53 6 : return;
54 : }
55 : #endif
56 :
57 555 : if (! tr_state->visual->DrawBitmap(tr_state->visual, tr_state, tr_state->ctx)) {
58 6 : visual_2d_texture_path(tr_state->visual, cache->drawable->path, tr_state->ctx, tr_state);
59 : }
60 555 : tr_state->ctx->aspect.fill_texture = old_txh;
61 : }
62 :
63 21 : GroupCache *group_cache_new(GF_Compositor *compositor, GF_Node *node)
64 : {
65 : GroupCache *cache;
66 21 : GF_SAFEALLOC(cache, GroupCache);
67 21 : if (!cache) {
68 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate group cache\n"));
69 : return NULL;
70 : }
71 21 : gf_sc_texture_setup(&cache->txh, compositor, node);
72 21 : cache->drawable = drawable_new();
73 : /*draw the cache through traverse callback*/
74 21 : cache->drawable->flags |= DRAWABLE_USE_TRAVERSE_DRAW;
75 21 : cache->drawable->node = node;
76 21 : cache->opacity = FIX_ONE;
77 21 : gf_sc_texture_allocate(&cache->txh);
78 21 : return cache;
79 : }
80 :
81 21 : void group_cache_del(GroupCache *cache)
82 : {
83 21 : drawable_del(cache->drawable);
84 21 : if (cache->txh.data) gf_free(cache->txh.data);
85 21 : gf_sc_texture_release(&cache->txh);
86 21 : gf_sc_texture_destroy(&cache->txh);
87 21 : gf_free(cache);
88 21 : }
89 :
90 22 : void group_cache_setup(GroupCache *cache, GF_Rect *path_bounds, GF_IRect *pix_bounds, GF_Compositor *compositor, Bool for_gl)
91 : {
92 : /*setup texture */
93 22 : cache->txh.compositor = compositor;
94 22 : cache->txh.height = pix_bounds->height;
95 22 : cache->txh.width = pix_bounds->width;
96 :
97 22 : cache->txh.stride = pix_bounds->width * 4;
98 22 : cache->txh.pixelformat = for_gl ? GF_PIXEL_RGBA : GF_PIXEL_ARGB;
99 22 : cache->txh.transparent = 1;
100 :
101 22 : if (cache->txh.data)
102 1 : gf_free(cache->txh.data);
103 : #ifdef CACHE_DEBUG_ALPHA
104 : cache->txh.stride = pix_bounds->width * 3;
105 : cache->txh.pixelformat = GF_PIXEL_RGB;
106 : cache->txh.transparent = 0;
107 : #endif
108 :
109 22 : cache->txh.data = (char *) gf_malloc (sizeof(char) * cache->txh.stride * cache->txh.height);
110 22 : memset(cache->txh.data, 0x0, sizeof(char) * cache->txh.stride * cache->txh.height);
111 : /*the path of drawable_cache is a rectangle one that is the the bound of the object*/
112 22 : gf_path_reset(cache->drawable->path);
113 :
114 : /*set a rectangle to the path
115 : Attention, we want to center the cached bitmap at the center of the screen (main visual), so we use
116 : the local coordinate to parameterize the path*/
117 66 : gf_path_add_rect_center(cache->drawable->path,
118 22 : path_bounds->x + path_bounds->width/2,
119 22 : path_bounds->y - path_bounds->height/2,
120 : path_bounds->width, path_bounds->height);
121 22 : }
122 :
123 678 : Bool group_cache_traverse(GF_Node *node, GroupCache *cache, GF_TraverseState *tr_state, Bool force_recompute, Bool is_mpeg4, Bool auto_fit_vp)
124 : {
125 : GF_Matrix2D backup;
126 : DrawableContext *group_ctx = NULL;
127 : GF_ChildNodeItem *l;
128 :
129 678 : if (!cache) return 0;
130 :
131 : /*do we need to recompute the cache*/
132 678 : if (cache->force_recompute) {
133 : force_recompute = 1;
134 18 : cache->force_recompute = 0;
135 : }
136 660 : else if (gf_node_dirty_get(node) & GF_SG_CHILD_DIRTY) {
137 : force_recompute = 1;
138 : }
139 :
140 : /*we need to redraw the group in an offscreen visual*/
141 656 : if (force_recompute) {
142 : GF_IRect rc1, rc2;
143 : u32 prev_flags;
144 : Bool prev_hybgl, visual_attached, for_3d=GF_FALSE;
145 : GF_Rect cache_bounds;
146 : GF_EVGSurface *offscreen_surface, *old_surf;
147 : DrawableContext *child_ctx;
148 : Fixed temp_x, temp_y, scale_x, scale_y;
149 : #ifndef GPAC_DISABLE_3D
150 : u32 type_3d;
151 : GF_Matrix2D transf;
152 : #endif
153 :
154 22 : GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Recomputing cache for subtree %s\n", gf_node_get_log_name(node)));
155 : /*step 1 : store current state and indicate children should not be cached*/
156 22 : tr_state->in_group_cache = 1;
157 22 : prev_flags = tr_state->immediate_draw;
158 : /*store the current transform matrix, create a new one for group_cache*/
159 22 : gf_mx2d_copy(backup, tr_state->transform);
160 22 : gf_mx2d_init(tr_state->transform);
161 :
162 : #ifndef GPAC_DISABLE_3D
163 : /*force 2D rendering*/
164 22 : type_3d = tr_state->visual->type_3d;
165 22 : tr_state->visual->type_3d = 0;
166 22 : if (type_3d || tr_state->visual->compositor->hybrid_opengl)
167 : for_3d = GF_TRUE;
168 : #endif
169 22 : prev_hybgl = tr_state->visual->compositor->hybrid_opengl;
170 22 : tr_state->visual->compositor->hybrid_opengl = GF_FALSE;
171 :
172 : /*step 2: collect the bounds of all children*/
173 22 : tr_state->traversing_mode = TRAVERSE_GET_BOUNDS;
174 22 : cache_bounds.width = cache_bounds.height = 0;
175 22 : l = ((GF_ParentNode*)node)->children;
176 120 : while (l) {
177 76 : tr_state->bounds.width = tr_state->bounds.height = 0;
178 76 : gf_node_traverse(l->node, tr_state);
179 76 : l = l->next;
180 76 : gf_rect_union(&cache_bounds, &tr_state->bounds);
181 : }
182 22 : tr_state->traversing_mode = TRAVERSE_SORT;
183 :
184 22 : if (!cache_bounds.width || !cache_bounds.height) {
185 0 : tr_state->in_group_cache = 0;
186 0 : tr_state->immediate_draw = prev_flags;
187 : gf_mx2d_copy(tr_state->transform, backup);
188 : #ifndef GPAC_DISABLE_3D
189 0 : tr_state->visual->type_3d = type_3d;
190 : #endif
191 0 : tr_state->visual->compositor->hybrid_opengl = prev_hybgl;
192 0 : return 0;
193 : }
194 :
195 : /*step 3: insert a DrawableContext for this group in the display list*/
196 22 : if (is_mpeg4) {
197 : #ifndef GPAC_DISABLE_VRML
198 4 : group_ctx = drawable_init_context_mpeg4(cache->drawable, tr_state);
199 : #endif
200 : } else {
201 : #ifndef GPAC_DISABLE_SVG
202 18 : group_ctx = drawable_init_context_svg(cache->drawable, tr_state);
203 : #endif
204 : }
205 22 : if (!group_ctx) return 0;
206 :
207 : /*step 4: now we have the bounds:
208 : allocate the offscreen memory
209 : create temp raster visual & attach to buffer
210 : override the tr_state->visual->the_surface with the temp raster
211 : add translation (shape is not always centered)
212 : setup top clipers
213 : */
214 22 : old_surf = tr_state->visual->raster_surface;
215 22 : offscreen_surface = gf_evg_surface_new(tr_state->visual->center_coords); /*a new temp raster visual*/
216 22 : tr_state->visual->raster_surface = offscreen_surface;
217 : #ifndef GPAC_DISABLE_3D
218 22 : if (type_3d) {
219 2 : gf_mx2d_from_mx(&transf, &tr_state->model_matrix);
220 2 : scale_x = transf.m[0];
221 2 : scale_y = transf.m[4];
222 : } else
223 : #endif
224 : {
225 20 : scale_x = backup.m[0];
226 20 : scale_y = backup.m[4];
227 : }
228 :
229 : /*use current surface coordinate scaling to compute the cache*/
230 : #ifdef GF_SR_USE_VIDEO_CACHE
231 : scale_x = tr_state->visual->compositor->vcscale * scale_x / 100;
232 : scale_y = tr_state->visual->compositor->vcscale * scale_y / 100;
233 : #endif
234 :
235 22 : if (scale_x<0) scale_x = -scale_x;
236 22 : if (scale_y<0) scale_y = -scale_y;
237 :
238 22 : cache->scale = MAX(scale_x, scale_y);
239 22 : tr_state->bounds = cache_bounds;
240 :
241 22 : gf_mx2d_add_scale(&tr_state->transform, scale_x, scale_y);
242 22 : gf_mx2d_apply_rect(&tr_state->transform, &cache_bounds);
243 :
244 22 : rc1 = gf_rect_pixelize(&cache_bounds);
245 22 : if (rc1.width % 2) rc1.width++;
246 22 : if (rc1.height%2) rc1.height++;
247 :
248 : //TODO - set min offscreen size in cfg file
249 36 : while (rc1.width && rc1.width<128) rc1.width *= 2;
250 58 : while (rc1.height && rc1.height<128) rc1.height *= 2;
251 :
252 : /* Initialize the group cache with the scaled pixelized bounds for texture but the original bounds for path*/
253 22 : group_cache_setup(cache, &tr_state->bounds, &rc1, tr_state->visual->compositor, for_3d);
254 :
255 :
256 : /*attach the buffer to visual*/
257 22 : gf_evg_surface_attach_to_buffer(offscreen_surface, cache->txh.data,
258 : cache->txh.width,
259 : cache->txh.height,
260 : 0,
261 22 : cache->txh.stride,
262 22 : cache->txh.pixelformat);
263 :
264 22 : visual_attached = tr_state->visual->is_attached;
265 22 : tr_state->visual->is_attached = 1;
266 :
267 : /*recompute the bounds with the final scaling used*/
268 22 : scale_x = gf_divfix(INT2FIX(rc1.width), tr_state->bounds.width);
269 22 : scale_y = gf_divfix(INT2FIX(rc1.height), tr_state->bounds.height);
270 22 : gf_mx2d_init(tr_state->transform);
271 22 : gf_mx2d_add_scale(&tr_state->transform, scale_x, scale_y);
272 22 : cache_bounds = tr_state->bounds;
273 22 : gf_mx2d_apply_rect(&tr_state->transform, &cache_bounds);
274 :
275 : /*centered the bitmap on the visual*/
276 22 : temp_x = -cache_bounds.x;
277 22 : temp_y = -cache_bounds.y;
278 22 : if (tr_state->visual->center_coords) {
279 4 : temp_x -= cache_bounds.width/2;
280 4 : temp_y += cache_bounds.height/2;
281 : } else {
282 18 : temp_y += cache_bounds.height;
283 : }
284 22 : gf_mx2d_add_translation(&tr_state->transform, temp_x, temp_y);
285 :
286 : /*override top clippers*/
287 22 : rc1 = tr_state->visual->surf_rect;
288 22 : rc2 = tr_state->visual->top_clipper;
289 22 : tr_state->visual->surf_rect.width = cache->txh.width;
290 22 : tr_state->visual->surf_rect.height = cache->txh.height;
291 22 : if (tr_state->visual->center_coords) {
292 4 : tr_state->visual->surf_rect.y = cache->txh.height/2;
293 4 : tr_state->visual->surf_rect.x = -1 * (s32) cache->txh.width/2;
294 : } else {
295 18 : tr_state->visual->surf_rect.y = cache->txh.height;
296 18 : tr_state->visual->surf_rect.x = 0;
297 : }
298 22 : tr_state->visual->top_clipper = tr_state->visual->surf_rect;
299 :
300 :
301 : /*step 5: traverse subtree in direct draw mode*/
302 22 : tr_state->immediate_draw = 1;
303 22 : group_ctx->flags &= ~CTX_NO_ANTIALIAS;
304 :
305 22 : l = ((GF_ParentNode*)node)->children;
306 120 : while (l) {
307 76 : gf_node_traverse(l->node, tr_state);
308 76 : l = l->next;
309 : }
310 : /*step 6: reset all contexts after the current group one*/
311 22 : child_ctx = group_ctx->next;
312 84 : while (child_ctx && child_ctx->drawable) {
313 40 : drawable_reset_bounds(child_ctx->drawable, tr_state->visual);
314 40 : child_ctx->drawable = NULL;
315 40 : child_ctx = child_ctx->next;
316 : }
317 :
318 : /*and set ourselves as the last context on the main visual*/
319 22 : tr_state->visual->cur_context = group_ctx;
320 :
321 : /*restore state and destroy whatever needs to be cleaned*/
322 : gf_mx2d_copy(tr_state->transform, backup);
323 22 : tr_state->in_group_cache = 0;
324 22 : tr_state->immediate_draw = prev_flags;
325 22 : tr_state->visual->compositor->hybrid_opengl = prev_hybgl;
326 22 : tr_state->visual->is_attached = visual_attached;
327 :
328 22 : gf_evg_surface_delete(offscreen_surface);
329 22 : tr_state->visual->raster_surface = old_surf;
330 22 : tr_state->traversing_mode = TRAVERSE_SORT;
331 :
332 : #ifndef GPAC_DISABLE_3D
333 22 : tr_state->visual->type_3d = type_3d;
334 : #endif
335 22 : tr_state->visual->surf_rect = rc1;
336 22 : tr_state->visual->top_clipper = rc2;
337 :
338 : /*update texture*/
339 22 : cache->txh.transparent = 1;
340 22 : if (tr_state->visual->center_coords)
341 4 : cache->txh.flags |= GF_SR_TEXTURE_NO_GL_FLIP;
342 :
343 22 : gf_sc_texture_set_data(&cache->txh);
344 22 : gf_sc_texture_push_image(&cache->txh, 0, for_3d ? 0 : 1);
345 :
346 22 : cache->orig_vp = tr_state->vp_size;
347 : }
348 : /*just setup the context*/
349 : else {
350 656 : if (is_mpeg4) {
351 : #ifndef GPAC_DISABLE_VRML
352 359 : group_ctx = drawable_init_context_mpeg4(cache->drawable, tr_state);
353 : #endif
354 : } else {
355 : #ifndef GPAC_DISABLE_SVG
356 297 : group_ctx = drawable_init_context_svg(cache->drawable, tr_state);
357 : #endif
358 : }
359 : }
360 678 : if (!group_ctx) return 0;
361 678 : group_ctx->flags |= CTX_NO_ANTIALIAS;
362 678 : if (cache->opacity != FIX_ONE)
363 315 : group_ctx->aspect.fill_color = GF_COL_ARGB_FIXED(cache->opacity, FIX_ONE, FIX_ONE, FIX_ONE);
364 : else
365 363 : group_ctx->aspect.fill_color = 0;
366 678 : group_ctx->aspect.fill_texture = &cache->txh;
367 :
368 678 : if (!cache->opacity) {
369 0 : group_ctx->drawable = NULL;
370 0 : return 0;
371 : }
372 :
373 678 : drawable_check_texture_dirty(group_ctx, group_ctx->drawable, tr_state);
374 :
375 678 : if (gf_node_dirty_get(node)) group_ctx->flags |= CTX_TEXTURE_DIRTY;
376 :
377 : #ifdef CACHE_DEBUG_CENTER
378 : gf_mx2d_copy(backup, tr_state->transform);
379 : gf_mx2d_init(tr_state->transform);
380 : #else
381 678 : gf_mx2d_copy(backup, tr_state->transform);
382 678 : if (auto_fit_vp) {
383 0 : if ((tr_state->vp_size.x != cache->orig_vp.x) || (tr_state->vp_size.y != cache->orig_vp.y)) {
384 : GF_Matrix2D m;
385 0 : gf_mx2d_init(m);
386 : gf_mx2d_copy(backup, tr_state->transform);
387 0 : gf_mx2d_add_scale(&m, gf_divfix(tr_state->vp_size.x, cache->orig_vp.x), gf_divfix(tr_state->vp_size.y, cache->orig_vp.y) );
388 0 : gf_mx2d_pre_multiply(&tr_state->transform, &m);
389 : } else {
390 : auto_fit_vp = 0;
391 : }
392 : }
393 : #endif
394 :
395 : #ifndef GPAC_DISABLE_3D
396 678 : if (tr_state->visual->type_3d) {
397 121 : if (!cache->drawable->mesh) {
398 1 : cache->drawable->mesh = new_mesh();
399 1 : mesh_from_path(cache->drawable->mesh, cache->drawable->path);
400 : }
401 121 : visual_3d_draw_from_context(group_ctx, tr_state);
402 121 : group_ctx->drawable = NULL;
403 : } else
404 : #endif
405 557 : drawable_finalize_sort(group_ctx, tr_state, NULL);
406 :
407 : #ifndef CACHE_DEBUG_CENTER
408 678 : if (auto_fit_vp)
409 : #endif
410 : {
411 : gf_mx2d_copy(tr_state->transform, backup);
412 : }
413 678 : return (force_recompute==1);
414 : }
415 :
416 :
417 : #ifdef GF_SR_USE_VIDEO_CACHE
418 :
419 : /*guarentee the tr_state->candidate has the lowest delta value*/
420 : static void group_cache_insert_entry(GF_Node *node, GroupingNode2D *group, GF_TraverseState *tr_state)
421 : {
422 : u32 i, count;
423 : GF_List *cache_candidates = tr_state->visual->compositor->cached_groups;
424 : GroupingNode2D *current;
425 :
426 : current = NULL;
427 : count = gf_list_count(cache_candidates);
428 : for (i=0; i<count; i++) {
429 : current = gf_list_get(cache_candidates, i);
430 : /*if entry's priority is higher than our group, insert our group here*/
431 : if (current->priority >= group->priority) {
432 : gf_list_insert(cache_candidates, group, i);
433 : break;
434 : }
435 : }
436 : if (i==count)
437 : gf_list_add(cache_candidates, group);
438 :
439 : tr_state->visual->compositor->video_cache_current_size += group->cached_size;
440 : /*log the information*/
441 : GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE]\tAdding object %s\tObjects: %d\tSlope: %g\tSize: %d\tTime: %d\n",
442 : gf_node_get_log_name(node),
443 : group->nb_objects,
444 : FIX2FLT(group->priority),
445 : group->cached_size,
446 : group->traverse_time));
447 :
448 : GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Status (KB): Max: %d\tUsed: %d\tNb Groups: %d\n",
449 : tr_state->visual->compositor->vcsize,
450 : tr_state->visual->compositor->video_cache_current_size,
451 : gf_list_count(tr_state->visual->compositor->cached_groups)
452 : ));
453 : }
454 :
455 :
456 : static Bool gf_cache_remove_entry(GF_Compositor *compositor, GF_Node *node, GroupingNode2D *group)
457 : {
458 : u32 bytes_remove = 0;
459 : GF_List *cache_candidates = compositor->cached_groups;
460 :
461 : /*auto mode*/
462 : if (!group) {
463 : group = gf_list_get(cache_candidates, 0);
464 : if (!group) return 0;
465 : /*remove entry*/
466 : gf_list_rem(cache_candidates, 0);
467 : node = NULL;
468 : } else {
469 : /*remove entry if present*/
470 : if (gf_list_del_item(cache_candidates, group)<0)
471 : return 0;
472 : }
473 :
474 : /*disable the caching flag of the group if it was marked as such*/
475 : if(group->flags & GROUP_IS_CACHABLE) {
476 : group->flags &= ~GROUP_IS_CACHABLE;
477 : /*the discarded bytes*/
478 : bytes_remove = group->cached_size;
479 : }
480 :
481 : /*indicates cache destruction for next frame*/
482 : if (group->cache && (group->flags & GROUP_IS_CACHED)) {
483 : group->flags &= ~GROUP_IS_CACHED;
484 : /*the discarded bytes*/
485 : bytes_remove = group->cached_size;
486 : }
487 :
488 : if (bytes_remove == 0) return 0;
489 :
490 : assert(compositor->video_cache_current_size >= bytes_remove);
491 : compositor->video_cache_current_size -= bytes_remove;
492 :
493 : GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Removing cache %s:\t Objects: %d\tSlope: %g\tBytes: %d\tTime: %d\n",
494 : gf_node_get_log_name(node),
495 : group->nb_objects,
496 : FIX2FLT(group->priority),
497 : group->cached_size,
498 : FIX2FLT(group->traverse_time)));
499 :
500 : GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Status (B): Max: %d\tUsed: %d\tNb Groups: %d\n",
501 : compositor->vcsize,
502 : compositor->video_cache_current_size,
503 : gf_list_count(compositor->cached_groups)
504 : ));
505 : return 1;
506 : }
507 :
508 :
509 : /**/
510 : Bool group_2d_cache_traverse(GF_Node *node, GroupingNode2D *group, GF_TraverseState *tr_state)
511 : {
512 : Bool is_dirty = gf_node_dirty_get(node) & GF_SG_CHILD_DIRTY;
513 : Bool zoom_changed = tr_state->visual->compositor->zoom_changed;
514 : Bool needs_recompute = 0;
515 :
516 : /*we are currently in a group cache, regular traversing*/
517 : if (tr_state->in_group_cache) return 0;
518 :
519 : /*draw mode*/
520 : if (tr_state->traversing_mode == TRAVERSE_DRAW_2D) {
521 : /*shall never happen*/
522 : assert(group->cache);
523 : /*draw it*/
524 : group_cache_draw(group->cache, tr_state);
525 : return 1;
526 : }
527 : /*other modes than sorting, use regular traversing*/
528 : if (tr_state->traversing_mode != TRAVERSE_SORT) return 0;
529 :
530 : /*this is not an offscreen group*/
531 : if (!(group->flags & GROUP_IS_CACHED) ) {
532 : Bool cache_on = 0;
533 :
534 : /*group cache has been turned on in the previous frame*/
535 : if (!is_dirty && (group->flags & GROUP_IS_CACHABLE)) {
536 : group->flags |= GROUP_IS_CACHED;
537 : group->flags &= ~GROUP_IS_CACHABLE;
538 : GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Turning group %s cache on - size %d\n", gf_node_get_log_name(node), group->cached_size ));
539 : cache_on = 1;
540 : }
541 : /*group cache has been turned off in the previous frame*/
542 : else if (group->cache) {
543 : group_cache_del(group->cache);
544 : group->cache = NULL;
545 : group->changed = is_dirty;
546 : group->nb_stats_frame = 0;
547 : group->traverse_time = 0;
548 : GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Turning group %s cache off\n", gf_node_get_log_name(node) ));
549 : return 0;
550 : }
551 :
552 : if (!cache_on) {
553 : if (is_dirty) {
554 : group->changed = 1;
555 : }
556 : /*ask for stats again*/
557 : else if (group->changed) {
558 : group->changed = 0;
559 : group->nb_stats_frame = 0;
560 : group->traverse_time = 0;
561 : } else if (zoom_changed) {
562 : group->nb_stats_frame = 0;
563 : group->traverse_time = 0;
564 : }
565 : if (is_dirty || (group->nb_stats_frame < NUM_STATS_FRAMES)) {
566 : /*force direct draw mode*/
567 : if (!is_dirty)
568 : tr_state->visual->compositor->traverse_state->invalidate_all = 1;
569 : /*force redraw*/
570 : tr_state->visual->compositor->draw_next_frame = 1;
571 : }
572 : return 0;
573 : }
574 : }
575 : /*cache is dirty*/
576 : else if (is_dirty) {
577 : /*permanent cache, just recompute*/
578 : if (group->flags & GROUP_PERMANENT_CACHE) {
579 : group->changed = 1;
580 : group->cache->force_recompute = 1;
581 : }
582 : /*otherwise destroy the cache*/
583 : else if (group->cache) {
584 : gf_cache_remove_entry(tr_state->visual->compositor, node, group);
585 : group_cache_del(group->cache);
586 : group->cache = NULL;
587 : group->flags &= ~GROUP_IS_CACHED;
588 : group->changed = 0;
589 : group->nb_stats_frame = 0;
590 : group->traverse_time = 0;
591 : GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Turning group %s cache off due to sub-tree modifications\n", gf_node_get_log_name(node) ));
592 : return 0;
593 : }
594 : }
595 : /*zoom has changed*/
596 : else if (zoom_changed) {
597 : /*permanent cache, just recompute*/
598 : if (group->flags & GROUP_PERMANENT_CACHE) {
599 : group->changed = 1;
600 : group->cache->force_recompute = 1;
601 : }
602 : /*otherwise check if we accept this scale ratio or if we must recompute*/
603 : else if (group->cache) {
604 : Fixed scale = MAX(tr_state->transform.m[0], tr_state->transform.m[4]);
605 :
606 : if (100*scale >= group->cache->scale*(100 + tr_state->visual->compositor->vctol))
607 : zoom_changed = 1;
608 : else if ((100+tr_state->visual->compositor->vctol)*scale <= 100*group->cache->scale)
609 : zoom_changed = 1;
610 : else
611 : zoom_changed = 0;
612 :
613 : if (zoom_changed) {
614 : gf_cache_remove_entry(tr_state->visual->compositor, node, group);
615 : group_cache_del(group->cache);
616 : group->cache = NULL;
617 : group->flags &= ~GROUP_IS_CACHED;
618 : group->changed = 0;
619 : group->nb_stats_frame = 0;
620 : group->traverse_time = 0;
621 : GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Turning group %s cache off due to zoom changes\n", gf_node_get_log_name(node) ));
622 : return 0;
623 : }
624 : }
625 : }
626 :
627 : /*keep track of this cache object for later removal*/
628 : if (!(group->flags & GROUP_PERMANENT_CACHE))
629 : gf_list_add(tr_state->visual->compositor->cached_groups_queue, group);
630 :
631 : if (!group->cache) {
632 : /*ALLOCATE THE CACHE*/
633 : group->cache = group_cache_new(tr_state->visual->compositor, node);
634 : needs_recompute = 1;
635 : }
636 :
637 : /*cache has been modified due to node changes, reset stats*/
638 : group_cache_traverse(node, group->cache, tr_state, needs_recompute, 1, 0);
639 : return 1;
640 : }
641 :
642 :
643 : Bool group_cache_compute_stats(GF_Node *node, GroupingNode2D *group, GF_TraverseState *tr_state, DrawableContext *first_child, Bool skip_first_child)
644 : {
645 : GF_Rect group_bounds;
646 : DrawableContext *ctx;
647 : u32 nb_segments, nb_objects;
648 : u32 alpha_pixels, opaque_pixels, area_world;
649 : u32 vcsize, cache_size, prev_cache_size;
650 : u32 i;
651 : GF_RectArray ra;
652 :
653 : /*compute stats*/
654 : nb_objects = 0;
655 : nb_segments = 0;
656 : alpha_pixels = opaque_pixels = 0;
657 : prev_cache_size = group->cached_size;
658 : /*reset bounds*/
659 : group_bounds.width = group_bounds.height = 0;
660 : vcsize = tr_state->visual->compositor->vcsize;
661 :
662 : /*never cache root node - this should be refined*/
663 : if (gf_node_get_parent(node, 0) == NULL) goto group_reject;
664 : if (!group->traverse_time) goto group_reject;
665 :
666 : ra_init(&ra);
667 :
668 : ctx = first_child;
669 : if (!first_child) ctx = tr_state->visual->context;
670 : if (skip_first_child) ctx = ctx->next;
671 : /*compute properties for the sub display list*/
672 : while (ctx && ctx->drawable) {
673 : //Fixed area;
674 : u32 alpha_comp;
675 :
676 : /*get area and compute alpha/opaque coverage*/
677 : alpha_comp = GF_COL_A(ctx->aspect.fill_color);
678 :
679 : /*add to group area*/
680 : gf_rect_union(&group_bounds, &ctx->bi->unclip);
681 : nb_objects++;
682 :
683 : /*no alpha*/
684 : if ((alpha_comp==0xFF)
685 : /*no transparent texture*/
686 : && (!ctx->aspect.fill_texture || !ctx->aspect.fill_texture->transparent)
687 : ) {
688 :
689 : ra_union_rect(&ra, &ctx->bi->clip);
690 : }
691 : nb_segments += ctx->drawable->path->n_points;
692 :
693 : ctx = ctx->next;
694 : }
695 :
696 : if (
697 : /*TEST 1: discard visually empty groups*/
698 : (!group_bounds.width || !group_bounds.height)
699 : ||
700 : /*TEST 2: discard small groups*/
701 : (nb_objects<MIN_OBJECTS_IN_CACHE)
702 : ||
703 : /*TEST 3: low complexity group*/
704 : (nb_segments && (nb_segments<10))
705 : ) {
706 : ra_del(&ra);
707 : goto group_reject;
708 : }
709 :
710 : ra_refresh(&ra);
711 : opaque_pixels = 0;
712 : for (i=0; i<ra.count; i++) {
713 : opaque_pixels += ra.list[i].width * ra.list[i].height;
714 : }
715 : ra_del(&ra);
716 :
717 : /*get coverage in world coords*/
718 : area_world = FIX2INT(group_bounds.width) * FIX2INT(group_bounds.height);
719 :
720 : /*TEST 4: discard low coverage groups in world coords (plenty of space wasted)
721 : we consider that this % of the area is actually drawn - this is of course wrong,
722 : we would need to compute each path coverage in local coords then get the ratio
723 : */
724 : if (10*opaque_pixels < 7*area_world) goto group_reject;
725 :
726 : /*the memory size allocated for the cache - cache is drawn in final coordinate system !!*/
727 : group_bounds.width = tr_state->visual->compositor->vcscale * group_bounds.width / 100;
728 : group_bounds.height = tr_state->visual->compositor->vcscale * group_bounds.height / 100;
729 : cache_size = FIX2INT(group_bounds.width) * FIX2INT(group_bounds.height) * 4 /* pixelFormat is ARGB*/;
730 :
731 : /*TEST 5: cache is less than 10x10 pixels: discard*/
732 : if (cache_size < 400) goto group_reject;
733 : /*TEST 6: cache is larger than our allowed memory: discard*/
734 : if (cache_size>=vcsize) {
735 : tr_state->cache_too_small = 1;
736 : goto group_reject;
737 : }
738 :
739 : /*compute the delta value for measuring the group importance for later discard
740 : (avg_time - Tcache) / (size_cache - drawable_gain)
741 : */
742 : group->priority = INT2FIX(nb_objects*1024*group->traverse_time) / cache_size / group->nb_stats_frame;
743 : /*OK, group is a good candidate for caching*/
744 : group->nb_objects = nb_objects;
745 : group->cached_size = cache_size;
746 :
747 :
748 : /*we're moving from non-cached to cached*/
749 : if (!(group->flags & GROUP_IS_CACHABLE)) {
750 : group->flags |= GROUP_IS_CACHABLE;
751 : tr_state->visual->compositor->draw_next_frame = 1;
752 :
753 : /*insert the candidate and then update the list in order*/
754 : group_cache_insert_entry(node, group, tr_state);
755 : /*keep track of this cache object for later removal*/
756 : gf_list_add(tr_state->visual->compositor->cached_groups_queue, group);
757 :
758 : GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Turning cache on during stat pass for node %s - %d kb used in all caches\n", gf_node_get_log_name(node), tr_state->visual->compositor->video_cache_current_size ));
759 : }
760 : /*update memory occupation*/
761 : else {
762 : tr_state->visual->compositor->video_cache_current_size -= prev_cache_size;
763 : tr_state->visual->compositor->video_cache_current_size += group->cached_size;
764 :
765 : if (group->cache)
766 : group->cache->force_recompute = 1;
767 : }
768 : return 1;
769 :
770 :
771 : group_reject:
772 : group->nb_objects = nb_objects;
773 :
774 : if ((group->flags & GROUP_IS_CACHABLE) || group->cache) {
775 : group->flags &= ~GROUP_IS_CACHABLE;
776 :
777 : if (group->cache) {
778 : group_cache_del(group->cache);
779 : group->cache = NULL;
780 : group->flags &= ~GROUP_IS_CACHED;
781 : }
782 : gf_list_del_item(tr_state->visual->compositor->cached_groups, group);
783 : tr_state->visual->compositor->video_cache_current_size -= cache_size;
784 : }
785 :
786 : #if 0
787 : GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] REJECT %s\tObjects: %d\tSlope: %g\tBytes: %d\tTime: %d\n",
788 : gf_node_get_log_name(node),
789 : group->nb_objects,
790 : FIX2FLT(group->priority),
791 : group->cached_size,
792 : group->traverse_time
793 : ));
794 :
795 : GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Status (B): Max: %d\tUsed: %d\tNb Groups: %d\n",
796 : tr_state->visual->compositor->vcsize,
797 : tr_state->visual->compositor->video_cache_current_size,
798 : gf_list_count(tr_state->visual->compositor->cached_groups)
799 : ));
800 : #endif
801 : return 0;
802 : }
803 :
804 :
805 : void group_2d_cache_evaluate(GF_Node *node, GroupingNode2D *group, GF_TraverseState *tr_state, DrawableContext *first_child, Bool skip_first_child, u32 last_cache_idx)
806 : {
807 : u32 nb_cache_added, i;
808 : GF_Compositor *compositor = tr_state->visual->compositor;
809 :
810 : /*first frame is unusable for stats because lot of time is being spent building the path and allocating
811 : the drawable contexts*/
812 : if (!compositor->vcsize || !compositor->frame_number || group->changed || tr_state->in_group_cache) {
813 : group->traverse_time = 0;
814 : return;
815 : }
816 :
817 : if (group->nb_stats_frame < NUM_STATS_FRAMES) {
818 : group->nb_stats_frame++;
819 : tr_state->visual->compositor->draw_next_frame = 1;
820 : return;
821 : }
822 : if (group->nb_stats_frame > NUM_STATS_FRAMES) return;
823 : group->nb_stats_frame++;
824 :
825 : /*the way to produce the result of memory-computation optimization*/
826 : if (group_cache_compute_stats(node, group, tr_state, first_child, skip_first_child)) {
827 : Fixed avg_time;
828 : nb_cache_added = gf_list_count(compositor->cached_groups_queue) - last_cache_idx - 1;
829 :
830 : /*force redraw*/
831 : tr_state->visual->compositor->draw_next_frame = 1;
832 :
833 : /*update priority by adding cache priorities */
834 : avg_time = group->priority * group->cached_size / (1024*group->nb_objects);
835 :
836 : /*remove all queued cached groups of this node's children*/
837 : for (i=0; i<nb_cache_added; i++) {
838 : Fixed cache_time;
839 : GroupingNode2D *cache = gf_list_get(compositor->cached_groups_queue, last_cache_idx);
840 : /*we have been computed the prioirity of the group using a cached subtree, update
841 : the priority to reflect that the new cache won't use a cached subtree*/
842 : if (cache->cache) {
843 : /*fixme - this assumes cache draw time is 0*/
844 : cache_time = cache->priority * cache->cached_size / (1024*group->nb_objects);
845 : avg_time += cache_time;
846 : }
847 : gf_cache_remove_entry(compositor, NULL, cache);
848 : cache->nb_stats_frame = 0;
849 : cache->traverse_time = 0;
850 : gf_list_rem(compositor->cached_groups_queue, last_cache_idx);
851 : }
852 : group->priority = INT2FIX (group->nb_objects*1024*1024*avg_time) / group->cached_size;
853 :
854 : /*when the memory exceeds the constraint, remove the subgroups that have the lowest deltas*/
855 : while (compositor->video_cache_current_size > compositor->vcsize) {
856 : gf_cache_remove_entry(compositor, node, NULL);
857 : GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Removing low priority cache - current total size %d\n", compositor->video_cache_current_size));
858 : }
859 : }
860 : }
861 :
862 : void compositor_set_cache_memory(GF_Compositor *compositor, u32 memory)
863 : {
864 : /*when the memory exceeds the constraint, remove the subgroups that have the lowest deltas*/
865 : while (compositor->video_cache_current_size) {
866 : gf_cache_remove_entry(compositor, NULL, NULL);
867 : }
868 : compositor->vcsize = memory;
869 : /*and force recompute*/
870 : compositor->zoom_changed = 1;
871 : }
872 :
873 : #endif /*GF_SR_USE_VIDEO_CACHE*/
874 :
875 0 : void group_2d_destroy_svg(GF_Node *node, GroupingNode2D *group)
876 : {
877 : #ifdef GF_SR_USE_VIDEO_CACHE
878 : GF_Compositor *compositor = gf_sc_get_compositor(node);
879 : if (gf_cache_remove_entry(compositor, node, group)) {
880 : /*simulate a zoom changed for cache recompute*/
881 : compositor->zoom_changed = 1;
882 : compositor->draw_next_frame = 1;
883 : }
884 : if (group->cache) group_cache_del(group->cache);
885 : #endif
886 0 : }
887 :
888 7108 : void group_2d_destroy(GF_Node *node, GroupingNode2D *group)
889 : {
890 : group_2d_destroy_svg(node, group);
891 7108 : if (group->sensors) gf_list_del(group->sensors);
892 7108 : }
893 :
|