| /* | |
| * GPAC - Multimedia Framework C SDK | |
| * | |
| * Authors: Jean Le Feuvre | |
| * Copyright (c) Telecom ParisTech 2000-2026 | |
| * 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 <gpac/internal/compositor_dev.h> | |
| #include <gpac/utf.h> | |
| #include <gpac/modules/hardcoded_proto.h> | |
| #include <gpac/modules/compositor_ext.h> | |
| #ifndef GPAC_DISABLE_COMPOSITOR | |
| #include "nodes_stacks.h" | |
| #include "visual_manager.h" | |
| #include "texturing.h" | |
| static void gf_sc_recompute_ar(GF_Compositor *compositor, GF_Node *top_node); | |
| #define SC_DEF_WIDTH 320 | |
| #define SC_DEF_HEIGHT 240 | |
| GF_EXPORT | |
| Bool gf_sc_send_event(GF_Compositor *compositor, GF_Event *evt) | |
| { | |
| return gf_filter_forward_gf_event(compositor->filter, evt, GF_FALSE, GF_FALSE); | |
| } | |
| void gf_sc_next_frame_state(GF_Compositor *compositor, u32 state) | |
| { | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Forcing frame redraw state: %d\n", state)); | |
| if (state==GF_SC_DRAW_FLUSH) { | |
| if (!compositor->skip_flush) | |
| compositor->skip_flush = 2; | |
| //if in OpenGL mode ignore refresh events (content of the window is still OK). This is only used for overlays in 2d | |
| if (!compositor->frame_draw_type | |
| #ifndef GPAC_DISABLE_3D | |
| && !compositor->visual->type_3d && !compositor->hybrid_opengl | |
| #endif | |
| ) { | |
| compositor->frame_draw_type = state; | |
| } | |
| } else { | |
| compositor->frame_draw_type = state; | |
| } | |
| } | |
| static void gf_sc_set_fullscreen(GF_Compositor *compositor) | |
| { | |
| GF_Err e; | |
| if (!compositor->video_out->SetFullScreen) return; | |
| GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Switching fullscreen %s\n", compositor->fullscreen ? "off" : "on")); | |
| /*move to FS*/ | |
| compositor->fullscreen = !compositor->fullscreen; | |
| //gf_sc_ar_control(compositor->audio_renderer, GF_SC_AR_PAUSE); | |
| //in windows (and other?) we may get blocked by SetWindowPos in the fullscreen method until another window thread dispatches a resize event, | |
| //which would try to grab the compositor mutex and thus deadlock us | |
| //to avoid this, unlock the compositor just for the SetFullscreen | |
| gf_mx_v(compositor->mx); | |
| if (compositor->fullscreen && (compositor->scene_width>=compositor->scene_height) | |
| #ifndef GPAC_DISABLE_3D | |
| && !compositor->visual->type_3d | |
| #endif | |
| ) { | |
| e = compositor->video_out->SetFullScreen(compositor->video_out, 2, &compositor->display_width, &compositor->display_height); | |
| } else { | |
| e = compositor->video_out->SetFullScreen(compositor->video_out, compositor->fullscreen, &compositor->display_width, &compositor->display_height); | |
| } | |
| gf_mx_p(compositor->mx); | |
| //gf_sc_ar_control(compositor->audio_renderer, GF_SC_AR_RESUME); | |
| if (e) { | |
| GF_Event evt; | |
| memset(&evt, 0, sizeof(GF_Event)); | |
| evt.type = GF_EVENT_MESSAGE; | |
| evt.message.message = "Cannot switch to fullscreen"; | |
| evt.message.error = e; | |
| gf_sc_send_event(compositor, &evt); | |
| compositor->fullscreen = 0; | |
| compositor->video_out->SetFullScreen(compositor->video_out, 0, &compositor->display_width, &compositor->display_height); | |
| } | |
| GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] recomputing aspect ratio\n")); | |
| compositor->recompute_ar = 1; | |
| /*force signaling graphics reset*/ | |
| if (!compositor->reset_graphics) gf_sc_reset_graphics(compositor); | |
| } | |
| /*this is needed for: | |
| - audio: since the audio compositor may not be threaded, it must be reconfigured by another thread otherwise | |
| we lock the audio module | |
| - video: this is typical to OpenGL&co: multithreaded is forbidden, so resizing/fullscreen MUST be done by the same | |
| thread accessing the HW resources | |
| */ | |
| static void gf_sc_reconfig_task(GF_Compositor *compositor) | |
| { | |
| GF_Event evt; | |
| Bool notif_size=GF_FALSE; | |
| u32 width,height; | |
| /*reconfig audio if needed (non-threaded compositors)*/ | |
| if (compositor->audio_renderer | |
| #ifdef ENABLE_AOUT | |
| && !compositor->audio_renderer->th | |
| #endif | |
| ) | |
| if (compositor->msg_type) { | |
| compositor->msg_type |= GF_SR_IN_RECONFIG; | |
| if (compositor->msg_type & GF_SR_CFG_INITIAL_RESIZE) { | |
| memset(&evt, 0, sizeof(GF_Event)); | |
| evt.type = GF_EVENT_VIDEO_SETUP; | |
| evt.setup.width = compositor->new_width; | |
| evt.setup.height = compositor->new_height; | |
| evt.setup.system_memory = compositor->video_memory ? GF_FALSE : GF_TRUE; | |
| evt.setup.disable_vsync = compositor->bench_mode ? GF_TRUE : GF_FALSE; | |
| #ifndef GPAC_DISABLE_3D | |
| if (compositor->hybrid_opengl || compositor->force_opengl_2d) { | |
| evt.setup.use_opengl = GF_TRUE; | |
| evt.setup.system_memory = GF_FALSE; | |
| evt.setup.back_buffer = GF_TRUE; | |
| } | |
| #endif | |
| compositor->video_out->ProcessEvent(compositor->video_out, &evt); | |
| if (evt.setup.use_opengl) { | |
| gf_opengl_init(); | |
| } | |
| compositor->msg_type &= ~GF_SR_CFG_INITIAL_RESIZE; | |
| } | |
| /*scene size has been overridden*/ | |
| if (compositor->msg_type & GF_SR_CFG_OVERRIDE_SIZE) { | |
| gf_assert(!(compositor->override_size_flags & 2)); | |
| compositor->msg_type &= ~GF_SR_CFG_OVERRIDE_SIZE; | |
| compositor->override_size_flags |= 2; | |
| width = compositor->scene_width; | |
| height = compositor->scene_height; | |
| compositor->has_size_info = 1; | |
| gf_sc_set_size(compositor, width, height); | |
| evt.type = GF_EVENT_SIZE; | |
| evt.size.width = width; | |
| evt.size.height = height; | |
| gf_sc_send_event(compositor, &evt); | |
| } | |
| /*size changed from scene cfg: resize window first*/ | |
| if (compositor->msg_type & GF_SR_CFG_SET_SIZE) { | |
| u32 fs_width, fs_height; | |
| Bool restore_fs = compositor->fullscreen; | |
| GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Changing display size to %d x %d\n", compositor->new_width, compositor->new_height)); | |
| fs_width = fs_height = 0; | |
| if (restore_fs) { | |
| #if defined(GPAC_CONFIG_ANDROID) || defined(GPAC_CONFIG_IOS) | |
| if ((compositor->new_width>compositor->display_width) || (compositor->new_height>compositor->display_height)) { | |
| u32 w = compositor->display_width; | |
| compositor->display_width = compositor->display_height; | |
| compositor->display_height = w; | |
| compositor->recompute_ar = 1; | |
| } | |
| #endif | |
| fs_width = compositor->display_width; | |
| fs_height = compositor->display_height; | |
| } | |
| evt.type = GF_EVENT_SIZE; | |
| evt.size.width = compositor->new_width; | |
| evt.size.height = compositor->new_height; | |
| /*send resize event*/ | |
| if ( !(compositor->msg_type & GF_SR_CFG_WINDOWSIZE_NOTIF)) { | |
| compositor->video_out->ProcessEvent(compositor->video_out, &evt); | |
| } | |
| compositor->msg_type &= ~GF_SR_CFG_WINDOWSIZE_NOTIF; | |
| if (restore_fs) { | |
| if ((compositor->display_width != fs_width) || (compositor->display_height != fs_height)) { | |
| compositor->display_width = fs_width; | |
| compositor->display_height = fs_height; | |
| compositor->recompute_ar = 1; | |
| } | |
| } else { | |
| compositor->display_width = evt.size.width; | |
| compositor->display_height = evt.size.height; | |
| compositor->recompute_ar = 1; | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| } | |
| notif_size=1; | |
| compositor->new_width = compositor->new_height = 0; | |
| compositor->msg_type &= ~GF_SR_CFG_SET_SIZE; | |
| GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Display size changed to %d x %d\n", compositor->new_width, compositor->new_height)); | |
| } | |
| /*aspect ratio modif*/ | |
| if (compositor->msg_type & GF_SR_CFG_AR) { | |
| compositor->msg_type &= ~GF_SR_CFG_AR; | |
| compositor->recompute_ar = 1; | |
| } | |
| /*fullscreen on/off request*/ | |
| if (compositor->msg_type & GF_SR_CFG_FULLSCREEN) { | |
| compositor->msg_type &= ~GF_SR_CFG_FULLSCREEN; | |
| //video is about to resetup, wait for the setup | |
| if (compositor->recompute_ar) { | |
| compositor->fullscreen_postponed = 1; | |
| } else { | |
| gf_sc_set_fullscreen(compositor); | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| notif_size=1; | |
| } | |
| } | |
| compositor->msg_type &= ~GF_SR_IN_RECONFIG; | |
| } | |
| if (notif_size && compositor->vout) { | |
| gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_WIDTH, &PROP_UINT(compositor->display_width)); | |
| gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_HEIGHT, &PROP_UINT(compositor->display_height)); | |
| gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_MIRROR, compositor->is_opengl ? &PROP_UINT(1) : NULL ); | |
| } | |
| /*3D driver changed message, recheck extensions*/ | |
| if (compositor->reset_graphics) { | |
| #ifndef GPAC_DISABLE_3D | |
| compositor->offscreen_width = compositor->offscreen_height = 0; | |
| #endif | |
| gf_sc_lock(compositor, 0); | |
| evt.type = GF_EVENT_SYS_COLORS; | |
| if (compositor->video_out->ProcessEvent(compositor->video_out, &evt) ) { | |
| u32 i; | |
| for (i=0; i<28; i++) { | |
| compositor->sys_colors[i] = evt.sys_cols.sys_colors[i] & 0x00FFFFFF; | |
| } | |
| } | |
| gf_sc_lock(compositor, 1); | |
| } | |
| } | |
| static void gf_sc_frame_ifce_done(GF_Filter *filter, GF_FilterPid *pid, GF_FilterPacket *pck) | |
| { | |
| GF_FilterFrameInterface *frame_ifce = gf_filter_pck_get_frame_interface(pck); | |
| GF_Compositor *compositor = gf_filter_get_udta(filter); | |
| if (frame_ifce) { | |
| if (compositor->fb.video_buffer) { | |
| gf_sc_release_screen_buffer(compositor, &compositor->fb); | |
| compositor->fb.video_buffer = NULL; | |
| } | |
| } | |
| compositor->frame_ifce.user_data = NULL; | |
| compositor->flush_pending = (compositor->skip_flush!=1) ? GF_TRUE : GF_FALSE; | |
| compositor->skip_flush = 0; | |
| } | |
| GF_Err gf_sc_frame_ifce_get_plane(GF_FilterFrameInterface *frame_ifce, u32 plane_idx, const u8 **outPlane, u32 *outStride) | |
| { | |
| GF_Err e = GF_BAD_PARAM; | |
| GF_Compositor *compositor = frame_ifce->user_data; | |
| if (plane_idx==0) { | |
| e = GF_OK; | |
| if (!compositor->fb.video_buffer) | |
| e = gf_sc_get_screen_buffer(compositor, &compositor->fb, GF_SC_GRAB_DEPTH_NONE); | |
| } | |
| *outPlane = compositor->fb.video_buffer; | |
| *outStride = compositor->fb.pitch_y; | |
| return e; | |
| } | |
| #ifndef GPAC_DISABLE_3D | |
| GF_Err gf_sc_frame_ifce_get_gl_texture(GF_FilterFrameInterface *frame_ifce, u32 plane_idx, u32 *gl_tex_format, u32 *gl_tex_id, GF_Matrix_unexposed * texcoordmatrix) | |
| { | |
| GF_Compositor *compositor = frame_ifce->user_data; | |
| if (!compositor->fbo_tx_id) return GF_BAD_PARAM; | |
| if (plane_idx) return GF_BAD_PARAM; | |
| if (gl_tex_id) *gl_tex_id = compositor->fbo_tx_id; | |
| if (gl_tex_format) *gl_tex_format = compositor_3d_get_fbo_pixfmt(); | |
| //framebuffer is already oriented as a GL texture not as an image | |
| if (texcoordmatrix) | |
| gf_mx_add_scale(texcoordmatrix, FIX_ONE, -FIX_ONE, FIX_ONE); | |
| return GF_OK; | |
| } | |
| #endif | |
| static void gf_sc_flush_video(GF_Compositor *compositor, Bool locked) | |
| { | |
| GF_Window rc; | |
| //release compositor in case we have vsync | |
| if (locked) gf_sc_lock(compositor, 0); | |
| rc.x = rc.y = 0; | |
| rc.w = compositor->display_width; | |
| rc.h = compositor->display_height; | |
| compositor->video_out->Flush(compositor->video_out, &rc); | |
| compositor->flush_pending = GF_FALSE; | |
| if (locked) gf_sc_lock(compositor, 1); | |
| } | |
| void gf_sc_render_frame(GF_Compositor *compositor); | |
| GF_EXPORT | |
| Bool gf_sc_draw_frame(GF_Compositor *compositor, Bool no_flush, s32 *ms_till_next) | |
| { | |
| Bool ret = GF_FALSE; | |
| gf_sc_ar_send_or_reconfig(compositor->audio_renderer); | |
| if (!compositor->vout) return GF_FALSE; | |
| //frame still pending | |
| if (compositor->frame_ifce.user_data) | |
| return GF_TRUE; | |
| if (compositor->flush_pending) { | |
| gf_sc_flush_video(compositor, GF_FALSE); | |
| } | |
| compositor->skip_flush = no_flush ? 1 : 0; | |
| gf_sc_render_frame(compositor); | |
| if (ms_till_next) { | |
| if ( compositor->ms_until_next_frame == GF_INT_MAX) | |
| *ms_till_next = compositor->frame_duration; | |
| else | |
| *ms_till_next = compositor->ms_until_next_frame; | |
| } | |
| //next frame is late, we should redraw | |
| if (compositor->ms_until_next_frame < 0) ret = GF_TRUE; | |
| else if (compositor->frame_draw_type) ret = GF_TRUE; | |
| else if (compositor->fonts_pending>0) ret = GF_TRUE; | |
| return ret; | |
| } | |
| /*forces graphics redraw*/ | |
| GF_EXPORT | |
| void gf_sc_reset_graphics(GF_Compositor *compositor) | |
| { | |
| if (compositor) { | |
| Bool locked = gf_mx_try_lock(compositor->mx); | |
| compositor->reset_graphics = 1; | |
| if (locked) gf_mx_v(compositor->mx); | |
| } | |
| } | |
| static void gf_sc_reset_framerate(GF_Compositor *compositor) | |
| { | |
| u32 i; | |
| for (i=0; i<GF_SR_FPS_COMPUTE_SIZE; i++) compositor->frame_time[i] = compositor->frame_dur[i] = 0; | |
| compositor->current_frame = 0; | |
| } | |
| enum | |
| { | |
| GF_COMPOSITOR_THREAD_START = 0, | |
| GF_COMPOSITOR_THREAD_RUN, | |
| GF_COMPOSITOR_THREAD_ABORTING, | |
| GF_COMPOSITOR_THREAD_DONE, | |
| GF_COMPOSITOR_THREAD_INIT_FAILED, | |
| }; | |
| static GF_Err nullvout_setup(struct _video_out *vout, void *os_handle, void *os_display, u32 init_flags) | |
| { | |
| return GF_OK; | |
| } | |
| static void nullvout_shutdown(struct _video_out *vout) | |
| { | |
| return; | |
| } | |
| static GF_Err nullvout_flush(struct _video_out *vout, GF_Window *dest) | |
| { | |
| return GF_OK; | |
| } | |
| static GF_Err nullvout_fullscreen(struct _video_out *vout, Bool fs_on, u32 *new_disp_width, u32 *new_disp_height) | |
| { | |
| return GF_OK; | |
| } | |
| static GF_Err nullvout_evt(struct _video_out *vout, GF_Event *event) | |
| { | |
| return GF_OK; | |
| } | |
| static GF_Err nullvout_lock(struct _video_out *vout, GF_VideoSurface *video_info, Bool do_lock) | |
| { | |
| return GF_IO_ERR; | |
| } | |
| static GF_Err nullvout_blit(struct _video_out *vout, GF_VideoSurface *video_src, GF_Window *src_wnd, GF_Window *dst_wnd, u32 overlay_type) | |
| { | |
| return GF_OK; | |
| } | |
| GF_VideoOutput null_vout = { | |
| .Setup = nullvout_setup, | |
| .Shutdown = nullvout_shutdown, | |
| .Flush = nullvout_flush, | |
| .SetFullScreen = nullvout_fullscreen, | |
| .ProcessEvent = nullvout_evt, | |
| .LockBackBuffer = nullvout_lock, | |
| .Blit = nullvout_blit, | |
| .hw_caps = GF_VIDEO_HW_HAS_RGB | GF_VIDEO_HW_HAS_RGBA | GF_VIDEO_HW_HAS_YUV | GF_VIDEO_HW_HAS_YUV_OVERLAY | GF_VIDEO_HW_HAS_STRETCH | |
| }; | |
| static GF_Err gl_vout_evt(struct _video_out *vout, GF_Event *evt) | |
| { | |
| GF_Compositor *compositor = (GF_Compositor *)vout->opaque; | |
| if (!evt || (evt->type != GF_EVENT_VIDEO_SETUP)) return GF_OK; | |
| if (!compositor->player && (compositor->passthrough_pfmt != GF_PIXEL_RGB)) { | |
| u32 pfmt = compositor->forced_alpha ? GF_PIXEL_RGBA : GF_PIXEL_RGB; | |
| compositor->passthrough_pfmt = pfmt; | |
| compositor->opfmt = pfmt; | |
| if (compositor->vout) { | |
| u32 stride = compositor->output_width * ( (pfmt == GF_PIXEL_RGBA) ? 4 : 3 ); | |
| gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_PIXFMT, &PROP_UINT(pfmt)); | |
| gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_STRIDE, &PROP_UINT(stride)); | |
| } | |
| } | |
| #ifndef GPAC_DISABLE_3D | |
| return compositor_3d_setup_fbo(evt->setup.width, evt->setup.height, &compositor->fbo_id, &compositor->fbo_tx_id, &compositor->fbo_depth_id); | |
| #else | |
| return GF_NOT_SUPPORTED; | |
| #endif | |
| } | |
| GF_VideoOutput gl_vout = { | |
| .Setup = nullvout_setup, | |
| .Shutdown = nullvout_shutdown, | |
| .Flush = nullvout_flush, | |
| .SetFullScreen = nullvout_fullscreen, | |
| .ProcessEvent = gl_vout_evt, | |
| .LockBackBuffer = NULL, | |
| .Blit = NULL, | |
| .hw_caps = GF_VIDEO_HW_OPENGL | |
| }; | |
| static GF_Err rawvout_lock(struct _video_out *vout, GF_VideoSurface *vi, Bool do_lock) | |
| { | |
| GF_Compositor *compositor = (GF_Compositor *)vout->opaque; | |
| if (do_lock) { | |
| u32 pfmt; | |
| if (!vi) return GF_BAD_PARAM; | |
| pfmt = compositor->opfmt; | |
| if (!pfmt && compositor->passthrough_txh) pfmt = compositor->passthrough_txh->pixelformat; | |
| if (!pfmt) { | |
| pfmt = compositor->forced_alpha ? GF_PIXEL_RGBA : GF_PIXEL_RGB; | |
| } | |
| memset(vi, 0, sizeof(GF_VideoSurface)); | |
| vi->width = compositor->display_width; | |
| vi->height = compositor->display_height; | |
| gf_pixel_get_size_info(pfmt, compositor->display_width, compositor->display_height, NULL, &vi->pitch_y, NULL, NULL, NULL); | |
| if (compositor->passthrough_txh && !compositor->passthrough_txh->frame_ifce && (pfmt == compositor->passthrough_txh->pixelformat)) { | |
| if (!compositor->passthrough_pck) { | |
| compositor->passthrough_pck = gf_filter_pck_new_clone(compositor->vout, compositor->passthrough_txh->stream->pck, &compositor->passthrough_data); | |
| if (!compositor->passthrough_pck) return GF_OUT_OF_MEM; | |
| } | |
| vi->video_buffer = compositor->passthrough_data; | |
| vi->pitch_y = compositor->passthrough_txh->stride; | |
| } else { | |
| vi->video_buffer = compositor->framebuffer; | |
| } | |
| vi->pitch_x = gf_pixel_get_bytes_per_pixel(pfmt); | |
| vi->pixel_format = pfmt; | |
| compositor->passthrough_pfmt = pfmt; | |
| } | |
| return GF_OK; | |
| } | |
| static GF_Err rawvout_evt(struct _video_out *vout, GF_Event *evt) | |
| { | |
| u32 pfmt, stride, stride_uv; | |
| GF_Compositor *compositor = (GF_Compositor *)vout->opaque; | |
| if (!evt || (evt->type != GF_EVENT_VIDEO_SETUP)) return GF_OK; | |
| pfmt = compositor->opfmt; | |
| if (!pfmt) { | |
| pfmt = compositor->forced_alpha ? GF_PIXEL_RGBA : GF_PIXEL_RGB; | |
| } | |
| compositor->passthrough_pfmt = pfmt; | |
| stride=0; | |
| stride_uv = 0; | |
| if (!gf_pixel_get_size_info(pfmt, evt->setup.width, evt->setup.height, &compositor->framebuffer_size, &stride, &stride_uv, NULL, NULL)) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[Compositor] Cannot get proper size info for pixel format %s and hxw %dx%d\n", gf_pixel_fmt_name(pfmt), evt->setup.height, evt->setup.width)); | |
| return GF_NOT_SUPPORTED; | |
| } | |
| if (compositor->vout) { | |
| gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_PIXFMT, &PROP_UINT(pfmt)); | |
| gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_STRIDE, &PROP_UINT(stride)); | |
| gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_STRIDE_UV, stride_uv ? &PROP_UINT(stride_uv) : NULL); | |
| } | |
| if (compositor->framebuffer_size > compositor->framebuffer_alloc) { | |
| compositor->framebuffer_alloc = compositor->framebuffer_size; | |
| compositor->framebuffer = gf_realloc(compositor->framebuffer, sizeof(char)*compositor->framebuffer_size); | |
| } | |
| if (!compositor->framebuffer) return GF_OUT_OF_MEM; | |
| memset(compositor->framebuffer, 0, sizeof(char)*compositor->framebuffer_size); | |
| #ifndef GPAC_DISABLE_3D | |
| if (compositor->needs_offscreen_gl && (compositor->ogl != GF_SC_GLMODE_OFF)) | |
| return compositor_3d_setup_fbo(evt->setup.width, evt->setup.height, &compositor->fbo_id, &compositor->fbo_tx_id, &compositor->fbo_depth_id); | |
| #endif | |
| evt->setup.use_opengl = GF_FALSE; | |
| return GF_OK; | |
| } | |
| GF_VideoOutput raw_vout = { | |
| .Setup = nullvout_setup, | |
| .Shutdown = nullvout_shutdown, | |
| .Flush = nullvout_flush, | |
| .SetFullScreen = nullvout_fullscreen, | |
| .ProcessEvent = rawvout_evt, | |
| .LockBackBuffer = rawvout_lock, | |
| .Blit = NULL, | |
| .hw_caps = GF_VIDEO_HW_HAS_RGB | GF_VIDEO_HW_HAS_RGBA | |
| }; | |
| void gf_sc_setup_passthrough(GF_Compositor *compositor) | |
| { | |
| u32 timescale; | |
| Bool is_raw_out = GF_FALSE; | |
| u32 update_pfmt = 0; | |
| compositor->passthrough_inplace = GF_FALSE; | |
| if (!compositor->passthrough_txh) { | |
| compositor->passthrough_timescale = 0; | |
| return; | |
| } | |
| timescale = gf_filter_pck_get_timescale(compositor->passthrough_txh->stream->pck); | |
| if (compositor->passthrough_timescale != timescale) { | |
| compositor->passthrough_timescale = timescale; | |
| gf_filter_pid_copy_properties(compositor->vout, compositor->passthrough_txh->stream->odm->pid); | |
| if (compositor->video_out==&raw_vout) { | |
| update_pfmt = compositor->opfmt ? compositor->opfmt : compositor->passthrough_txh->pixelformat; | |
| } else { | |
| update_pfmt = GF_PIXEL_RGB; | |
| } | |
| } | |
| if (compositor->video_out == &raw_vout) { | |
| is_raw_out = GF_TRUE; | |
| if (!compositor->opfmt || (compositor->opfmt == compositor->passthrough_txh->pixelformat) ) { | |
| if (!compositor->passthrough_txh->frame_ifce) { | |
| compositor->passthrough_inplace = GF_TRUE; | |
| update_pfmt = GF_FALSE; | |
| } | |
| if (!compositor->opfmt && (compositor->passthrough_pfmt != compositor->passthrough_txh->pixelformat)) { | |
| compositor->passthrough_pfmt = compositor->passthrough_txh->pixelformat; | |
| gf_filter_pid_copy_properties(compositor->vout, compositor->passthrough_txh->stream->odm->pid); | |
| } | |
| } | |
| } | |
| if (update_pfmt) { | |
| u32 stride=0, stride_uv=0, out_size=0; | |
| gf_pixel_get_size_info(update_pfmt, compositor->display_width, compositor->display_width, &out_size, &stride, &stride_uv, NULL, NULL); | |
| gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_PIXFMT, &PROP_UINT(update_pfmt)); | |
| gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_STRIDE, &PROP_UINT(stride)); | |
| gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_STRIDE_UV, stride_uv ? &PROP_UINT(stride_uv) : NULL); | |
| if (is_raw_out && !compositor->passthrough_inplace && (compositor->framebuffer_size != out_size) ) { | |
| compositor->framebuffer_size = out_size; | |
| if (out_size > compositor->framebuffer_alloc) { | |
| compositor->framebuffer_alloc = out_size; | |
| compositor->framebuffer = gf_realloc(compositor->framebuffer, out_size); | |
| } | |
| } | |
| } | |
| } | |
| static GF_Err gf_sc_load_driver(GF_Compositor *compositor) | |
| { | |
| GF_Err e; | |
| const char *sOpt; | |
| void *os_disp=NULL; | |
| //filter mode | |
| if (!compositor->player) { | |
| compositor->video_out = &null_vout; | |
| #ifdef GPAC_ENABLE_COVERAGE | |
| if (gf_sys_is_cov_mode()) { | |
| nullvout_setup(NULL, NULL, NULL, 0); | |
| nullvout_fullscreen(NULL, GF_FALSE, NULL, NULL); | |
| nullvout_evt(NULL, NULL); | |
| nullvout_lock(NULL, NULL, GF_FALSE); | |
| nullvout_blit(NULL, NULL, NULL, NULL, 0); | |
| } | |
| #endif | |
| e = gf_filter_request_opengl(compositor->filter); | |
| if (e) return e; | |
| #ifndef GPAC_DISABLE_3D | |
| gf_sc_load_opengl_extensions(compositor, GF_TRUE); | |
| if (!compositor->gl_caps.fbo) | |
| #endif | |
| { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[Compositor] No support for OpenGL framebuffer object, cannot run in GL mode.\n")); | |
| compositor->drv = GF_SC_DRV_OFF; | |
| return GF_NOT_SUPPORTED; | |
| } | |
| compositor->video_out = &gl_vout; | |
| compositor->video_out->opaque = compositor; | |
| return GF_OK; | |
| } | |
| //player mode | |
| #ifndef GPAC_DISABLE_3D | |
| sOpt = gf_opts_get_key("core", "video-output"); | |
| if (sOpt && !strcmp(sOpt, "glfbo")) { | |
| compositor->video_out = &gl_vout; | |
| compositor->video_out->opaque = compositor; | |
| sOpt = gf_opts_get_key("core", "glfbo-txid"); | |
| if (sOpt) { | |
| compositor->fbo_tx_id = atoi(sOpt); | |
| compositor->external_tx_id = GF_TRUE; | |
| } | |
| if (!compositor->fbo_tx_id) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[Compositor] glfbo driver specified but no target texture ID found, cannot initialize\n")); | |
| compositor->drv = GF_SC_DRV_OFF; | |
| return GF_BAD_PARAM; | |
| } | |
| gf_sc_load_opengl_extensions(compositor, GF_TRUE); | |
| if (!compositor->gl_caps.fbo) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[Compositor] No support for OpenGL framebuffer object, cannot run in glfbo mode.\n")); | |
| compositor->drv = GF_SC_DRV_OFF; | |
| return GF_NOT_SUPPORTED; | |
| } | |
| return GF_OK; | |
| } | |
| #endif | |
| compositor->video_out = (GF_VideoOutput *) gf_module_load(GF_VIDEO_OUTPUT_INTERFACE, NULL); | |
| if (!compositor->video_out ) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[Compositor] Failed to load a video output module.\n")); | |
| return GF_IO_ERR; | |
| } | |
| #ifdef GPAC_CONFIG_EMSCRIPTEN | |
| if (compositor->player) { | |
| void gpac_force_step_mode(Bool for_display); | |
| gpac_force_step_mode(compositor->player ? GF_TRUE : GF_FALSE); | |
| } | |
| #endif | |
| sOpt = gf_opts_get_key("temp", "window-display"); | |
| if (sOpt) sscanf(sOpt, "%p", &os_disp); | |
| compositor->video_out->evt_cbk_hdl = compositor; | |
| compositor->video_out->on_event = gf_sc_on_event; | |
| /*init hw*/ | |
| e = compositor->video_out->Setup(compositor->video_out, compositor->os_wnd, os_disp, compositor->init_flags); | |
| if (e != GF_OK) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[Compositor] Error setuing up video output: %s\n", gf_error_to_string(e) )); | |
| return e; | |
| } | |
| if (!gf_opts_get_key("core", "video-output")) { | |
| gf_opts_set_key("core", "video-output", compositor->video_out->module_name); | |
| } | |
| //also send a video setup as some drivers may init openGL only then | |
| GF_Event evt; | |
| memset(&evt, 0, sizeof(GF_Event)); | |
| evt.type = GF_EVENT_VIDEO_SETUP; | |
| evt.setup.width = 128; | |
| evt.setup.height = 128; | |
| evt.setup.disable_vsync = compositor->bench_mode ? GF_TRUE : GF_FALSE; | |
| #ifndef GPAC_DISABLE_3D | |
| evt.setup.use_opengl = GF_TRUE; | |
| evt.setup.system_memory = GF_FALSE; | |
| evt.setup.back_buffer = GF_TRUE; | |
| #endif | |
| compositor->video_out->ProcessEvent(compositor->video_out, &evt); | |
| gf_filter_register_opengl_provider(compositor->filter, GF_TRUE); | |
| return GF_OK; | |
| } | |
| #ifndef GPAC_DISABLE_3D | |
| static void gf_sc_check_video_driver(GF_Compositor *compositor) | |
| { | |
| Bool force_gl_out = GF_FALSE; | |
| if (compositor->player) return; | |
| if (compositor->video_out == &null_vout) return; | |
| if (compositor->visual->type_3d || compositor->hybrid_opengl) { | |
| force_gl_out = GF_TRUE; | |
| } else if (compositor->needs_offscreen_gl && (compositor->ogl!=GF_SC_GLMODE_OFF) ) { | |
| force_gl_out = GF_TRUE; | |
| } | |
| if (compositor->root_scene && (compositor->root_scene->is_tiled_srd || compositor->root_scene->vr_type)) | |
| force_gl_out = GF_TRUE; | |
| if (force_gl_out) { | |
| GF_Err e; | |
| if (compositor->video_out != &raw_vout) return; | |
| e = gf_sc_load_driver(compositor); | |
| if (e) { | |
| compositor->video_out = &raw_vout; | |
| compositor->video_memory = 2; | |
| } | |
| return; | |
| } | |
| if (compositor->video_out == &raw_vout) { | |
| if (compositor->needs_offscreen_gl) { | |
| gf_filter_request_opengl(compositor->filter); | |
| } | |
| return; | |
| } | |
| compositor->video_out = &raw_vout; | |
| compositor->video_memory = 2; | |
| } | |
| #endif | |
| GF_Err gf_sc_load(GF_Compositor *compositor) | |
| { | |
| u32 i; | |
| const char *sOpt; | |
| compositor->mx = gf_mx_new("Compositor"); | |
| if (!compositor->mx) return GF_OUT_OF_MEM; | |
| /*load proto modules*/ | |
| compositor->proto_modules = gf_list_new(); | |
| if (!compositor->proto_modules) return GF_OUT_OF_MEM; | |
| for (i=0; i< gf_modules_count(); i++) { | |
| GF_HardcodedProto *ifce = (GF_HardcodedProto *) gf_modules_load(i, GF_HARDCODED_PROTO_INTERFACE); | |
| if (ifce) gf_list_add(compositor->proto_modules, ifce); | |
| } | |
| compositor->init_flags = 0; | |
| compositor->os_wnd = NULL; | |
| sOpt = gf_opts_get_key("temp", "window-handle"); | |
| if (sOpt) sscanf(sOpt, "%p", &compositor->os_wnd); | |
| sOpt = gf_opts_get_key("temp", "window-flags"); | |
| if (sOpt) compositor->init_flags = atoi(sOpt); | |
| /*force initial for 2D/3D setup*/ | |
| compositor->msg_type |= GF_SR_CFG_INITIAL_RESIZE; | |
| /*set default size if owning output*/ | |
| if (!compositor->os_wnd) { | |
| compositor->new_width = compositor->osize.x>0 ? compositor->osize.x : SC_DEF_WIDTH; | |
| compositor->new_height = compositor->osize.y>0 ? compositor->osize.y : SC_DEF_HEIGHT; | |
| compositor->msg_type |= GF_SR_CFG_SET_SIZE; | |
| } | |
| if (!compositor->player) | |
| compositor->init_flags = GF_VOUT_INIT_HIDE; | |
| if (gf_opts_get_key("temp", "gpac-help")) { | |
| gf_opts_set_key("temp", "no-video", "yes"); | |
| compositor->noaudio = GF_TRUE; | |
| } | |
| //used for GUI help and in bench mode without vout | |
| if (gf_opts_get_bool("temp", "no-video")) { | |
| compositor->video_out = &null_vout; | |
| compositor->ogl = GF_SC_GLMODE_OFF; | |
| } else if (compositor->player || (compositor->drv==GF_SC_DRV_ON) || (compositor->ogl==GF_SC_GLMODE_HYBRID) ){ | |
| GF_Err e; | |
| e = gf_sc_load_driver(compositor); | |
| if (e) return e; | |
| } else { | |
| raw_vout.opaque = compositor; | |
| compositor->video_out = &raw_vout; | |
| compositor->video_memory = 2; | |
| } | |
| compositor->strike_bank = gf_list_new(); | |
| if (!compositor->strike_bank) return GF_OUT_OF_MEM; | |
| compositor->visuals = gf_list_new(); | |
| if (!compositor->visuals) return GF_OUT_OF_MEM; | |
| GF_SAFEALLOC(compositor->traverse_state, GF_TraverseState); | |
| if (!compositor->traverse_state) return GF_OUT_OF_MEM; | |
| compositor->traverse_state->vrml_sensors = gf_list_new(); | |
| if (!compositor->traverse_state->vrml_sensors) return GF_OUT_OF_MEM; | |
| compositor->traverse_state->use_stack = gf_list_new(); | |
| if (!compositor->traverse_state->use_stack) return GF_OUT_OF_MEM; | |
| #ifndef GPAC_DISABLE_3D | |
| compositor->traverse_state->local_lights = gf_list_new(); | |
| if (!compositor->traverse_state->local_lights) return GF_OUT_OF_MEM; | |
| #endif | |
| compositor->sensors = gf_list_new(); | |
| if (!compositor->sensors) return GF_OUT_OF_MEM; | |
| compositor->previous_sensors = gf_list_new(); | |
| if (!compositor->previous_sensors) return GF_OUT_OF_MEM; | |
| compositor->hit_use_stack = gf_list_new(); | |
| if (!compositor->hit_use_stack) return GF_OUT_OF_MEM; | |
| compositor->prev_hit_use_stack = gf_list_new(); | |
| if (!compositor->prev_hit_use_stack) return GF_OUT_OF_MEM; | |
| compositor->focus_ancestors = gf_list_new(); | |
| if (!compositor->focus_ancestors) return GF_OUT_OF_MEM; | |
| compositor->focus_use_stack = gf_list_new(); | |
| if (!compositor->focus_use_stack) return GF_OUT_OF_MEM; | |
| compositor->env_tests = gf_list_new(); | |
| if (!compositor->env_tests) return GF_OUT_OF_MEM; | |
| /*setup main visual*/ | |
| compositor->visual = visual_new(compositor); | |
| if (!compositor->visual) return GF_OUT_OF_MEM; | |
| compositor->visual->GetSurfaceAccess = compositor_2d_get_video_access; | |
| compositor->visual->ReleaseSurfaceAccess = compositor_2d_release_video_access; | |
| compositor->visual->CheckAttached = compositor_2d_check_attached; | |
| compositor->visual->ClearSurface = compositor_2d_clear_surface; | |
| compositor_2d_init_callbacks(compositor); | |
| if (compositor->video_out->FlushRectangles) | |
| compositor->visual->direct_flush = GF_TRUE; | |
| // default value for depth gain is not zero | |
| #ifdef GF_SR_USE_DEPTH | |
| compositor->traverse_state->depth_gain = FIX_ONE; | |
| #endif | |
| gf_list_add(compositor->visuals, compositor->visual); | |
| compositor->zoom = compositor->scale_x = compositor->scale_y = FIX_ONE; | |
| /*create a drawable for focus highlight*/ | |
| compositor->focus_highlight = drawable_new(); | |
| if (!compositor->focus_highlight) return GF_OUT_OF_MEM; | |
| /*associate a dummy node for traversing*/ | |
| compositor->focus_highlight->node = gf_node_new(NULL, TAG_UndefinedNode); | |
| if (!compositor->focus_highlight->node) return GF_OUT_OF_MEM; | |
| gf_node_register(compositor->focus_highlight->node, NULL); | |
| gf_node_set_callback_function(compositor->focus_highlight->node, drawable_traverse_focus); | |
| #ifndef GPAC_DISABLE_3D | |
| /*default collision mode*/ | |
| compositor->collide_mode = GF_COLLISION_DISPLACEMENT; //GF_COLLISION_NORMAL | |
| compositor->gravity_on = GF_TRUE; | |
| /*create default unit sphere and box for bounds*/ | |
| compositor->unit_bbox = new_mesh(); | |
| if (!compositor->unit_bbox) return GF_OUT_OF_MEM; | |
| mesh_new_unit_bbox(compositor->unit_bbox); | |
| gf_mx_init(compositor->traverse_state->model_matrix); | |
| #endif | |
| compositor->was_system_memory=1; | |
| compositor->systems_pids = gf_list_new(); | |
| if (!compositor->systems_pids) return GF_OUT_OF_MEM; | |
| compositor->textures = gf_list_new(); | |
| if (!compositor->textures) return GF_OUT_OF_MEM; | |
| compositor->textures_gc = gf_list_new(); | |
| if (!compositor->textures_gc) return GF_OUT_OF_MEM; | |
| if (!compositor->fps.num || !compositor->fps.den) { | |
| compositor->fps.num = 30; | |
| compositor->fps.den = 1; | |
| } | |
| compositor->frame_duration = compositor->fps.num * 1000; | |
| compositor->frame_duration /= compositor->fps.den; | |
| compositor->time_nodes = gf_list_new(); | |
| if (!compositor->time_nodes) return GF_OUT_OF_MEM; | |
| compositor->event_queue = gf_list_new(); | |
| if (!compositor->event_queue) return GF_OUT_OF_MEM; | |
| compositor->event_queue_back = gf_list_new(); | |
| if (!compositor->event_queue_back) return GF_OUT_OF_MEM; | |
| compositor->evq_mx = gf_mx_new("EventQueue"); | |
| if (!compositor->evq_mx) return GF_OUT_OF_MEM; | |
| #ifdef GF_SR_USE_VIDEO_CACHE | |
| compositor->cached_groups = gf_list_new(); | |
| if (!compositor->cached_groups) return GF_OUT_OF_MEM; | |
| compositor->cached_groups_queue = gf_list_new(); | |
| if (!compositor->cached_groups_queue) return GF_OUT_OF_MEM; | |
| #endif | |
| /*load audio renderer*/ | |
| if (!compositor->audio_renderer) | |
| compositor->audio_renderer = gf_sc_ar_load(compositor, compositor->init_flags); | |
| gf_sc_reset_framerate(compositor); | |
| compositor->font_manager = gf_filter_get_font_manager(compositor->filter); | |
| if (!compositor->font_manager) return GF_OUT_OF_MEM; | |
| compositor->extra_scenes = gf_list_new(); | |
| if (!compositor->extra_scenes) return GF_OUT_OF_MEM; | |
| compositor->interaction_level = GF_INTERACT_NORMAL | GF_INTERACT_INPUT_SENSOR | GF_INTERACT_NAVIGATION; | |
| compositor->scene_sampled_clock = 0; | |
| compositor->video_th_id = gf_th_id(); | |
| /*load extensions*/ | |
| compositor->extensions = gf_list_new(); | |
| if (!compositor->extensions) return GF_OUT_OF_MEM; | |
| compositor->unthreaded_extensions = gf_list_new(); | |
| if (!compositor->unthreaded_extensions) return GF_OUT_OF_MEM; | |
| for (i=0; i< gf_modules_count(); i++) { | |
| GF_CompositorExt *ifce = (GF_CompositorExt *) gf_modules_load(i, GF_COMPOSITOR_EXT_INTERFACE); | |
| if (ifce) { | |
| if (!ifce->process(ifce, GF_COMPOSITOR_EXT_START, compositor)) { | |
| gf_modules_close_interface((GF_BaseInterface *) ifce); | |
| continue; | |
| } | |
| gf_list_add(compositor->extensions, ifce); | |
| if (ifce->caps & GF_COMPOSITOR_EXTENSION_NOT_THREADED) | |
| gf_list_add(compositor->unthreaded_extensions, ifce); | |
| } | |
| } | |
| if (!gf_list_count(compositor->unthreaded_extensions)) { | |
| gf_list_del(compositor->unthreaded_extensions); | |
| compositor->unthreaded_extensions = NULL; | |
| } | |
| compositor->display_width = 320; | |
| compositor->display_height = 240; | |
| compositor->recompute_ar = GF_TRUE; | |
| compositor->scene_sampled_clock = 0; | |
| if (compositor->autoconfig_opengl || compositor->hybrid_opengl) | |
| gf_sc_recompute_ar(compositor, NULL); | |
| /*try to load GL extensions*/ | |
| #ifndef GPAC_DISABLE_3D | |
| gf_sc_load_opengl_extensions(compositor, GF_FALSE); | |
| #endif | |
| gf_sc_reload_config(compositor); | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_RTI, ("[RTI]\tCompositor Cycle Log\tNetworks\tDecoders\tFrame\tDirect Draw\tVisual Config\tEvent\tRoute\tSMIL Timing\tTime node\tTexture\tSMIL Anim\tTraverse setup\tTraverse (and direct Draw)\tTraverse (and direct Draw) without anim\tIndirect Draw\tTraverse And Draw (Indirect or Not)\tFlush\tCycle\n")); | |
| return GF_OK; | |
| } | |
| void gf_sc_unload(GF_Compositor *compositor) | |
| { | |
| u32 i; | |
| if (!compositor) return; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Destroying\n")); | |
| compositor->discard_input_events = GF_TRUE; | |
| gf_sc_lock(compositor, GF_TRUE); | |
| #ifndef GPAC_DISABLE_3D | |
| compositor_2d_reset_gl_auto(compositor); | |
| #endif | |
| gf_sc_texture_cleanup_hw(compositor); | |
| if (compositor->video_out) { | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Closing video output\n")); | |
| compositor->video_out->Shutdown(compositor->video_out); | |
| gf_modules_close_interface((GF_BaseInterface *)compositor->video_out); | |
| } | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Closing visual compositor\n")); | |
| if (compositor->focus_highlight) { | |
| gf_node_unregister(compositor->focus_highlight->node, NULL); | |
| drawable_del_ex(compositor->focus_highlight, compositor, GF_FALSE); | |
| } | |
| if (compositor->selected_text) gf_free(compositor->selected_text); | |
| if (compositor->sel_buffer) gf_free(compositor->sel_buffer); | |
| if (compositor->visual) visual_del(compositor->visual); | |
| if (compositor->sensors) gf_list_del(compositor->sensors); | |
| if (compositor->previous_sensors) gf_list_del(compositor->previous_sensors); | |
| if (compositor->visuals) gf_list_del(compositor->visuals); | |
| if (compositor->strike_bank) gf_list_del(compositor->strike_bank); | |
| if (compositor->hit_use_stack) gf_list_del(compositor->hit_use_stack); | |
| if (compositor->prev_hit_use_stack) gf_list_del(compositor->prev_hit_use_stack); | |
| if (compositor->focus_ancestors) gf_list_del(compositor->focus_ancestors); | |
| if (compositor->focus_use_stack) gf_list_del(compositor->focus_use_stack); | |
| if (compositor->env_tests) gf_list_del(compositor->env_tests); | |
| if (compositor->systems_pids) gf_list_del(compositor->systems_pids); | |
| if (compositor->nodes_pending) gf_list_del(compositor->nodes_pending); | |
| if (compositor->traverse_state) { | |
| gf_list_del(compositor->traverse_state->vrml_sensors); | |
| gf_list_del(compositor->traverse_state->use_stack); | |
| #ifndef GPAC_DISABLE_3D | |
| gf_list_del(compositor->traverse_state->local_lights); | |
| #endif | |
| gf_free(compositor->traverse_state); | |
| } | |
| #ifndef GPAC_DISABLE_3D | |
| if (compositor->unit_bbox) mesh_free(compositor->unit_bbox); | |
| if (compositor->screen_buffer) gf_free(compositor->screen_buffer); | |
| if (compositor->line_buffer) gf_free(compositor->line_buffer); | |
| #endif | |
| if (compositor->framebuffer) gf_free(compositor->framebuffer); | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Unloading visual compositor module\n")); | |
| if (compositor->audio_renderer) gf_sc_ar_del(compositor->audio_renderer); | |
| compositor->audio_renderer = NULL; | |
| /*unload proto modules*/ | |
| if (compositor->proto_modules) { | |
| for (i=0; i< gf_list_count(compositor->proto_modules); i++) { | |
| GF_HardcodedProto *ifce = gf_list_get(compositor->proto_modules, i); | |
| gf_modules_close_interface((GF_BaseInterface *) ifce); | |
| } | |
| gf_list_del(compositor->proto_modules); | |
| } | |
| if (compositor->evq_mx) gf_mx_p(compositor->evq_mx); | |
| while (gf_list_count(compositor->event_queue)) { | |
| GF_QueuedEvent *qev = (GF_QueuedEvent *)gf_list_get(compositor->event_queue, 0); | |
| gf_list_rem(compositor->event_queue, 0); | |
| gf_free(qev); | |
| } | |
| while (gf_list_count(compositor->event_queue_back)) { | |
| GF_QueuedEvent *qev = (GF_QueuedEvent *)gf_list_get(compositor->event_queue_back, 0); | |
| gf_list_rem(compositor->event_queue, 0); | |
| gf_free(qev); | |
| } | |
| if (compositor->evq_mx) { | |
| gf_mx_v(compositor->evq_mx); | |
| gf_mx_del(compositor->evq_mx); | |
| } | |
| gf_list_del(compositor->event_queue); | |
| gf_list_del(compositor->event_queue_back); | |
| #ifdef GF_SR_USE_VIDEO_CACHE | |
| gf_list_del(compositor->cached_groups); | |
| gf_list_del(compositor->cached_groups_queue); | |
| #endif | |
| if (compositor->textures) gf_list_del(compositor->textures); | |
| if (compositor->textures_gc) gf_list_del(compositor->textures_gc); | |
| if (compositor->time_nodes) gf_list_del(compositor->time_nodes); | |
| if (compositor->extra_scenes) gf_list_del(compositor->extra_scenes); | |
| if (compositor->input_streams) gf_list_del(compositor->input_streams); | |
| if (compositor->x3d_sensors) gf_list_del(compositor->x3d_sensors); | |
| /*unload extensions*/ | |
| for (i=0; i< gf_list_count(compositor->extensions); i++) { | |
| GF_CompositorExt *ifce = gf_list_get(compositor->extensions, i); | |
| ifce->process(ifce, GF_COMPOSITOR_EXT_STOP, NULL); | |
| } | |
| /*remove all event filters*/ | |
| // gf_list_reset(term->event_filters); | |
| /*unload extensions*/ | |
| for (i=0; i< gf_list_count(compositor->extensions); i++) { | |
| GF_CompositorExt *ifce = gf_list_get(compositor->extensions, i); | |
| gf_modules_close_interface((GF_BaseInterface *) ifce); | |
| } | |
| gf_list_del(compositor->extensions); | |
| gf_list_del(compositor->unthreaded_extensions); | |
| gf_sc_lock(compositor, GF_FALSE); | |
| gf_mx_del(compositor->mx); | |
| if (compositor->reload_url) gf_free(compositor->reload_url); | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Destroyed\n")); | |
| } | |
| void gf_sc_set_fps(GF_Compositor *compositor, GF_Fraction fps) | |
| { | |
| if (fps.den) { | |
| u64 dur; | |
| compositor->fps = fps; | |
| dur = fps.den; | |
| dur *= 1000; | |
| dur /= fps.num; | |
| compositor->frame_duration = (u32) dur; | |
| gf_sc_reset_framerate(compositor); | |
| if (compositor->vout && !compositor->timescale) { | |
| gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_TIMESCALE, &PROP_UINT(fps.num) ); | |
| } | |
| } | |
| } | |
| static void gf_sc_set_play_state(GF_Compositor *compositor, u32 PlayState) | |
| { | |
| if (!compositor || !compositor->audio_renderer) return; | |
| if (!compositor->paused && !PlayState) return; | |
| if (compositor->paused && (PlayState==GF_STATE_PAUSED)) return; | |
| /*step mode*/ | |
| if (PlayState==GF_STATE_STEP_PAUSE) { | |
| compositor->step_mode = GF_TRUE; | |
| compositor->paused = 1; | |
| } else { | |
| compositor->step_mode = GF_FALSE; | |
| if (compositor->audio_renderer) gf_sc_ar_control(compositor->audio_renderer, (compositor->paused && (PlayState==0xFF)) ? GF_SC_AR_RESET_HW_AND_PLAY : (compositor->paused ? GF_SC_AR_RESUME : GF_SC_AR_PAUSE) ); | |
| compositor->paused = (PlayState==GF_STATE_PAUSED) ? 1 : 0; | |
| } | |
| } | |
| GF_EXPORT | |
| u32 gf_sc_get_clock(GF_Compositor *compositor) | |
| { | |
| if (!compositor->bench_mode && compositor->player) { | |
| return gf_sc_ar_get_clock(compositor->audio_renderer); | |
| } | |
| return compositor->scene_sampled_clock; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_sc_set_scene_size(GF_Compositor *compositor, u32 Width, u32 Height, Bool force_size) | |
| { | |
| if (!Width || !Height) { | |
| if (compositor->override_size_flags) { | |
| /*specify a small size to detect biggest bitmap but not 0 in case only audio..*/ | |
| compositor->scene_height = SC_DEF_HEIGHT; | |
| compositor->scene_width = SC_DEF_WIDTH; | |
| } else { | |
| /*use current res*/ | |
| compositor->scene_width = compositor->new_width ? compositor->new_width : compositor->display_width; | |
| compositor->scene_height = compositor->new_height ? compositor->new_height : compositor->display_height; | |
| } | |
| } else { | |
| compositor->scene_height = Height; | |
| compositor->scene_width = Width; | |
| } | |
| if (force_size) | |
| compositor->has_size_info = 1; | |
| return GF_OK; | |
| } | |
| #ifndef GPAC_DISABLE_SVG | |
| GF_EXPORT | |
| Fixed gf_sc_svg_convert_length_to_display(GF_Compositor *compositor, SVG_Length *length) | |
| { | |
| /* Assuming the environment is 90dpi*/ | |
| u32 dpi = 90; | |
| if (!length) return 0; | |
| switch (length->type) { | |
| case SVG_NUMBER_PERCENTAGE: | |
| break; | |
| case SVG_NUMBER_EMS: | |
| break; | |
| case SVG_NUMBER_EXS: | |
| break; | |
| case SVG_NUMBER_VALUE: | |
| break; | |
| case SVG_NUMBER_PX: | |
| return length->value; | |
| case SVG_NUMBER_CM: | |
| return gf_mulfix(length->value, dpi*FLT2FIX(0.39)); | |
| break; | |
| case SVG_NUMBER_MM: | |
| return gf_mulfix(length->value, dpi*FLT2FIX(0.039)); | |
| case SVG_NUMBER_IN: | |
| return length->value * dpi; | |
| case SVG_NUMBER_PT: | |
| return (dpi * length->value) / 12; | |
| case SVG_NUMBER_PC: | |
| return (dpi*length->value) / 6; | |
| case SVG_NUMBER_INHERIT: | |
| break; | |
| } | |
| return length->value; | |
| } | |
| #endif | |
| void compositor_set_ar_scale(GF_Compositor *compositor, Fixed scaleX, Fixed scaleY) | |
| { | |
| compositor->trans_x = gf_muldiv(compositor->trans_x, scaleX, compositor->scale_x); | |
| compositor->trans_y = gf_muldiv(compositor->trans_y, scaleY, compositor->scale_y); | |
| compositor->zoom_changed = 1; | |
| compositor->scale_x = scaleX; | |
| compositor->scale_y = scaleY; | |
| compositor_2d_set_user_transform(compositor, compositor->zoom, compositor->trans_x, compositor->trans_y, 1); | |
| } | |
| static void gf_sc_reset(GF_Compositor *compositor, Bool has_scene) | |
| { | |
| Bool mode2d; | |
| GF_VisualManager *visual; | |
| u32 i=0; | |
| //init failed | |
| if (!compositor->traverse_state) | |
| return; | |
| while ((visual = (GF_VisualManager *)gf_list_enum(compositor->visuals, &i))) { | |
| /*reset display list*/ | |
| visual->cur_context = visual->context; | |
| if (visual->cur_context) visual->cur_context->drawable = NULL; | |
| while (visual->prev_nodes) { | |
| struct _drawable_store *cur = visual->prev_nodes; | |
| visual->prev_nodes = cur->next; | |
| gf_free(cur); | |
| } | |
| visual->last_prev_entry = NULL; | |
| visual->to_redraw.count = 0; | |
| /*reset the surface as well*/ | |
| if (visual->raster_surface) gf_evg_surface_delete(visual->raster_surface); | |
| visual->raster_surface = NULL; | |
| } | |
| gf_list_reset(compositor->sensors); | |
| gf_list_reset(compositor->previous_sensors); | |
| /*reset traverse state*/ | |
| mode2d = compositor->traverse_state->immediate_draw; | |
| gf_list_del(compositor->traverse_state->vrml_sensors); | |
| gf_list_del(compositor->traverse_state->use_stack); | |
| #ifndef GPAC_DISABLE_3D | |
| gf_list_del(compositor->traverse_state->local_lights); | |
| compositor_3d_delete_fbo(&compositor->fbo_id, &compositor->fbo_tx_id, &compositor->fbo_depth_id, compositor->external_tx_id); | |
| #endif | |
| memset(compositor->traverse_state, 0, sizeof(GF_TraverseState)); | |
| compositor->traverse_state->vrml_sensors = gf_list_new(); | |
| compositor->traverse_state->use_stack = gf_list_new(); | |
| #ifndef GPAC_DISABLE_3D | |
| compositor->traverse_state->local_lights = gf_list_new(); | |
| #endif | |
| gf_mx2d_init(compositor->traverse_state->transform); | |
| gf_cmx_init(&compositor->traverse_state->color_mat); | |
| compositor->traverse_state->immediate_draw = mode2d; | |
| #ifdef GF_SR_USE_DEPTH | |
| compositor->traverse_state->depth_gain = FIX_ONE; | |
| compositor->traverse_state->depth_offset = 0; | |
| #endif | |
| #ifndef GPAC_DISABLE_3D | |
| //force a recompute of the canvas | |
| if (has_scene && compositor->hybgl_txh) { | |
| compositor->hybgl_txh->width = compositor->hybgl_txh->height = 0; | |
| } | |
| #endif | |
| gf_assert(!compositor->visual->overlays); | |
| compositor->reset_graphics = 0; | |
| compositor->trans_x = compositor->trans_y = 0; | |
| compositor->zoom = FIX_ONE; | |
| compositor->grab_node = NULL; | |
| compositor->grab_use = NULL; | |
| compositor->focus_node = NULL; | |
| compositor->focus_text_type = 0; | |
| if (compositor->video_memory!=2) | |
| compositor->video_memory = compositor->was_system_memory ? 0 : 1; | |
| compositor->rotation = 0; | |
| gf_list_reset(compositor->focus_ancestors); | |
| gf_list_reset(compositor->focus_use_stack); | |
| #ifdef GF_SR_USE_VIDEO_CACHE | |
| gf_list_reset(compositor->cached_groups); | |
| compositor->video_cache_current_size = 0; | |
| gf_list_reset(compositor->cached_groups_queue); | |
| #endif | |
| /*force resetup in case we're switching coord system*/ | |
| compositor->root_visual_setup = 0; | |
| compositor_set_ar_scale(compositor, compositor->scale_x, compositor->scale_x); | |
| } | |
| GF_Err gf_sc_set_scene(GF_Compositor *compositor, GF_SceneGraph *scene_graph) | |
| { | |
| u32 width, height; | |
| Bool do_notif; | |
| if (!compositor) return GF_BAD_PARAM; | |
| gf_sc_lock(compositor, 1); | |
| if (scene_graph && !compositor->scene) { | |
| GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Attaching new scene\n")); | |
| } else if (!scene_graph && compositor->scene) { | |
| GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Detaching scene\n")); | |
| } | |
| if (compositor->audio_renderer && (compositor->scene != scene_graph)) { | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] resetting audio compositor\n")); | |
| gf_sc_ar_reset(compositor->audio_renderer); | |
| } | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] resetting event queue\n")); | |
| gf_mx_p(compositor->evq_mx); | |
| while (gf_list_count(compositor->event_queue)) { | |
| GF_QueuedEvent *qev = (GF_QueuedEvent*)gf_list_get(compositor->event_queue, 0); | |
| gf_list_rem(compositor->event_queue, 0); | |
| gf_free(qev); | |
| } | |
| gf_mx_v(compositor->evq_mx); | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] resetting compositor module\n")); | |
| /*reset main surface*/ | |
| gf_sc_reset(compositor, scene_graph ? 1 : 0); | |
| /*set current graph*/ | |
| compositor->scene = scene_graph; | |
| do_notif = GF_FALSE; | |
| if (scene_graph) { | |
| GF_Scene *scene_ctx = gf_sg_get_private(scene_graph); | |
| #ifndef GPAC_DISABLE_SVG | |
| SVG_Length *w, *h; | |
| SVG_ViewBox *vb; | |
| Bool is_svg = GF_FALSE; | |
| u32 tag; | |
| GF_Node *top_node; | |
| #endif | |
| Bool had_size_info = compositor->has_size_info; | |
| compositor->timed_nodes_valid = GF_TRUE; | |
| if (scene_ctx && scene_ctx->is_dynamic_scene) | |
| compositor->timed_nodes_valid = GF_FALSE; | |
| /*get pixel size if any*/ | |
| gf_sg_get_scene_size_info(compositor->scene, &width, &height); | |
| compositor->has_size_info = (width && height) ? 1 : 0; | |
| if (compositor->has_size_info != had_size_info) compositor->scene_width = compositor->scene_height = 0; | |
| if (compositor->video_memory!=2) | |
| compositor->video_memory = gf_scene_is_dynamic_scene(scene_graph); | |
| #ifndef GPAC_DISABLE_3D | |
| compositor->visual->camera.world_bbox.is_set = 0; | |
| #endif | |
| /*default back color is black*/ | |
| if (! (compositor->init_flags & GF_VOUT_WINDOWLESS)) { | |
| if (compositor->bc) { | |
| compositor->back_color = compositor->bc; | |
| } else { | |
| compositor->back_color = 0xFF000000; | |
| } | |
| } | |
| #ifndef GPAC_DISABLE_SVG | |
| top_node = gf_sg_get_root_node(compositor->scene); | |
| tag = 0; | |
| if (top_node) tag = gf_node_get_tag(top_node); | |
| w = h = NULL; | |
| vb = NULL; | |
| if ((tag>=GF_NODE_RANGE_FIRST_SVG) && (tag<=GF_NODE_RANGE_LAST_SVG)) { | |
| GF_FieldInfo info; | |
| is_svg = 1; | |
| if (gf_node_get_attribute_by_tag(top_node, TAG_SVG_ATT_width, 0, 0, &info)==GF_OK) | |
| w = info.far_ptr; | |
| if (gf_node_get_attribute_by_tag(top_node, TAG_SVG_ATT_height, 0, 0, &info)==GF_OK) | |
| h = info.far_ptr; | |
| if (gf_node_get_attribute_by_tag(top_node, TAG_SVG_ATT_viewBox, 0, 0, &info)==GF_OK) | |
| vb = info.far_ptr; | |
| } | |
| /*default back color is white*/ | |
| if (is_svg && ! (compositor->init_flags & GF_VOUT_WINDOWLESS)) compositor->back_color = 0xFFFFFFFF; | |
| /*hack for SVG where size is set in % - negotiate a canvas size*/ | |
| if (!compositor->has_size_info && w && h && vb) { | |
| do_notif = 1; | |
| if (w->type!=SVG_NUMBER_PERCENTAGE) { | |
| width = FIX2INT(gf_sc_svg_convert_length_to_display(compositor, w) ); | |
| if (width>=GF_INT_MAX) | |
| width = SC_DEF_WIDTH; | |
| } else if ((u32) FIX2INT(vb->width)<compositor->video_out->max_screen_width) { | |
| width = FIX2INT(vb->width); | |
| } else { | |
| width = SC_DEF_WIDTH; | |
| do_notif = 0; | |
| } | |
| if (h->type!=SVG_NUMBER_PERCENTAGE) { | |
| height = FIX2INT(gf_sc_svg_convert_length_to_display(compositor, h) ); | |
| if (height>=GF_INT_MAX) | |
| height = SC_DEF_WIDTH; | |
| } else if ((u32) FIX2INT(vb->height)<compositor->video_out->max_screen_height) { | |
| height = FIX2INT(vb->height); | |
| } else { | |
| height = SC_DEF_HEIGHT; | |
| do_notif = 0; | |
| } | |
| } | |
| /*we consider that SVG has no size onfo per say, everything is handled by the viewBox if any*/ | |
| if (is_svg) { | |
| compositor->has_size_info = 0; | |
| gf_sc_focus_switch_ring(compositor, 0, NULL, 0); | |
| } else | |
| #endif | |
| { | |
| GF_Node *keynav = gf_scene_get_keynav(compositor->scene, NULL); | |
| if (keynav) gf_sc_change_key_navigator(compositor, keynav); | |
| } | |
| /*default back color is key color*/ | |
| if (compositor->init_flags & GF_VOUT_WINDOWLESS) { | |
| if (compositor->ckey) compositor->back_color = compositor->ckey; | |
| } | |
| /*set scene size only if different, otherwise keep scaling/FS*/ | |
| if ( !width || (compositor->scene_width!=width) || !height || (compositor->scene_height!=height)) { | |
| do_notif = do_notif || compositor->has_size_info || (!compositor->scene_width && !compositor->scene_height); | |
| gf_sc_set_scene_size(compositor, width, height, 0); | |
| /*get actual size in pixels*/ | |
| width = compositor->scene_width; | |
| height = compositor->scene_height; | |
| if (!compositor->os_wnd) { | |
| /*only notify user if we are attached to a window*/ | |
| //do_notif = 0; | |
| if (compositor->video_out->max_screen_width && (width > compositor->video_out->max_screen_width)) { | |
| height *= compositor->video_out->max_screen_width; | |
| height /= width; | |
| width = compositor->video_out->max_screen_width; | |
| } | |
| if (compositor->video_out->max_screen_height && (height > compositor->video_out->max_screen_height)) { | |
| width *= compositor->video_out->max_screen_height; | |
| width /= height; | |
| height = compositor->video_out->max_screen_height; | |
| } | |
| gf_sc_set_size(compositor,width, height); | |
| } | |
| } | |
| } else { | |
| gf_sc_ar_reset(compositor->audio_renderer); | |
| compositor->needs_offscreen_gl = GF_FALSE; | |
| } | |
| gf_sc_reset_framerate(compositor); | |
| gf_sc_lock(compositor, 0); | |
| if (scene_graph) | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| /*here's a nasty trick: the app may respond to this by calling a gf_sc_set_size from a different | |
| thread, but in an atomic way (typically happen on Win32 when changing the window size). WE MUST | |
| NOTIFY THE SIZE CHANGE AFTER RELEASING THE RENDERER MUTEX*/ | |
| if (do_notif) { | |
| GF_Event evt; | |
| /*wait for user ack*/ | |
| // gf_sc_next_frame_state(compositor, GF_SC_DRAW_NONE); | |
| evt.type = GF_EVENT_SCENE_SIZE; | |
| evt.size.width = width; | |
| evt.size.height = height; | |
| evt.size.orientation = 0; | |
| if (compositor->scene && gf_sg_get_root_node(compositor->scene)) | |
| evt.size.window_id = 1; | |
| else | |
| evt.size.window_id = 0; | |
| gf_sc_send_event(compositor, &evt); | |
| } | |
| return GF_OK; | |
| } | |
| GF_EXPORT | |
| void gf_sc_lock(GF_Compositor *compositor, Bool doLock) | |
| { | |
| //GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[Compositor] Thread ID %d is %s the scene\n", gf_th_id(), doLock ? "locking" : "unlocking" )); | |
| if (doLock) | |
| gf_mx_p(compositor->mx); | |
| else { | |
| gf_mx_v(compositor->mx); | |
| } | |
| } | |
| GF_EXPORT | |
| GF_Err gf_sc_set_size(GF_Compositor *compositor, u32 NewWidth, u32 NewHeight) | |
| { | |
| Bool lock_ok; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("sc_set_size %dx%d\n", NewWidth, NewHeight)); | |
| if (compositor->osize.x && compositor->osize.y) { | |
| NewWidth = compositor->osize.x; | |
| NewHeight = compositor->osize.y; | |
| } | |
| if ((compositor->display_width == NewWidth) && (compositor->display_height == NewHeight)) | |
| return GF_OK; | |
| if (!NewWidth || !NewHeight) { | |
| compositor->override_size_flags &= ~2; | |
| compositor->msg_type |= GF_SR_CFG_AR; | |
| return GF_OK; | |
| } | |
| /*EXTRA CARE HERE: the caller (user app) is likely a different thread than the compositor one, and depending on window | |
| manager we may get called here as a result of a message sent to user but not yet returned */ | |
| lock_ok = gf_mx_try_lock(compositor->mx); | |
| compositor->new_width = NewWidth; | |
| compositor->new_height = NewHeight; | |
| compositor->msg_type |= GF_SR_CFG_SET_SIZE; | |
| gf_assert(compositor->new_width); | |
| /*if same size only request for video setup */ | |
| compositor->msg_type &= ~GF_SR_CFG_WINDOWSIZE_NOTIF; | |
| if ((compositor->display_width == NewWidth) && (compositor->display_height == NewHeight) ) { | |
| compositor->msg_type |= GF_SR_CFG_WINDOWSIZE_NOTIF; | |
| } | |
| if (lock_ok) gf_sc_lock(compositor, 0); | |
| //forward scene size event before actual resize in case the user cancels the resize | |
| { | |
| GF_Event evt; | |
| evt.type = GF_EVENT_SCENE_SIZE; | |
| evt.size.width = NewWidth; | |
| evt.size.height = NewHeight; | |
| evt.size.orientation = 0; | |
| if (compositor->scene && gf_sg_get_root_node(compositor->scene)) | |
| evt.size.window_id = 1; | |
| else | |
| evt.size.window_id = 0; | |
| gf_sc_send_event(compositor, &evt); | |
| } | |
| return GF_OK; | |
| } | |
| GF_EXPORT | |
| void gf_sc_reload_config(GF_Compositor *compositor) | |
| { | |
| /*changing drivers needs exclusive access*/ | |
| gf_sc_lock(compositor, 1); | |
| gf_sc_set_fps(compositor, compositor->fps); | |
| if (compositor->fsize) | |
| compositor->override_size_flags |= 1; | |
| else | |
| compositor->override_size_flags &= ~1; | |
| #ifndef GPAC_DISABLE_3D | |
| if (! (compositor->video_out->hw_caps & GF_VIDEO_HW_OPENGL)) { | |
| if (compositor->player && (compositor->ogl > GF_SC_GLMODE_OFF)) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Compositor] OpenGL mode requested but no opengl-capable output - disabling OpenGL\n")); | |
| } | |
| compositor->force_opengl_2d = 0; | |
| compositor->autoconfig_opengl = 0; | |
| compositor->hybrid_opengl = 0; | |
| } else { | |
| compositor->force_opengl_2d = (compositor->ogl==GF_SC_GLMODE_ON) ? 1 : 0; | |
| if (compositor->ogl == GF_SC_GLMODE_AUTO) { | |
| compositor->recompute_ar = 1; | |
| compositor->autoconfig_opengl = 1; | |
| } else { | |
| compositor->hybrid_opengl = (compositor->ogl==GF_SC_GLMODE_HYBRID) ? 1 : 0; | |
| } | |
| } | |
| #ifdef GPAC_USE_GLES1X | |
| compositor->linegl = 1; | |
| compositor->epow2 = 1; | |
| #endif | |
| compositor->visual->nb_views = compositor->nbviews; | |
| compositor->visual->camlay = compositor->camlay; | |
| compositor->visual->autostereo_type = compositor->stereo; | |
| if (compositor->visual->autostereo_type == GF_3D_STEREO_5VSP19) { | |
| if (compositor->visual->nb_views != 5) { | |
| if (compositor->visual->nb_views) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Compositor] SPV19 interleaving used but only %d views indicated - adjusting to 5 view\n", compositor->visual->nb_views )); | |
| } | |
| compositor->visual->nb_views = 5; | |
| } | |
| } | |
| else if (compositor->visual->autostereo_type == GF_3D_STEREO_8VALIO) { | |
| if (compositor->visual->nb_views != 8) { | |
| if (compositor->visual->nb_views) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Compositor] ALIO interleaving used but only %d views indicated - adjusting to 8 view\n", compositor->visual->nb_views )); | |
| compositor->visual->nb_views = 8; | |
| } | |
| } | |
| } | |
| if ((compositor->visual->autostereo_type!=GF_3D_STEREO_NONE) && (compositor->visual->autostereo_type <= GF_3D_STEREO_HEADSET)) { | |
| compositor->visual->nb_views = 2; | |
| } | |
| if (compositor->visual->autostereo_type) | |
| compositor->force_opengl_2d = 1; | |
| switch (compositor->visual->autostereo_type) { | |
| case GF_3D_STEREO_ANAGLYPH: | |
| case GF_3D_STEREO_COLUMNS: | |
| case GF_3D_STEREO_ROWS: | |
| if (compositor->visual->nb_views != 2) { | |
| if (compositor->visual->nb_views) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Compositor] Stereo interleaving used but %d views indicated - adjusting to 2 view\n", compositor->visual->nb_views )); | |
| } | |
| compositor->visual->nb_views = 2; | |
| } | |
| break; | |
| } | |
| if (!compositor->visual->nb_views) compositor->visual->nb_views = 1; | |
| #endif //GPAC_DISABLE_3D | |
| /*load defer mode only once hybrid_opengl is known. If no hybrid OpenGL and no backbuffer 2D, disable defer rendering*/ | |
| if (!compositor->hybrid_opengl && compositor->video_out->hw_caps & GF_VIDEO_HW_DIRECT_ONLY) { | |
| compositor->traverse_state->immediate_draw = 1; | |
| } else { | |
| if (compositor->mode2d==GF_DRAW_MODE_IMMEDIATE) | |
| compositor->traverse_state->immediate_draw = 1; | |
| else if (compositor->mode2d==GF_DRAW_MODE_DEFER_DEBUG) { | |
| compositor->traverse_state->immediate_draw = 0; | |
| compositor->debug_defer = 1; | |
| } else { | |
| compositor->traverse_state->immediate_draw = 0; | |
| } | |
| } | |
| #ifdef GF_SR_USE_DEPTH | |
| #ifndef GPAC_DISABLE_3D | |
| /*if auto-stereo mode, turn on display depth by default*/ | |
| if (compositor->visual->autostereo_type && !compositor->dispdepth) { | |
| compositor->dispdepth = -1; | |
| } | |
| #endif | |
| #endif | |
| if (!compositor->video_out->dispdist) { | |
| compositor->video_out->dispdist = compositor->dispdist; | |
| } | |
| if (compositor->sgaze) compositor->gazer_enabled = GF_TRUE; | |
| if (!compositor->video_out->dpi_x) { | |
| compositor->video_out->dpi_x = compositor->dpi.x; | |
| compositor->video_out->dpi_y = compositor->dpi.y; | |
| } | |
| gf_sc_reset_graphics(compositor); | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| if (compositor->audio_renderer) { | |
| gf_sc_ar_set_volume(compositor->audio_renderer, compositor->avol); | |
| gf_sc_ar_set_pan(compositor->audio_renderer, compositor->apan); | |
| } | |
| gf_sc_lock(compositor, 0); | |
| } | |
| static void gf_sc_set_play_state_internal(GF_Compositor *compositor, u32 PlayState, Bool reset_audio, Bool pause_clocks); | |
| GF_EXPORT | |
| GF_Err gf_sc_set_option(GF_Compositor *compositor, GF_CompositorOption type, u32 value) | |
| { | |
| GF_Err e; | |
| gf_sc_lock(compositor, 1); | |
| e = GF_OK; | |
| switch (type) { | |
| case GF_OPT_PLAY_STATE: | |
| gf_sc_set_play_state(compositor, value); | |
| break; | |
| case GF_OPT_AUDIO_VOLUME: | |
| gf_sc_ar_set_volume(compositor->audio_renderer, value); | |
| break; | |
| case GF_OPT_AUDIO_PAN: | |
| gf_sc_ar_set_pan(compositor->audio_renderer, value); | |
| break; | |
| case GF_OPT_AUDIO_MUTE: | |
| gf_sc_ar_mute(compositor->audio_renderer, value); | |
| break; | |
| case GF_OPT_OVERRIDE_SIZE: | |
| compositor->override_size_flags = value ? 1 : 0; | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| break; | |
| case GF_OPT_STRESS_MODE: | |
| compositor->stress = value; | |
| break; | |
| case GF_OPT_ANTIALIAS: | |
| compositor->aa = value; | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| break; | |
| case GF_OPT_HIGHSPEED: | |
| compositor->fast = value; | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| break; | |
| case GF_OPT_DRAW_BOUNDS: | |
| compositor->bvol = value; | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| break; | |
| case GF_OPT_TEXTURE_TEXT: | |
| compositor->textxt = value; | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| break; | |
| case GF_OPT_ASPECT_RATIO: | |
| compositor->aspect_ratio = value; | |
| compositor->msg_type |= GF_SR_CFG_AR; | |
| break; | |
| case GF_OPT_INTERACTION_LEVEL: | |
| compositor->interaction_level = value; | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| break; | |
| case GF_OPT_REFRESH: | |
| gf_sc_reset_graphics(compositor); | |
| compositor->traverse_state->invalidate_all = 1; | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| break; | |
| case GF_OPT_FULLSCREEN: | |
| if (compositor->fullscreen != value) compositor->msg_type |= GF_SR_CFG_FULLSCREEN; | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| break; | |
| case GF_OPT_ORIGINAL_VIEW: | |
| compositor_2d_set_user_transform(compositor, FIX_ONE, 0, 0, 0); | |
| gf_sc_set_size(compositor, compositor->scene_width, compositor->scene_height); | |
| break; | |
| case GF_OPT_VISIBLE: | |
| compositor->is_hidden = !value; | |
| if (compositor->video_out->ProcessEvent) { | |
| GF_Event evt; | |
| evt.type = GF_EVENT_SHOWHIDE; | |
| evt.show.show_type = value ? 1 : 0; | |
| e = compositor->video_out->ProcessEvent(compositor->video_out, &evt); | |
| } | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| break; | |
| case GF_OPT_FREEZE_DISPLAY: | |
| compositor->freeze_display = value; | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| break; | |
| case GF_OPT_FORCE_AUDIO_CONFIG: | |
| if (value) { | |
| compositor->audio_renderer->config_forced++; | |
| } else if (compositor->audio_renderer->config_forced) { | |
| compositor->audio_renderer->config_forced --; | |
| } | |
| break; | |
| case GF_OPT_RELOAD_CONFIG: | |
| gf_sc_reload_config(compositor); | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| break; | |
| case GF_OPT_DRAW_MODE: | |
| if (!(compositor->video_out->hw_caps & GF_VIDEO_HW_DIRECT_ONLY)) { | |
| compositor->traverse_state->immediate_draw = (value==GF_DRAW_MODE_IMMEDIATE) ? 1 : 0; | |
| compositor->debug_defer = (value==GF_DRAW_MODE_DEFER_DEBUG) ? 1 : 0; | |
| /*force redraw*/ | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| } | |
| break; | |
| case GF_OPT_SCALABLE_ZOOM: | |
| compositor->sz = value; | |
| /*emulate size message to force AR recompute*/ | |
| compositor->msg_type |= GF_SR_CFG_AR; | |
| break; | |
| case GF_OPT_USE_OPENGL: | |
| if (compositor->force_opengl_2d != value) { | |
| compositor->force_opengl_2d = value; | |
| /*force resetup*/ | |
| compositor->root_visual_setup = 0; | |
| /*force texture setup when switching to OpenGL*/ | |
| if (value) gf_sc_reset_graphics(compositor); | |
| /*force redraw*/ | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| } | |
| break; | |
| case GF_OPT_VIDEO_BENCH: | |
| compositor->bench_mode = value ? GF_TRUE : GF_FALSE; | |
| break; | |
| case GF_OPT_YUV_HARDWARE: | |
| compositor->yuvhw = value; | |
| break; | |
| case GF_OPT_NAVIGATION_TYPE: | |
| compositor->rotation = 0; | |
| compositor_2d_set_user_transform(compositor, FIX_ONE, 0, 0, 0); | |
| #ifndef GPAC_DISABLE_3D | |
| compositor_3d_reset_camera(compositor); | |
| #endif | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| break; | |
| case GF_OPT_NAVIGATION: | |
| if (! gf_sc_navigation_supported(compositor, value)) { | |
| e = GF_NOT_SUPPORTED; | |
| } | |
| #ifndef GPAC_DISABLE_3D | |
| else if (compositor->visual->type_3d || compositor->active_layer) { | |
| GF_Camera *cam = compositor_3d_get_camera(compositor); | |
| cam->navigate_mode = value; | |
| } | |
| #endif | |
| else { | |
| compositor->navigate_mode = value; | |
| } | |
| break; | |
| case GF_OPT_HEADLIGHT: | |
| #ifndef GPAC_DISABLE_3D | |
| if (compositor->visual->type_3d || compositor->active_layer) { | |
| GF_Camera *cam = compositor_3d_get_camera(compositor); | |
| if (cam->navigation_flags & NAV_ANY) { | |
| if (value) | |
| cam->navigation_flags |= NAV_HEADLIGHT; | |
| else | |
| cam->navigation_flags &= ~NAV_HEADLIGHT; | |
| break; | |
| } | |
| } | |
| #endif | |
| e = GF_NOT_SUPPORTED; | |
| break; | |
| #ifndef GPAC_DISABLE_3D | |
| case GF_OPT_EMULATE_POW2: | |
| compositor->epow2 = value; | |
| break; | |
| case GF_OPT_POLYGON_ANTIALIAS: | |
| compositor->paa = value; | |
| break; | |
| case GF_OPT_BACK_CULL: | |
| compositor->bcull = value; | |
| break; | |
| case GF_OPT_WIREFRAME: | |
| compositor->wire = value; | |
| break; | |
| case GF_OPT_NORMALS: | |
| compositor->norms = value; | |
| break; | |
| #ifdef GPAC_HAS_GLU | |
| case GF_OPT_RASTER_OUTLINES: | |
| compositor->linegl = value; | |
| break; | |
| #endif | |
| case GF_OPT_NO_RECT_TEXTURE: | |
| if (value != compositor->rext) { | |
| compositor->rext = value; | |
| /*RECT texture support - we must reload HW*/ | |
| gf_sc_reset_graphics(compositor); | |
| } | |
| break; | |
| case GF_OPT_COLLISION: | |
| compositor->collide_mode = value; | |
| break; | |
| case GF_OPT_GRAVITY: | |
| { | |
| GF_Camera *cam = compositor_3d_get_camera(compositor); | |
| compositor->gravity_on = value; | |
| /*force collision pass*/ | |
| cam->last_pos.z -= 1; | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| } | |
| break; | |
| #endif | |
| case GF_OPT_VIDEO_CACHE_SIZE: | |
| #ifdef GF_SR_USE_VIDEO_CACHE | |
| compositor_set_cache_memory(compositor, 1024*value); | |
| #else | |
| e = GF_NOT_SUPPORTED; | |
| #endif | |
| break; | |
| case GF_OPT_MULTIVIEW_MODE: | |
| compositor->multiview_mode = value; | |
| break; | |
| case GF_OPT_HTTP_MAX_RATE: | |
| { | |
| #ifndef GPAC_DISABLE_NETWORK | |
| GF_DownloadManager *dm = gf_filter_get_download_manager(compositor->filter); | |
| if (!dm) return GF_SERVICE_ERROR; | |
| gf_dm_set_data_rate(dm, value); | |
| #endif | |
| e = GF_OK; | |
| break; | |
| } | |
| default: | |
| e = GF_BAD_PARAM; | |
| break; | |
| } | |
| gf_sc_lock(compositor, 0); | |
| return e; | |
| } | |
| GF_EXPORT | |
| Bool gf_sc_is_over(GF_Compositor *compositor, GF_SceneGraph *scene_graph) | |
| { | |
| u32 i, count; | |
| count = gf_list_count(compositor->time_nodes); | |
| for (i=0; i<count; i++) { | |
| GF_TimeNode *tn = (GF_TimeNode *)gf_list_get(compositor->time_nodes, i); | |
| if (tn->needs_unregister) continue; | |
| if (scene_graph && (gf_node_get_graph((GF_Node *)tn->udta) != scene_graph)) | |
| continue; | |
| switch (gf_node_get_tag((GF_Node *)tn->udta)) { | |
| #ifndef GPAC_DISABLE_VRML | |
| case TAG_MPEG4_TimeSensor: | |
| #endif | |
| #ifndef GPAC_DISABLE_X3D | |
| case TAG_X3D_TimeSensor: | |
| #endif | |
| return 0; | |
| #ifndef GPAC_DISABLE_VRML | |
| case TAG_MPEG4_MovieTexture: | |
| #ifndef GPAC_DISABLE_X3D | |
| case TAG_X3D_MovieTexture: | |
| #endif | |
| if (((M_MovieTexture *)tn->udta)->loop) return 0; | |
| break; | |
| case TAG_MPEG4_AudioClip: | |
| #ifndef GPAC_DISABLE_X3D | |
| case TAG_X3D_AudioClip: | |
| #endif | |
| if (((M_AudioClip*)tn->udta)->loop) return 0; | |
| break; | |
| case TAG_MPEG4_AnimationStream: | |
| if (((M_AnimationStream*)tn->udta)->loop) return 0; | |
| break; | |
| #endif | |
| } | |
| } | |
| /*FIXME - this does not work with SVG/SMIL*/ | |
| return 1; | |
| } | |
| GF_EXPORT | |
| u32 gf_sc_get_option(GF_Compositor *compositor, GF_CompositorOption type) | |
| { | |
| switch (type) { | |
| case GF_OPT_PLAY_STATE: | |
| return compositor->paused ? 1 : 0; | |
| case GF_OPT_OVERRIDE_SIZE: | |
| return (compositor->override_size_flags & 1) ? 1 : 0; | |
| case GF_OPT_IS_FINISHED: | |
| if (compositor->interaction_sensors) return 0; | |
| case GF_OPT_IS_OVER: | |
| return gf_sc_is_over(compositor, NULL); | |
| case GF_OPT_STRESS_MODE: | |
| return compositor->stress; | |
| case GF_OPT_AUDIO_VOLUME: | |
| return compositor->audio_renderer->volume; | |
| case GF_OPT_AUDIO_PAN: | |
| return compositor->audio_renderer->pan; | |
| case GF_OPT_AUDIO_MUTE: | |
| return compositor->audio_renderer->mute; | |
| case GF_OPT_ANTIALIAS: | |
| return compositor->aa; | |
| case GF_OPT_HIGHSPEED: | |
| return compositor->fast; | |
| case GF_OPT_ASPECT_RATIO: | |
| return compositor->aspect_ratio; | |
| case GF_OPT_FULLSCREEN: | |
| return compositor->fullscreen; | |
| case GF_OPT_INTERACTION_LEVEL: | |
| return compositor->interaction_level; | |
| case GF_OPT_VISIBLE: | |
| return !compositor->is_hidden; | |
| case GF_OPT_FREEZE_DISPLAY: | |
| return compositor->freeze_display; | |
| case GF_OPT_TEXTURE_TEXT: | |
| return compositor->textxt; | |
| case GF_OPT_USE_OPENGL: | |
| return compositor->force_opengl_2d; | |
| case GF_OPT_DRAW_MODE: | |
| if (compositor->traverse_state->immediate_draw) return GF_DRAW_MODE_IMMEDIATE; | |
| if (compositor->debug_defer) return GF_DRAW_MODE_DEFER_DEBUG; | |
| return GF_DRAW_MODE_DEFER; | |
| case GF_OPT_SCALABLE_ZOOM: | |
| return compositor->sz; | |
| case GF_OPT_YUV_HARDWARE: | |
| return compositor->yuvhw; | |
| case GF_OPT_YUV_FORMAT: | |
| return compositor->yuvhw ? compositor->video_out->yuv_pixel_format : 0; | |
| case GF_OPT_NAVIGATION_TYPE: | |
| #ifndef GPAC_DISABLE_3D | |
| if (compositor->navigation_disabled) return GF_NAVIGATE_TYPE_NONE; | |
| if (compositor->visual->type_3d || compositor->active_layer) { | |
| GF_Camera *cam = compositor_3d_get_camera(compositor); | |
| if ((cam->navigation_flags & NAV_SELECTABLE)) return GF_NAVIGATE_TYPE_3D; | |
| if (!(cam->navigation_flags & NAV_ANY)) return GF_NAVIGATE_TYPE_NONE; | |
| // return ((cam->is_3D || compositor->active_layer) ? GF_NAVIGATE_TYPE_3D : GF_NAVIGATE_TYPE_2D); | |
| return GF_NAVIGATE_TYPE_3D; | |
| } else | |
| #endif | |
| { | |
| return GF_NAVIGATE_TYPE_2D; | |
| } | |
| case GF_OPT_NAVIGATION: | |
| #ifndef GPAC_DISABLE_3D | |
| if (compositor->visual->type_3d || compositor->active_layer) { | |
| GF_Camera *cam = compositor_3d_get_camera(compositor); | |
| return cam->navigate_mode; | |
| } | |
| #endif | |
| return compositor->navigate_mode; | |
| case GF_OPT_HEADLIGHT: | |
| return 0; | |
| case GF_OPT_COLLISION: | |
| return GF_COLLISION_NONE; | |
| case GF_OPT_GRAVITY: | |
| return 0; | |
| case GF_OPT_VIDEO_CACHE_SIZE: | |
| #ifdef GF_SR_USE_VIDEO_CACHE | |
| return compositor->vcsize/1024; | |
| #else | |
| return 0; | |
| #endif | |
| break; | |
| case GF_OPT_NUM_STEREO_VIEWS: | |
| #ifndef GPAC_DISABLE_3D | |
| if (compositor->visual->type_3d) { | |
| if (compositor->visual->nb_views && compositor->visual->autostereo_type > GF_3D_STEREO_LAST_SINGLE_BUFFER) | |
| return compositor->visual->nb_views; | |
| } | |
| #endif | |
| return 1; | |
| default: | |
| return 0; | |
| } | |
| } | |
| GF_EXPORT | |
| void gf_sc_map_point(GF_Compositor *compositor, s32 X, s32 Y, Fixed *bifsX, Fixed *bifsY) | |
| { | |
| /*coordinates are in user-like OS....*/ | |
| X = X - compositor->display_width/2; | |
| Y = compositor->display_height/2 - Y; | |
| *bifsX = INT2FIX(X); | |
| *bifsY = INT2FIX(Y); | |
| } | |
| GF_EXPORT | |
| GF_Err gf_sc_get_screen_buffer(GF_Compositor *compositor, GF_VideoSurface *framebuffer, GF_CompositorGrabMode depth_dump_mode) | |
| { | |
| GF_Err e; | |
| if (!compositor || !framebuffer) return GF_BAD_PARAM; | |
| gf_mx_p(compositor->mx); | |
| #ifndef GPAC_DISABLE_3D | |
| if (compositor->visual->type_3d || compositor->hybrid_opengl) | |
| e = compositor_3d_get_screen_buffer(compositor, framebuffer, depth_dump_mode); | |
| else | |
| #endif | |
| /*no depth dump in 2D mode*/ | |
| if (depth_dump_mode) e = GF_NOT_SUPPORTED; | |
| else if (!compositor->video_out->LockBackBuffer) e = GF_NOT_SUPPORTED; | |
| else e = compositor->video_out->LockBackBuffer(compositor->video_out, framebuffer, 1); | |
| if (e != GF_OK) gf_mx_v(compositor->mx); | |
| return e; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_sc_get_offscreen_buffer(GF_Compositor *compositor, GF_VideoSurface *framebuffer, u32 view_idx, GF_CompositorGrabMode depth_dump_mode) | |
| { | |
| if (!compositor || !framebuffer) return GF_BAD_PARAM; | |
| #ifndef GPAC_DISABLE_3D | |
| if (compositor->visual->type_3d && compositor->visual->nb_views && (compositor->visual->autostereo_type > GF_3D_STEREO_LAST_SINGLE_BUFFER)) { | |
| GF_Err e; | |
| gf_mx_p(compositor->mx); | |
| e = compositor_3d_get_offscreen_buffer(compositor, framebuffer, view_idx, depth_dump_mode); | |
| if (e != GF_OK) gf_mx_v(compositor->mx); | |
| return e; | |
| } | |
| #endif | |
| return GF_BAD_PARAM; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_sc_release_screen_buffer(GF_Compositor *compositor, GF_VideoSurface *framebuffer) | |
| { | |
| GF_Err e; | |
| if (!compositor || !framebuffer) return GF_BAD_PARAM; | |
| #ifndef GPAC_DISABLE_3D | |
| if (compositor->visual->type_3d || compositor->hybrid_opengl) | |
| e = compositor_3d_release_screen_buffer(compositor, framebuffer); | |
| else | |
| #endif | |
| e = compositor->video_out->LockBackBuffer(compositor->video_out, framebuffer, 0); | |
| gf_mx_v(compositor->mx); | |
| return e; | |
| } | |
| GF_EXPORT | |
| Double gf_sc_get_fps(GF_Compositor *compositor, Bool absoluteFPS) | |
| { | |
| Double fps; | |
| u32 fidx, num, frames, run_time; | |
| gf_mx_p(compositor->mx); | |
| if (absoluteFPS) { | |
| /*start from last frame and get first frame time*/ | |
| fidx = compositor->current_frame; | |
| frames = 0; | |
| run_time = compositor->frame_dur[fidx]; | |
| for (num=0; num<GF_SR_FPS_COMPUTE_SIZE; num++) { | |
| run_time += compositor->frame_dur[fidx]; | |
| frames++; | |
| if (frames==GF_SR_FPS_COMPUTE_SIZE) break; | |
| if (!fidx) { | |
| fidx = GF_SR_FPS_COMPUTE_SIZE-1; | |
| } else { | |
| fidx--; | |
| } | |
| } | |
| } else { | |
| /*start from last frame and get first frame time*/ | |
| fidx = compositor->current_frame; | |
| run_time = compositor->frame_time[fidx]; | |
| fidx = (fidx+1)% GF_SR_FPS_COMPUTE_SIZE; | |
| s64 diff = run_time; | |
| diff -= compositor->frame_time[fidx]; | |
| if (diff<0) { | |
| diff += 0xFFFFFFFFUL; | |
| gf_assert(diff >= 0); | |
| } | |
| run_time = (u32) diff; | |
| frames = GF_SR_FPS_COMPUTE_SIZE-1; | |
| } | |
| gf_mx_v(compositor->mx); | |
| if (!run_time) return ((Double) compositor->fps.num)/compositor->fps.den; | |
| fps = 1000*frames; | |
| fps /= run_time; | |
| return fps; | |
| } | |
| GF_EXPORT | |
| void gf_sc_register_time_node(GF_Compositor *compositor, GF_TimeNode *tn) | |
| { | |
| /*may happen with DEF/USE */ | |
| if (tn->is_registered) return; | |
| if (tn->needs_unregister) return; | |
| gf_list_add(compositor->time_nodes, tn); | |
| tn->is_registered = 1; | |
| } | |
| GF_EXPORT | |
| void gf_sc_unregister_time_node(GF_Compositor *compositor, GF_TimeNode *tn) | |
| { | |
| gf_list_del_item(compositor->time_nodes, tn); | |
| } | |
| static void gf_sc_recompute_ar(GF_Compositor *compositor, GF_Node *top_node) | |
| { | |
| Bool force_pause; | |
| // force_pause = compositor->audio_renderer->Frozen ? 0 : 1; | |
| force_pause = GF_FALSE; | |
| #ifndef GPAC_DISABLE_LOG | |
| compositor->visual_config_time = 0; | |
| #endif | |
| if (compositor->recompute_ar) { | |
| #ifndef GPAC_DISABLE_3D | |
| u32 prev_type_3d = compositor->visual->type_3d; | |
| #endif | |
| #ifndef GPAC_DISABLE_LOG | |
| u32 time=0; | |
| if (gf_log_tool_level_on(GF_LOG_RTI, GF_LOG_DEBUG)) { | |
| time = gf_sys_clock(); | |
| } | |
| #endif | |
| if (force_pause) | |
| gf_sc_ar_control(compositor->audio_renderer, GF_SC_AR_PAUSE); | |
| #ifndef GPAC_DISABLE_3D | |
| if (compositor->autoconfig_opengl) { | |
| compositor->visual->type_3d = 1; | |
| } | |
| if (compositor->visual->type_3d) { | |
| compositor_3d_set_aspect_ratio(compositor); | |
| gf_sc_load_opengl_extensions(compositor, compositor->visual->type_3d ? GF_TRUE : GF_FALSE); | |
| #ifndef GPAC_USE_GLES1X | |
| visual_3d_init_shaders(compositor->visual); | |
| #endif | |
| if (compositor->autoconfig_opengl) { | |
| u32 mode = compositor->ogl; | |
| compositor->autoconfig_opengl = 0; | |
| compositor->force_opengl_2d = 0; | |
| compositor->hybrid_opengl = GF_FALSE; | |
| compositor->visual->type_3d = prev_type_3d; | |
| #if !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X) | |
| //enable hybrid mode by default | |
| if (compositor->visual->compositor->shader_mode_disabled && (mode==GF_SC_GLMODE_HYBRID) ) { | |
| mode = GF_SC_GLMODE_OFF; | |
| } | |
| #endif | |
| switch (mode) { | |
| case GF_SC_GLMODE_OFF: | |
| break; | |
| case GF_SC_GLMODE_ON: | |
| compositor->force_opengl_2d = 1; | |
| if (!compositor->visual->type_3d) | |
| compositor->visual->type_3d = 1; | |
| break; | |
| case GF_SC_GLMODE_AUTO: | |
| case GF_SC_GLMODE_HYBRID: | |
| compositor->hybrid_opengl = GF_TRUE; | |
| break; | |
| } | |
| } | |
| } | |
| if (!compositor->visual->type_3d) | |
| #endif | |
| { | |
| compositor_2d_set_aspect_ratio(compositor); | |
| #ifndef GPAC_DISABLE_3D | |
| if (compositor->hybrid_opengl) { | |
| gf_sc_load_opengl_extensions(compositor, GF_TRUE); | |
| #ifndef GPAC_USE_GLES1X | |
| visual_3d_init_shaders(compositor->visual); | |
| #endif | |
| if (!compositor->visual->hybgl_drawn.list) { | |
| ra_init(&compositor->visual->hybgl_drawn); | |
| } | |
| } | |
| #endif | |
| } | |
| if (force_pause ) | |
| gf_sc_ar_control(compositor->audio_renderer, GF_SC_AR_RESUME); | |
| #ifndef GPAC_DISABLE_LOG | |
| if (gf_log_tool_level_on(GF_LOG_RTI, GF_LOG_DEBUG)) { | |
| compositor->visual_config_time = gf_sys_clock() - time; | |
| } | |
| #endif | |
| #ifndef GPAC_DISABLE_VRML | |
| compositor_evaluate_envtests(compositor, 0); | |
| #endif | |
| //fullscreen was postponed, retry now that the AR has been recomputed | |
| if (compositor->fullscreen_postponed) { | |
| compositor->fullscreen_postponed = 0; | |
| compositor->msg_type |= GF_SR_CFG_FULLSCREEN; | |
| } | |
| if (compositor->vout) { | |
| gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_WIDTH, &PROP_UINT(compositor->output_width) ); | |
| gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_HEIGHT, &PROP_UINT(compositor->output_height) ); | |
| gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_FPS, &PROP_FRAC(compositor->fps) ); | |
| gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_MIRROR, compositor->is_opengl ? &PROP_UINT(1) : NULL ); | |
| } | |
| } | |
| } | |
| static void gf_sc_setup_root_visual(GF_Compositor *compositor, GF_Node *top_node) | |
| { | |
| if (top_node && !compositor->root_visual_setup) { | |
| GF_SceneGraph *scene = compositor->scene; | |
| u32 node_tag; | |
| #ifndef GPAC_DISABLE_3D | |
| Bool force_navigate=0; | |
| Bool was_3d = compositor->visual->type_3d; | |
| #endif | |
| compositor->root_visual_setup = 1; | |
| compositor->visual->center_coords = 1; | |
| compositor->traverse_state->pixel_metrics = 1; | |
| compositor->traverse_state->min_hsize = INT2FIX(MIN(compositor->scene_width, compositor->scene_height)) / 2; | |
| node_tag = gf_node_get_tag(top_node); | |
| switch (node_tag) { | |
| #ifndef GPAC_DISABLE_VRML | |
| case TAG_MPEG4_OrderedGroup: | |
| case TAG_MPEG4_Layer2D: | |
| #ifndef GPAC_DISABLE_3D | |
| /*move to perspective 3D when simulating depth*/ | |
| #ifdef GF_SR_USE_DEPTH | |
| if (compositor->dispdepth) { | |
| compositor->visual->type_3d = 0; | |
| compositor->visual->camera.is_3D = 0; | |
| } else | |
| #endif | |
| { | |
| compositor->visual->type_3d = 0; | |
| compositor->visual->camera.is_3D = 0; | |
| } | |
| #endif | |
| compositor->traverse_state->pixel_metrics = gf_sg_use_pixel_metrics(scene); | |
| break; | |
| case TAG_MPEG4_Group: | |
| case TAG_MPEG4_Layer3D: | |
| #ifndef GPAC_DISABLE_3D | |
| compositor->visual->type_3d = 2; | |
| compositor->visual->camera.is_3D = 1; | |
| #endif | |
| compositor->traverse_state->pixel_metrics = gf_sg_use_pixel_metrics(scene); | |
| break; | |
| #ifndef GPAC_DISABLE_X3D | |
| case TAG_X3D_Group: | |
| #ifndef GPAC_DISABLE_3D | |
| compositor->visual->type_3d = 3; | |
| #endif | |
| compositor->traverse_state->pixel_metrics = gf_sg_use_pixel_metrics(scene); | |
| break; | |
| #endif /*GPAC_DISABLE_X3D*/ | |
| #endif /*GPAC_DISABLE_VRML*/ | |
| #ifndef GPAC_DISABLE_SVG | |
| case TAG_SVG_svg: | |
| #ifndef GPAC_DISABLE_3D | |
| #ifdef GF_SR_USE_DEPTH | |
| if (compositor->dispdepth>=0) { | |
| compositor->visual->type_3d = 2; | |
| compositor->visual->camera.is_3D = 1; | |
| } else | |
| #endif | |
| { | |
| compositor->visual->type_3d = 0; | |
| compositor->visual->camera.is_3D = 0; | |
| } | |
| #endif | |
| compositor->visual->center_coords = 0; | |
| compositor->root_visual_setup = 2; | |
| break; | |
| #endif /*GPAC_DISABLE_SVG*/ | |
| } | |
| /*!! by default we don't set the focus on the content - this is conform to SVG and avoids displaying the | |
| focus box for MPEG-4 !! */ | |
| /*setup OpenGL & camera mode*/ | |
| #ifndef GPAC_DISABLE_3D | |
| if (compositor->inherit_type_3d && !compositor->visual->type_3d) { | |
| compositor->visual->type_3d = 2; | |
| compositor->visual->camera.is_3D = 1; | |
| } | |
| /*request for OpenGL drawing in 2D*/ | |
| else if ( (compositor->force_opengl_2d && !compositor->visual->type_3d) | |
| || (compositor->hybrid_opengl && compositor->force_type_3d)) { | |
| compositor->force_type_3d=0; | |
| compositor->visual->type_3d = 1; | |
| if (compositor->force_opengl_2d==2) force_navigate=1; | |
| } | |
| if (compositor->drv==GF_SC_DRV_AUTO) | |
| gf_sc_check_video_driver(compositor); | |
| if (! (compositor->video_out->hw_caps & GF_VIDEO_HW_OPENGL)) { | |
| compositor->visual->type_3d = 0; | |
| compositor->visual->camera.is_3D = 0; | |
| } | |
| compositor->visual->camera.is_3D = (compositor->visual->type_3d>1) ? 1 : 0; | |
| if (!compositor->visual->camera.is_3D) | |
| camera_set_2d(&compositor->visual->camera); | |
| camera_invalidate(&compositor->visual->camera); | |
| if (force_navigate) { | |
| compositor->visual->camera.navigate_mode = GF_NAVIGATE_EXAMINE; | |
| compositor->visual->camera.had_nav_info = 0; | |
| } | |
| #endif | |
| GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Main scene setup - pixel metrics %d - center coords %d\n", compositor->traverse_state->pixel_metrics, compositor->visual->center_coords)); | |
| compositor->recompute_ar = 1; | |
| #ifndef GPAC_DISABLE_3D | |
| /*change in 2D/3D config, force AR recompute/video restup*/ | |
| if (was_3d != compositor->visual->type_3d) compositor->recompute_ar = was_3d ? 1 : 2; | |
| #endif | |
| } | |
| } | |
| static Bool gf_sc_draw_scene(GF_Compositor *compositor) | |
| { | |
| u32 flags; | |
| GF_Node *top_node = gf_sg_get_root_node(compositor->scene); | |
| if (!top_node && !compositor->visual->last_had_back && !compositor->visual->cur_context) { | |
| //GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Scene has no root node, nothing to draw\n")); | |
| //nothing to draw, skip flush | |
| compositor->skip_flush = 1; | |
| return GF_FALSE; | |
| } | |
| #ifdef GF_SR_USE_VIDEO_CACHE | |
| if (!compositor->vcsize) | |
| compositor->traverse_state->in_group_cache = 1; | |
| #endif | |
| flags = compositor->traverse_state->immediate_draw; | |
| if (compositor->video_setup_failed) { | |
| compositor->skip_flush = 1; | |
| } | |
| else { | |
| if (compositor->nodes_pending) { | |
| u32 i, count, n_count; | |
| i=0; | |
| count = gf_list_count(compositor->nodes_pending); | |
| while (i<count) { | |
| GF_Node *n = (GF_Node *)gf_list_get(compositor->nodes_pending, i); | |
| gf_node_traverse(n, NULL); | |
| if (!compositor->nodes_pending) break; | |
| n_count = gf_list_count(compositor->nodes_pending); | |
| if (n_count==count) i++; | |
| else count=n_count; | |
| } | |
| } | |
| if (compositor->passthrough_txh) { | |
| gf_sc_setup_passthrough(compositor); | |
| compositor->traverse_state->immediate_draw = 1; | |
| } | |
| if (! visual_draw_frame(compositor->visual, top_node, compositor->traverse_state, 1)) { | |
| /*android backend uses opengl without telling it to us, we need an ugly hack here ...*/ | |
| #ifdef GPAC_CONFIG_ANDROID | |
| compositor->skip_flush = 0; | |
| #else | |
| if (compositor->skip_flush==2) { | |
| compositor->skip_flush = 0; | |
| } else { | |
| compositor->skip_flush = 1; | |
| } | |
| #endif | |
| } | |
| } | |
| compositor->traverse_state->immediate_draw = flags; | |
| #ifdef GF_SR_USE_VIDEO_CACHE | |
| gf_list_reset(compositor->cached_groups_queue); | |
| #endif | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Frame %d - drawing done\n", compositor->frame_number)); | |
| /*only send the resize notification once the frame has been dra*/ | |
| if (compositor->recompute_ar) { | |
| compositor_send_resize_event(compositor, NULL, 0, 0, 0, 1); | |
| compositor->recompute_ar = 0; | |
| } | |
| compositor->zoom_changed = 0; | |
| compositor->audio_renderer->scene_ready = GF_TRUE; | |
| compositor->gaze_changed = GF_FALSE; | |
| return GF_TRUE; | |
| } | |
| #ifndef GPAC_DISABLE_LOG | |
| //defined in smil_anim ... | |
| extern u32 time_spent_in_anim; | |
| #endif | |
| static void compositor_release_textures(GF_Compositor *compositor, Bool frame_drawn) | |
| { | |
| u32 i, count; | |
| /*release all textures - we must release them to handle a same OD being used by several textures*/ | |
| count = gf_list_count(compositor->textures); | |
| for (i=0; i<count; i++) { | |
| GF_TextureHandler *txh = (GF_TextureHandler *)gf_list_get(compositor->textures, i); | |
| gf_sc_texture_release_stream(txh); | |
| if (frame_drawn && txh->tx_io && !(txh->flags & GF_SR_TEXTURE_USED)) | |
| gf_sc_texture_reset(txh); | |
| /*remove the use flag*/ | |
| txh->flags &= ~GF_SR_TEXTURE_USED; | |
| } | |
| } | |
| GF_EXPORT | |
| void gf_sc_invalidate_next_frame(GF_Compositor *compositor) | |
| { | |
| if (compositor) { | |
| //force frame draw type | |
| compositor->frame_draw_type = GF_SC_DRAW_FRAME; | |
| //reset frame produced flag and run until it is set (backbuffer ready to be swapped) | |
| compositor->frame_was_produced = GF_FALSE; | |
| } | |
| } | |
| GF_EXPORT | |
| Bool gf_sc_frame_was_produced(GF_Compositor *compositor) | |
| { | |
| return compositor ? compositor->frame_was_produced : GF_TRUE; | |
| } | |
| void gf_sc_render_frame(GF_Compositor *compositor) | |
| { | |
| #ifndef GPAC_DISABLE_SCENEGRAPH | |
| GF_SceneGraph *sg; | |
| #endif | |
| GF_List *temp_queue; | |
| u32 in_time, end_time, i, count, frame_duration, frame_ts, frame_draw_type_bck; | |
| Bool frame_drawn, has_timed_nodes=GF_FALSE, has_tx_streams=GF_FALSE, all_tx_done=GF_TRUE; | |
| #ifndef GPAC_DISABLE_LOG | |
| s32 event_time, route_time, smil_timing_time=0, time_node_time, texture_time, traverse_time, flush_time, txtime; | |
| #endif | |
| /*lock compositor for the whole cycle*/ | |
| gf_sc_lock(compositor, 1); | |
| // GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Entering render_frame \n")); | |
| in_time = gf_sys_clock(); | |
| gf_sc_texture_cleanup_hw(compositor); | |
| compositor->frame_was_produced = GF_FALSE; | |
| /*first thing to do, let the video output handle user event if it is not threaded*/ | |
| compositor->video_out->ProcessEvent(compositor->video_out, NULL); | |
| //handle all unthreaded extensions | |
| if (compositor->unthreaded_extensions) { | |
| count = gf_list_count(compositor->unthreaded_extensions); | |
| for (i=0; i<count; i++) { | |
| GF_CompositorExt *ifce = gf_list_get(compositor->unthreaded_extensions, i); | |
| ifce->process(ifce, GF_COMPOSITOR_EXT_PROCESS, NULL); | |
| } | |
| } | |
| if (compositor->freeze_display) { | |
| gf_sc_lock(compositor, 0); | |
| if (!compositor->bench_mode && compositor->player) { | |
| compositor->scene_sampled_clock = gf_sc_ar_get_clock(compositor->audio_renderer); | |
| } | |
| return; | |
| } | |
| gf_sc_reconfig_task(compositor); | |
| /* if there is no scene*/ | |
| if (!compositor->scene && !gf_list_count(compositor->extra_scenes) ) { | |
| gf_sc_draw_scene(compositor); | |
| if (compositor->player) { | |
| //increase clock in bench mode before releasing mutex | |
| if (compositor->bench_mode && (compositor->force_bench_frame==1)) { | |
| compositor->scene_sampled_clock += compositor->frame_duration; | |
| } | |
| } else { | |
| if (compositor->root_scene && compositor->root_scene->graph_attached) { | |
| compositor->check_eos_state = 2; | |
| } | |
| } | |
| gf_sc_lock(compositor, 0); | |
| compositor->force_bench_frame=0; | |
| compositor->frame_draw_type = 0; | |
| compositor->recompute_ar = 0; | |
| compositor->ms_until_next_frame = compositor->frame_duration; | |
| return; | |
| } | |
| if (compositor->reset_graphics) { | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| visual_reset_graphics(compositor->visual); | |
| #ifndef GPAC_DISABLE_3D | |
| compositor_3d_delete_fbo(&compositor->fbo_id, &compositor->fbo_tx_id, &compositor->fbo_depth_id, compositor->external_tx_id); | |
| #endif | |
| } | |
| /*process pending user events*/ | |
| #ifndef GPAC_DISABLE_LOG | |
| event_time = gf_sys_clock(); | |
| #endif | |
| //swap event queus | |
| gf_mx_p(compositor->evq_mx); | |
| temp_queue = compositor->event_queue; | |
| compositor->event_queue = compositor->event_queue_back; | |
| compositor->event_queue_back = temp_queue; | |
| gf_mx_v(compositor->evq_mx); | |
| while (gf_list_count(compositor->event_queue_back)) { | |
| GF_QueuedEvent *qev = (GF_QueuedEvent*)gf_list_get(compositor->event_queue_back, 0); | |
| gf_list_rem(compositor->event_queue_back, 0); | |
| if (qev->target) { | |
| #ifndef GPAC_DISABLE_SVG | |
| gf_sg_fire_dom_event(qev->target, &qev->dom_evt, qev->sg, NULL); | |
| #endif | |
| } else if (qev->node) { | |
| #ifndef GPAC_DISABLE_SVG | |
| gf_dom_event_fire(qev->node, &qev->dom_evt); | |
| #endif | |
| } else { | |
| gf_sc_exec_event(compositor, &qev->evt); | |
| } | |
| gf_free(qev); | |
| } | |
| #ifndef GPAC_DISABLE_LOG | |
| event_time = gf_sys_clock() - event_time; | |
| #endif | |
| if (compositor->player) { | |
| if (!compositor->bench_mode) { | |
| compositor->scene_sampled_clock = gf_sc_ar_get_clock(compositor->audio_renderer); | |
| } else { | |
| if (compositor->force_bench_frame==1) { | |
| //a system frame is pending on a future frame - we must increase our time | |
| compositor->scene_sampled_clock += compositor->frame_duration; | |
| } | |
| compositor->force_bench_frame = 0; | |
| } | |
| } | |
| //first update all natural textures to figure out timing | |
| compositor->frame_delay = 0; | |
| compositor->ms_until_next_frame = GF_INT_MAX; | |
| frame_duration = compositor->frame_duration; | |
| if (compositor->auto_rotate) | |
| compositor_handle_auto_navigation(compositor); | |
| /*first update time nodes since they may trigger play/stop request*/ | |
| compositor->force_late_frame_draw = GF_FALSE; | |
| #ifndef GPAC_DISABLE_SVG | |
| #ifndef GPAC_DISABLE_LOG | |
| smil_timing_time = gf_sys_clock(); | |
| #endif | |
| if (gf_smil_notify_timed_elements(compositor->scene)) { | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| } | |
| #ifndef GPAC_DISABLE_SCENEGRAPH | |
| i = 0; | |
| while ((sg = (GF_SceneGraph*)gf_list_enum(compositor->extra_scenes, &i))) { | |
| if (gf_smil_notify_timed_elements(sg)) { | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| } | |
| } | |
| #endif | |
| #ifndef GPAC_DISABLE_LOG | |
| smil_timing_time = gf_sys_clock() - smil_timing_time; | |
| #endif | |
| #endif //GPAC_DISABLE_SVG | |
| #ifndef GPAC_DISABLE_LOG | |
| time_node_time = gf_sys_clock(); | |
| #endif | |
| /*update all timed nodes */ | |
| count = gf_list_count(compositor->time_nodes); | |
| for (i=0; i<count; i++) { | |
| GF_TimeNode *tn = (GF_TimeNode *)gf_list_get(compositor->time_nodes, i); | |
| if (!tn->needs_unregister) tn->UpdateTimeNode(tn); | |
| if (tn->needs_unregister) { | |
| tn->is_registered = 0; | |
| tn->needs_unregister = 0; | |
| gf_list_rem(compositor->time_nodes, i); | |
| i--; | |
| count--; | |
| continue; | |
| } | |
| has_timed_nodes = compositor->timed_nodes_valid; | |
| } | |
| #ifndef GPAC_DISABLE_LOG | |
| time_node_time = gf_sys_clock() - time_node_time; | |
| #endif | |
| compositor->passthrough_txh = NULL; | |
| #ifndef GPAC_DISABLE_LOG | |
| texture_time = gf_sys_clock(); | |
| #endif | |
| //compute earliest frame TS in all textures that need refresh (skip textures with same timing) | |
| frame_ts = (u32) -1; | |
| /*update all video textures*/ | |
| frame_draw_type_bck = compositor->frame_draw_type; | |
| compositor->frame_draw_type = 0; | |
| has_tx_streams = GF_FALSE; | |
| count = gf_list_count(compositor->textures); | |
| for (i=0; i<count; i++) { | |
| GF_TextureHandler *txh = (GF_TextureHandler *)gf_list_get(compositor->textures, i); | |
| if (!txh) break; | |
| //this is not a natural (video) texture | |
| if (! txh->stream) continue; | |
| has_tx_streams = GF_TRUE; | |
| /*signal graphics reset before updating*/ | |
| if (compositor->reset_graphics && txh->tx_io) gf_sc_texture_reset(txh); | |
| txh->update_texture_fcnt(txh); | |
| if (!txh->stream_finished && txh->is_open) { | |
| u32 d = gf_mo_get_min_frame_dur(txh->stream); | |
| if (d && (d < frame_duration)) frame_duration = d; | |
| //if the texture needs update (new frame), compute its timestamp in system timebase | |
| if (txh->needs_refresh) { | |
| u32 ts = gf_mo_map_timestamp_to_sys_clock(txh->stream, txh->stream->timestamp); | |
| if (ts<frame_ts) frame_ts = ts; | |
| } | |
| //VFR and one frame is pending | |
| else if (compositor->vfr && (compositor->ms_until_next_frame>0) && (compositor->ms_until_next_frame!=GF_INT_MAX)) { | |
| compositor->frame_draw_type = GF_SC_DRAW_FRAME; | |
| } | |
| all_tx_done=0; | |
| } | |
| } | |
| #ifndef GPAC_DISABLE_LOG | |
| texture_time = gf_sys_clock() - texture_time; | |
| #endif | |
| if (compositor->player) { | |
| if (frame_draw_type_bck) | |
| compositor->frame_draw_type = frame_draw_type_bck; | |
| } | |
| //in non player mode, don't redraw frame if due to end of streams on textures | |
| else if (!frame_draw_type_bck && compositor->frame_draw_type && all_tx_done) | |
| compositor->frame_draw_type = 0; | |
| if (!compositor->player) { | |
| if (compositor->passthrough_txh && compositor->passthrough_txh->frame_ifce && !compositor->passthrough_txh->frame_ifce->get_plane) { | |
| compositor->passthrough_txh = NULL; | |
| } | |
| if (!compositor->passthrough_txh && frame_draw_type_bck) | |
| compositor->frame_draw_type = frame_draw_type_bck; | |
| if (compositor->passthrough_txh && compositor->passthrough_txh->width && compositor->passthrough_txh->needs_refresh) { | |
| compositor->scene_sampled_clock = compositor->passthrough_txh->last_frame_time/* + dur*/; | |
| } | |
| //we were buffering and are now no longer, update scene clock | |
| if (compositor->passthrough_txh && compositor->passthrough_check_buffer && !gf_mo_is_buffering(compositor->passthrough_txh->stream)) { | |
| u32 dur = gf_mo_get_min_frame_dur(compositor->passthrough_txh->stream); | |
| compositor->passthrough_check_buffer = GF_FALSE; | |
| compositor->scene_sampled_clock = compositor->passthrough_txh->last_frame_time + dur; | |
| } | |
| } | |
| //it may happen that we have a reconfigure request at this stage, especially if updating one of the textures | |
| //forced a relayout - do it right away | |
| if (compositor->msg_type) { | |
| //release textures ! | |
| compositor_release_textures(compositor, GF_FALSE); | |
| gf_sc_lock(compositor, 0); | |
| return; | |
| } | |
| if (compositor->focus_text_type) { | |
| if (!compositor->caret_next_draw_time) { | |
| compositor->caret_next_draw_time = gf_sys_clock(); | |
| compositor->show_caret = 1; | |
| } | |
| if (compositor->caret_next_draw_time <= compositor->last_frame_time) { | |
| compositor->frame_draw_type = GF_SC_DRAW_FRAME; | |
| compositor->caret_next_draw_time+=500; | |
| compositor->show_caret = !compositor->show_caret; | |
| compositor->text_edit_changed = 1; | |
| } | |
| } | |
| #ifndef GPAC_DISABLE_VRML | |
| /*execute all routes: | |
| before updating composite textures, otherwise nodes inside composite texture may never see their dirty flag set | |
| after updating timed nodes to trigger all events based on time | |
| */ | |
| #ifndef GPAC_DISABLE_LOG | |
| route_time = gf_sys_clock(); | |
| #endif | |
| gf_sg_activate_routes(compositor->scene); | |
| #ifndef GPAC_DISABLE_SCENEGRAPH | |
| i = 0; | |
| while ((sg = (GF_SceneGraph*)gf_list_enum(compositor->extra_scenes, &i))) { | |
| gf_sg_activate_routes(sg); | |
| } | |
| #endif | |
| #ifndef GPAC_DISABLE_LOG | |
| route_time = gf_sys_clock() - route_time; | |
| #endif | |
| #else | |
| #ifndef GPAC_DISABLE_LOG | |
| route_time = 0; | |
| #endif | |
| #endif /*GPAC_DISABLE_VRML*/ | |
| /*setup root visual BEFORE updating the composite textures (since they may depend on root setup)*/ | |
| gf_sc_setup_root_visual(compositor, gf_sg_get_root_node(compositor->scene)); | |
| /*setup display before updating composite textures (some may require a valid OpenGL context)*/ | |
| gf_sc_recompute_ar(compositor, gf_sg_get_root_node(compositor->scene) ); | |
| if (compositor->video_setup_failed) { | |
| compositor_release_textures(compositor, GF_FALSE); | |
| gf_sc_lock(compositor, 0); | |
| return; | |
| } | |
| #ifndef GPAC_DISABLE_LOG | |
| txtime = gf_sys_clock(); | |
| #endif | |
| /*update all composite textures*/ | |
| compositor->texture_inserted = GF_FALSE; | |
| count = gf_list_count(compositor->textures); | |
| for (i=0; i<count; i++) { | |
| GF_TextureHandler *txh = (GF_TextureHandler *)gf_list_get(compositor->textures, i); | |
| if (!txh) break; | |
| //this is not a composite texture | |
| if (txh->stream) continue; | |
| /*signal graphics reset before updating*/ | |
| if (compositor->reset_graphics && txh->tx_io) gf_sc_texture_reset(txh); | |
| txh->update_texture_fcnt(txh); | |
| if (compositor->texture_inserted) { | |
| compositor->texture_inserted = GF_FALSE; | |
| count = gf_list_count(compositor->textures); | |
| i = gf_list_find(compositor->textures, txh); | |
| } | |
| } | |
| //it may happen that we have a reconfigure request at this stage, especially if updating one of the textures update | |
| //forced a relayout - do it right away | |
| if (compositor->msg_type) { | |
| //reset AR recompute flag, it will be reset when msg is handled | |
| compositor->recompute_ar = 0; | |
| compositor_release_textures(compositor, GF_FALSE); | |
| gf_sc_lock(compositor, 0); | |
| return; | |
| } | |
| #ifndef GPAC_DISABLE_LOG | |
| texture_time += gf_sys_clock() - txtime; | |
| #endif | |
| compositor->text_edit_changed = 0; | |
| compositor->rebuild_offscreen_textures = 0; | |
| if (compositor->force_next_frame_redraw) { | |
| compositor->force_next_frame_redraw=0; | |
| compositor->frame_draw_type = GF_SC_DRAW_FRAME; | |
| } | |
| //if hidden and player mode, do not draw the scene | |
| if (compositor->is_hidden && compositor->player) { | |
| compositor->frame_draw_type = 0; | |
| } | |
| if (!compositor->player) { | |
| if (compositor->check_eos_state<=1) { | |
| /*check if we have to force a frame dispatch */ | |
| //no passthrough texture | |
| if (!compositor->passthrough_txh) { | |
| if (!compositor->vfr) { | |
| //in CFR and no texture associated, always force a redraw | |
| if (!has_tx_streams && has_timed_nodes) | |
| compositor->frame_draw_type = GF_SC_DRAW_FRAME; | |
| //otherwise if texture(s) but not all done, force a redraw | |
| else if (!all_tx_done) | |
| compositor->frame_draw_type = GF_SC_DRAW_FRAME; | |
| //we still have active system pids (BIFS/LASeR/DIMS commands), force a redraw | |
| if (gf_list_count(compositor->systems_pids)) | |
| compositor->frame_draw_type = GF_SC_DRAW_FRAME; | |
| //validator always triggers redraw to make sure the scene clock is incremented so that events can be fired | |
| if (compositor->validator_mode) | |
| compositor->frame_draw_type = GF_SC_DRAW_FRAME; | |
| } | |
| } | |
| //we have a passthrough texture, only generate an output frame when we have a new input on the passthrough | |
| else { | |
| //still waiting for initialization either from the passthrough stream or a texture used in the scene | |
| if (!compositor->passthrough_txh->width || !compositor->passthrough_txh->stream->pck || (compositor->ms_until_next_frame<0) ) { | |
| compositor->frame_draw_type = GF_SC_DRAW_NONE; | |
| //prevent release of frame | |
| if (compositor->passthrough_txh->needs_release) | |
| compositor->passthrough_txh->needs_release = 2; | |
| } | |
| //done | |
| else if ((compositor->passthrough_txh->stream_finished) && (compositor->ms_until_next_frame >= 0)) { | |
| compositor->check_eos_state = 2; | |
| } | |
| } | |
| if (compositor->frame_draw_type==GF_SC_DRAW_FRAME) | |
| compositor->check_eos_state = 0; | |
| } | |
| } | |
| frame_drawn = (compositor->frame_draw_type==GF_SC_DRAW_FRAME) ? 1 : 0; | |
| /*if invalidated, draw*/ | |
| if (compositor->frame_draw_type) { | |
| Bool emit_frame = GF_FALSE; | |
| Bool textures_released = 0; | |
| #ifndef GPAC_DISABLE_LOG | |
| traverse_time = gf_sys_clock(); | |
| time_spent_in_anim = 0; | |
| #endif | |
| if (compositor->traverse_state->immediate_draw) { | |
| compositor->frame_draw_type = GF_SC_DRAW_FRAME; | |
| } | |
| /*video flush only*/ | |
| if (compositor->frame_draw_type==GF_SC_DRAW_FLUSH) { | |
| compositor->frame_draw_type = 0; | |
| } | |
| /*full render*/ | |
| else { | |
| Bool scene_drawn; | |
| compositor->frame_draw_type = 0; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Redrawing scene - STB %d\n", compositor->scene_sampled_clock)); | |
| scene_drawn = gf_sc_draw_scene(compositor); | |
| #ifndef GPAC_DISABLE_LOG | |
| traverse_time = gf_sys_clock() - traverse_time; | |
| #endif | |
| if (compositor->ms_until_next_frame < 0) { | |
| emit_frame = GF_FALSE; | |
| compositor->last_error = GF_OK; | |
| } | |
| else if (!scene_drawn) | |
| emit_frame = GF_FALSE; | |
| else if (compositor->frame_draw_type) | |
| emit_frame = GF_FALSE; | |
| else if (compositor->fonts_pending>0) | |
| emit_frame = GF_FALSE; | |
| else if (compositor->clipframe && (!compositor->visual->frame_bounds.width || !compositor->visual->frame_bounds.height)) { | |
| emit_frame = GF_FALSE; | |
| } | |
| else | |
| emit_frame = GF_TRUE; | |
| #ifdef GPAC_CONFIG_ANDROID | |
| if (!emit_frame && scene_drawn) { | |
| compositor->frame_was_produced = GF_TRUE; | |
| } | |
| #endif | |
| } | |
| /*and flush*/ | |
| #ifndef GPAC_DISABLE_LOG | |
| flush_time = gf_sys_clock(); | |
| #endif | |
| if (compositor->init_flags & GF_VOUT_INIT_HIDE) | |
| compositor->skip_flush = 1; | |
| //new frame generated, emit packet | |
| if (emit_frame) { | |
| u64 pck_frame_ts=0; | |
| GF_FilterPacket *pck; | |
| frame_ts = 0; | |
| if (compositor->passthrough_pck) { | |
| pck = compositor->passthrough_pck; | |
| compositor->passthrough_pck = NULL; | |
| pck_frame_ts = gf_filter_pck_get_cts(pck); | |
| } else { | |
| u32 offset_x=0, offset_y=0; | |
| //assign udta of frame interface event when using shared packet, as it is used to test when frame is released | |
| compositor->frame_ifce.user_data = compositor; | |
| u32 nb_bpp=0; | |
| u32 pf = compositor->passthrough_pfmt; | |
| if (compositor->video_out!=&raw_vout) | |
| pf = compositor->opfmt; | |
| if (!pf && compositor->forced_alpha) | |
| pf = GF_PIXEL_RGBA; | |
| u32 c_w=compositor->display_width, c_h=compositor->display_height; | |
| if (compositor->clipframe) { | |
| offset_x = compositor->visual->frame_bounds.x + compositor->display_width/2; | |
| offset_y = compositor->display_height/2 - compositor->visual->frame_bounds.y; | |
| switch (pf) { | |
| case GF_PIXEL_ARGB: | |
| case GF_PIXEL_RGBA: | |
| nb_bpp = 4; | |
| break; | |
| case GF_PIXEL_RGB: | |
| case GF_PIXEL_BGR: | |
| nb_bpp = 3; | |
| break; | |
| } | |
| if (nb_bpp) { | |
| c_w = compositor->visual->frame_bounds.width; | |
| c_h = compositor->visual->frame_bounds.height; | |
| gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_WIDTH, &PROP_UINT(c_w)); | |
| gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_HEIGHT, &PROP_UINT(c_h)); | |
| gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_STRIDE, NULL); | |
| gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_STRIDE_UV, NULL); | |
| gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_CROP_POS, &PROP_VEC2I_INT(offset_x, offset_y)); | |
| gf_filter_pid_set_property(compositor->vout, GF_PROP_PID_ORIG_SIZE, &PROP_VEC2I_INT(compositor->display_width, compositor->display_height)); | |
| } | |
| } | |
| if ((c_w==compositor->display_width) && (c_h==compositor->display_height)) { | |
| if (compositor->video_out==&raw_vout) { | |
| pck = gf_filter_pck_new_shared(compositor->vout, compositor->framebuffer, compositor->framebuffer_size, gf_sc_frame_ifce_done); | |
| } else { | |
| compositor->frame_ifce.get_plane = gf_sc_frame_ifce_get_plane; | |
| compositor->frame_ifce.get_gl_texture = NULL; | |
| #ifndef GPAC_DISABLE_3D | |
| if (compositor->fbo_tx_id) { | |
| compositor->frame_ifce.get_gl_texture = gf_sc_frame_ifce_get_gl_texture; | |
| } | |
| #endif | |
| compositor->frame_ifce.flags = GF_FRAME_IFCE_BLOCKING; | |
| pck = gf_filter_pck_new_frame_interface(compositor->vout, &compositor->frame_ifce, gf_sc_frame_ifce_done); | |
| } | |
| } else { | |
| u32 j, osize = c_w * c_h * nb_bpp; | |
| Bool release_fb=GF_FALSE; | |
| u8 *output; | |
| u8 *src=NULL; | |
| pck = NULL; | |
| if (compositor->video_out==&raw_vout) { | |
| src = compositor->framebuffer; | |
| } else { | |
| if (gf_sc_get_screen_buffer(compositor, &compositor->fb, GF_SC_GRAB_DEPTH_NONE) == GF_OK) { | |
| src = compositor->fb.video_buffer; | |
| release_fb = GF_TRUE; | |
| } | |
| } | |
| if (src) { | |
| src += offset_x * nb_bpp + offset_y * nb_bpp * compositor->display_width; | |
| pck = gf_filter_pck_new_alloc(compositor->vout, osize, &output); | |
| if (output) { | |
| for (j=0; j<c_h; j++) { | |
| memcpy(output, src, nb_bpp*c_w); | |
| output += nb_bpp*c_w; | |
| src += nb_bpp * compositor->display_width; | |
| } | |
| } | |
| compositor->frame_ifce.user_data = NULL; | |
| if (release_fb) | |
| gf_sc_release_screen_buffer(compositor, &compositor->fb); | |
| } | |
| } | |
| if (!pck) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate output video packet\n")); | |
| compositor_release_textures(compositor, frame_drawn); | |
| gf_sc_lock(compositor, 0); | |
| return; | |
| } | |
| if (compositor->passthrough_txh) { | |
| gf_filter_pck_merge_properties(compositor->passthrough_txh->stream->pck, pck); | |
| pck_frame_ts = gf_filter_pck_get_cts(compositor->passthrough_txh->stream->pck); | |
| } else { | |
| //no texture found/updated, use scene sampled clock | |
| if (compositor->timescale) { | |
| frame_ts = compositor->frame_number * compositor->fps.den * compositor->timescale; | |
| frame_ts /= compositor->fps.num; | |
| } else { | |
| frame_ts = compositor->frame_number * compositor->fps.den; | |
| } | |
| gf_filter_pck_set_cts(pck, frame_ts); | |
| if (compositor->hint_extra_scene_cts_plus_one) { | |
| gf_filter_pck_set_cts(pck, compositor->hint_extra_scene_cts_plus_one-1); | |
| compositor->hint_extra_scene_cts_plus_one = 0; | |
| } | |
| if (compositor->hint_extra_scene_dur_plus_one) { | |
| gf_filter_pck_set_duration(pck, compositor->hint_extra_scene_dur_plus_one-1); | |
| compositor->hint_extra_scene_dur_plus_one = 0; | |
| } | |
| } | |
| } | |
| if (pck_frame_ts) { | |
| u64 ts = gf_timestamp_rescale(pck_frame_ts, compositor->passthrough_timescale, 1000); | |
| frame_ts = (u32) ts; | |
| } | |
| gf_filter_pck_send(pck); | |
| gf_sc_ar_update_video_clock(compositor->audio_renderer, frame_ts); | |
| if (!compositor->player) { | |
| //in regular mode don't change fps once we sent one frame | |
| compositor->autofps = GF_FALSE; | |
| if (compositor->passthrough_txh) { | |
| // update clock if not buffering | |
| if (!gf_mo_is_buffering(compositor->passthrough_txh->stream)) | |
| { | |
| u64 next_frame; | |
| next_frame = pck_frame_ts + gf_filter_pck_get_duration(pck); | |
| next_frame *= 1000; | |
| next_frame /= compositor->passthrough_timescale; | |
| gf_assert(next_frame>=compositor->scene_sampled_clock); | |
| compositor->scene_sampled_clock = (u32) next_frame; | |
| } | |
| // if buffering, remember to update clock at next frame | |
| else { | |
| compositor->passthrough_check_buffer = GF_TRUE; | |
| } | |
| if (compositor->passthrough_txh->stream_finished) | |
| compositor->check_eos_state = 2; | |
| } else { | |
| u64 res; | |
| compositor->last_frame_ts = frame_ts; | |
| res = (compositor->frame_number+1); | |
| res *= compositor->fps.den; | |
| res *= 1000; | |
| res /= compositor->fps.num; | |
| compositor->scene_sampled_clock = (u32) res; | |
| } | |
| GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Send video frame TS %u (%u ms) - next frame scene clock %d ms - passthrough %p\n", frame_ts, (frame_ts*1000)/compositor->fps.num, compositor->scene_sampled_clock, compositor->passthrough_txh)); | |
| compositor->frame_was_produced = GF_TRUE; | |
| } else { | |
| GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Send video frame TS %u - AR clock %d\n", frame_ts, compositor->audio_renderer->current_time)); | |
| if (compositor->bench_mode) { | |
| compositor->force_bench_frame = 1; | |
| } | |
| compositor->frame_was_produced = GF_TRUE; | |
| } | |
| } else if (!compositor->player) { | |
| frame_drawn = GF_FALSE; | |
| //prevent release of frame | |
| if (compositor->passthrough_txh && compositor->passthrough_txh->needs_release) | |
| compositor->passthrough_txh->needs_release = 2; | |
| } | |
| if (compositor->passthrough_pck) { | |
| gf_filter_pck_discard(compositor->passthrough_pck); | |
| compositor->passthrough_pck = NULL; | |
| } | |
| //if no overlays, release textures before flushing, otherwise we might loose time waiting for vsync | |
| if (!compositor->visual->has_overlays) { | |
| compositor_release_textures(compositor, frame_drawn); | |
| textures_released = 1; | |
| } | |
| //no flush pending | |
| if (!compositor->flush_pending && !compositor->skip_flush) { | |
| gf_sc_flush_video(compositor, GF_TRUE); | |
| } | |
| #ifndef GPAC_DISABLE_LOG | |
| flush_time = gf_sys_clock() - flush_time; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPTIME, ("[Compositor] done flushing frame in %d ms\n", flush_time)); | |
| #endif | |
| visual_2d_draw_overlays(compositor->visual); | |
| compositor->last_had_overlays = compositor->visual->has_overlays; | |
| if (!textures_released) { | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPTIME, ("[Compositor] Releasing textures after flush\n" )); | |
| compositor_release_textures(compositor, frame_drawn); | |
| } | |
| if (compositor->stress) { | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| gf_sc_reset_graphics(compositor); | |
| } | |
| compositor->reset_fonts = 0; | |
| } else { | |
| //frame not drawn, release textures | |
| compositor_release_textures(compositor, frame_drawn); | |
| #ifndef GPAC_DISABLE_LOG | |
| traverse_time = 0; | |
| time_spent_in_anim = 0; | |
| flush_time = 0; | |
| compositor->traverse_setup_time = 0; | |
| compositor->traverse_and_direct_draw_time = 0; | |
| compositor->indirect_draw_time = 0; | |
| #endif | |
| if (!compositor->player) { | |
| //if systems frames are pending (not executed because too early), increase clock | |
| if ((compositor->sys_frames_pending && (compositor->ms_until_next_frame>=0) ) | |
| //if timed nodes or validator mode (which acts as a timed node firing events), increase scene clock | |
| //in vfr mode | |
| || (compositor->vfr && (has_timed_nodes || compositor->validator_mode) ) | |
| ) { | |
| u64 res; | |
| compositor->sys_frames_pending = GF_FALSE; | |
| compositor->frame_number++; | |
| res = compositor->frame_number; | |
| res *= compositor->fps.den; | |
| res *= 1000; | |
| res /= compositor->fps.num; | |
| gf_assert(res >= compositor->scene_sampled_clock); | |
| compositor->scene_sampled_clock = (u32) res; | |
| } | |
| if (all_tx_done && !has_timed_nodes) { | |
| //we were in eos, notify we are done | |
| if (compositor->check_eos_state) | |
| compositor->check_eos_state = 2; | |
| //we were not in eos, force flushing pending audio | |
| else | |
| compositor->flush_audio = GF_TRUE; | |
| } | |
| } | |
| } | |
| compositor->reset_graphics = 0; | |
| compositor->last_frame_time = gf_sys_clock(); | |
| end_time = compositor->last_frame_time - in_time; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_RTI, ("[RTI]\tCompositor Cycle Log\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n", | |
| compositor->networks_time, | |
| compositor->decoders_time, | |
| compositor->frame_number, | |
| compositor->traverse_state->immediate_draw, | |
| compositor->visual_config_time, | |
| event_time, | |
| route_time, | |
| smil_timing_time, | |
| time_node_time, | |
| texture_time, | |
| time_spent_in_anim, | |
| compositor->traverse_setup_time, | |
| compositor->traverse_and_direct_draw_time, | |
| compositor->traverse_and_direct_draw_time - time_spent_in_anim, | |
| compositor->indirect_draw_time, | |
| traverse_time, | |
| flush_time, | |
| end_time)); | |
| if (frame_drawn) { | |
| compositor->current_frame = (compositor->current_frame+1) % GF_SR_FPS_COMPUTE_SIZE; | |
| compositor->frame_dur[compositor->current_frame] = end_time; | |
| compositor->frame_time[compositor->current_frame] = compositor->last_frame_time; | |
| compositor->frame_number++; | |
| } | |
| if (compositor->bench_mode && (frame_drawn || (has_timed_nodes&&all_tx_done) )) { | |
| //in bench mode we always increase the clock of the fixed target simulation rate - this needs refinement if video is used ... | |
| compositor->scene_sampled_clock += frame_duration; | |
| } | |
| gf_sc_lock(compositor, 0); | |
| if (frame_drawn) compositor->step_mode = GF_FALSE; | |
| /*old arch code kept for reference*/ | |
| #if 0 | |
| /*let the owner decide*/ | |
| if (compositor->no_regulation) | |
| return; | |
| /*we are in bench mode, just release for a moment the composition, oherwise we will constantly lock the compositor which may have impact on scene decoding*/ | |
| if (compositor->bench_mode) { | |
| gf_sleep(0); | |
| return; | |
| } | |
| //we have a pending frame, return asap - we could sleep until frames matures but this give weird regulation | |
| if (compositor->ms_until_next_frame != GF_INT_MAX) { | |
| compositor->ms_until_next_frame -= end_time; | |
| if (compositor->ms_until_next_frame<=0) { | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Next frame already due (%d ms late) - not going to sleep\n", - compositor->ms_until_next_frame)); | |
| compositor->ms_until_next_frame=0; | |
| return; | |
| } | |
| compositor->ms_until_next_frame = MIN(compositor->ms_until_next_frame, (s32) frame_duration ); | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Next frame due in %d ms\n", compositor->ms_until_next_frame)); | |
| if (compositor->ms_until_next_frame > 2) { | |
| u64 start = gf_sys_clock_high_res(); | |
| u64 diff=0; | |
| u64 wait_for = 1000 * (u64) compositor->ms_until_next_frame; | |
| while (! compositor->msg_type) { | |
| gf_sleep(1); | |
| diff = gf_sys_clock_high_res() - start; | |
| if (diff >= wait_for) | |
| break; | |
| } | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Compositor slept %d ms until next frame (msg type %d)\n", diff/1000, compositor->msg_type)); | |
| } | |
| return; | |
| } | |
| if (end_time > frame_duration) { | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Compositor did not go to sleep\n")); | |
| return; | |
| } | |
| /*compute sleep time till next frame*/ | |
| end_time %= frame_duration; | |
| gf_sleep(frame_duration - end_time); | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Compositor slept for %d ms\n", frame_duration - end_time)); | |
| #endif | |
| } | |
| Bool gf_sc_visual_is_registered(GF_Compositor *compositor, GF_VisualManager *visual) | |
| { | |
| GF_VisualManager *tmp; | |
| u32 i = 0; | |
| while ((tmp = (GF_VisualManager *)gf_list_enum(compositor->visuals, &i))) { | |
| if (tmp == visual) return 1; | |
| } | |
| return 0; | |
| } | |
| void gf_sc_visual_register(GF_Compositor *compositor, GF_VisualManager *visual) | |
| { | |
| if (gf_sc_visual_is_registered(compositor, visual)) return; | |
| gf_list_add(compositor->visuals, visual); | |
| } | |
| void gf_sc_visual_unregister(GF_Compositor *compositor, GF_VisualManager *visual) | |
| { | |
| gf_list_del_item(compositor->visuals, visual); | |
| } | |
| void gf_sc_traverse_subscene_ex(GF_Compositor *compositor, GF_Node *inline_parent, GF_SceneGraph *subscene, void *rs) | |
| { | |
| Fixed min_hsize, vp_scale; | |
| Bool use_pm, prev_pm, prev_coord; | |
| SFVec2f prev_vp; | |
| s32 flip_coords; | |
| u32 h, w, tag; | |
| GF_Matrix2D transf; | |
| GF_SceneGraph *in_scene; | |
| GF_Node *inline_root; | |
| GF_TraverseState *tr_state = (GF_TraverseState *)rs; | |
| /*we don't traverse subscenes until the root one is setup*/ | |
| if (!compositor->root_visual_setup) return; | |
| inline_root = gf_sg_get_root_node(subscene); | |
| if (!inline_root) return; | |
| if (!gf_scene_is_over(subscene)) | |
| tr_state->subscene_not_over ++; | |
| flip_coords = 0; | |
| in_scene = gf_node_get_graph(inline_root); | |
| w = h = 0; | |
| /*check parent/child doc orientation*/ | |
| /*check child doc*/ | |
| tag = gf_node_get_tag(inline_root); | |
| if (tag < GF_NODE_RANGE_LAST_VRML) { | |
| #ifndef GPAC_DISABLE_VRML | |
| u32 new_tag = 0; | |
| use_pm = gf_sg_use_pixel_metrics(in_scene); | |
| if (gf_node_get_tag(inline_parent)>GF_NODE_RANGE_LAST_VRML) { | |
| /*moving from SVG to VRML-based, need positive translation*/ | |
| flip_coords = 1; | |
| /*if our root nodes are not LayerXD ones, insert one. This prevents mixing | |
| of bindable stacks and gives us free 2D/3D integration*/ | |
| if (tag==TAG_MPEG4_OrderedGroup) { | |
| new_tag = TAG_MPEG4_Layer2D; | |
| } else if (tag==TAG_MPEG4_Group) { | |
| new_tag = TAG_MPEG4_Layer3D; | |
| } | |
| #ifndef GPAC_DISABLE_X3D | |
| else if (tag==TAG_X3D_Group) { | |
| new_tag = TAG_MPEG4_Layer3D; | |
| } | |
| #endif | |
| } | |
| #if !defined(GPAC_DISABLE_X3D) && !defined(GPAC_DISABLE_3D) | |
| /*if the inline root node is a 3D one except Layer3D and we are not in a 3D context, insert | |
| a Layer3D at the root*/ | |
| else if (!tr_state->visual->type_3d && ((tag==TAG_MPEG4_Group) || (tag==TAG_X3D_Group))) { | |
| new_tag = TAG_MPEG4_Layer3D; | |
| } | |
| #endif | |
| /*create new root*/ | |
| if (new_tag) { | |
| GF_SceneGraph *sg = gf_node_get_graph(inline_root); | |
| GF_Node *new_root = gf_node_new(sg, new_tag); | |
| gf_node_register(new_root, NULL); | |
| gf_sg_set_root_node(sg, new_root); | |
| gf_node_list_add_child(& ((GF_ParentNode*)new_root)->children, inline_root); | |
| gf_node_register(inline_root, new_root); | |
| gf_node_unregister(inline_root, NULL); | |
| inline_root = new_root; | |
| gf_node_init(new_root); | |
| } | |
| #endif /*GPAC_DISABLE_VRML*/ | |
| gf_sg_get_scene_size_info(in_scene, &w, &h); | |
| } else { | |
| use_pm = 1; | |
| if (gf_node_get_tag(inline_parent)<GF_NODE_RANGE_LAST_VRML) { | |
| /*moving from VRML-based to SVG, need negative translation*/ | |
| flip_coords = -1; | |
| } | |
| } | |
| min_hsize = tr_state->min_hsize; | |
| prev_pm = tr_state->pixel_metrics; | |
| prev_vp = tr_state->vp_size; | |
| prev_coord = tr_state->fliped_coords; | |
| vp_scale = FIX_ONE; | |
| gf_mx2d_init(transf); | |
| /*compute center<->top-left transform*/ | |
| if (flip_coords) | |
| gf_mx2d_add_scale(&transf, FIX_ONE, -FIX_ONE); | |
| /*if scene size is given in the child document, scale to fit the entire vp unless our VP is the full output*/ | |
| if (w && h) { | |
| if ((compositor->scene_width != tr_state->vp_size.x) || (compositor->scene_height != tr_state->vp_size.y)) { | |
| Fixed scale_w = tr_state->vp_size.x/w; | |
| Fixed scale_h = tr_state->vp_size.y/h; | |
| vp_scale = MIN(scale_w, scale_h); | |
| gf_mx2d_add_scale(&transf, vp_scale, vp_scale); | |
| } | |
| } | |
| if (flip_coords) { | |
| gf_mx2d_add_translation(&transf, flip_coords * tr_state->vp_size.x/2, tr_state->vp_size.y/2); | |
| tr_state->fliped_coords = !tr_state->fliped_coords; | |
| } | |
| /*if scene size is given in the child document, scale back vp to take into account the above scale | |
| otherwise the scene won't be properly clipped*/ | |
| if (w && h) { | |
| tr_state->vp_size.x = gf_divfix(tr_state->vp_size.x, vp_scale); | |
| tr_state->vp_size.y = gf_divfix(tr_state->vp_size.y, vp_scale); | |
| } | |
| /*compute pixel<->meter transform*/ | |
| if (use_pm != tr_state->pixel_metrics) { | |
| /*override aspect ratio if any size info is given in the scene*/ | |
| if (w && h) { | |
| Fixed scale = INT2FIX( MIN(w, h) / 2); | |
| if (scale) tr_state->min_hsize = scale; | |
| } | |
| if (!use_pm) { | |
| gf_mx2d_add_scale(&transf, tr_state->min_hsize, tr_state->min_hsize); | |
| } else { | |
| Fixed inv_scale = gf_invfix(tr_state->min_hsize); | |
| gf_mx2d_add_scale(&transf, inv_scale, inv_scale); | |
| } | |
| tr_state->pixel_metrics = use_pm; | |
| } | |
| #ifndef GPAC_DISABLE_3D | |
| if (tr_state->visual->type_3d) { | |
| GF_Matrix mx_bck, mx; | |
| gf_mx_copy(mx_bck, tr_state->model_matrix); | |
| gf_mx_from_mx2d(&mx, &transf); | |
| /*copy over z scale*/ | |
| mx.m[10] = mx.m[5]; | |
| gf_mx_add_matrix(&tr_state->model_matrix, &mx); | |
| gf_node_traverse(inline_root, rs); | |
| gf_mx_copy(tr_state->model_matrix, mx_bck); | |
| } else | |
| #endif | |
| { | |
| GF_Matrix2D mx_bck; | |
| gf_mx2d_copy(mx_bck, tr_state->transform); | |
| gf_mx2d_pre_multiply(&tr_state->transform, &transf); | |
| gf_node_traverse(inline_root, rs); | |
| gf_mx2d_copy(tr_state->transform, mx_bck); | |
| } | |
| tr_state->pixel_metrics = prev_pm; | |
| tr_state->min_hsize = min_hsize; | |
| tr_state->vp_size = prev_vp; | |
| tr_state->fliped_coords = prev_coord; | |
| } | |
| static Bool gf_sc_handle_event_intern(GF_Compositor *compositor, GF_Event *event, Bool from_user) | |
| { | |
| Bool ret; | |
| if ( (compositor->interaction_level & GF_INTERACT_INPUT_SENSOR) && (event->type <= GF_EVENT_LAST_MOUSE)) { | |
| GF_Event evt = *event; | |
| gf_sc_input_sensor_mouse_input(compositor, &evt.mouse); | |
| } | |
| /* if (!compositor->interaction_level || (compositor->interaction_level==GF_INTERACT_INPUT_SENSOR) ) { | |
| if (!from_user) { | |
| GF_USER_SENDEVENT(compositor->user, event); | |
| } | |
| return 0; | |
| } | |
| */ | |
| gf_mx_p(compositor->mx); | |
| ret = gf_sc_exec_event(compositor, event); | |
| gf_sc_lock(compositor, GF_FALSE); | |
| // if (!from_user) { } | |
| return ret; | |
| } | |
| void gf_sc_traverse_subscene(GF_Compositor *compositor, GF_Node *inline_parent, GF_SceneGraph *subscene, void *rs) | |
| { | |
| u32 i=0; | |
| GF_SceneGraph *subsg; | |
| gf_sc_traverse_subscene_ex(compositor, inline_parent, subscene, rs); | |
| while ( (subsg = gf_scene_enum_extra_scene(subscene, &i))) | |
| gf_sc_traverse_subscene_ex(compositor, inline_parent, subsg, rs); | |
| } | |
| static Bool gf_sc_on_event_ex(GF_Compositor *compositor , GF_Event *event, Bool from_user) | |
| { | |
| //special case for gui, we may get the callback before the script is initialized even of the scene is created | |
| //so we always store it in the config file | |
| if ((!compositor || (compositor->player==2)) && (event->type==GF_EVENT_NAVIGATE)) { | |
| gf_opts_set_key("temp", "gui_load_urls", event->navigate.to_url); | |
| if (compositor && !compositor->root_scene) return GF_FALSE; | |
| } | |
| /*not assigned yet*/ | |
| if (!compositor || !compositor->visual || compositor->discard_input_events) { | |
| return GF_FALSE; | |
| } | |
| /*we're reconfiguring the video output, cancel all messages except GL reconfig (context lost)*/ | |
| if (compositor->msg_type & GF_SR_IN_RECONFIG) { | |
| if (event->type==GF_EVENT_VIDEO_SETUP) { | |
| if (event->setup.hw_reset) | |
| gf_sc_reset_graphics(compositor); | |
| if (event->setup.back_buffer) | |
| compositor->recompute_ar = 1; | |
| } | |
| return GF_FALSE; | |
| } | |
| switch (event->type) { | |
| case GF_EVENT_SHOWHIDE: | |
| if (!from_user) { | |
| /*switch fullscreen off!!!*/ | |
| compositor->is_hidden = event->show.show_type ? GF_FALSE : GF_TRUE; | |
| break; | |
| } | |
| case GF_EVENT_SET_CAPTION: | |
| case GF_EVENT_MOVE: | |
| if (!from_user) { | |
| if (compositor->last_had_overlays) { | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| } | |
| break; | |
| } | |
| compositor->video_out->ProcessEvent(compositor->video_out, event); | |
| break; | |
| break; | |
| case GF_EVENT_REFRESH: | |
| /*when refreshing a window with overlays we redraw the scene */ | |
| gf_sc_next_frame_state(compositor, compositor->last_had_overlays ? GF_SC_DRAW_FRAME : GF_SC_DRAW_FLUSH); | |
| break; | |
| case GF_EVENT_VIDEO_SETUP: | |
| { | |
| Bool locked = gf_mx_try_lock(compositor->mx); | |
| if (event->setup.hw_reset) { | |
| gf_sc_reset_graphics(compositor); | |
| compositor->reset_graphics = 2; | |
| } | |
| if (event->setup.back_buffer) | |
| compositor->recompute_ar = 1; | |
| if (locked) gf_mx_v(compositor->mx); | |
| } | |
| break; | |
| case GF_EVENT_SIZE: | |
| /*user consumed the resize event, do nothing*/ | |
| if ( gf_sc_send_event(compositor, event) ) | |
| return GF_TRUE; | |
| /*not consumed and compositor "owns" the output window (created by the output module), resize*/ | |
| if (!compositor->os_wnd) { | |
| /*EXTRA CARE HERE: the caller (video output) is likely a different thread than the compositor one, and the | |
| compositor may be locked on the video output (flush or whatever)!! | |
| */ | |
| Bool lock_ok = gf_mx_try_lock(compositor->mx); | |
| if ((compositor->display_width!=event->size.width) | |
| || (compositor->display_height!=event->size.height) | |
| || (compositor->new_width && (compositor->new_width!=event->size.width)) | |
| || (compositor->new_width && (compositor->new_height!=event->size.height)) | |
| ) { | |
| //OSX bug with SDL when requesting 4k window we get max screen height but 4k width ... | |
| #if defined(GPAC_CONFIG_DARWIN) && !defined(GPAC_CONFIG_IOS) | |
| if (compositor->display_width==event->size.width) { | |
| if (compositor->display_height > 2*event->size.height) { | |
| event->size.width = compositor->display_width * event->size.height / compositor->display_height; | |
| } | |
| } | |
| #endif | |
| compositor->new_width = event->size.width; | |
| compositor->new_height = event->size.height; | |
| compositor->msg_type |= GF_SR_CFG_SET_SIZE; | |
| if (from_user) compositor->msg_type &= ~GF_SR_CFG_WINDOWSIZE_NOTIF; | |
| } else { | |
| /*remove pending resize notif but not resize requests*/ | |
| compositor->msg_type &= ~GF_SR_CFG_WINDOWSIZE_NOTIF; | |
| } | |
| if (compositor->disp_ori != event->size.orientation) { | |
| u32 i; | |
| compositor->disp_ori = event->size.orientation; | |
| for (i=0; i<gf_list_count(compositor->textures); i++) { | |
| GF_TextureHandler *txh = gf_list_get(compositor->textures, i); | |
| if (!txh || !txh->stream || !txh->stream->odm || !txh->stream->width) continue; | |
| gf_mo_update_caps_ex(txh->stream, GF_FALSE); | |
| if (txh->stream->odm->parentscene | |
| && txh->stream->odm->parentscene->is_dynamic_scene | |
| && (txh->stream->odm->state==GF_ODM_STATE_PLAY) | |
| ) { | |
| gf_scene_force_size_to_video(txh->stream->odm->parentscene, txh->stream); | |
| } | |
| } | |
| } | |
| if (lock_ok) gf_sc_lock(compositor, GF_FALSE); | |
| } | |
| return GF_FALSE; | |
| case GF_EVENT_KEYDOWN: | |
| case GF_EVENT_KEYUP: | |
| { | |
| Bool ret; | |
| switch (event->key.key_code) { | |
| case GF_KEY_SHIFT: | |
| if (event->type==GF_EVENT_KEYDOWN) { | |
| compositor->key_states |= GF_KEY_MOD_SHIFT; | |
| } else { | |
| compositor->key_states &= ~GF_KEY_MOD_SHIFT; | |
| } | |
| break; | |
| case GF_KEY_CONTROL: | |
| if (event->type==GF_EVENT_KEYDOWN) { | |
| compositor->key_states |= GF_KEY_MOD_CTRL; | |
| } else { | |
| compositor->key_states &= ~GF_KEY_MOD_CTRL; | |
| } | |
| break; | |
| case GF_KEY_ALT: | |
| if (event->type==GF_EVENT_KEYDOWN) { | |
| compositor->key_states |= GF_KEY_MOD_ALT; | |
| } else { | |
| compositor->key_states &= ~GF_KEY_MOD_ALT; | |
| } | |
| break; | |
| } | |
| ret = GF_FALSE; | |
| event->key.flags |= compositor->key_states; | |
| /*key sensor*/ | |
| if ((compositor->interaction_level & GF_INTERACT_INPUT_SENSOR) ) { | |
| ret = gf_sc_input_sensor_keyboard_input(compositor, event->key.key_code, event->key.hw_code, (event->type==GF_EVENT_KEYUP) ? GF_TRUE : GF_FALSE); | |
| } | |
| ret += gf_sc_handle_event_intern(compositor, event, from_user); | |
| return ret; | |
| } | |
| case GF_EVENT_TEXTINPUT: | |
| if (compositor->interaction_level & GF_INTERACT_INPUT_SENSOR) | |
| gf_sc_input_sensor_string_input(compositor , event->character.unicode_char); | |
| return gf_sc_handle_event_intern(compositor, event, from_user); | |
| case GF_EVENT_MOUSEMOVE: | |
| event->mouse.button = 0; | |
| case GF_EVENT_MOUSEDOWN: | |
| case GF_EVENT_MOUSEUP: | |
| case GF_EVENT_MOUSEWHEEL: | |
| event->mouse.key_states = compositor->key_states; | |
| case GF_EVENT_SENSOR_ORIENTATION: | |
| case GF_EVENT_MULTITOUCH: | |
| return gf_sc_handle_event_intern(compositor, event, from_user); | |
| case GF_EVENT_PASTE_TEXT: | |
| gf_sc_paste_text(compositor, event->clipboard.text); | |
| break; | |
| case GF_EVENT_COPY_TEXT: | |
| if (gf_sc_has_text_selection(compositor)) { | |
| const char *str = gf_sc_get_selected_text(compositor); | |
| event->clipboard.text = str ? gf_strdup(event->clipboard.text) : NULL; | |
| } else { | |
| event->clipboard.text = NULL; | |
| } | |
| break; | |
| case GF_EVENT_QUIT: | |
| if (compositor->audio_renderer) | |
| compositor->audio_renderer->non_rt_output = 2; | |
| compositor->check_eos_state = 1; | |
| return gf_sc_send_event(compositor, event); | |
| /*when we process events we don't forward them to the user*/ | |
| default: | |
| return gf_sc_send_event(compositor, event); | |
| } | |
| /*if we get here, event has been consumed*/ | |
| return GF_TRUE; | |
| } | |
| GF_EXPORT | |
| Bool gf_sc_on_event(void *cbck, GF_Event *event) | |
| { | |
| return gf_sc_on_event_ex((GF_Compositor *)cbck, event, GF_FALSE); | |
| } | |
| GF_EXPORT | |
| Bool gf_sc_user_event(GF_Compositor *compositor, GF_Event *event) | |
| { | |
| switch (event->type) { | |
| case GF_EVENT_SHOWHIDE: | |
| case GF_EVENT_MOVE: | |
| case GF_EVENT_SET_CAPTION: | |
| compositor->video_out->ProcessEvent(compositor->video_out, event); | |
| return GF_FALSE; | |
| default: | |
| return gf_sc_on_event_ex(compositor, event, GF_TRUE); | |
| } | |
| } | |
| GF_EXPORT | |
| void gf_sc_register_extra_graph(GF_Compositor *compositor, GF_SceneGraph *extra_scene, Bool do_remove) | |
| { | |
| gf_sc_lock(compositor, GF_TRUE); | |
| if (do_remove) gf_list_del_item(compositor->extra_scenes, extra_scene); | |
| else if (gf_list_find(compositor->extra_scenes, extra_scene)<0) gf_list_add(compositor->extra_scenes, extra_scene); | |
| gf_sc_lock(compositor, GF_FALSE); | |
| } | |
| Bool gf_sc_script_action(GF_Compositor *compositor, GF_JSAPIActionType type, GF_Node *n, GF_JSAPIParam *param) | |
| { | |
| switch (type) { | |
| case GF_JSAPI_OP_GET_SCALE: | |
| param->val = compositor->zoom; | |
| return GF_TRUE; | |
| case GF_JSAPI_OP_SET_SCALE: | |
| compositor_2d_set_user_transform(compositor, param->val, compositor->trans_x, compositor->trans_y, GF_FALSE); | |
| return GF_TRUE; | |
| case GF_JSAPI_OP_GET_ROTATION: | |
| param->val = gf_divfix(180*compositor->rotation, GF_PI); | |
| return GF_TRUE; | |
| case GF_JSAPI_OP_SET_ROTATION: | |
| compositor->rotation = gf_mulfix(GF_PI, param->val/180); | |
| compositor_2d_set_user_transform(compositor, compositor->zoom, compositor->trans_x, compositor->trans_y, GF_FALSE); | |
| return GF_TRUE; | |
| case GF_JSAPI_OP_GET_TRANSLATE: | |
| param->pt.x = compositor->trans_x; | |
| param->pt.y = compositor->trans_y; | |
| return GF_TRUE; | |
| case GF_JSAPI_OP_SET_TRANSLATE: | |
| compositor_2d_set_user_transform(compositor, compositor->zoom, param->pt.x, param->pt.y, GF_FALSE); | |
| return GF_TRUE; | |
| /*FIXME - better SMIL timelines support*/ | |
| case GF_JSAPI_OP_GET_TIME: | |
| param->time = gf_node_get_scene_time(n); | |
| return GF_TRUE; | |
| case GF_JSAPI_OP_SET_TIME: /*seek_to(param->time);*/ | |
| return GF_FALSE; | |
| /*FIXME - this will not work for inline docs, we will have to store the clipper at the <svg> level*/ | |
| case GF_JSAPI_OP_GET_VIEWPORT: | |
| #ifndef GPAC_DISABLE_SVG | |
| if (compositor_svg_get_viewport(n, ¶m->rc)) return GF_TRUE; | |
| #endif | |
| param->rc = gf_rect_center(compositor->traverse_state->vp_size.x, compositor->traverse_state->vp_size.y); | |
| if (!compositor->visual->center_coords) { | |
| param->rc.x = 0; | |
| param->rc.y = 0; | |
| } | |
| return GF_TRUE; | |
| case GF_JSAPI_OP_SET_FOCUS: | |
| compositor->focus_node = param->node; | |
| return GF_TRUE; | |
| case GF_JSAPI_OP_GET_FOCUS: | |
| param->node = compositor->focus_node; | |
| return GF_TRUE; | |
| /*same routine: traverse tree from root to target, collecting both bounds and transform matrix*/ | |
| case GF_JSAPI_OP_GET_LOCAL_BBOX: | |
| case GF_JSAPI_OP_GET_SCREEN_BBOX: | |
| case GF_JSAPI_OP_GET_TRANSFORM: | |
| { | |
| GF_TraverseState tr_state; | |
| memset(&tr_state, 0, sizeof(tr_state)); | |
| tr_state.traversing_mode = TRAVERSE_GET_BOUNDS; | |
| tr_state.visual = compositor->visual; | |
| tr_state.vp_size = compositor->traverse_state->vp_size; | |
| tr_state.for_node = n; | |
| tr_state.ignore_strike = GF_TRUE; | |
| tr_state.min_hsize = compositor->traverse_state->min_hsize; | |
| tr_state.pixel_metrics = compositor->traverse_state->pixel_metrics; | |
| gf_mx2d_init(tr_state.transform); | |
| gf_mx2d_init(tr_state.mx_at_node); | |
| if (type==GF_JSAPI_OP_GET_LOCAL_BBOX) { | |
| #ifndef GPAC_DISABLE_SVG | |
| GF_SAFEALLOC(tr_state.svg_props, SVGPropertiesPointers); | |
| if (tr_state.svg_props) { | |
| gf_svg_properties_init_pointers(tr_state.svg_props); | |
| tr_state.abort_bounds_traverse = GF_TRUE; | |
| gf_node_traverse(n, &tr_state); | |
| gf_svg_properties_reset_pointers(tr_state.svg_props); | |
| gf_free(tr_state.svg_props); | |
| } | |
| #endif | |
| } else { | |
| gf_node_traverse(gf_sg_get_root_node(compositor->scene), &tr_state); | |
| } | |
| if (!tr_state.bounds.height && !tr_state.bounds.width && !tr_state.bounds.x && !tr_state.bounds.y) | |
| tr_state.abort_bounds_traverse = GF_FALSE; | |
| gf_mx2d_pre_multiply(&tr_state.mx_at_node, &compositor->traverse_state->transform); | |
| if (type==GF_JSAPI_OP_GET_LOCAL_BBOX) { | |
| gf_bbox_from_rect(¶m->bbox, &tr_state.bounds); | |
| } else if (type==GF_JSAPI_OP_GET_TRANSFORM) { | |
| gf_mx_from_mx2d(¶m->mx, &tr_state.mx_at_node); | |
| } else { | |
| gf_mx2d_apply_rect(&tr_state.mx_at_node, &tr_state.bounds); | |
| gf_bbox_from_rect(¶m->bbox, &tr_state.bounds); | |
| } | |
| if (!tr_state.abort_bounds_traverse) param->bbox.is_set = GF_FALSE; | |
| } | |
| return GF_TRUE; | |
| case GF_JSAPI_OP_LOAD_URL: | |
| { | |
| #ifndef GPAC_DISABLE_VRML | |
| GF_Node *target; | |
| char *sub_url = strrchr(param->uri.url, '#'); | |
| if (!sub_url) return GF_FALSE; | |
| target = gf_sg_find_node_by_name(gf_node_get_graph(n), sub_url+1); | |
| if (target && (gf_node_get_tag(target)==TAG_MPEG4_Viewport) ) { | |
| ((M_Viewport *)target)->set_bind = 1; | |
| ((M_Viewport *)target)->on_set_bind(n, NULL); | |
| return GF_TRUE; | |
| } | |
| #endif | |
| return GF_FALSE; | |
| } | |
| case GF_JSAPI_OP_GET_FPS: | |
| param->time = gf_sc_get_fps(compositor, GF_FALSE); | |
| return GF_TRUE; | |
| case GF_JSAPI_OP_GET_SPEED: | |
| param->time = 0; | |
| #ifndef GPAC_DISABLE_3D | |
| if (compositor->visual->type_3d==2) { | |
| param->time = FIX2FLT(compositor->visual->camera.speed); | |
| } | |
| #endif | |
| return GF_TRUE; | |
| case GF_JSAPI_OP_PAUSE_SVG: | |
| if (n) { | |
| u32 tag = gf_node_get_tag(n); | |
| switch(tag) { | |
| #ifndef GPAC_DISABLE_SVG | |
| case TAG_SVG_audio: | |
| svg_pause_audio(n, GF_TRUE); | |
| break; | |
| case TAG_SVG_video: | |
| svg_pause_video(n, GF_TRUE); | |
| break; | |
| case TAG_SVG_animation: | |
| svg_pause_animation(n, GF_TRUE); | |
| break; | |
| #endif | |
| } | |
| } else { | |
| return GF_FALSE; | |
| } | |
| return GF_TRUE; | |
| case GF_JSAPI_OP_RESUME_SVG: | |
| { | |
| u32 tag = gf_node_get_tag(n); | |
| switch(tag) { | |
| #ifndef GPAC_DISABLE_SVG | |
| case TAG_SVG_audio: | |
| svg_pause_audio(n, GF_FALSE); | |
| break; | |
| case TAG_SVG_video: | |
| svg_pause_video(n, GF_FALSE); | |
| break; | |
| case TAG_SVG_animation: | |
| svg_pause_animation(n, GF_FALSE); | |
| break; | |
| #endif | |
| } | |
| } | |
| return GF_TRUE; | |
| case GF_JSAPI_OP_GET_DPI_X: | |
| param->opt = compositor->video_out->dpi_x; | |
| return GF_TRUE; | |
| case GF_JSAPI_OP_GET_DPI_Y: | |
| param->opt = compositor->video_out->dpi_y; | |
| return GF_TRUE; | |
| default: | |
| return GF_FALSE; | |
| } | |
| return GF_FALSE; | |
| } | |
| Bool gf_sc_pick_in_clipper(GF_TraverseState *tr_state, GF_Rect *clip) | |
| { | |
| #ifndef GPAC_DISABLE_3D | |
| if (tr_state->visual->type_3d) { | |
| SFVec3f pos; | |
| GF_Matrix mx; | |
| GF_Ray r; | |
| gf_mx_copy(mx, tr_state->model_matrix); | |
| gf_mx_inverse(&mx); | |
| r = tr_state->ray; | |
| gf_mx_apply_ray(&mx, &r); | |
| if (!compositor_get_2d_plane_intersection(&r, &pos)) return GF_FALSE; | |
| if ( (pos.x < clip->x) || (pos.y > clip->y) | |
| || (pos.x > clip->x + clip->width) || (pos.y < clip->y - clip->height) ) return GF_FALSE; | |
| } else | |
| #endif | |
| { | |
| GF_Rect rc = *clip; | |
| GF_Point2D pt; | |
| gf_mx2d_apply_rect(&tr_state->transform, &rc); | |
| pt.x = tr_state->ray.orig.x; | |
| pt.y = tr_state->ray.orig.y; | |
| if ( (pt.x < rc.x) || (pt.y > rc.y) | |
| || (pt.x > rc.x + rc.width) || (pt.y < rc.y - rc.height) ) return GF_FALSE; | |
| } | |
| return GF_TRUE; | |
| } | |
| GF_EXPORT | |
| Bool gf_sc_has_text_selection(GF_Compositor *compositor) | |
| { | |
| return (compositor->store_text_state==GF_SC_TSEL_FROZEN) ? GF_TRUE : GF_FALSE; | |
| } | |
| GF_EXPORT | |
| const char *gf_sc_get_selected_text(GF_Compositor *compositor) | |
| { | |
| const u16 *srcp; | |
| u32 len; | |
| if (compositor->store_text_state != GF_SC_TSEL_FROZEN) return NULL; | |
| gf_sc_lock(compositor, GF_TRUE); | |
| compositor->traverse_state->traversing_mode = TRAVERSE_GET_TEXT; | |
| if (compositor->sel_buffer) { | |
| gf_free(compositor->sel_buffer); | |
| compositor->sel_buffer = NULL; | |
| } | |
| compositor->sel_buffer_len = 0; | |
| compositor->sel_buffer_alloc = 0; | |
| gf_node_traverse(compositor->text_selection, compositor->traverse_state); | |
| compositor->traverse_state->traversing_mode = 0; | |
| if (compositor->sel_buffer) compositor->sel_buffer[compositor->sel_buffer_len]=0; | |
| srcp = compositor->sel_buffer; | |
| if (compositor->selected_text) gf_free(compositor->selected_text); | |
| compositor->selected_text = gf_malloc(sizeof(char)*2*compositor->sel_buffer_len); | |
| len = gf_utf8_wcstombs((char *) compositor->selected_text, 2*compositor->sel_buffer_len, &srcp); | |
| if (len == GF_UTF8_FAIL) len = 0; | |
| compositor->selected_text[len] = 0; | |
| gf_sc_lock(compositor, GF_FALSE); | |
| return (const char *) compositor->selected_text; | |
| } | |
| void gf_sc_check_focus_upon_destroy(GF_Node *n) | |
| { | |
| GF_Compositor *compositor = gf_sc_get_compositor(n); | |
| if (!compositor) return; | |
| if (compositor->focus_node==n) { | |
| compositor->focus_node = NULL; | |
| compositor->focus_text_type = 0; | |
| compositor->focus_uses_dom_events = GF_FALSE; | |
| gf_list_reset(compositor->focus_ancestors); | |
| gf_list_reset(compositor->focus_use_stack); | |
| } | |
| if (compositor->hit_node==n) compositor->hit_node = NULL; | |
| if (compositor->hit_text==n) compositor->hit_text = NULL; | |
| } | |
| #if 0 //unused | |
| void gf_sc_set_system_pending_frame(GF_Compositor *compositor, Bool frame_pending) | |
| { | |
| if (frame_pending) { | |
| if (!compositor->force_bench_frame) | |
| compositor->force_bench_frame = 1; | |
| } else { | |
| //do not increase clock | |
| compositor->force_bench_frame = 2; | |
| } | |
| } | |
| void gf_sc_queue_event(GF_Compositor *compositor, GF_Event *evt) | |
| { | |
| u32 i, count; | |
| GF_QueuedEvent *qev; | |
| gf_mx_p(compositor->evq_mx); | |
| count = gf_list_count(compositor->event_queue); | |
| for (i=0; i<count; i++) { | |
| qev = gf_list_get(compositor->event_queue, i); | |
| if (!qev->node && (qev->evt.type==evt->type)) { | |
| qev->evt = *evt; | |
| gf_mx_v(compositor->evq_mx); | |
| return; | |
| } | |
| } | |
| GF_SAFEALLOC(qev, GF_QueuedEvent); | |
| if (!qev) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate event for queuing\n")); | |
| } else { | |
| qev->evt = *evt; | |
| gf_list_add(compositor->event_queue, qev); | |
| } | |
| gf_mx_v(compositor->evq_mx); | |
| } | |
| #endif | |
| void gf_sc_queue_dom_event(GF_Compositor *compositor, GF_Node *node, GF_DOM_Event *evt) | |
| { | |
| u32 i, count; | |
| GF_QueuedEvent *qev; | |
| gf_mx_p(compositor->evq_mx); | |
| count = gf_list_count(compositor->event_queue); | |
| for (i=0; i<count; i++) { | |
| qev = gf_list_get(compositor->event_queue, i); | |
| if ((qev->node==node) && (qev->dom_evt.type==evt->type)) { | |
| qev->dom_evt = *evt; | |
| gf_mx_v(compositor->evq_mx); | |
| return; | |
| } | |
| } | |
| GF_SAFEALLOC(qev, GF_QueuedEvent); | |
| if (!qev) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate event for queuing\n")); | |
| } else { | |
| qev->node = node; | |
| qev->dom_evt = *evt; | |
| gf_list_add(compositor->event_queue, qev); | |
| } | |
| gf_mx_v(compositor->evq_mx); | |
| } | |
| void gf_sc_queue_dom_event_on_target(GF_Compositor *compositor, GF_DOM_Event *evt, GF_DOMEventTarget *target, GF_SceneGraph *sg) | |
| { | |
| u32 i, count; | |
| GF_QueuedEvent *qev; | |
| gf_mx_p(compositor->evq_mx); | |
| count = gf_list_count(compositor->event_queue); | |
| for (i=0; i<count; i++) { | |
| qev = gf_list_get(compositor->event_queue, i); | |
| if ((qev->target==target) && (qev->dom_evt.type==evt->type) && (qev->sg==sg) ) { | |
| //do not override any pending dowload progress event by new buffer state events | |
| if ((evt->type!=GF_EVENT_MEDIA_PROGRESS) || !qev->dom_evt.media_event.loaded_size) { | |
| qev->dom_evt = *evt; | |
| } | |
| gf_mx_v(compositor->evq_mx); | |
| return; | |
| } | |
| } | |
| GF_SAFEALLOC(qev, GF_QueuedEvent); | |
| if (!qev) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate event for queuing\n")); | |
| } else { | |
| qev->sg = sg; | |
| qev->target = target; | |
| qev->dom_evt = *evt; | |
| gf_list_add(compositor->event_queue, qev); | |
| } | |
| gf_mx_v(compositor->evq_mx); | |
| } | |
| void sc_cleanup_event_queue(GF_List *evq, GF_Node *node, GF_SceneGraph *sg) | |
| { | |
| u32 i, count = gf_list_count(evq); | |
| for (i=0; i<count; i++) { | |
| Bool del = 0; | |
| GF_QueuedEvent *qev = gf_list_get(evq, i); | |
| if (qev->node) { | |
| if (node == qev->node) | |
| del = 1; | |
| if (sg && (gf_node_get_graph(qev->node)==sg)) | |
| del = 1; | |
| } | |
| if (qev->sg && (qev->sg==sg)) | |
| del = 1; | |
| else if (qev->target && (qev->target->ptr_type == GF_DOM_EVENT_TARGET_NODE)) { | |
| if (node && ((GF_Node *)qev->target->ptr==node)) | |
| del = 1; | |
| if (sg && (gf_node_get_graph((GF_Node *)qev->target->ptr)==sg)) | |
| del = 1; | |
| } | |
| if (del) { | |
| gf_list_rem(evq, i); | |
| i--; | |
| count--; | |
| gf_free(qev); | |
| } | |
| } | |
| } | |
| void gf_sc_node_destroy(GF_Compositor *compositor, GF_Node *node, GF_SceneGraph *sg) | |
| { | |
| gf_mx_p(compositor->evq_mx); | |
| sc_cleanup_event_queue(compositor->event_queue, node, sg); | |
| sc_cleanup_event_queue(compositor->event_queue_back, node, sg); | |
| gf_mx_v(compositor->evq_mx); | |
| } | |
| GF_EXPORT | |
| Bool gf_sc_navigation_supported(GF_Compositor *compositor, u32 type) | |
| { | |
| if (compositor->navigation_disabled ) return GF_FALSE; | |
| #ifndef GPAC_DISABLE_3D | |
| if (compositor->visual->type_3d || compositor->active_layer) { | |
| GF_Camera *cam = compositor_3d_get_camera(compositor); | |
| if (cam->navigation_flags & NAV_ANY) { | |
| return GF_TRUE; | |
| } else { | |
| #ifndef GPAC_DISABLE_VRML | |
| M_NavigationInfo *ni = (M_NavigationInfo *)gf_list_get(compositor->visual->navigation_stack, 0); | |
| if (ni) { | |
| u32 i; | |
| for (i=0; i<ni->type.count; i++) { | |
| if (!ni->type.vals[i]) continue; | |
| if (!stricmp(ni->type.vals[i], "WALK") && (type==GF_NAVIGATE_WALK)) return GF_TRUE; | |
| else if (!stricmp(ni->type.vals[i], "NONE") && (type==GF_NAVIGATE_NONE)) return GF_TRUE; | |
| else if (!stricmp(ni->type.vals[i], "EXAMINE") && (type==GF_NAVIGATE_EXAMINE)) return GF_TRUE; | |
| else if (!stricmp(ni->type.vals[i], "FLY") && (type==GF_NAVIGATE_FLY)) return GF_TRUE; | |
| else if (!stricmp(ni->type.vals[i], "VR") && (type==GF_NAVIGATE_VR)) return GF_TRUE; | |
| else if (!stricmp(ni->type.vals[i], "GAME") && (type==GF_NAVIGATE_GAME)) return GF_TRUE; | |
| else if (!stricmp(ni->type.vals[i], "ORBIT") && (type==GF_NAVIGATE_ORBIT)) return GF_TRUE; | |
| } | |
| } | |
| #endif | |
| return GF_FALSE; | |
| } | |
| } else | |
| #endif | |
| if ((type!=GF_NAVIGATE_NONE) && (type!=GF_NAVIGATE_SLIDE) && (type!=GF_NAVIGATE_EXAMINE)) { | |
| return GF_FALSE; | |
| } | |
| return GF_TRUE; | |
| } | |
| u32 gf_sc_check_end_of_scene(GF_Compositor *compositor, Bool skip_interactions) | |
| { | |
| if (!compositor->root_scene || !compositor->root_scene->root_od || !compositor->root_scene->root_od->scene_ns) return 1; | |
| if (!skip_interactions) { | |
| /*if input sensors consider the scene runs forever*/ | |
| if (gf_list_count(compositor->input_streams)) return 0; | |
| if (gf_list_count(compositor->x3d_sensors)) return 0; | |
| } | |
| /*check no clocks are still running*/ | |
| if (!gf_scene_check_clocks(compositor->root_scene->root_od->scene_ns, compositor->root_scene, 0)) return 0; | |
| if (compositor->root_scene->is_dynamic_scene) return 1; | |
| /*ask compositor if there are sensors*/ | |
| return gf_sc_get_option(compositor, skip_interactions ? GF_OPT_IS_OVER : GF_OPT_IS_FINISHED); | |
| } | |
| void gf_sc_queue_node_traverse(GF_Compositor *compositor, GF_Node *node) | |
| { | |
| gf_sc_lock(compositor, GF_TRUE); | |
| if (!compositor->nodes_pending) compositor->nodes_pending = gf_list_new(); | |
| gf_list_add(compositor->nodes_pending, node); | |
| gf_sc_lock(compositor, GF_FALSE); | |
| } | |
| void gf_sc_unqueue_node_traverse(GF_Compositor *compositor, GF_Node *node) | |
| { | |
| gf_sc_lock(compositor, GF_TRUE); | |
| if (compositor->nodes_pending) { | |
| gf_list_del_item(compositor->nodes_pending, node); | |
| if (!gf_list_count(compositor->nodes_pending)) { | |
| gf_list_del(compositor->nodes_pending); | |
| compositor->nodes_pending = NULL; | |
| } | |
| } | |
| gf_sc_lock(compositor, GF_FALSE); | |
| } | |
| void gf_sc_sys_frame_pending(GF_Compositor *compositor, u32 cts, u32 obj_time, GF_Filter *from_filter) | |
| { | |
| if (!compositor->player) { | |
| if (!compositor->sys_frames_pending) { | |
| compositor->sys_frames_pending = GF_TRUE; | |
| gf_filter_post_process_task(compositor->filter); | |
| } | |
| if (!from_filter) return; | |
| //clock is not realtime but frame base, estimate how fast we generate frames | |
| //if last frame time diff is less than 1s, consider the output is not consumed in real-time | |
| //and reschedule asap | |
| //otherwise reschedule in 1 ms | |
| //This is needed to avoid high cpu usage when output is realtime (compositor->vout) | |
| u32 fidx = compositor->current_frame; | |
| s64 run_time = compositor->frame_time[fidx]; | |
| if (fidx) fidx--; | |
| else fidx = GF_SR_FPS_COMPUTE_SIZE-1; | |
| run_time -= compositor->frame_time[fidx]; | |
| if (run_time<0) | |
| run_time += 0xFFFFFFFFUL; | |
| if (run_time<1000) { | |
| //request in 1 us (0 would only reschedule filter if pending packets which may not always be the case, cf vtt dec) | |
| gf_filter_ask_rt_reschedule(from_filter, 1); | |
| } else { | |
| gf_filter_ask_rt_reschedule(from_filter, 1000); | |
| } | |
| } else { | |
| u32 wait_ms; | |
| if (cts < obj_time) wait_ms = (u32) (0xFFFFFFFFUL + cts - obj_time); | |
| else wait_ms = cts - obj_time; | |
| if (wait_ms>1000) | |
| wait_ms = 1000; | |
| if (!compositor->ms_until_next_frame || ((s32) wait_ms < compositor->ms_until_next_frame)) { | |
| compositor->ms_until_next_frame = (s32) wait_ms; | |
| } | |
| if (from_filter) { | |
| gf_filter_ask_rt_reschedule(from_filter, wait_ms*500); | |
| } | |
| //in player mode, force scene ready as soon as we have a pending frame | |
| //otherwise the audio renderer will not increase current time | |
| compositor->audio_renderer->scene_ready = GF_TRUE; | |
| } | |
| } | |
| Bool gf_sc_check_sys_frame(GF_Scene *scene, GF_ObjectManager *odm, GF_FilterPid *for_pid, GF_Filter *from_filter, u64 cts_in_ms, u32 dur_in_ms) | |
| { | |
| Bool is_early=GF_FALSE; | |
| if (!odm) return GF_FALSE; | |
| if (for_pid) | |
| gf_odm_check_buffering(odm, for_pid); | |
| u32 obj_time = gf_clock_time(odm->ck); | |
| if (cts_in_ms > obj_time) | |
| is_early = GF_TRUE; | |
| else if ((obj_time>0x7FFFFFFF) && (cts_in_ms<0x7FFFFFFF)) | |
| is_early = GF_TRUE; | |
| if (is_early) { | |
| //little opt: if single resource, more than 10s in future and we dump in VFR, non player mode | |
| //jump to the target time | |
| if (scene->compositor->vfr | |
| && !scene->compositor->player | |
| && (gf_list_count(scene->resources)==1) | |
| && (cts_in_ms > obj_time + 10000) | |
| ) { | |
| gf_clock_reset(odm->ck); | |
| gf_clock_set_time(odm->ck, cts_in_ms, 1000); | |
| return GF_TRUE; | |
| } | |
| gf_sc_sys_frame_pending(scene->compositor, (u32)cts_in_ms, obj_time, from_filter); | |
| return GF_FALSE; | |
| } | |
| if (scene->compositor->vfr) { | |
| u32 ts = scene->compositor->timescale; | |
| if (!ts) ts = scene->compositor->fps.num; | |
| scene->compositor->hint_extra_scene_cts_plus_one = 1 + gf_timestamp_rescale(cts_in_ms, 1000, ts); | |
| scene->compositor->hint_extra_scene_dur_plus_one = (u32) ( 1 + gf_timestamp_rescale(dur_in_ms, 1000, ts)); | |
| } | |
| return GF_TRUE; | |
| } | |
| Bool gf_sc_check_gl_support(GF_Compositor *compositor) | |
| { | |
| #ifdef GPAC_DISABLE_3D | |
| return GF_FALSE; | |
| #else | |
| if (!compositor->player && !compositor->is_opengl) { | |
| if (compositor->drv==GF_SC_DRV_OFF) { | |
| return GF_FALSE; | |
| } | |
| compositor->needs_offscreen_gl = GF_TRUE; | |
| compositor->autoconfig_opengl = 1; | |
| compositor->recompute_ar = 1; | |
| return GF_TRUE; | |
| } | |
| return GF_TRUE; | |
| #endif | |
| } | |
| GF_EXPORT | |
| u32 gf_sc_get_time_in_ms(GF_Compositor *sc) | |
| { | |
| if (!sc || !sc->root_scene) return 0; | |
| GF_Clock *ck = sc->root_scene->root_od->ck; | |
| if (!ck) return 0; | |
| return (u32) gf_clock_media_time(ck); | |
| } | |
| GF_EXPORT | |
| void gf_sc_switch_quality(GF_Compositor *compositor, Bool up) | |
| { | |
| if (compositor) | |
| gf_scene_switch_quality(compositor->root_scene, up); | |
| } | |
| GF_EXPORT | |
| void gf_sc_toggle_addons(GF_Compositor *compositor, Bool show_addons) | |
| { | |
| if (!compositor || !compositor->root_scene || !compositor->root_scene->is_dynamic_scene) return; | |
| #ifndef GPAC_DISABLE_VRML | |
| gf_scene_toggle_addons(compositor->root_scene, show_addons); | |
| #endif | |
| } | |
| #include <gpac/avparse.h> | |
| GF_EXPORT | |
| GF_Err gf_sc_set_speed(GF_Compositor *compositor, Fixed speed) | |
| { | |
| GF_Fraction fps; | |
| u32 i, j; | |
| GF_SceneNamespace *ns; | |
| Bool restart = 0; | |
| if (!compositor || !speed) return GF_BAD_PARAM; | |
| u32 scene_time = gf_sc_get_time_in_ms(compositor); | |
| if (speed<0) { | |
| i=0; | |
| while ( (ns = (GF_SceneNamespace*)gf_list_enum(compositor->root_scene->namespaces, &i)) ) { | |
| u32 k=0; | |
| GF_ObjectManager *odm; | |
| if (!ns->owner || !ns->owner->subscene) continue; | |
| while ( (odm = gf_list_enum(ns->owner->subscene->resources, &k))) { | |
| const GF_PropertyValue *p = gf_filter_pid_get_property(odm->pid, GF_PROP_PID_PLAYBACK_MODE); | |
| if (!p || (p->value.uint!=GF_PLAYBACK_MODE_REWIND)) | |
| return GF_NOT_SUPPORTED; | |
| } | |
| } | |
| } | |
| /*adjust all clocks on all services, if possible*/ | |
| i=0; | |
| while ( (ns = (GF_SceneNamespace*)gf_list_enum(compositor->root_scene->namespaces, &i)) ) { | |
| GF_Clock *ck; | |
| ns->set_speed = speed; | |
| j=0; | |
| while (ns->clocks && (ck = (GF_Clock *)gf_list_enum(ns->clocks, &j)) ) { | |
| //we will have to reissue a PLAY command since playback direction changed | |
| if ( gf_mulfix(ck->speed,speed) < 0) | |
| restart = 1; | |
| gf_clock_set_speed(ck, speed); | |
| if (ns->owner) { | |
| gf_odm_set_speed(ns->owner, speed, GF_FALSE); | |
| if (ns->owner->subscene) { | |
| u32 k=0; | |
| GF_ObjectManager *odm; | |
| GF_Scene *scene = ns->owner->subscene; | |
| while ( (odm = gf_list_enum(scene->resources, &k))) { | |
| gf_odm_set_speed(odm, speed, GF_FALSE); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| if (restart) { | |
| if (compositor->root_scene->is_dynamic_scene) { | |
| gf_scene_restart_dynamic(compositor->root_scene, scene_time, 0, 0); | |
| } | |
| } | |
| if (speed<0) | |
| speed = -speed; | |
| fps = compositor->fps; | |
| if (fps.den<1000) { | |
| fps.num = fps.num * (u32) (1000 * FIX2FLT(speed)); | |
| fps.den *= 1000; | |
| } else { | |
| fps.num = (u32) (fps.num * FIX2FLT(speed)); | |
| } | |
| gf_media_get_reduced_frame_rate(&fps.num, &fps.den); | |
| gf_sc_set_fps(compositor, fps); | |
| return GF_OK; | |
| } | |
| GF_EXPORT | |
| Bool gf_sc_is_supported_url(GF_Compositor *compositor, const char *fileName, Bool use_parent_url) | |
| { | |
| char *parent_url = NULL; | |
| if (!compositor || !compositor->root_scene || !compositor->root_scene->root_od || !compositor->root_scene->root_od->scene_ns) | |
| return GF_FALSE; | |
| if (use_parent_url) | |
| parent_url = compositor->root_scene->root_od->scene_ns->url; | |
| return gf_filter_is_supported_source(compositor->filter, fileName, parent_url); | |
| } | |
| #include <gpac/network.h> | |
| /*get rendering option*/ | |
| static u32 gf_sc_get_option_internal(GF_Compositor *compositor, u32 type) | |
| { | |
| if (!compositor) return 0; | |
| switch (type) { | |
| case GF_OPT_HAS_JAVASCRIPT: | |
| return gf_sg_has_scripting(); | |
| case GF_OPT_IS_FINISHED: | |
| return gf_sc_check_end_of_scene(compositor, 0); | |
| case GF_OPT_IS_OVER: | |
| return gf_sc_check_end_of_scene(compositor, 1); | |
| case GF_OPT_MAIN_ADDON: | |
| return compositor->root_scene ? compositor->root_scene->main_addon_selected : 0; | |
| case GF_OPT_PLAY_STATE: | |
| if (compositor->step_mode) return GF_STATE_STEP_PAUSE; | |
| if (compositor->root_scene) { | |
| GF_Clock *ck = compositor->root_scene->root_od->ck; | |
| if (!ck) return GF_STATE_PAUSED; | |
| // if (ck->Buffering) return GF_STATE_PLAYING; | |
| } | |
| if (compositor->play_state != GF_STATE_PLAYING) return GF_STATE_PAUSED; | |
| return GF_STATE_PLAYING; | |
| case GF_OPT_CAN_SELECT_STREAMS: | |
| return (compositor->root_scene && compositor->root_scene->is_dynamic_scene) ? 1 : 0; | |
| case GF_OPT_HTTP_MAX_RATE: | |
| #ifndef GPAC_DISABLE_NETWORK | |
| { | |
| GF_DownloadManager *dm = gf_filter_get_download_manager(compositor->filter); | |
| if (!dm) return 0; | |
| return gf_dm_get_data_rate(dm); | |
| } | |
| #else | |
| return 0; | |
| #endif | |
| case GF_OPT_VIDEO_BENCH: | |
| return compositor->bench_mode ? GF_TRUE : GF_FALSE; | |
| case GF_OPT_ORIENTATION_SENSORS_ACTIVE: | |
| return compositor->orientation_sensors_active; | |
| default: | |
| return gf_sc_get_option(compositor, type); | |
| } | |
| } | |
| GF_EXPORT | |
| void gf_sc_navigate_to(GF_Compositor *compositor, const char *toURL) | |
| { | |
| if (!compositor) return; | |
| if (!toURL && !compositor->root_scene) return; | |
| if (compositor->reload_url) gf_free(compositor->reload_url); | |
| compositor->reload_url = NULL; | |
| if (toURL) { | |
| if (compositor->root_scene && compositor->root_scene->root_od && compositor->root_scene->root_od->scene_ns) | |
| compositor->reload_url = gf_url_concatenate(compositor->root_scene->root_od->scene_ns->url, toURL); | |
| if (!compositor->reload_url) compositor->reload_url = gf_strdup(toURL); | |
| } | |
| compositor->reload_state = 1; | |
| } | |
| static GF_Err gf_sc_step_clocks_intern(GF_Compositor *compositor, u32 ms_diff, Bool force_resume_pause) | |
| { | |
| /*only play/pause if connected*/ | |
| if (!compositor || !compositor->root_scene || !compositor->root_scene->root_od) return GF_BAD_PARAM; | |
| if (ms_diff) { | |
| u32 i, j; | |
| GF_SceneNamespace *ns; | |
| GF_Clock *ck; | |
| if (compositor->play_state == GF_STATE_PLAYING) return GF_BAD_PARAM; | |
| gf_sc_lock(compositor, 1); | |
| i = 0; | |
| while ((ns = (GF_SceneNamespace*)gf_list_enum(compositor->root_scene->namespaces, &i))) { | |
| j = 0; | |
| while (ns->clocks && (ck = (GF_Clock *)gf_list_enum(ns->clocks, &j))) { | |
| ck->init_timestamp += ms_diff; | |
| ck->media_ts_orig += ms_diff; | |
| ck->media_time_orig += ms_diff; | |
| //make sure we don't touch clock while doing resume/pause below | |
| if (force_resume_pause) | |
| ck->nb_paused++; | |
| } | |
| } | |
| compositor->step_mode = GF_TRUE; | |
| compositor->use_step_mode = GF_TRUE; | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| //resume/pause to trigger codecs state change | |
| if (force_resume_pause) { | |
| mediacontrol_resume(compositor->root_scene->root_od, 0); | |
| mediacontrol_pause(compositor->root_scene->root_od); | |
| //release our safety | |
| i = 0; | |
| while ((ns = (GF_SceneNamespace*)gf_list_enum(compositor->root_scene->namespaces, &i))) { | |
| j = 0; | |
| while (ns->clocks && (ck = (GF_Clock *)gf_list_enum(ns->clocks, &j))) { | |
| ck->nb_paused--; | |
| } | |
| } | |
| } | |
| gf_sc_lock(compositor, 0); | |
| } | |
| return GF_OK; | |
| } | |
| static void gf_sc_set_play_state_internal(GF_Compositor *compositor, u32 PlayState, Bool reset_audio, Bool pause_clocks) | |
| { | |
| Bool resume_live = 0; | |
| u32 prev_state; | |
| /*only play/pause if connected*/ | |
| if (!compositor || !compositor->root_scene) return; | |
| prev_state = compositor->play_state; | |
| compositor->use_step_mode = GF_FALSE; | |
| if (PlayState==GF_STATE_PLAY_LIVE) { | |
| PlayState = GF_STATE_PLAYING; | |
| resume_live = 1; | |
| if (compositor->play_state == GF_STATE_PLAYING) { | |
| compositor->play_state = GF_STATE_PAUSED; | |
| mediacontrol_pause(compositor->root_scene->root_od); | |
| } | |
| } | |
| /*and if not already paused/playing*/ | |
| if ((compositor->play_state == GF_STATE_PLAYING) && (PlayState == GF_STATE_PLAYING)) return; | |
| if ((compositor->play_state != GF_STATE_PLAYING) && (PlayState == GF_STATE_PAUSED)) return; | |
| /*pause compositor*/ | |
| if ((PlayState==GF_STATE_PLAYING) && reset_audio) | |
| gf_sc_set_option(compositor, GF_OPT_PLAY_STATE, 0xFF); | |
| else | |
| gf_sc_set_option(compositor, GF_OPT_PLAY_STATE, PlayState); | |
| /* step mode specific */ | |
| if (PlayState==GF_STATE_STEP_PAUSE) { | |
| if (prev_state==GF_STATE_PLAYING) { | |
| mediacontrol_pause(compositor->root_scene->root_od); | |
| compositor->play_state = GF_STATE_PAUSED; | |
| } else { | |
| u32 diff=1; | |
| if (compositor->ms_until_next_frame>0) diff = compositor->ms_until_next_frame; | |
| gf_sc_step_clocks_intern(compositor, diff, GF_TRUE); | |
| } | |
| return; | |
| } | |
| /* nothing to change*/ | |
| if (compositor->play_state == PlayState) return; | |
| compositor->play_state = PlayState; | |
| if (compositor->root_scene->first_frame_pause_type && (PlayState == GF_STATE_PLAYING)) | |
| compositor->root_scene->first_frame_pause_type = 0; | |
| if (!pause_clocks) return; | |
| if (PlayState != GF_STATE_PLAYING) { | |
| mediacontrol_pause(compositor->root_scene->root_od); | |
| } else { | |
| mediacontrol_resume(compositor->root_scene->root_od, resume_live); | |
| } | |
| } | |
| GF_EXPORT | |
| u32 gf_sc_play_from_time(GF_Compositor *compositor, u64 from_time, u32 pause_at_first_frame) | |
| { | |
| if (!compositor || !compositor->root_scene || !compositor->root_scene->root_od) return 0; | |
| if (compositor->root_scene->root_od->flags & GF_ODM_NO_TIME_CTRL) return 1; | |
| if (pause_at_first_frame==2) { | |
| if (gf_sc_get_option_internal(compositor, GF_OPT_PLAY_STATE) != GF_STATE_PLAYING) | |
| pause_at_first_frame = 1; | |
| else | |
| pause_at_first_frame = 0; | |
| } | |
| //in case we seek in non-player mode, adjust frame number to desired seek time | |
| //this is not really precise (we don't take into account seek) but is enough for vout UI | |
| if (!compositor->player) { | |
| u64 output_ts = gf_timestamp_rescale(from_time, 1000, compositor->fps.num); | |
| compositor->frame_number = (u32) (output_ts / compositor->fps.den); | |
| } | |
| /*for dynamic scene OD resources are static and all object use the same clock, so don't restart the root | |
| OD, just act as a mediaControl on all playing streams*/ | |
| if (compositor->root_scene->is_dynamic_scene) { | |
| /*exit pause mode*/ | |
| gf_sc_set_play_state_internal(compositor, GF_STATE_PLAYING, 1, 1); | |
| if (pause_at_first_frame) | |
| gf_sc_set_play_state_internal(compositor, GF_STATE_STEP_PAUSE, 0, 0); | |
| gf_sc_lock(compositor, 1); | |
| gf_scene_restart_dynamic(compositor->root_scene, from_time, 0, 0); | |
| gf_sc_lock(compositor, 0); | |
| return 2; | |
| } | |
| /*pause everything*/ | |
| gf_sc_set_play_state_internal(compositor, GF_STATE_PAUSED, 0, 1); | |
| /*stop root*/ | |
| gf_odm_stop(compositor->root_scene->root_od, 1); | |
| gf_scene_disconnect(compositor->root_scene, 0); | |
| compositor->root_scene->root_od->media_start_time = from_time; | |
| gf_odm_start(compositor->root_scene->root_od); | |
| gf_sc_set_play_state_internal(compositor, GF_STATE_PLAYING, 0, 1); | |
| if (pause_at_first_frame) | |
| gf_sc_set_option(compositor, GF_OPT_PLAY_STATE, GF_STATE_STEP_PAUSE); | |
| return 2; | |
| } | |
| GF_EXPORT | |
| void gf_sc_connect_from_time(GF_Compositor *compositor, const char *URL, u64 startTime, u32 pause_at_first_frame, Bool secondary_scene, const char *parent_path) | |
| { | |
| GF_Scene *scene; | |
| GF_ObjectManager *odm; | |
| Bool is_self = GF_FALSE; | |
| if (!URL || !strlen(URL)) | |
| is_self = GF_TRUE; | |
| if (compositor->root_scene) { | |
| if (is_self && !compositor->root_scene->root_od->ck) { | |
| odm = compositor->root_scene->root_od; | |
| odm->media_start_time = startTime; | |
| /*render first visual frame and pause*/ | |
| if (pause_at_first_frame) { | |
| gf_sc_set_play_state_internal(compositor, GF_STATE_STEP_PAUSE, 0, 0); | |
| compositor->root_scene->first_frame_pause_type = pause_at_first_frame; | |
| } | |
| return; | |
| } | |
| if (compositor->root_scene->root_od && compositor->root_scene->root_od->scene_ns) { | |
| const char *main_url = compositor->root_scene->root_od->scene_ns->url; | |
| if (is_self || (main_url && !strcmp(main_url, URL))) { | |
| gf_sc_play_from_time(compositor, startTime, pause_at_first_frame); | |
| return; | |
| } | |
| } | |
| if (is_self) return; | |
| /*disconnect*/ | |
| gf_sc_disconnect(compositor); | |
| } | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPTIME, ("[Compositor] Connecting to %s\n", URL)); | |
| gf_assert(!compositor->root_scene); | |
| /*create a new scene*/ | |
| scene = gf_scene_new(compositor, NULL); | |
| odm = gf_odm_new(); | |
| scene->root_od = odm; | |
| odm->subscene = scene; | |
| //by default all scenes are dynamic, until we get a BIFS attached | |
| scene->is_dynamic_scene = GF_TRUE; | |
| odm->media_start_time = startTime; | |
| // we are not in compositor:process at this point of time since the compositor thread drives the compositor | |
| compositor->root_scene = scene; | |
| /*render first visual frame and pause*/ | |
| if (pause_at_first_frame) { | |
| gf_sc_set_play_state_internal(compositor, GF_STATE_STEP_PAUSE, 0, 0); | |
| scene->first_frame_pause_type = pause_at_first_frame; | |
| } | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPTIME, ("[Compositor] root scene created\n", URL)); | |
| if (!strnicmp(URL, "views://", 8)) { | |
| gf_scene_generate_views(compositor->root_scene, (char *) URL+8, (char*)parent_path); | |
| return; | |
| } | |
| else if (!strnicmp(URL, "mosaic://", 9)) { | |
| gf_scene_generate_mosaic(compositor->root_scene, (char *) URL+9, (char*)parent_path); | |
| return; | |
| } | |
| gf_scene_ns_connect_object(scene, odm, (char *) URL, (char*)parent_path, NULL); | |
| } | |
| GF_EXPORT | |
| void gf_sc_disconnect(GF_Compositor *compositor) | |
| { | |
| /*resume*/ | |
| if (compositor->play_state != GF_STATE_PLAYING) gf_sc_set_play_state_internal(compositor, GF_STATE_PLAYING, 1, 1); | |
| if (compositor->root_scene && compositor->root_scene->root_od) { | |
| GF_ObjectManager *root = compositor->root_scene->root_od; | |
| gf_sc_lock(compositor, GF_TRUE); | |
| compositor->root_scene = NULL; | |
| gf_odm_disconnect(root, 2); | |
| gf_sc_lock(compositor, GF_FALSE); | |
| } | |
| } | |
| #include <gpac/scene_manager.h> | |
| static Bool check_in_scene(GF_Scene *scene, GF_ObjectManager *odm) | |
| { | |
| u32 i; | |
| GF_ObjectManager *ptr, *root; | |
| if (!scene) return 0; | |
| root = scene->root_od; | |
| if (odm == root) return 1; | |
| scene = root->subscene; | |
| i=0; | |
| while ((ptr = (GF_ObjectManager *)gf_list_enum(scene->resources, &i))) { | |
| if (ptr == odm) return 1; | |
| if (check_in_scene(ptr->subscene, odm)) return 1; | |
| } | |
| return 0; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_sc_dump_scene_ex(GF_Compositor *compositor, char *rad_name, char **filename, Bool xml_dump, Bool skip_protos, GF_ObjectManager *scene_od) | |
| { | |
| #ifndef GPAC_DISABLE_SCENE_DUMP | |
| GF_SceneGraph *sg; | |
| GF_ObjectManager *odm; | |
| GF_SceneDumper *dumper; | |
| GF_List *extra_graphs; | |
| u32 mode; | |
| u32 i; | |
| char *ext; | |
| GF_Err e; | |
| if (!compositor || !compositor->root_scene) return GF_BAD_PARAM; | |
| if (!scene_od) { | |
| if (!compositor->root_scene) return GF_BAD_PARAM; | |
| odm = compositor->root_scene->root_od; | |
| } else { | |
| odm = scene_od; | |
| if (! check_in_scene(compositor->root_scene, scene_od)) | |
| odm = compositor->root_scene->root_od; | |
| } | |
| if (odm->subscene) { | |
| if (!odm->subscene->graph) return GF_IO_ERR; | |
| sg = odm->subscene->graph; | |
| extra_graphs = odm->subscene->extra_scenes; | |
| } else { | |
| if (!odm->parentscene->graph) return GF_IO_ERR; | |
| sg = odm->parentscene->graph; | |
| extra_graphs = odm->parentscene->extra_scenes; | |
| } | |
| mode = xml_dump ? GF_SM_DUMP_AUTO_XML : GF_SM_DUMP_AUTO_TXT; | |
| /*figure out best dump format based on extension*/ | |
| ext = odm->scene_ns ? gf_file_ext_start(odm->scene_ns->url) : NULL; | |
| if (ext) { | |
| char szExt[20]; | |
| gf_strcpy(szExt, ext); | |
| strlwr(szExt); | |
| if (!strcmp(szExt, ".wrl")) mode = xml_dump ? GF_SM_DUMP_X3D_XML : GF_SM_DUMP_VRML; | |
| else if(!strncmp(szExt, ".x3d", 4) || !strncmp(szExt, ".x3dv", 5) ) mode = xml_dump ? GF_SM_DUMP_X3D_XML : GF_SM_DUMP_X3D_VRML; | |
| else if(!strncmp(szExt, ".bt", 3) || !strncmp(szExt, ".xmt", 4) || !strncmp(szExt, ".mp4", 4) ) mode = xml_dump ? GF_SM_DUMP_XMTA : GF_SM_DUMP_BT; | |
| } | |
| dumper = gf_sm_dumper_new(sg, rad_name, GF_FALSE, ' ', mode); | |
| if (!dumper) return GF_IO_ERR; | |
| e = gf_sm_dump_graph(dumper, skip_protos, 0); | |
| for (i = 0; i < gf_list_count(extra_graphs); i++) { | |
| GF_SceneGraph *extra = (GF_SceneGraph *)gf_list_get(extra_graphs, i); | |
| gf_sm_dumper_set_extra_graph(dumper, extra); | |
| e = gf_sm_dump_graph(dumper, skip_protos, 0); | |
| } | |
| #ifdef GPAC_ENABLE_COVERAGE | |
| if (gf_sys_is_cov_mode()) { | |
| gf_sm_dumper_set_extra_graph(dumper, NULL); | |
| gf_sm_dump_get_name(dumper); | |
| } | |
| #endif | |
| if (filename) *filename = gf_strdup(gf_sm_dump_get_name(dumper)); | |
| gf_sm_dumper_del(dumper); | |
| return e; | |
| #else | |
| return GF_NOT_SUPPORTED; | |
| #endif | |
| } | |
| GF_EXPORT | |
| GF_Err gf_sc_dump_scene(GF_Compositor *compositor, char *rad_name, char **filename, Bool xml_dump, Bool skip_protos) | |
| { | |
| return gf_sc_dump_scene_ex(compositor, rad_name, filename, xml_dump, skip_protos, NULL); | |
| } | |
| #include <gpac/internal/scenegraph_dev.h> | |
| GF_EXPORT | |
| GF_Err gf_sc_scene_update(GF_Compositor *compositor, char *type, char *com) | |
| { | |
| #ifndef GPAC_DISABLE_SMGR | |
| GF_Err e; | |
| GF_StreamContext *sc; | |
| Bool is_xml = 0; | |
| Double time = 0; | |
| u32 i, tag; | |
| GF_SceneLoader load; | |
| if (!compositor || !com) return GF_BAD_PARAM; | |
| if (type && (!stricmp(type, "application/ecmascript") || !stricmp(type, "js")) ) { | |
| #if defined(GPAC_HAS_QJS) && !defined(GPAC_DISABLE_SVG) | |
| u32 tag; | |
| GF_Node *root = gf_sg_get_root_node(compositor->root_scene->graph); | |
| if (!root) return GF_BAD_PARAM; | |
| tag = gf_node_get_tag(root); | |
| if (tag >= GF_NODE_RANGE_FIRST_SVG) { | |
| if (compositor->root_scene->graph->svg_js) { | |
| GF_Err svg_exec_script(struct __tag_svg_script_ctx *svg_js, GF_SceneGraph *sg, const char *com); | |
| return svg_exec_script(compositor->root_scene->graph->svg_js, compositor->root_scene->graph, (char *)com); | |
| } | |
| return GF_NOT_FOUND; | |
| } | |
| return GF_NOT_SUPPORTED; | |
| #else | |
| return GF_NOT_SUPPORTED; | |
| #endif | |
| } | |
| if (!type && !strncmp(com, "gpac ", 5)) { | |
| com += 5; | |
| //new add-on | |
| if (compositor->root_scene && !strncmp(com, "add ", 4)) { | |
| GF_AssociatedContentLocation addon_info; | |
| memset(&addon_info, 0, sizeof(GF_AssociatedContentLocation)); | |
| addon_info.external_URL = com + 4; | |
| addon_info.timeline_id = -100; | |
| gf_scene_register_associated_media(compositor->root_scene, &addon_info); | |
| return GF_OK; | |
| } | |
| //new splicing add-on | |
| if (compositor->root_scene && !strncmp(com, "splice ", 7)) { | |
| char *sep; | |
| Double start, end; | |
| Bool is_pts = GF_FALSE; | |
| GF_AssociatedContentLocation addon_info; | |
| memset(&addon_info, 0, sizeof(GF_AssociatedContentLocation)); | |
| com += 7; | |
| if (!strnicmp(com, "pts ", 4)) { | |
| is_pts = GF_TRUE; | |
| com += 4; | |
| } | |
| sep = strchr(com, ':'); | |
| start = 0; | |
| end = -1; | |
| if (sep) { | |
| sep[0]=0; | |
| if (sscanf(com, "%lf-%lf", &start, &end) != 2) { | |
| end = -1; | |
| sscanf(com, "%lf", &start); | |
| } | |
| sep[0]=':'; | |
| addon_info.external_URL = sep+1; | |
| } | |
| //splice end, locate first splice with no end set and set it | |
| else if (sscanf(com, "%lf", &end)==1) { | |
| u32 count = gf_list_count(compositor->root_scene->declared_addons); | |
| for (i=0; i<count; i++) { | |
| GF_AddonMedia *addon = gf_list_get(compositor->root_scene->declared_addons, i); | |
| if (addon->is_splicing && (addon->splice_end<0) ) { | |
| addon->splice_end = end; | |
| break; | |
| } | |
| } | |
| return GF_OK; | |
| } else { | |
| //splice now, until end of spliced media | |
| addon_info.external_URL = com; | |
| } | |
| addon_info.is_splicing = GF_TRUE; | |
| addon_info.timeline_id = -100; | |
| addon_info.splice_start_time = start; | |
| addon_info.splice_end_time = end; | |
| addon_info.splice_time_pts = is_pts; | |
| gf_scene_register_associated_media(compositor->root_scene, &addon_info); | |
| return GF_OK; | |
| } | |
| //select object | |
| if (compositor->root_scene && !strncmp(com, "select ", 7)) { | |
| u32 idx = atoi(com+7); | |
| gf_scene_select_object(compositor->root_scene, gf_list_get(compositor->root_scene->resources, idx)); | |
| return GF_OK; | |
| } | |
| return GF_OK; | |
| } | |
| memset(&load, 0, sizeof(GF_SceneLoader)); | |
| load.localPath = gf_opts_get_key("core", "cache"); | |
| load.flags = GF_SM_LOAD_FOR_PLAYBACK | GF_SM_LOAD_CONTEXT_READY; | |
| load.type = GF_SM_LOAD_BT; | |
| if (!compositor->root_scene) { | |
| /*create a new scene*/ | |
| compositor->root_scene = gf_scene_new(compositor, NULL); | |
| compositor->root_scene->root_od = gf_odm_new(); | |
| compositor->root_scene->root_od->parentscene = NULL; | |
| compositor->root_scene->root_od->subscene = compositor->root_scene; | |
| load.ctx = gf_sm_new(compositor->root_scene->graph); | |
| } else if (compositor->root_scene->root_od) { | |
| u32 nb_ch = 0; | |
| load.ctx = gf_sm_new(compositor->root_scene->graph); | |
| if (compositor->root_scene->root_od->pid) nb_ch++; | |
| if (compositor->root_scene->root_od->extra_pids) nb_ch += gf_list_count(compositor->root_scene->root_od->extra_pids); | |
| for (i=0; i<nb_ch; i++) { | |
| u32 stream_type, es_id, oti; | |
| const GF_PropertyValue *p; | |
| GF_FilterPid *pid = compositor->root_scene->root_od->pid; | |
| if (i) { | |
| GF_ODMExtraPid *xpid = gf_list_get(compositor->root_scene->root_od->extra_pids, i-1); | |
| pid = xpid->pid; | |
| } | |
| p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); | |
| if (!p) continue; | |
| stream_type = p->value.uint; | |
| switch (stream_type) { | |
| case GF_STREAM_OD: | |
| case GF_STREAM_SCENE: | |
| case GF_STREAM_PRIVATE_SCENE: | |
| p = gf_filter_pid_get_property(pid, GF_PROP_PID_ESID); | |
| if (!p) | |
| p = gf_filter_pid_get_property(pid, GF_PROP_PID_ID); | |
| es_id = p ? p->value.uint : 0; | |
| p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID); | |
| oti = p ? p->value.uint : 1; | |
| sc = gf_sm_stream_new(load.ctx, es_id, stream_type, oti); | |
| if (stream_type==GF_STREAM_PRIVATE_SCENE) sc->streamType = GF_STREAM_SCENE; | |
| p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); | |
| sc->timeScale = p ? p->value.uint : 1000; | |
| break; | |
| } | |
| } | |
| } else { | |
| return GF_BAD_PARAM; | |
| } | |
| load.ctx->max_node_id = gf_sg_get_max_node_id(compositor->root_scene->graph); | |
| i=0; | |
| while ((com[i] == ' ') || (com[i] == '\r') || (com[i] == '\n') || (com[i] == '\t')) i++; | |
| if (com[i]=='<') is_xml = 1; | |
| load.type = is_xml ? GF_SM_LOAD_XMTA : GF_SM_LOAD_BT; | |
| time = gf_scene_get_time(compositor->root_scene); | |
| if (type && (!stricmp(type, "application/x-laser+xml") || !stricmp(type, "laser"))) { | |
| load.type = GF_SM_LOAD_XSR; | |
| time = gf_scene_get_time(compositor->root_scene); | |
| } | |
| else if (type && (!stricmp(type, "image/svg+xml") || !stricmp(type, "svg")) ) { | |
| load.type = GF_SM_LOAD_XSR; | |
| time = gf_scene_get_time(compositor->root_scene); | |
| } | |
| else if (type && (!stricmp(type, "model/x3d+xml") || !stricmp(type, "x3d")) ) load.type = GF_SM_LOAD_X3D; | |
| else if (type && (!stricmp(type, "model/x3d+vrml") || !stricmp(type, "x3dv")) ) load.type = GF_SM_LOAD_X3DV; | |
| else if (type && (!stricmp(type, "model/vrml") || !stricmp(type, "vrml")) ) load.type = GF_SM_LOAD_VRML; | |
| else if (type && (!stricmp(type, "application/x-xmt") || !stricmp(type, "xmt")) ) load.type = GF_SM_LOAD_XMTA; | |
| else if (type && (!stricmp(type, "application/x-bt") || !stricmp(type, "bt")) ) load.type = GF_SM_LOAD_BT; | |
| else if (gf_sg_get_root_node(compositor->root_scene->graph)) { | |
| tag = gf_node_get_tag(gf_sg_get_root_node(compositor->root_scene->graph)); | |
| if (tag >= GF_NODE_RANGE_FIRST_SVG) { | |
| load.type = GF_SM_LOAD_XSR; | |
| time = gf_scene_get_time(compositor->root_scene); | |
| } else if (tag>=GF_NODE_RANGE_FIRST_X3D) { | |
| load.type = is_xml ? GF_SM_LOAD_X3D : GF_SM_LOAD_X3DV; | |
| } else { | |
| load.type = is_xml ? GF_SM_LOAD_XMTA : GF_SM_LOAD_BT; | |
| time = gf_scene_get_time(compositor->root_scene); | |
| } | |
| } | |
| e = gf_sm_load_init(&load); | |
| if (!e) e = gf_sm_load_string(&load, com, 1); | |
| gf_sm_load_done(&load); | |
| if (!e) { | |
| u32 j, au_count, st_count; | |
| st_count = gf_list_count(load.ctx->streams); | |
| for (i=0; i<st_count; i++) { | |
| sc = (GF_StreamContext*)gf_list_get(load.ctx->streams, i); | |
| au_count = gf_list_count(sc->AUs); | |
| for (j=0; j<au_count; j++) { | |
| GF_AUContext *au = (GF_AUContext *)gf_list_get(sc->AUs, j); | |
| e = gf_sg_command_apply_list(compositor->root_scene->graph, au->commands, time); | |
| if (e) break; | |
| } | |
| } | |
| } | |
| if (!compositor->root_scene->graph_attached) { | |
| if (!load.ctx->scene_width || !load.ctx->scene_height) { | |
| // load.ctx->scene_width = term->compositor->width; | |
| // load.ctx->scene_height = term->compositor->height; | |
| } | |
| gf_sg_set_scene_size_info(compositor->root_scene->graph, load.ctx->scene_width, load.ctx->scene_height, load.ctx->is_pixel_metrics); | |
| gf_scene_attach_to_compositor(compositor->root_scene); | |
| } | |
| gf_sm_del(load.ctx); | |
| return e; | |
| #else | |
| return GF_NOT_SUPPORTED; | |
| #endif | |
| } | |
| GF_EXPORT | |
| void gf_sc_select_service(GF_Compositor *compositor, u32 service_id) | |
| { | |
| if (!compositor || !compositor->root_scene) return; | |
| gf_scene_set_service_id(compositor->root_scene, service_id); | |
| } | |
| GF_EXPORT | |
| const char *gf_sc_get_url(GF_Compositor *compositor) | |
| { | |
| if (!compositor || !compositor->root_scene || !compositor->root_scene->root_od || !compositor->root_scene->root_od->scene_ns) return NULL; | |
| return compositor->root_scene->root_od->scene_ns->url; | |
| } | |
| GF_EXPORT | |
| const void *gf_sc_get_main_pid(GF_Compositor *compositor) | |
| { | |
| if (!compositor || !compositor->root_scene) return NULL; | |
| GF_ObjectManager *odm = compositor->root_scene->root_od; | |
| if (odm->pid) return odm->pid; | |
| if (odm->subscene) { | |
| GF_ObjectManager *anodm = gf_list_get(odm->subscene->resources, 0); | |
| if (!anodm) return NULL; | |
| return anodm->pid; | |
| } | |
| return NULL; | |
| } | |
| GF_EXPORT | |
| const char *gf_sc_get_world_info(GF_Compositor *compositor, GF_List *descriptions) | |
| { | |
| GF_Node *info; | |
| if (!compositor || !compositor->root_scene || !compositor->root_scene->world_info) return NULL; | |
| info = (GF_Node*) compositor->root_scene->world_info; | |
| #ifndef GPAC_DISABLE_SVG | |
| if (gf_node_get_tag(info) == TAG_SVG_title) { | |
| SVG_Element *elt = (SVG_Element *)info; | |
| GF_ChildNodeItem *l = elt->children; | |
| while (l) { | |
| if (gf_node_get_tag(l->node)==TAG_DOMText) { | |
| GF_DOMText *txt = (GF_DOMText *)l->node; | |
| return txt->textContent; | |
| } | |
| l = l->next; | |
| } | |
| } else | |
| #endif | |
| { | |
| #ifndef GPAC_DISABLE_VRML | |
| M_WorldInfo *wi = (M_WorldInfo *) info; | |
| if (descriptions) { | |
| u32 i; | |
| for (i=0; i<wi->info.count; i++) { | |
| gf_list_add(descriptions, wi->info.vals[i]); | |
| } | |
| } | |
| return wi->title.buffer; | |
| #endif | |
| } | |
| return "GPAC"; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_sc_add_object(GF_Compositor *compositor, const char *url, Bool auto_play) | |
| { | |
| GF_MediaObject *mo=NULL; | |
| //this needs refinement | |
| SFURL sfurl; | |
| MFURL mfurl; | |
| if (!url || !compositor->root_scene || !compositor->root_scene->is_dynamic_scene) return GF_BAD_PARAM; | |
| sfurl.OD_ID = GF_MEDIA_EXTERNAL_ID; | |
| sfurl.url = (char *) url; | |
| mfurl.count = 1; | |
| mfurl.vals = &sfurl; | |
| /*only text tracks are supported for now...*/ | |
| mo = gf_scene_get_media_object(compositor->root_scene, &mfurl, GF_MEDIA_OBJECT_TEXT, 1); | |
| if (mo) { | |
| /*check if we must deactivate it*/ | |
| if (mo->odm) { | |
| if (mo->num_open && !auto_play) { | |
| gf_scene_select_object(compositor->root_scene, mo->odm); | |
| } | |
| } else { | |
| gf_list_del_item(compositor->root_scene->scene_objects, mo); | |
| gf_sg_vrml_mf_reset(&mo->URLs, GF_SG_VRML_MFURL); | |
| gf_free(mo); | |
| mo = NULL; | |
| } | |
| } | |
| return mo ? GF_OK : GF_NOT_SUPPORTED; | |
| } | |
| GF_EXPORT | |
| Double gf_sc_get_simulation_frame_rate(GF_Compositor *compositor, u32 *nb_frames_drawn) | |
| { | |
| Double fps; | |
| if (!compositor) return 0.0; | |
| if (nb_frames_drawn) *nb_frames_drawn = compositor->frame_number; | |
| fps = compositor->fps.num; | |
| fps /= compositor->fps.den; | |
| return fps; | |
| } | |
| GF_EXPORT | |
| u32 gf_sc_get_elapsed_time_in_ms(GF_Compositor *compositor) | |
| { | |
| u32 i, count; | |
| if (!compositor || !compositor->root_scene) return 0; | |
| count = gf_list_count(compositor->root_scene->namespaces); | |
| for (i=0; i<count; i++) { | |
| GF_SceneNamespace *sns = gf_list_get(compositor->root_scene->namespaces, i); | |
| GF_Clock *ck = gf_list_get(sns->clocks, 0); | |
| if (ck) return gf_clock_elapsed_time(ck); | |
| } | |
| return 0; | |
| } | |
| GF_EXPORT | |
| u32 gf_sc_get_current_service_id(GF_Compositor *compositor) | |
| { | |
| SFURL *the_url; | |
| GF_MediaObject *mo; | |
| if (!compositor || !compositor->root_scene) return 0; | |
| if (!compositor->root_scene->is_dynamic_scene) return compositor->root_scene->root_od->ServiceID; | |
| if (compositor->root_scene->visual_url.OD_ID || compositor->root_scene->visual_url.url) | |
| the_url = &compositor->root_scene->visual_url; | |
| else | |
| the_url = &compositor->root_scene->audio_url; | |
| mo = gf_scene_find_object(compositor->root_scene, the_url->OD_ID, the_url->url); | |
| if (mo && mo->odm) return mo->odm->ServiceID; | |
| return 0; | |
| } | |
| #endif // GPAC_DISABLE_COMPOSITOR |