| /* | |
| * GPAC - Multimedia Framework C SDK | |
| * | |
| * Authors: Jean Le Feuvre | |
| * Copyright (c) Telecom ParisTech 2006-2023 | |
| * All rights reserved | |
| * | |
| * This file is part of GPAC / Scene Compositor sub-project | |
| * | |
| * GPAC is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU Lesser General Public License as published by | |
| * the Free Software Foundation; either version 2, or (at your option) | |
| * any later version. | |
| * | |
| * GPAC is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; see the file COPYING. If not, write to | |
| * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | |
| * | |
| */ | |
| #include "offscreen_cache.h" | |
| #include "visual_manager.h" | |
| #include "mpeg4_grouping.h" | |
| #include "texturing.h" | |
| #if !defined(GPAC_DISABLE_COMPOSITOR) | |
| #define NUM_STATS_FRAMES 2 | |
| #define MIN_OBJECTS_IN_CACHE 2 | |
| //#define CACHE_DEBUG_ALPHA | |
| //#define CACHE_DEBUG_CENTER | |
| void group_cache_draw(GroupCache *cache, GF_TraverseState *tr_state) | |
| { | |
| GF_TextureHandler *old_txh = tr_state->ctx->aspect.fill_texture; | |
| /*switch the texture to our offscreen cache*/ | |
| tr_state->ctx->aspect.fill_texture = &cache->txh; | |
| #if !defined( GPAC_DISABLE_3D) && !defined(GPAC_DISABLE_VRML) | |
| if (tr_state->traversing_mode == TRAVERSE_DRAW_3D) { | |
| if (!cache->drawable->mesh) { | |
| cache->drawable->mesh = new_mesh(); | |
| } | |
| mesh_from_path(cache->drawable->mesh, cache->drawable->path); | |
| visual_3d_draw_2d_with_aspect(cache->drawable, tr_state, &tr_state->ctx->aspect); | |
| return; | |
| } | |
| #endif | |
| if (! tr_state->visual->DrawBitmap(tr_state->visual, tr_state, tr_state->ctx)) { | |
| visual_2d_texture_path(tr_state->visual, cache->drawable->path, tr_state->ctx, tr_state); | |
| } | |
| tr_state->ctx->aspect.fill_texture = old_txh; | |
| } | |
| GroupCache *group_cache_new(GF_Compositor *compositor, GF_Node *node) | |
| { | |
| GroupCache *cache; | |
| GF_SAFEALLOC(cache, GroupCache); | |
| if (!cache) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate group cache\n")); | |
| return NULL; | |
| } | |
| gf_sc_texture_setup(&cache->txh, compositor, node); | |
| cache->drawable = drawable_new(); | |
| /*draw the cache through traverse callback*/ | |
| cache->drawable->flags |= DRAWABLE_USE_TRAVERSE_DRAW; | |
| cache->drawable->node = node; | |
| cache->opacity = FIX_ONE; | |
| gf_sc_texture_allocate(&cache->txh); | |
| return cache; | |
| } | |
| void group_cache_del(GroupCache *cache) | |
| { | |
| drawable_del(cache->drawable); | |
| if (cache->txh.data) gf_free(cache->txh.data); | |
| gf_sc_texture_release(&cache->txh); | |
| gf_sc_texture_destroy(&cache->txh); | |
| gf_free(cache); | |
| } | |
| void group_cache_setup(GroupCache *cache, GF_Rect *path_bounds, GF_IRect *pix_bounds, GF_Compositor *compositor, Bool for_gl) | |
| { | |
| /*setup texture */ | |
| cache->txh.compositor = compositor; | |
| cache->txh.height = pix_bounds->height; | |
| cache->txh.width = pix_bounds->width; | |
| cache->txh.stride = pix_bounds->width * 4; | |
| cache->txh.pixelformat = for_gl ? GF_PIXEL_RGBA : GF_PIXEL_ARGB; | |
| cache->txh.transparent = 1; | |
| if (cache->txh.data) | |
| gf_free(cache->txh.data); | |
| #ifdef CACHE_DEBUG_ALPHA | |
| cache->txh.stride = pix_bounds->width * 3; | |
| cache->txh.pixelformat = GF_PIXEL_RGB; | |
| cache->txh.transparent = 0; | |
| #endif | |
| cache->txh.data = (char *) gf_malloc (sizeof(char) * cache->txh.stride * cache->txh.height); | |
| memset(cache->txh.data, 0x0, sizeof(char) * cache->txh.stride * cache->txh.height); | |
| /*the path of drawable_cache is a rectangle one that is the the bound of the object*/ | |
| gf_path_reset(cache->drawable->path); | |
| /*set a rectangle to the path | |
| Attention, we want to center the cached bitmap at the center of the screen (main visual), so we use | |
| the local coordinate to parameterize the path*/ | |
| gf_path_add_rect_center(cache->drawable->path, | |
| path_bounds->x + path_bounds->width/2, | |
| path_bounds->y - path_bounds->height/2, | |
| path_bounds->width, path_bounds->height); | |
| } | |
| Bool group_cache_traverse(GF_Node *node, GroupCache *cache, GF_TraverseState *tr_state, Bool force_recompute, Bool is_mpeg4, Bool auto_fit_vp) | |
| { | |
| GF_Matrix2D backup; | |
| DrawableContext *group_ctx = NULL; | |
| GF_ChildNodeItem *l; | |
| if (!cache) return 0; | |
| /*do we need to recompute the cache*/ | |
| if (cache->force_recompute) { | |
| force_recompute = 1; | |
| cache->force_recompute = 0; | |
| } | |
| else if (gf_node_dirty_get(node) & GF_SG_CHILD_DIRTY) { | |
| force_recompute = 1; | |
| } | |
| /*we need to redraw the group in an offscreen visual*/ | |
| if (force_recompute) { | |
| GF_IRect rc1, rc2; | |
| u32 prev_flags; | |
| Bool prev_hybgl, visual_attached, for_3d=GF_FALSE; | |
| GF_Rect cache_bounds; | |
| GF_EVGSurface *offscreen_surface, *old_surf; | |
| DrawableContext *child_ctx; | |
| Fixed temp_x, temp_y, scale_x, scale_y; | |
| #ifndef GPAC_DISABLE_3D | |
| u32 type_3d; | |
| GF_Matrix2D transf; | |
| #endif | |
| GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Recomputing cache for subtree %s\n", gf_node_get_log_name(node))); | |
| /*step 1 : store current state and indicate children should not be cached*/ | |
| tr_state->in_group_cache = 1; | |
| prev_flags = tr_state->immediate_draw; | |
| /*store the current transform matrix, create a new one for group_cache*/ | |
| gf_mx2d_copy(backup, tr_state->transform); | |
| gf_mx2d_init(tr_state->transform); | |
| #ifndef GPAC_DISABLE_3D | |
| /*force 2D rendering*/ | |
| type_3d = tr_state->visual->type_3d; | |
| tr_state->visual->type_3d = 0; | |
| if (type_3d || tr_state->visual->compositor->hybrid_opengl) | |
| for_3d = GF_TRUE; | |
| #endif | |
| prev_hybgl = tr_state->visual->compositor->hybrid_opengl; | |
| tr_state->visual->compositor->hybrid_opengl = GF_FALSE; | |
| /*step 2: collect the bounds of all children*/ | |
| tr_state->traversing_mode = TRAVERSE_GET_BOUNDS; | |
| cache_bounds.width = cache_bounds.height = 0; | |
| l = ((GF_ParentNode*)node)->children; | |
| while (l) { | |
| tr_state->bounds.width = tr_state->bounds.height = 0; | |
| gf_node_traverse(l->node, tr_state); | |
| l = l->next; | |
| gf_rect_union(&cache_bounds, &tr_state->bounds); | |
| } | |
| tr_state->traversing_mode = TRAVERSE_SORT; | |
| if (!cache_bounds.width || !cache_bounds.height) { | |
| tr_state->in_group_cache = 0; | |
| tr_state->immediate_draw = prev_flags; | |
| gf_mx2d_copy(tr_state->transform, backup); | |
| #ifndef GPAC_DISABLE_3D | |
| tr_state->visual->type_3d = type_3d; | |
| #endif | |
| tr_state->visual->compositor->hybrid_opengl = prev_hybgl; | |
| return 0; | |
| } | |
| /*step 3: insert a DrawableContext for this group in the display list*/ | |
| if (is_mpeg4) { | |
| #ifndef GPAC_DISABLE_VRML | |
| group_ctx = drawable_init_context_mpeg4(cache->drawable, tr_state); | |
| #endif | |
| } else { | |
| #ifndef GPAC_DISABLE_SVG | |
| group_ctx = drawable_init_context_svg(cache->drawable, tr_state, NULL); | |
| #endif | |
| } | |
| if (!group_ctx) return 0; | |
| /*step 4: now we have the bounds: | |
| allocate the offscreen memory | |
| create temp raster visual & attach to buffer | |
| override the tr_state->visual->the_surface with the temp raster | |
| add translation (shape is not always centered) | |
| setup top clipers | |
| */ | |
| old_surf = tr_state->visual->raster_surface; | |
| offscreen_surface = gf_evg_surface_new(tr_state->visual->center_coords); /*a new temp raster visual*/ | |
| tr_state->visual->raster_surface = offscreen_surface; | |
| #ifndef GPAC_DISABLE_3D | |
| if (type_3d) { | |
| gf_mx2d_from_mx(&transf, &tr_state->model_matrix); | |
| scale_x = transf.m[0]; | |
| scale_y = transf.m[4]; | |
| } else | |
| #endif | |
| { | |
| scale_x = backup.m[0]; | |
| scale_y = backup.m[4]; | |
| } | |
| /*use current surface coordinate scaling to compute the cache*/ | |
| #ifdef GF_SR_USE_VIDEO_CACHE | |
| scale_x = tr_state->visual->compositor->vcscale * scale_x / 100; | |
| scale_y = tr_state->visual->compositor->vcscale * scale_y / 100; | |
| #endif | |
| if (scale_x<0) scale_x = -scale_x; | |
| if (scale_y<0) scale_y = -scale_y; | |
| cache->scale = MAX(scale_x, scale_y); | |
| tr_state->bounds = cache_bounds; | |
| gf_mx2d_add_scale(&tr_state->transform, scale_x, scale_y); | |
| gf_mx2d_apply_rect(&tr_state->transform, &cache_bounds); | |
| rc1 = gf_rect_pixelize(&cache_bounds); | |
| if (rc1.width % 2) rc1.width++; | |
| if (rc1.height%2) rc1.height++; | |
| //TODO - set min offscreen size in cfg file | |
| while (rc1.width && rc1.width<128) rc1.width *= 2; | |
| while (rc1.height && rc1.height<128) rc1.height *= 2; | |
| /* Initialize the group cache with the scaled pixelized bounds for texture but the original bounds for path*/ | |
| group_cache_setup(cache, &tr_state->bounds, &rc1, tr_state->visual->compositor, for_3d); | |
| /*attach the buffer to visual*/ | |
| gf_evg_surface_attach_to_buffer(offscreen_surface, cache->txh.data, | |
| cache->txh.width, | |
| cache->txh.height, | |
| 0, | |
| cache->txh.stride, | |
| cache->txh.pixelformat); | |
| visual_attached = tr_state->visual->is_attached; | |
| tr_state->visual->is_attached = 1; | |
| /*recompute the bounds with the final scaling used*/ | |
| scale_x = gf_divfix(INT2FIX(rc1.width), tr_state->bounds.width); | |
| scale_y = gf_divfix(INT2FIX(rc1.height), tr_state->bounds.height); | |
| gf_mx2d_init(tr_state->transform); | |
| gf_mx2d_add_scale(&tr_state->transform, scale_x, scale_y); | |
| cache_bounds = tr_state->bounds; | |
| gf_mx2d_apply_rect(&tr_state->transform, &cache_bounds); | |
| /*centered the bitmap on the visual*/ | |
| temp_x = -cache_bounds.x; | |
| temp_y = -cache_bounds.y; | |
| if (tr_state->visual->center_coords) { | |
| temp_x -= cache_bounds.width/2; | |
| temp_y += cache_bounds.height/2; | |
| } else { | |
| temp_y += cache_bounds.height; | |
| } | |
| gf_mx2d_add_translation(&tr_state->transform, temp_x, temp_y); | |
| /*override top clippers*/ | |
| rc1 = tr_state->visual->surf_rect; | |
| rc2 = tr_state->visual->top_clipper; | |
| tr_state->visual->surf_rect.width = cache->txh.width; | |
| tr_state->visual->surf_rect.height = cache->txh.height; | |
| if (tr_state->visual->center_coords) { | |
| tr_state->visual->surf_rect.y = cache->txh.height/2; | |
| tr_state->visual->surf_rect.x = -1 * (s32) cache->txh.width/2; | |
| } else { | |
| tr_state->visual->surf_rect.y = cache->txh.height; | |
| tr_state->visual->surf_rect.x = 0; | |
| } | |
| tr_state->visual->top_clipper = tr_state->visual->surf_rect; | |
| /*step 5: traverse subtree in direct draw mode*/ | |
| tr_state->immediate_draw = 1; | |
| group_ctx->flags &= ~CTX_NO_ANTIALIAS; | |
| l = ((GF_ParentNode*)node)->children; | |
| while (l) { | |
| gf_node_traverse(l->node, tr_state); | |
| l = l->next; | |
| } | |
| /*step 6: reset all contexts after the current group one*/ | |
| child_ctx = group_ctx->next; | |
| while (child_ctx && child_ctx->drawable) { | |
| drawable_reset_bounds(child_ctx->drawable, tr_state->visual); | |
| child_ctx->drawable = NULL; | |
| child_ctx = child_ctx->next; | |
| } | |
| /*and set ourselves as the last context on the main visual*/ | |
| tr_state->visual->cur_context = group_ctx; | |
| /*restore state and destroy whatever needs to be cleaned*/ | |
| gf_mx2d_copy(tr_state->transform, backup); | |
| tr_state->in_group_cache = 0; | |
| tr_state->immediate_draw = prev_flags; | |
| tr_state->visual->compositor->hybrid_opengl = prev_hybgl; | |
| tr_state->visual->is_attached = visual_attached; | |
| gf_evg_surface_delete(offscreen_surface); | |
| tr_state->visual->raster_surface = old_surf; | |
| tr_state->traversing_mode = TRAVERSE_SORT; | |
| #ifndef GPAC_DISABLE_3D | |
| tr_state->visual->type_3d = type_3d; | |
| #endif | |
| tr_state->visual->surf_rect = rc1; | |
| tr_state->visual->top_clipper = rc2; | |
| /*update texture*/ | |
| cache->txh.transparent = 1; | |
| if (tr_state->visual->center_coords) | |
| cache->txh.flags |= GF_SR_TEXTURE_NO_GL_FLIP; | |
| gf_sc_texture_set_data(&cache->txh); | |
| gf_sc_texture_push_image(&cache->txh, 0, for_3d ? 0 : 1); | |
| cache->orig_vp = tr_state->vp_size; | |
| } | |
| /*just setup the context*/ | |
| else { | |
| if (is_mpeg4) { | |
| #ifndef GPAC_DISABLE_VRML | |
| group_ctx = drawable_init_context_mpeg4(cache->drawable, tr_state); | |
| #endif | |
| } else { | |
| #ifndef GPAC_DISABLE_SVG | |
| group_ctx = drawable_init_context_svg(cache->drawable, tr_state, NULL); | |
| #endif | |
| } | |
| } | |
| if (!group_ctx) return 0; | |
| group_ctx->flags |= CTX_NO_ANTIALIAS; | |
| if (cache->opacity != FIX_ONE) | |
| group_ctx->aspect.fill_color = GF_COL_ARGB_FIXED(cache->opacity, FIX_ONE, FIX_ONE, FIX_ONE); | |
| else | |
| group_ctx->aspect.fill_color = 0; | |
| group_ctx->aspect.fill_texture = &cache->txh; | |
| if (!cache->opacity) { | |
| group_ctx->drawable = NULL; | |
| return 0; | |
| } | |
| drawable_check_texture_dirty(group_ctx, group_ctx->drawable, tr_state); | |
| if (gf_node_dirty_get(node)) group_ctx->flags |= CTX_TEXTURE_DIRTY; | |
| #ifdef CACHE_DEBUG_CENTER | |
| gf_mx2d_copy(backup, tr_state->transform); | |
| gf_mx2d_init(tr_state->transform); | |
| #else | |
| gf_mx2d_copy(backup, tr_state->transform); | |
| if (auto_fit_vp) { | |
| if ((tr_state->vp_size.x != cache->orig_vp.x) || (tr_state->vp_size.y != cache->orig_vp.y)) { | |
| GF_Matrix2D m; | |
| gf_mx2d_init(m); | |
| gf_mx2d_copy(backup, tr_state->transform); | |
| 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) ); | |
| gf_mx2d_pre_multiply(&tr_state->transform, &m); | |
| } else { | |
| auto_fit_vp = 0; | |
| } | |
| } | |
| #endif | |
| #ifndef GPAC_DISABLE_3D | |
| if (tr_state->visual->type_3d) { | |
| if (!cache->drawable->mesh) { | |
| cache->drawable->mesh = new_mesh(); | |
| mesh_from_path(cache->drawable->mesh, cache->drawable->path); | |
| } | |
| visual_3d_draw_from_context(group_ctx, tr_state); | |
| group_ctx->drawable = NULL; | |
| } else | |
| #endif | |
| drawable_finalize_sort(group_ctx, tr_state, NULL); | |
| #ifndef CACHE_DEBUG_CENTER | |
| if (auto_fit_vp) | |
| #endif | |
| { | |
| gf_mx2d_copy(tr_state->transform, backup); | |
| } | |
| return (force_recompute==1); | |
| } | |
| #ifdef GF_SR_USE_VIDEO_CACHE | |
| /*guarentee the tr_state->candidate has the lowest delta value*/ | |
| static void group_cache_insert_entry(GF_Node *node, GroupingNode2D *group, GF_TraverseState *tr_state) | |
| { | |
| u32 i, count; | |
| GF_List *cache_candidates = tr_state->visual->compositor->cached_groups; | |
| GroupingNode2D *current; | |
| current = NULL; | |
| count = gf_list_count(cache_candidates); | |
| for (i=0; i<count; i++) { | |
| current = gf_list_get(cache_candidates, i); | |
| /*if entry's priority is higher than our group, insert our group here*/ | |
| if (current->priority >= group->priority) { | |
| gf_list_insert(cache_candidates, group, i); | |
| break; | |
| } | |
| } | |
| if (i==count) | |
| gf_list_add(cache_candidates, group); | |
| tr_state->visual->compositor->video_cache_current_size += group->cached_size; | |
| /*log the information*/ | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE]\tAdding object %s\tObjects: %d\tSlope: %g\tSize: %d\tTime: %d\n", | |
| gf_node_get_log_name(node), | |
| group->nb_objects, | |
| FIX2FLT(group->priority), | |
| group->cached_size, | |
| group->traverse_time)); | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Status (KB): Max: %d\tUsed: %d\tNb Groups: %d\n", | |
| tr_state->visual->compositor->vcsize, | |
| tr_state->visual->compositor->video_cache_current_size, | |
| gf_list_count(tr_state->visual->compositor->cached_groups) | |
| )); | |
| } | |
| static Bool gf_cache_remove_entry(GF_Compositor *compositor, GF_Node *node, GroupingNode2D *group) | |
| { | |
| u32 bytes_remove = 0; | |
| GF_List *cache_candidates = compositor->cached_groups; | |
| /*auto mode*/ | |
| if (!group) { | |
| group = gf_list_get(cache_candidates, 0); | |
| if (!group) return 0; | |
| /*remove entry*/ | |
| gf_list_rem(cache_candidates, 0); | |
| node = NULL; | |
| } else { | |
| /*remove entry if present*/ | |
| if (gf_list_del_item(cache_candidates, group)<0) | |
| return 0; | |
| } | |
| /*disable the caching flag of the group if it was marked as such*/ | |
| if(group->flags & GROUP_IS_CACHABLE) { | |
| group->flags &= ~GROUP_IS_CACHABLE; | |
| /*the discarded bytes*/ | |
| bytes_remove = group->cached_size; | |
| } | |
| /*indicates cache destruction for next frame*/ | |
| if (group->cache && (group->flags & GROUP_IS_CACHED)) { | |
| group->flags &= ~GROUP_IS_CACHED; | |
| /*the discarded bytes*/ | |
| bytes_remove = group->cached_size; | |
| } | |
| if (bytes_remove == 0) return 0; | |
| gf_fatal_assert(compositor->video_cache_current_size >= bytes_remove); | |
| compositor->video_cache_current_size -= bytes_remove; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Removing cache %s:\t Objects: %d\tSlope: %g\tBytes: %d\tTime: %d\n", | |
| gf_node_get_log_name(node), | |
| group->nb_objects, | |
| FIX2FLT(group->priority), | |
| group->cached_size, | |
| FIX2FLT(group->traverse_time))); | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Status (B): Max: %d\tUsed: %d\tNb Groups: %d\n", | |
| compositor->vcsize, | |
| compositor->video_cache_current_size, | |
| gf_list_count(compositor->cached_groups) | |
| )); | |
| return 1; | |
| } | |
| /**/ | |
| Bool group_2d_cache_traverse(GF_Node *node, GroupingNode2D *group, GF_TraverseState *tr_state) | |
| { | |
| Bool is_dirty = gf_node_dirty_get(node) & GF_SG_CHILD_DIRTY; | |
| Bool zoom_changed = tr_state->visual->compositor->zoom_changed; | |
| Bool needs_recompute = 0; | |
| /*we are currently in a group cache, regular traversing*/ | |
| if (tr_state->in_group_cache) return 0; | |
| /*draw mode*/ | |
| if (tr_state->traversing_mode == TRAVERSE_DRAW_2D) { | |
| /*shall never happen*/ | |
| gf_fatal_assert(group->cache); | |
| /*draw it*/ | |
| group_cache_draw(group->cache, tr_state); | |
| return 1; | |
| } | |
| /*other modes than sorting, use regular traversing*/ | |
| if (tr_state->traversing_mode != TRAVERSE_SORT) return 0; | |
| /*this is not an offscreen group*/ | |
| if (!(group->flags & GROUP_IS_CACHED) ) { | |
| Bool cache_on = 0; | |
| /*group cache has been turned on in the previous frame*/ | |
| if (!is_dirty && (group->flags & GROUP_IS_CACHABLE)) { | |
| group->flags |= GROUP_IS_CACHED; | |
| group->flags &= ~GROUP_IS_CACHABLE; | |
| 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 )); | |
| cache_on = 1; | |
| } | |
| /*group cache has been turned off in the previous frame*/ | |
| else if (group->cache) { | |
| group_cache_del(group->cache); | |
| group->cache = NULL; | |
| group->changed = is_dirty; | |
| group->nb_stats_frame = 0; | |
| group->traverse_time = 0; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Turning group %s cache off\n", gf_node_get_log_name(node) )); | |
| return 0; | |
| } | |
| if (!cache_on) { | |
| if (is_dirty) { | |
| group->changed = 1; | |
| } | |
| /*ask for stats again*/ | |
| else if (group->changed) { | |
| group->changed = 0; | |
| group->nb_stats_frame = 0; | |
| group->traverse_time = 0; | |
| } else if (zoom_changed) { | |
| group->nb_stats_frame = 0; | |
| group->traverse_time = 0; | |
| } | |
| if (is_dirty || (group->nb_stats_frame < NUM_STATS_FRAMES)) { | |
| /*force direct draw mode*/ | |
| if (!is_dirty) | |
| tr_state->visual->compositor->traverse_state->invalidate_all = 1; | |
| /*force redraw*/ | |
| tr_state->visual->compositor->draw_next_frame = 1; | |
| } | |
| return 0; | |
| } | |
| } | |
| /*cache is dirty*/ | |
| else if (is_dirty) { | |
| /*permanent cache, just recompute*/ | |
| if (group->flags & GROUP_PERMANENT_CACHE) { | |
| group->changed = 1; | |
| group->cache->force_recompute = 1; | |
| } | |
| /*otherwise destroy the cache*/ | |
| else if (group->cache) { | |
| gf_cache_remove_entry(tr_state->visual->compositor, node, group); | |
| group_cache_del(group->cache); | |
| group->cache = NULL; | |
| group->flags &= ~GROUP_IS_CACHED; | |
| group->changed = 0; | |
| group->nb_stats_frame = 0; | |
| group->traverse_time = 0; | |
| 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) )); | |
| return 0; | |
| } | |
| } | |
| /*zoom has changed*/ | |
| else if (zoom_changed) { | |
| /*permanent cache, just recompute*/ | |
| if (group->flags & GROUP_PERMANENT_CACHE) { | |
| group->changed = 1; | |
| group->cache->force_recompute = 1; | |
| } | |
| /*otherwise check if we accept this scale ratio or if we must recompute*/ | |
| else if (group->cache) { | |
| Fixed scale = MAX(tr_state->transform.m[0], tr_state->transform.m[4]); | |
| if (100*scale >= group->cache->scale*(100 + tr_state->visual->compositor->vctol)) | |
| zoom_changed = 1; | |
| else if ((100+tr_state->visual->compositor->vctol)*scale <= 100*group->cache->scale) | |
| zoom_changed = 1; | |
| else | |
| zoom_changed = 0; | |
| if (zoom_changed) { | |
| gf_cache_remove_entry(tr_state->visual->compositor, node, group); | |
| group_cache_del(group->cache); | |
| group->cache = NULL; | |
| group->flags &= ~GROUP_IS_CACHED; | |
| group->changed = 0; | |
| group->nb_stats_frame = 0; | |
| group->traverse_time = 0; | |
| 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) )); | |
| return 0; | |
| } | |
| } | |
| } | |
| /*keep track of this cache object for later removal*/ | |
| if (!(group->flags & GROUP_PERMANENT_CACHE)) | |
| gf_list_add(tr_state->visual->compositor->cached_groups_queue, group); | |
| if (!group->cache) { | |
| /*ALLOCATE THE CACHE*/ | |
| group->cache = group_cache_new(tr_state->visual->compositor, node); | |
| needs_recompute = 1; | |
| } | |
| /*cache has been modified due to node changes, reset stats*/ | |
| group_cache_traverse(node, group->cache, tr_state, needs_recompute, 1, 0); | |
| return 1; | |
| } | |
| Bool group_cache_compute_stats(GF_Node *node, GroupingNode2D *group, GF_TraverseState *tr_state, DrawableContext *first_child, Bool skip_first_child) | |
| { | |
| GF_Rect group_bounds; | |
| DrawableContext *ctx; | |
| u32 nb_segments, nb_objects; | |
| u32 alpha_pixels, opaque_pixels, area_world; | |
| u32 vcsize, cache_size, prev_cache_size; | |
| u32 i; | |
| GF_RectArray ra; | |
| /*compute stats*/ | |
| nb_objects = 0; | |
| nb_segments = 0; | |
| alpha_pixels = opaque_pixels = 0; | |
| prev_cache_size = group->cached_size; | |
| /*reset bounds*/ | |
| group_bounds.width = group_bounds.height = 0; | |
| vcsize = tr_state->visual->compositor->vcsize; | |
| /*never cache root node - this should be refined*/ | |
| if (gf_node_get_parent(node, 0) == NULL) goto group_reject; | |
| if (!group->traverse_time) goto group_reject; | |
| ra_init(&ra); | |
| ctx = first_child; | |
| if (!first_child) ctx = tr_state->visual->context; | |
| if (skip_first_child) ctx = ctx->next; | |
| /*compute properties for the sub display list*/ | |
| while (ctx && ctx->drawable) { | |
| //Fixed area; | |
| u32 alpha_comp; | |
| /*get area and compute alpha/opaque coverage*/ | |
| alpha_comp = GF_COL_A(ctx->aspect.fill_color); | |
| /*add to group area*/ | |
| gf_rect_union(&group_bounds, &ctx->bi->unclip); | |
| nb_objects++; | |
| /*no alpha*/ | |
| if ((alpha_comp==0xFF) | |
| /*no transparent texture*/ | |
| && (!ctx->aspect.fill_texture || !ctx->aspect.fill_texture->transparent) | |
| ) { | |
| ra_union_rect(&ra, &ctx->bi->clip); | |
| } | |
| nb_segments += ctx->drawable->path->n_points; | |
| ctx = ctx->next; | |
| } | |
| if ( | |
| /*TEST 1: discard visually empty groups*/ | |
| (!group_bounds.width || !group_bounds.height) | |
| || | |
| /*TEST 2: discard small groups*/ | |
| (nb_objects<MIN_OBJECTS_IN_CACHE) | |
| || | |
| /*TEST 3: low complexity group*/ | |
| (nb_segments && (nb_segments<10)) | |
| ) { | |
| ra_del(&ra); | |
| goto group_reject; | |
| } | |
| ra_refresh(&ra); | |
| opaque_pixels = 0; | |
| for (i=0; i<ra.count; i++) { | |
| opaque_pixels += ra.list[i].width * ra.list[i].height; | |
| } | |
| ra_del(&ra); | |
| /*get coverage in world coords*/ | |
| area_world = FIX2INT(group_bounds.width) * FIX2INT(group_bounds.height); | |
| /*TEST 4: discard low coverage groups in world coords (plenty of space wasted) | |
| we consider that this % of the area is actually drawn - this is of course wrong, | |
| we would need to compute each path coverage in local coords then get the ratio | |
| */ | |
| if (10*opaque_pixels < 7*area_world) goto group_reject; | |
| /*the memory size allocated for the cache - cache is drawn in final coordinate system !!*/ | |
| group_bounds.width = tr_state->visual->compositor->vcscale * group_bounds.width / 100; | |
| group_bounds.height = tr_state->visual->compositor->vcscale * group_bounds.height / 100; | |
| cache_size = FIX2INT(group_bounds.width) * FIX2INT(group_bounds.height) * 4 /* pixelFormat is ARGB*/; | |
| /*TEST 5: cache is less than 10x10 pixels: discard*/ | |
| if (cache_size < 400) goto group_reject; | |
| /*TEST 6: cache is larger than our allowed memory: discard*/ | |
| if (cache_size>=vcsize) { | |
| tr_state->cache_too_small = 1; | |
| goto group_reject; | |
| } | |
| /*compute the delta value for measuring the group importance for later discard | |
| (avg_time - Tcache) / (size_cache - drawable_gain) | |
| */ | |
| group->priority = INT2FIX(nb_objects*1024*group->traverse_time) / cache_size / group->nb_stats_frame; | |
| /*OK, group is a good candidate for caching*/ | |
| group->nb_objects = nb_objects; | |
| group->cached_size = cache_size; | |
| /*we're moving from non-cached to cached*/ | |
| if (!(group->flags & GROUP_IS_CACHABLE)) { | |
| group->flags |= GROUP_IS_CACHABLE; | |
| tr_state->visual->compositor->draw_next_frame = 1; | |
| /*insert the candidate and then update the list in order*/ | |
| group_cache_insert_entry(node, group, tr_state); | |
| /*keep track of this cache object for later removal*/ | |
| gf_list_add(tr_state->visual->compositor->cached_groups_queue, group); | |
| 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 )); | |
| } | |
| /*update memory occupation*/ | |
| else { | |
| tr_state->visual->compositor->video_cache_current_size -= prev_cache_size; | |
| tr_state->visual->compositor->video_cache_current_size += group->cached_size; | |
| if (group->cache) | |
| group->cache->force_recompute = 1; | |
| } | |
| return 1; | |
| group_reject: | |
| group->nb_objects = nb_objects; | |
| if ((group->flags & GROUP_IS_CACHABLE) || group->cache) { | |
| group->flags &= ~GROUP_IS_CACHABLE; | |
| if (group->cache) { | |
| group_cache_del(group->cache); | |
| group->cache = NULL; | |
| group->flags &= ~GROUP_IS_CACHED; | |
| } | |
| gf_list_del_item(tr_state->visual->compositor->cached_groups, group); | |
| tr_state->visual->compositor->video_cache_current_size -= cache_size; | |
| } | |
| #if 0 | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] REJECT %s\tObjects: %d\tSlope: %g\tBytes: %d\tTime: %d\n", | |
| gf_node_get_log_name(node), | |
| group->nb_objects, | |
| FIX2FLT(group->priority), | |
| group->cached_size, | |
| group->traverse_time | |
| )); | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Status (B): Max: %d\tUsed: %d\tNb Groups: %d\n", | |
| tr_state->visual->compositor->vcsize, | |
| tr_state->visual->compositor->video_cache_current_size, | |
| gf_list_count(tr_state->visual->compositor->cached_groups) | |
| )); | |
| #endif | |
| return 0; | |
| } | |
| 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) | |
| { | |
| u32 nb_cache_added, i; | |
| GF_Compositor *compositor = tr_state->visual->compositor; | |
| /*first frame is unusable for stats because lot of time is being spent building the path and allocating | |
| the drawable contexts*/ | |
| if (!compositor->vcsize || !compositor->frame_number || group->changed || tr_state->in_group_cache) { | |
| group->traverse_time = 0; | |
| return; | |
| } | |
| if (group->nb_stats_frame < NUM_STATS_FRAMES) { | |
| group->nb_stats_frame++; | |
| tr_state->visual->compositor->draw_next_frame = 1; | |
| return; | |
| } | |
| if (group->nb_stats_frame > NUM_STATS_FRAMES) return; | |
| group->nb_stats_frame++; | |
| /*the way to produce the result of memory-computation optimization*/ | |
| if (group_cache_compute_stats(node, group, tr_state, first_child, skip_first_child)) { | |
| Fixed avg_time; | |
| nb_cache_added = gf_list_count(compositor->cached_groups_queue) - last_cache_idx - 1; | |
| /*force redraw*/ | |
| tr_state->visual->compositor->draw_next_frame = 1; | |
| /*update priority by adding cache priorities */ | |
| avg_time = group->priority * group->cached_size / (1024*group->nb_objects); | |
| /*remove all queued cached groups of this node's children*/ | |
| for (i=0; i<nb_cache_added; i++) { | |
| Fixed cache_time; | |
| GroupingNode2D *cache = gf_list_get(compositor->cached_groups_queue, last_cache_idx); | |
| /*we have been computed the prioirity of the group using a cached subtree, update | |
| the priority to reflect that the new cache won't use a cached subtree*/ | |
| if (cache->cache) { | |
| /*fixme - this assumes cache draw time is 0*/ | |
| cache_time = cache->priority * cache->cached_size / (1024*group->nb_objects); | |
| avg_time += cache_time; | |
| } | |
| gf_cache_remove_entry(compositor, NULL, cache); | |
| cache->nb_stats_frame = 0; | |
| cache->traverse_time = 0; | |
| gf_list_rem(compositor->cached_groups_queue, last_cache_idx); | |
| } | |
| group->priority = INT2FIX (group->nb_objects*1024*1024*avg_time) / group->cached_size; | |
| /*when the memory exceeds the constraint, remove the subgroups that have the lowest deltas*/ | |
| while (compositor->video_cache_current_size > compositor->vcsize) { | |
| gf_cache_remove_entry(compositor, node, NULL); | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CACHE, ("[CACHE] Removing low priority cache - current total size %d\n", compositor->video_cache_current_size)); | |
| } | |
| } | |
| } | |
| void compositor_set_cache_memory(GF_Compositor *compositor, u32 memory) | |
| { | |
| /*when the memory exceeds the constraint, remove the subgroups that have the lowest deltas*/ | |
| while (compositor->video_cache_current_size) { | |
| gf_cache_remove_entry(compositor, NULL, NULL); | |
| } | |
| compositor->vcsize = memory; | |
| /*and force recompute*/ | |
| compositor->zoom_changed = 1; | |
| } | |
| #endif /*GF_SR_USE_VIDEO_CACHE*/ | |
| void group_2d_destroy_svg(GF_Node *node, GroupingNode2D *group) | |
| { | |
| #ifdef GF_SR_USE_VIDEO_CACHE | |
| GF_Compositor *compositor = gf_sc_get_compositor(node); | |
| if (gf_cache_remove_entry(compositor, node, group)) { | |
| /*simulate a zoom changed for cache recompute*/ | |
| compositor->zoom_changed = 1; | |
| compositor->draw_next_frame = 1; | |
| } | |
| if (group->cache) group_cache_del(group->cache); | |
| #endif | |
| } | |
| void group_2d_destroy(GF_Node *node, GroupingNode2D *group) | |
| { | |
| group_2d_destroy_svg(node, group); | |
| if (group->sensors) gf_list_del(group->sensors); | |
| } | |
| #endif //!defined(GPAC_DISABLE_COMPOSITOR) |