/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: Jean Le Feuvre
 *			Copyright (c) Telecom ParisTech 2000-2023
 *					All rights reserved
 *
 *  This file is part of GPAC / Scene Compositor sub-project
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  GPAC is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */



#include "visual_manager.h"
#ifndef GPAC_DISABLE_COMPOSITOR

#include "nodes_stacks.h"
#include "texturing.h"

#include "gl_inc.h"

#ifndef GPAC_DISABLE_3D
void compositor_2d_hybgl_clear_surface(GF_VisualManager *visual, GF_IRect *rc, u32 BackColor, u32 is_offscreen_clear)
{
	SFColor rgb;
	Fixed alpha = INT2FIX( GF_COL_A(BackColor) )/255;
	if (!visual->is_attached) return;

	if (!BackColor && !visual->offscreen && !is_offscreen_clear) {
		if ( !(visual->compositor->init_flags & GF_VOUT_WINDOW_TRANSPARENT)) {
			BackColor = visual->compositor->back_color & 0x00FFFFFF;
		}
	}
	if (is_offscreen_clear) {
		gf_evg_surface_clear(visual->raster_surface, rc, BackColor);
		//if we clear the canvas with non-0 alpha, remember the area cleared in case we have to erase it later (overlapping bitmap)
		//if we clear dirty area of the canvas, remember the area to force gl flush 
		if (GF_COL_A(BackColor) || (is_offscreen_clear==2))
		{
			ra_union_rect(&visual->hybgl_drawn, rc);
		}
	} else {
		rgb.red = INT2FIX( GF_COL_R(BackColor) ) / 255;
		rgb.green = INT2FIX( GF_COL_G(BackColor) )/255;
		rgb.blue = INT2FIX( GF_COL_B(BackColor) )/255;
		visual_3d_clear(visual, rgb , alpha);
	}
}


void compositor_2d_hybgl_flush_video(GF_Compositor *compositor, GF_IRect *area)
{
	GF_TraverseState a_tr_state;

	//check if texture data has changed - if so, mark texture to be updated
	if (compositor->traverse_state->immediate_draw) {
		//nothing drawn, nothing to do
		if (!compositor->visual->hybgl_drawn.count) {
			return;
		}
		//nodes where drawn on canvas, push texture
		gf_sc_texture_set_data(compositor->hybgl_txh);
	} else {
		//nodes where drawn on canvas, push texture
		if (compositor->visual->hybgl_drawn.count) {
			//we could actually use texSubImage here, but we would need either to copy the data before pushing it or use current stride which will result in pushing too much data ...
			gf_sc_texture_set_data(compositor->hybgl_txh);
		}
	}
	//if no object drawn since the last flush, no need to draw the texture
	if (!compositor->visual->nb_objects_on_canvas_since_last_ogl_flush)
		goto exit;

	compositor->visual->prev_hybgl_canvas_not_empty = GF_TRUE;

	memset(&a_tr_state, 0, sizeof(GF_TraverseState));
	a_tr_state.color_mat.identity = 1;
	a_tr_state.visual = compositor->visual;
	a_tr_state.camera = &compositor->visual->camera;
	gf_mx_init(a_tr_state.model_matrix);

	visual_3d_set_state(compositor->visual, V3D_STATE_LIGHT, GF_FALSE);
	visual_3d_enable_antialias(compositor->visual, GF_FALSE);
	gf_sc_texture_set_blend_mode(compositor->hybgl_txh, TX_MODULATE);
	//visual_3d_set_material_2d_argb(compositor->visual, 0xFFFFFFFF);
	compositor->visual->has_material_2d = GF_FALSE;
	a_tr_state.mesh_num_textures = gf_sc_texture_enable(compositor->hybgl_txh, NULL);
	if (a_tr_state.mesh_num_textures ) {
		if (area) {
			u32 i;
			Fixed umin, umax, vmin, vmax;
			SFVec2f size, orig;
			size.x = INT2FIX(area->width);
			size.y = INT2FIX(area->height);

			orig.x = INT2FIX(area->x);
			orig.y = INT2FIX(area->y);
			mesh_new_rectangle(compositor->hybgl_mesh, size, &orig, GF_TRUE);

			orig.x = INT2FIX(area->x) + INT2FIX(compositor->vp_width)/2;
			orig.y = INT2FIX(compositor->vp_height)/2 - INT2FIX(area->y) + INT2FIX(area->height);
			//set our coor texture so that we update only the right part of the canvas
			umin = gf_divfix(orig.x, INT2FIX(compositor->vp_width));
			umax = gf_divfix(orig.x+size.x, INT2FIX(compositor->vp_width));
			vmin = gf_divfix(orig.y-size.y, INT2FIX(compositor->vp_height));
			vmax = gf_divfix(orig.y, INT2FIX(compositor->vp_height));

			for (i=0; i<compositor->hybgl_mesh->v_count; i++) {
				if (compositor->hybgl_mesh->vertices[i].texcoords.x == FIX_ONE) {
					compositor->hybgl_mesh->vertices[i].texcoords.x = umax;
				} else {
					compositor->hybgl_mesh->vertices[i].texcoords.x = umin;
				}
				if (compositor->hybgl_mesh->vertices[i].texcoords.y == FIX_ONE) {
					compositor->hybgl_mesh->vertices[i].texcoords.y = vmax;
				} else {
					compositor->hybgl_mesh->vertices[i].texcoords.y = vmin;
				}
			}
		}

		visual_3d_mesh_paint(&a_tr_state, compositor->hybgl_mesh);
		gf_sc_texture_disable(compositor->hybgl_txh);

		if (area) {
			SFVec2f size;
			size.x = INT2FIX(compositor->vp_width);
			size.y = INT2FIX(compositor->vp_height);
			mesh_new_rectangle(compositor->hybgl_mesh, size, NULL, GF_TRUE);
		}
	}

exit:
	//in immediate mode always clear the canvas
	if (compositor->traverse_state->immediate_draw) {
		compositor->visual->hybgl_drawn.count = 0;
		//partial flush, reset
		if (area)
			memset(compositor->hybgl_txh->data, 0, compositor->hybgl_txh->stride*compositor->hybgl_txh->height);
	}
	//complete flush (end of frame)
	else if (!area) {
		compositor->visual->hybgl_drawn.count = 0;
	}
	compositor->visual->nb_objects_on_canvas_since_last_ogl_flush = 0;
}

Bool compositor_2d_hybgl_draw_bitmap(GF_VisualManager *visual, GF_TraverseState *tr_state, DrawableContext *ctx)
{
	GF_Node *txtrans = NULL;
	//for anything but background use regular routines
	if (!(ctx->flags & CTX_IS_BACKGROUND)) return GF_FALSE;

#ifndef GPAC_DISABLE_VRML
	if (tr_state->appear ) txtrans = ((M_Appearance *)tr_state->appear)->textureTransform;
#endif

	/*ignore texture transform for bitmap*/
	tr_state->mesh_num_textures = gf_sc_texture_enable(ctx->aspect.fill_texture, txtrans);

	if (tr_state->mesh_num_textures) {
		SFVec2f size, orig;
		size.x = ctx->bi->unclip.width;
		size.y = ctx->bi->unclip.height;
		orig.x = ctx->bi->unclip.x ;
		orig.y = ctx->bi->unclip.y;
		mesh_new_rectangle(visual->compositor->hybgl_mesh_background, size, &orig, GF_FALSE);

		visual_3d_mesh_paint(tr_state, visual->compositor->hybgl_mesh_background);
		gf_sc_texture_disable(ctx->aspect.fill_texture);
		tr_state->mesh_num_textures = 0;
	}
	return GF_TRUE;
}
#endif

#ifndef GPAC_DISABLE_3D
void compositor_2d_reset_gl_auto(GF_Compositor *compositor)
{
	if (compositor->hybgl_txh) {
		if (compositor->hybgl_txh->data) {
			gf_free(compositor->hybgl_txh->data);
			compositor->hybgl_txh->data = NULL;
		}
		if (compositor->hybgl_txh->tx_io)
			gf_sc_texture_release(compositor->hybgl_txh);

		gf_free(compositor->hybgl_txh);
		compositor->hybgl_txh = NULL;
	}
	if (compositor->hybgl_mesh) {
		mesh_free(compositor->hybgl_mesh);
		compositor->hybgl_mesh = NULL;
	}
	if (compositor->hybgl_mesh_background) {
		mesh_free(compositor->hybgl_mesh_background);
		compositor->hybgl_mesh_background = NULL;
	}
}

static GF_Err compositor_2d_setup_opengl(GF_VisualManager *visual)
{
	GF_Compositor *compositor = visual->compositor;
	visual->is_attached = GF_TRUE;

	visual_3d_setup(visual);
	visual->compositor->traverse_state->camera = &visual->camera;


	glViewport(0, 0, compositor->vp_width, compositor->vp_height);

	visual->camera.vp.x = visual->camera.vp.y = 0;
	visual->camera.vp.width = visual->camera.width = INT2FIX(compositor->vp_width);
	visual->camera.vp.height = visual->camera.height = INT2FIX(compositor->vp_height);
	visual->camera.up.y = FIX_ONE;
	visual->camera.end_zoom = FIX_ONE;
	visual->camera.position.z = INT2FIX(1000);
	visual->camera.flags = CAM_IS_DIRTY;

	camera_update(&visual->camera, NULL, visual->compositor->hybrid_opengl ? GF_TRUE : visual->center_coords);

	visual_3d_projection_matrix_modified(visual);
	return GF_OK;
}
#endif

#ifndef GPAC_DISABLE_3D

static GF_Err c2d_video_access_hybrid_opengl(GF_VisualManager *visual)
{
	GF_Err e;
	GF_Compositor *compositor = visual->compositor;

	if (!compositor->hybgl_txh) {
		GF_SAFEALLOC(compositor->hybgl_txh, GF_TextureHandler);
		if (!compositor->hybgl_txh) return GF_IO_ERR;
		compositor->hybgl_txh->compositor = compositor;
	}

	if ((compositor->hybgl_txh->width != compositor->vp_width) || (compositor->hybgl_txh->height != compositor->vp_height)) {
		SFVec2f size;
		compositor->hybgl_txh->data = (char*)gf_realloc(compositor->hybgl_txh->data, 4*compositor->vp_width*compositor->vp_height);

		if (compositor->hybgl_txh->tx_io)
			gf_sc_texture_release(compositor->hybgl_txh);

		compositor->hybgl_txh->width = compositor->vp_width;
		compositor->hybgl_txh->height = compositor->vp_height;
		compositor->hybgl_txh->stride = 4*compositor->vp_width;
		compositor->hybgl_txh->pixelformat = GF_PIXEL_RGBA;
		compositor->hybgl_txh->transparent = GF_TRUE;
		compositor->hybgl_txh->flags = GF_SR_TEXTURE_PRIVATE_MEDIA | GF_SR_TEXTURE_NO_GL_FLIP;

		memset(compositor->hybgl_txh->data, 0, 4*compositor->hybgl_txh->width*compositor->hybgl_txh->height);
		gf_sc_texture_allocate(compositor->hybgl_txh);
		gf_sc_texture_set_data(compositor->hybgl_txh);

		if (!compositor->hybgl_mesh)
			compositor->hybgl_mesh = new_mesh();

		if (!compositor->hybgl_mesh_background)
			compositor->hybgl_mesh_background = new_mesh();

		size.x = INT2FIX(compositor->vp_width);
		size.y = INT2FIX(compositor->vp_height);
		mesh_new_rectangle(compositor->hybgl_mesh, size, NULL, GF_TRUE);
		mesh_new_rectangle(compositor->hybgl_mesh_background, size, NULL, GF_FALSE);
	}
	if (!compositor->hybgl_txh->data) return GF_IO_ERR;

	if (visual->compositor->traverse_state->immediate_draw)
		memset(compositor->hybgl_txh->data, 0, 4*compositor->hybgl_txh->width*compositor->hybgl_txh->height);

	e = gf_evg_surface_attach_to_buffer(visual->raster_surface, compositor->hybgl_txh->data,
	        compositor->hybgl_txh->width,
	        compositor->hybgl_txh->height,
	        0,
	        compositor->hybgl_txh->width * 4,
	        (GF_PixelFormat) GF_PIXEL_RGBA);
	if (e) return e;
	e = compositor_2d_setup_opengl(visual);
	if (e) return e;
	visual->ClearSurface = compositor_2d_hybgl_clear_surface;
	visual->DrawBitmap = compositor_2d_hybgl_draw_bitmap;
	return GF_OK;
}
#endif

static GF_Err c2d_get_video_access_normal(GF_VisualManager *visual)
{
	GF_Err e;
	GF_Compositor *compositor = visual->compositor;
	if (!compositor->video_out->LockBackBuffer) return GF_NOT_SUPPORTED;
	compositor->hw_locked = GF_FALSE;

	e = compositor->video_out->LockBackBuffer(compositor->video_out, &compositor->hw_surface, GF_TRUE);
	if (e==GF_OK) {
		compositor->hw_locked = GF_TRUE;

		e = gf_evg_surface_attach_to_buffer(visual->raster_surface, compositor->hw_surface.video_buffer,
		        compositor->hw_surface.width,
		        compositor->hw_surface.height,
		        compositor->hw_surface.pitch_x,
		        compositor->hw_surface.pitch_y,
		        (GF_PixelFormat) compositor->hw_surface.pixel_format);
		if (!e) {
			visual->is_attached = GF_TRUE;
			GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor2D] Video surface memory attached to raster - w=%d h=%d pitch_x=%d pitch_y=%d\n", compositor->hw_surface.width, compositor->hw_surface.height, compositor->hw_surface.pitch_x, compositor->hw_surface.pitch_y));
			return GF_OK;
		}
		GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor2D] Cannot attach video surface memory to raster for pixel format %s: %s\n", gf_pixel_fmt_name(compositor->hw_surface.pixel_format), gf_error_to_string(e) ));
		compositor->last_error = e;
		compositor->video_out->LockBackBuffer(compositor->video_out, &compositor->hw_surface, GF_FALSE);
	}
	compositor->hw_locked = GF_FALSE;
	visual->is_attached = GF_FALSE;
	/*if using BlitTexture, return OK to still be able to blit images*/
	if (compositor->video_out->BlitTexture) e = GF_OK;
	return e;
}

GF_Err compositor_2d_get_video_access(GF_VisualManager *visual)
{
	if (!visual->raster_surface) return GF_BAD_PARAM;

#ifndef GPAC_DISABLE_3D
	if (visual->compositor->hybrid_opengl) {
		return c2d_video_access_hybrid_opengl(visual);
	}
#endif
	//do nothing until asked to really attach
	return GF_OK;
}

Bool compositor_2d_check_attached(GF_VisualManager *visual)
{
	if (!visual->is_attached) {
		c2d_get_video_access_normal(visual);
	}

	return visual->is_attached;
}

void compositor_2d_clear_surface(GF_VisualManager *visual, GF_IRect *rc, u32 BackColor, u32 offscreen_clear)
{
	//visual not attached on main (direct video) visual, use texture bliting
	if (!visual->is_attached && visual->compositor->video_out->Blit && (visual->compositor->video_out->hw_caps & GF_VIDEO_HW_HAS_RGB)) {
		char data[12];
		GF_Err e;
		GF_VideoSurface video_src;
		GF_Window src_wnd, dst_wnd;

		if (!BackColor && !visual->offscreen) {
			if ( !(visual->compositor->init_flags & GF_VOUT_WINDOW_TRANSPARENT)) {
				BackColor = visual->compositor->back_color;
			}
		}

		data[0] = data[3] = data[6] = data[9] = GF_COL_R(BackColor);
		data[1] = data[4] = data[7] = data[10] = GF_COL_G(BackColor);
		data[2] = data[5] = data[8] = data[11] = GF_COL_B(BackColor);

		memset(&video_src, 0, sizeof(GF_VideoSurface));
		video_src.height = 2;
		video_src.width = 2;
		video_src.pitch_x = 0;
		video_src.pitch_y = 6;
		video_src.pixel_format = GF_PIXEL_RGB;
		video_src.video_buffer = data;

		src_wnd.x = src_wnd.y = 0;
		src_wnd.w = src_wnd.h = 1;
		if (rc) {
			if (visual->center_coords) {
				dst_wnd.x = rc->x + visual->width/2;
				dst_wnd.y = visual->height/2 - rc->y;
			} else {
				dst_wnd.x = rc->x;
				dst_wnd.y = rc->y - visual->height/2;
			}
			dst_wnd.w = rc->width;
			dst_wnd.h = rc->height;
		} else {
			dst_wnd.x = dst_wnd.y = 0;
			dst_wnd.w = visual->width;
			dst_wnd.h = visual->height;
		}

		e = visual->compositor->video_out->Blit(visual->compositor->video_out, &video_src, &src_wnd, &dst_wnd, 0);
		if (e==GF_OK) return;
	}

	visual_2d_clear_surface(visual, rc, BackColor, offscreen_clear);
}



void compositor_2d_release_video_access(GF_VisualManager *visual)
{
	GF_Compositor *compositor = visual->compositor;
	if (visual->is_attached) {
		visual->is_attached = GF_FALSE;
	}

#ifndef GPAC_DISABLE_3D
	if (compositor->hybrid_opengl) {
		compositor_2d_hybgl_flush_video(compositor, NULL);
		return;
	}
#endif //GPAC_DISABLE_3D

	if (compositor->hw_context) {
		compositor->video_out->LockOSContext(compositor->video_out, GF_FALSE);
		compositor->hw_context = NULL;
	} else if (compositor->hw_locked) {
		compositor->video_out->LockBackBuffer(compositor->video_out, &compositor->hw_surface, GF_FALSE);
		compositor->hw_locked = GF_FALSE;
	}
}

static void store_blit_times(GF_TextureHandler *txh, u32 push_time)
{
#ifndef GPAC_DISABLE_LOG
	u32 ck;
#endif

	push_time = gf_sys_clock() - push_time;
	txh->nb_frames ++;
	txh->upload_time += push_time;

#ifndef GPAC_DISABLE_LOG
	gf_mo_get_object_time(txh->stream, &ck);
	if (ck>txh->last_frame_time) {
		GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor2D] Bliting frame (CTS %d) %d ms too late\n", txh->last_frame_time, ck - txh->last_frame_time ));
	}

	GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPTIME, ("[2D Blitter] At %u Blit texture (CTS %u) %d ms after due date - blit in %d ms - average push time %d ms\n", ck, txh->last_frame_time, ck - txh->last_frame_time, push_time, txh->upload_time / txh->nb_frames));
#endif
}

Bool compositor_texture_rectangles(GF_VisualManager *visual, GF_TextureHandler *txh, GF_IRect *clip, GF_Rect *unclip, GF_Window *src, GF_Window *dst, Bool *disable_blit, Bool *has_scale)
{
	Fixed w_scale, h_scale, tmp;
	u32 output_width, output_height;
	GF_IRect clipped_final = *clip;
	GF_Rect final = *unclip;
	Bool use_blit;

	src->w = src->h = 0;
	dst->w = dst->h = 0;
	if (disable_blit) *disable_blit = GF_FALSE;
	if (has_scale) *has_scale = GF_FALSE;

	if (final.width<=0 || final.height <=0) return GF_FALSE;
	if (txh->width==0 || txh->height==0) return GF_FALSE;

	w_scale = final.width / txh->width;
	h_scale = final.height / txh->height;

	if ((w_scale != FIX_ONE) || (h_scale!=FIX_ONE)) {
		if (has_scale) *has_scale = GF_TRUE;
	}

	if (visual->offscreen) {
		output_width = visual->width;
		output_height = visual->height;
	} else {
		/*use entire video surface for un-centering coord system*/
		output_width = visual->compositor->vp_width;
		output_height = visual->compositor->vp_height;
	}
	/*take care of pixel rounding for odd width/height and make sure we strictly draw in the clipped bounds*/
	if (visual->center_coords) {
		clipped_final.x += output_width / 2;
		final.x += INT2FIX( output_width / 2 );

		clipped_final.y = output_height/ 2 - clipped_final.y;
		final.y = INT2FIX( output_height / 2) - final.y;

	} else {
		final.y -= final.height;
		clipped_final.y -= clipped_final.height;
	}

	/*make sure we lie in the final rect (this is needed for directdraw mode)*/
	if (clipped_final.x<0) {
		clipped_final.width += clipped_final.x;
		clipped_final.x = 0;
		if (clipped_final.width <= 0) return GF_FALSE;
	}
	if (clipped_final.y<0) {
		clipped_final.height += clipped_final.y;
		clipped_final.y = 0;
		if (clipped_final.height <= 0) return GF_FALSE;
	}
	if (clipped_final.x + clipped_final.width > (s32) output_width) {
		clipped_final.width = output_width - clipped_final.x;
		clipped_final.x = output_width - clipped_final.width;
	}
	if (clipped_final.y + clipped_final.height > (s32) output_height) {
		clipped_final.height = output_height - clipped_final.y;
		clipped_final.y = output_height - clipped_final.height;
	}
	/*needed in direct drawing since clipping is not performed*/
	if (clipped_final.width<=0 || clipped_final.height <=0)
		return GF_FALSE;

	if (clipped_final.width-1>= FIX2INT(final.width) ) clipped_final.width = FIX2INT(final.width);
	if (clipped_final.height-1>= FIX2INT(final.height) ) clipped_final.height = FIX2INT(final.height);

	/*set dest window*/
	dst->x = (u32) clipped_final.x;
	dst->y = (u32) clipped_final.y;
	dst->w = (u32) clipped_final.width;
	dst->h = (u32) clipped_final.height;

	if (!dst->w || !dst->h) return GF_FALSE;

#ifdef GPAC_FIXED_POINT
#define ROUND_FIX(_v)	\
	_v = FIX2INT(tmp);
#define CEILING(_v)	\
	_v = FIX2INT(tmp);	\
	if (INT2FIX(_v)!=tmp) _v++;
#else
#define ROUND_FIX(_v)	\
	_v = FIX2INT(tmp);	\
	tmp -= INT2FIX(_v);	\
	if (tmp>99*FIX_ONE/100) { _v++; tmp = 0; }	\
	if (ABS(tmp) > FIX_EPSILON) use_blit = 0;
#define CEILING(_v)	\
	_v = FIX2INT(tmp);	\
	tmp -= INT2FIX(_v);	\
	if (tmp>0) { _v++; tmp = 0; }	\
	if (ABS(tmp) > FIX_EPSILON) use_blit = 0;
#endif

	use_blit = GF_TRUE;

	if (txh->data && !txh->size && (txh->width==2) && (txh->height==2) ) {
		src->x = src->y = 0;
		src->w = 1;
		src->h = 1;
	} else {
		/*compute SRC window*/
		tmp = gf_divfix(INT2FIX(clipped_final.x) - final.x, w_scale);
		if (tmp<0) tmp=0;
		CEILING(src->x);

		tmp = gf_divfix(INT2FIX(clipped_final.y) - final.y, h_scale);
		if (tmp<0) tmp=0;
		CEILING(src->y);

		tmp = gf_divfix(INT2FIX(clip->width), w_scale);
		ROUND_FIX(src->w);

		tmp = gf_divfix(INT2FIX(clip->height), h_scale);
		ROUND_FIX(src->h);


		if (src->w>txh->width) src->w=txh->width;
		if (src->h>txh->height) src->h=txh->height;

		if (!src->w || !src->h) return GF_FALSE;

		/*make sure we lie in src bounds*/
		if (src->x + src->w>txh->width) src->w = txh->width - src->x;
		if (src->y + src->h>txh->height) src->h = txh->height - src->y;
	}
#undef ROUND_FIX

	if (disable_blit) *disable_blit = use_blit ? GF_FALSE : GF_TRUE;
	return GF_TRUE;
}

static Bool compositor_2d_draw_bitmap_ex(GF_VisualManager *visual, GF_TextureHandler *txh, DrawableContext *ctx, GF_IRect *clip, GF_Rect *unclip, u8 alpha, GF_TraverseState *tr_state, Bool force_soft_blt)
{
	GF_VideoSurface video_src;
	GF_Err e;
	Bool use_soft_stretch, use_blit, flush_video, is_attached, has_scale;
	u32 overlay_type;
	GF_Window src_wnd, dst_wnd;
	u32 output_width, output_height, hw_caps;


	if (!txh->data) return GF_TRUE;

	if (!visual->compositor->has_size_info && !(visual->compositor->msg_type & GF_SR_CFG_OVERRIDE_SIZE)
	        && (visual->compositor->override_size_flags & 1)
	        && !(visual->compositor->override_size_flags & 2)
	   ) {
		if ( (visual->compositor->scene_width < txh->width)
		        || (visual->compositor->scene_height < txh->height)) {
			visual->compositor->scene_width = txh->width;
			visual->compositor->scene_height = txh->height;
			visual->compositor->msg_type |= GF_SR_CFG_OVERRIDE_SIZE;
			return GF_TRUE;
		}
	}

	if (!compositor_texture_rectangles(visual, txh, clip, unclip, &src_wnd, &dst_wnd, &use_blit, &has_scale)) return GF_TRUE;

	//blitter is disabled
	if (txh->flags & GF_SR_TEXTURE_DISABLE_BLIT) return GF_FALSE;

	/*can we use hardware blitter ?*/
	hw_caps = visual->compositor->video_out->hw_caps;
	overlay_type = 0;
	flush_video = GF_FALSE;
	use_soft_stretch = GF_TRUE;

	output_width = visual->compositor->vp_width;
	output_height = visual->compositor->vp_height;

	if (visual->compositor->disable_hardware_blit) force_soft_blt = GF_TRUE;

	if (!force_soft_blt) {

		/*avoid partial redraw that don't come close to src pixels with the bliter, this leads to ugly artefacts -
		fall back to rasterizer*/
//		if (!(ctx->flags & CTX_TEXTURE_DIRTY) && !use_blit && (src_wnd.x || src_wnd.y) )
//			return 0;

		switch (txh->pixelformat) {
		case GF_PIXEL_RGB:
		case GF_PIXEL_BGR:
		case GF_PIXEL_RGBD:
//		case GF_PIXEL_RGB_555:
//		case GF_PIXEL_RGB_565:
			if ((alpha==0xFF) && (hw_caps & GF_VIDEO_HW_HAS_RGB)) {
				use_soft_stretch = GF_FALSE;
			}
			else if ((alpha!=0xFF) && (hw_caps & GF_VIDEO_HW_HAS_RGBA)) {
				use_soft_stretch = GF_FALSE;
			}
			break;
		case GF_PIXEL_ARGB:
		case GF_PIXEL_RGBA:
		case GF_PIXEL_RGBDS:
			if (hw_caps & GF_VIDEO_HW_HAS_RGBA)
				use_soft_stretch = GF_FALSE;
			break;
		default:
			if (gf_pixel_fmt_is_yuv(txh->pixelformat)) {
				if (hw_caps & GF_VIDEO_HW_HAS_YUV) use_soft_stretch = GF_FALSE;
				else if (hw_caps & GF_VIDEO_HW_HAS_YUV_OVERLAY) overlay_type = 1;
			}
			break;
		}
		/*disable based on settings*/
		if (!visual->compositor->yuvhw
		        || (ctx->col_mat || !visual->compositor->video_out->Blit)
		   ) {
			use_soft_stretch = GF_TRUE;
			overlay_type = 0;
		}
		if (!visual->compositor->blitp && ((src_wnd.w!=txh->width) || (src_wnd.h!=txh->height) )) {
			use_soft_stretch = GF_TRUE;
		}

		/*disable HW color keying - not compatible with MPEG-4 MaterialKey*/
		if (tr_state->col_key) {
			use_soft_stretch = GF_TRUE;
			overlay_type = 0;
		}

		if (overlay_type) {
			/*no more than one overlay is supported at the current time*/
			if (visual->overlays) {
				ctx->drawable->flags &= ~DRAWABLE_IS_OVERLAY;
				overlay_type = 0;
			}
			/*direct draw or not last context: we must queue the overlay*/
			else if (tr_state->immediate_draw || (ctx->next && ctx->next->drawable)) {
				overlay_type = 2;
			}
			/*OK we can overlay this video - if full display, don't flush*/
			if (overlay_type==1) {
				if (dst_wnd.w==visual->compositor->display_width) flush_video = GF_FALSE;
				else if (dst_wnd.h==visual->compositor->display_height) flush_video = GF_FALSE;
				else flush_video = visual->has_modif;
			}
			/*if no color keying, we cannot queue the overlay*/
			else if (!visual->compositor->video_out->overlay_color_key) {
				overlay_type = 0;
			}
		}
	}

	if (has_scale && !(hw_caps & GF_VIDEO_HW_HAS_STRETCH) && !overlay_type) {
		use_soft_stretch = GF_TRUE;
	}

	memset(&video_src, 0, sizeof(GF_VideoSurface));
	video_src.height = txh->height;
	video_src.width = txh->width;
	video_src.pitch_x = 0;
	if (!txh->stride)
		gf_pixel_get_size_info(txh->pixelformat, txh->width, txh->height, NULL, &txh->stride, NULL, NULL, NULL);
	video_src.pitch_y = txh->stride;
	video_src.pixel_format = txh->pixelformat;
#ifdef GF_SR_USE_DEPTH
	if (txh->pixelformat==GF_PIXEL_YUVD) video_src.pixel_format = GF_PIXEL_YUV;
#endif
	video_src.video_buffer = txh->data;
	if (txh->frame_ifce) {
		u32 stride;
		if (!txh->frame_ifce->get_plane) return GF_FALSE;

		e = txh->frame_ifce->get_plane(txh->frame_ifce, 0, (const u8 **) &video_src.video_buffer, &video_src.pitch_y);
		if (e) return GF_FALSE;
		txh->frame_ifce->get_plane(txh->frame_ifce, 1, (const u8 **) video_src.u_ptr, &stride);
		txh->frame_ifce->get_plane(txh->frame_ifce, 2, (const u8 **) video_src.v_ptr, &stride);
	}
	video_src.global_alpha = alpha;

	//overlay queuing
	if (overlay_type==2) {
		GF_IRect o_rc;
		GF_OverlayStack *ol, *first;

		/*queue overlay in order*/
		GF_SAFEALLOC(ol, GF_OverlayStack);
		if (!ol) {
			return GF_FALSE;
		}
		ol->ctx = ctx;
		ol->dst = dst_wnd;
		ol->src = src_wnd;
		first = visual->overlays;
		if (first) {
			while (first->next) first = first->next;
			first->next = ol;
		} else {
			visual->overlays = ol;
		}

		if (visual->center_coords) {
			o_rc.x = dst_wnd.x - output_width/2;
			o_rc.y = output_height/2- dst_wnd.y;
		} else {
			o_rc.x = dst_wnd.x;
			o_rc.y = dst_wnd.y + dst_wnd.h;
		}

		o_rc.width = dst_wnd.w;
		o_rc.height = dst_wnd.h;
		visual->ClearSurface(visual, &o_rc, visual->compositor->video_out->overlay_color_key, GF_FALSE);
		visual->has_overlays = GF_TRUE;
		/*mark drawable as overlay*/
		ctx->drawable->flags |= DRAWABLE_IS_OVERLAY;

		/*prevents this context from being removed in direct draw mode by requesting a new one
		but not allocating it*/
		if (tr_state->immediate_draw)
			visual_2d_get_drawable_context(visual);

		return GF_TRUE;
	}

	//will pause clock if first HW load
	gf_sc_texture_check_pause_on_first_load(txh, GF_TRUE);

	if (overlay_type) {
		u32 push_time;

		/*top level overlay*/
		if (flush_video) {
			GF_Window rc;
			rc.x = rc.y = 0;
			rc.w = visual->compositor->display_width;
			rc.h = visual->compositor->display_height;

			visual_2d_release_raster(visual);
			visual->compositor->video_out->Flush(visual->compositor->video_out, &rc);
			visual_2d_init_raster(visual);
		}
		visual->compositor->skip_flush = 1;

		push_time = gf_sys_clock();
		e = visual->compositor->video_out->Blit(visual->compositor->video_out, &video_src, &src_wnd, &dst_wnd, 1);

		if (!e) {
			store_blit_times(txh, push_time);
			/*mark drawable as overlay*/
			ctx->drawable->flags |= DRAWABLE_IS_OVERLAY;
			visual->has_overlays = GF_TRUE;

			//will resume clock if first HW load
			gf_sc_texture_check_pause_on_first_load(txh, GF_FALSE);
			return GF_TRUE;
		}
		GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor2D] Error during overlay blit - trying with soft one\n"));
		visual->compositor->skip_flush = GF_FALSE;
	}

	/*most graphic cards can't perform bliting on locked surface - force unlock by releasing the hardware*/
	is_attached = visual->is_attached;
	if (is_attached) visual_2d_release_raster(visual);

	if (!use_soft_stretch) {
		u32 push_time = gf_sys_clock();
		e = visual->compositor->video_out->Blit(visual->compositor->video_out, &video_src, &src_wnd, &dst_wnd, 0);

		/*HW pb, try soft*/
		if (e) {
			use_soft_stretch = GF_TRUE;
			if (visual->compositor->video_memory==1) {
				GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Compositor2D] Error during hardware blit - will use soft one\n"));
				visual->compositor->video_memory = 2;
			}
			/*force a reconfigure of video output*/
			else if (visual->compositor->video_memory!=2) {
				GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor2D] Reconfiguring video output to use video memory\n"));
				visual->compositor->request_video_memory = GF_TRUE;
				visual->compositor->root_visual_setup = GF_FALSE;
				gf_sc_next_frame_state(visual->compositor, GF_SC_DRAW_FRAME);
			}
		} else {
			store_blit_times(txh, push_time);
		}
	}

	//will resume clock if first HW load
	gf_sc_texture_check_pause_on_first_load(txh, GF_FALSE);

	if (use_soft_stretch) {
		GF_VideoSurface backbuffer;
		if (!visual->compositor->softblt) {
			if (is_attached) visual_2d_init_raster(visual);
			txh->flags |= GF_SR_TEXTURE_DISABLE_BLIT;
			return GF_FALSE;
		}
		if (!visual->compositor->video_out->LockBackBuffer)
			e = GF_NOT_SUPPORTED;
		else
			e = visual->compositor->video_out->LockBackBuffer(visual->compositor->video_out, &backbuffer, GF_TRUE);

		if (!e) {
			u32 push_time = gf_sys_clock();
#ifndef GPAC_DISABLE_COMPOSITOR
			e = gf_stretch_bits(&backbuffer, &video_src, &dst_wnd, &src_wnd, alpha, GF_FALSE, tr_state->col_key, ctx->col_mat);
#else
			e = GF_NOT_SUPPORTED;
#endif
			store_blit_times(txh, push_time);
			visual->compositor->video_out->LockBackBuffer(visual->compositor->video_out, &backbuffer, GF_FALSE);
			if (e) {
				if (e != GF_NOT_SUPPORTED) {
					GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Compositor2D] Cannot soft blit surface (error %s) - will try using software rasterizer\n", gf_error_to_string(e) ));
					visual->compositor->last_error = e;
				}
				if (is_attached) visual_2d_init_raster(visual);
				txh->flags |= GF_SR_TEXTURE_DISABLE_BLIT;
				return GF_FALSE;
			}
		} else {
			if (e != GF_NOT_SUPPORTED) {
				GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor2D] Cannot lock back buffer - Error %s\n", gf_error_to_string(e) ));
				visual->compositor->last_error = e;
			}
			if (is_attached) visual_2d_init_raster(visual);
			return GF_FALSE;
		}
		if (!visual->compositor->video_memory) {
			GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor2D] Reconfiguring video output to use video memory\n"));
			visual->compositor->video_memory = 1;
			visual->compositor->root_visual_setup = GF_FALSE;
			gf_sc_next_frame_state(visual->compositor, GF_SC_DRAW_FRAME);
		}
	}
	visual->has_modif = GF_TRUE;
	if (is_attached) visual_2d_init_raster(visual);
	return GF_TRUE;
}



Bool compositor_2d_draw_bitmap(GF_VisualManager *visual, GF_TraverseState *tr_state, DrawableContext *ctx)
{
	u8 alpha = 0xFF;

	if (!ctx->aspect.fill_texture) return GF_TRUE;
	if ((ctx->aspect.fill_texture == visual->compositor->passthrough_txh) && visual->compositor->passthrough_inplace)
		return GF_TRUE;
	/*check if texture is ready - if not pretend we drew it*/
	if (!ctx->aspect.fill_texture->data) return GF_TRUE;
	if (ctx->transform.m[0]<0) return GF_FALSE;
	/*check if the <0 value is due to a flip in the scene description or
	due to bifs<->svg... context switching*/
	if (ctx->transform.m[4]<0) {
		if (!(ctx->flags & CTX_FLIPED_COORDS)) return GF_FALSE;
	} else {
		if (ctx->flags & CTX_FLIPED_COORDS) return GF_FALSE;
	}
	if (ctx->transform.m[1] || ctx->transform.m[3]) return GF_FALSE;
#ifndef GPAC_DISABLE_VRML
	if ((ctx->flags & CTX_HAS_APPEARANCE) && ctx->appear && ((M_Appearance*)ctx->appear)->textureTransform)
		return GF_FALSE;
#endif

	alpha = GF_COL_A(ctx->aspect.fill_color);
	/*THIS IS A HACK, will not work when setting filled=0, transparency and XLineProps*/
	if (!alpha) alpha = GF_COL_A(ctx->aspect.line_color);

	if (!alpha) return GF_TRUE;

	switch (ctx->aspect.fill_texture->pixelformat) {
	case GF_PIXEL_YUVD:
	case GF_PIXEL_RGBD:
	case GF_PIXEL_RGBDS:
#ifndef GPAC_DISABLE_3D
		/*using OpenGL to render depth images*/
		if (visual->compositor->depth_gl_type) {
			gf_sc_set_option(visual->compositor, GF_OPT_USE_OPENGL, 2);
			return GF_TRUE;
		}
#endif
		//fallthrought
	default:
		break;
	}

	/*direct drawing, no clippers */
	if (tr_state->immediate_draw) {
		if (visual->compositor->video_out->BlitTexture) {
			if (! visual->compositor->video_out->BlitTexture(visual->compositor->video_out, ctx->aspect.fill_texture, &ctx->transform, &ctx->bi->clip, alpha, tr_state->col_key
#ifdef GF_SR_USE_DEPTH
			        , ctx->depth_offset, ctx->depth_gain
#else
			        , 0, 0
#endif
			                                                ))
				return GF_FALSE;
		} else {
			if (!compositor_2d_draw_bitmap_ex(visual, ctx->aspect.fill_texture, ctx, &ctx->bi->clip, &ctx->bi->unclip, alpha, tr_state, GF_FALSE))
				return GF_FALSE;
		}
	}
	/*draw bitmap for all dirty rects*/
	else {
		u32 i;
		GF_IRect clip;
		for (i=0; i<tr_state->visual->to_redraw.count; i++) {
			/*there's an opaque region above, don't draw*/
#ifdef TRACK_OPAQUE_REGIONS
			if (tr_state->visual->draw_node_index < tr_state->visual->to_redraw.list[i].opaque_node_index) continue;
#endif
			clip = ctx->bi->clip;
			gf_irect_intersect(&clip, &tr_state->visual->to_redraw.list[i].rect);
			if (clip.width && clip.height) {
				if (visual->compositor->video_out->BlitTexture) {
					if (!visual->compositor->video_out->BlitTexture(visual->compositor->video_out, ctx->aspect.fill_texture, &ctx->transform, &ctx->bi->clip, alpha, tr_state->col_key
#ifdef GF_SR_USE_DEPTH
					        , ctx->depth_offset, ctx->depth_gain
#else
					        , 0, 0
#endif
					                                               ))
						return GF_FALSE;
				} else if (!compositor_2d_draw_bitmap_ex(visual, ctx->aspect.fill_texture, ctx, &clip, &ctx->bi->unclip, alpha, tr_state, GF_FALSE)) {
					return GF_FALSE;
				}
			}
		}
	}
	ctx->aspect.fill_texture->flags |= GF_SR_TEXTURE_USED;
	return GF_TRUE;
}


GF_Err compositor_2d_set_aspect_ratio(GF_Compositor *compositor)
{
	u32 old_vp_width, old_vp_height;
	Bool changed = GF_FALSE;
	Double ratio;
	GF_Event evt;
	GF_Err e;
	Fixed scaleX, scaleY;

	compositor->output_width = compositor->scene_width;
	compositor->output_height = compositor->scene_height;
	compositor->vp_x = compositor->vp_y = 0;
	scaleX = scaleY = FIX_ONE;

	old_vp_width = compositor->vp_width;
	old_vp_height = compositor->vp_height;

	/*force complete clean*/
	compositor->traverse_state->invalidate_all = GF_TRUE;

	if (!compositor->has_size_info && !(compositor->override_size_flags & 2) ) {
		compositor->output_width = compositor->display_width;
		compositor->output_height = compositor->display_height;
		compositor->vp_width = compositor->visual->width = compositor->output_width;
		compositor->vp_height = compositor->visual->height = compositor->output_height;
	} else {
		if (compositor->rotate_mode % 2) {
			compositor->vp_height = compositor->display_width;
			compositor->vp_width = compositor->display_height;
		} else {
			compositor->vp_width = compositor->display_width;
			compositor->vp_height = compositor->display_height;
		}

		switch (compositor->aspect_ratio) {
		case GF_ASPECT_RATIO_FILL_SCREEN:
			break;
		case GF_ASPECT_RATIO_16_9:
			compositor->vp_width = compositor->display_width;
			compositor->vp_height = 9 * compositor->display_width / 16;
			if (compositor->vp_height>compositor->display_height) {
				compositor->vp_height = compositor->display_height;
				compositor->vp_width = 16 * compositor->display_height / 9;
			}
			break;
		case GF_ASPECT_RATIO_4_3:
			compositor->vp_width = compositor->display_width;
			compositor->vp_height = 3 * compositor->display_width / 4;
			if (compositor->vp_height>compositor->display_height) {
				compositor->vp_height = compositor->display_height;
				compositor->vp_width = 4 * compositor->display_height / 3;
			}
			break;
		default:
			ratio = compositor->scene_height;
			ratio /= compositor->scene_width;
			if (compositor->vp_width * ratio > compositor->vp_height) {
				compositor->vp_width = compositor->vp_height * compositor->scene_width;
				compositor->vp_width /= compositor->scene_height;
			}
			else {
				compositor->vp_height = compositor->vp_width * compositor->scene_height;
				compositor->vp_height /= compositor->scene_width;
			}
			break;
		}
		compositor->vp_x = (compositor->display_width - compositor->vp_width) / 2;
		compositor->vp_y = (compositor->display_height - compositor->vp_height) / 2;

		scaleX = gf_divfix(INT2FIX(compositor->vp_width), INT2FIX(compositor->scene_width));
		if (!scaleX) scaleX = FIX_ONE;
		scaleY = gf_divfix(INT2FIX(compositor->vp_height), INT2FIX(compositor->scene_height));
		if (!scaleY) scaleY = FIX_ONE;

		if (!compositor->sz) {
			compositor->output_width = compositor->scene_width;
			compositor->output_height = compositor->scene_height;
			compositor->vp_width = FIX2INT(gf_divfix(INT2FIX(compositor->display_width), scaleX));
			compositor->vp_height = FIX2INT(gf_divfix(INT2FIX(compositor->display_height), scaleY));

			compositor->vp_x = (compositor->vp_width - compositor->output_width) / 2;
			compositor->vp_y = (compositor->vp_height - compositor->output_height) / 2;

			scaleX = scaleY = FIX_ONE;
		} else {
			compositor->output_width = compositor->display_width;
			compositor->output_height = compositor->display_height;
			compositor->vp_width = compositor->display_width;
			compositor->vp_height = compositor->display_height;
		}
		compositor->visual->width = compositor->output_width;
		compositor->visual->height = compositor->output_height;
	}

	/*resize hardware surface*/
	memset(&evt, 0, sizeof(GF_Event));
	evt.type = GF_EVENT_VIDEO_SETUP;
	evt.setup.width = compositor->vp_width;
	evt.setup.height = compositor->vp_height;
	evt.setup.use_opengl = GF_FALSE;
	evt.setup.disable_vsync = compositor->bench_mode ? GF_TRUE : GF_FALSE;
	/*copy over settings*/
	evt.setup.system_memory = compositor->video_memory ? GF_FALSE : GF_TRUE;
	if (compositor->request_video_memory) evt.setup.system_memory = GF_FALSE;
	compositor->request_video_memory = GF_FALSE;

#ifndef GPAC_DISABLE_3D
	if (compositor->hybrid_opengl) {
		evt.setup.use_opengl = GF_TRUE;
		evt.setup.system_memory = GF_FALSE;
		evt.setup.back_buffer = GF_TRUE;
	}
#endif

	if (compositor->was_system_memory != evt.setup.system_memory) changed = GF_TRUE;
	else if (old_vp_width != compositor->vp_width) changed = GF_TRUE;
	else if (old_vp_height != compositor->vp_height) changed = GF_TRUE;
	else if (compositor->is_opengl != evt.setup.use_opengl) changed = GF_TRUE;


	if (changed) {
		GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor2D] Reconfiguring display size %d x %d - opengl %s - use %s memory\n", evt.setup.width, evt.setup.height,
		                                     evt.setup.use_opengl ? "yes" : "no", evt.setup.system_memory ? "systems" : "video"
		                                    ));

		e = compositor->video_out->ProcessEvent(compositor->video_out, &evt);
		if (e) {
#ifndef GPAC_DISABLE_3D
			if (!compositor->hybrid_opengl) {
				compositor->hybrid_opengl = GF_TRUE;
				GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor2D] Failed to configure 2D output (%s) - retrying in OpenGL mode\n", gf_error_to_string(e) ));
				return compositor_2d_set_aspect_ratio(compositor);
			}
#endif
			compositor->video_setup_failed = GF_TRUE;
			memset(&evt, 0, sizeof(GF_Event));
			evt.type = GF_EVENT_QUIT;
			evt.message.error = e;
			evt.message.message = "Cannot setup video output";
			gf_sc_send_event(compositor, &evt);
			return e;
		}

		compositor->is_opengl = evt.setup.use_opengl;
		compositor->was_system_memory = evt.setup.system_memory;

		if (evt.setup.use_opengl) {
			gf_opengl_init();
		}
	}
	if (compositor->has_size_info) {
		compositor->traverse_state->vp_size.x = INT2FIX(compositor->scene_width);
		compositor->traverse_state->vp_size.y = INT2FIX(compositor->scene_height);
	} else {
		compositor->traverse_state->vp_size.x = INT2FIX(compositor->output_width);
		compositor->traverse_state->vp_size.y = INT2FIX(compositor->output_height);
	}
	GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor2D] Reconfigured display size %d x %d done\n", evt.setup.width, evt.setup.height));

	/*set scale factor*/
	compositor_set_ar_scale(compositor, scaleX, scaleY);
	return GF_OK;
}

void compositor_send_resize_event(GF_Compositor *compositor, GF_SceneGraph *subscene, Fixed old_z, Fixed old_tx, Fixed old_ty, Bool is_resize)
{
#ifndef GPAC_DISABLE_SVG
	GF_DOM_Event evt;
	u32 i;
	GF_SceneGraph *scene = (subscene ? subscene : compositor->scene);
	GF_Node *root = gf_sg_get_root_node(scene);
	/*if root node is not DOM, sent a resize event (for VRML/BIFS). Otherwise this must be handled
	by the composition code of the node*/
	if (!root || (gf_node_get_tag(root) > GF_NODE_RANGE_LAST_VRML) )
		return;

	memset(&evt, 0, sizeof(GF_DOM_Event));
	evt.prev_scale = compositor->scale_x*old_z;
	evt.new_scale = compositor->scale_x*compositor->zoom;
	evt.bubbles = 1;

	if (is_resize) {
		evt.type = GF_EVENT_RESIZE;
		if (subscene == NULL) {
			evt.screen_rect.width = INT2FIX(compositor->display_width);
			evt.screen_rect.height = INT2FIX(compositor->display_height);
		} else {
			u32 w, h;
			gf_sg_get_scene_size_info(scene, &w, &h);
			evt.screen_rect.width = INT2FIX(w);
			evt.screen_rect.height = INT2FIX(h);
		}
	} else if (evt.prev_scale == evt.new_scale) {
		/*cannot get params for scroll events*/
		evt.type = GF_EVENT_SCROLL;
	} else {
		evt.screen_rect.x = INT2FIX(compositor->vp_x);
		evt.screen_rect.y = INT2FIX(compositor->vp_y);
		evt.screen_rect.width = INT2FIX(compositor->output_width);
		evt.screen_rect.height = INT2FIX(compositor->output_height);
		evt.prev_translate.x = old_tx;
		evt.prev_translate.y = old_ty;
		evt.new_translate.x = compositor->trans_x;
		evt.new_translate.y = compositor->trans_y;
		evt.type = GF_EVENT_ZOOM;
		evt.bubbles = 0;
	}
	gf_dom_event_fire(gf_sg_get_root_node(scene), &evt);

	i=0;
	while ((scene = (GF_SceneGraph*)gf_list_enum(compositor->extra_scenes, &i))) {
		gf_dom_event_fire(gf_sg_get_root_node(scene), &evt);
	}

#endif
}
void compositor_2d_set_user_transform(GF_Compositor *compositor, Fixed zoom, Fixed tx, Fixed ty, Bool is_resize)
{
	Fixed ratio;
	Fixed old_tx, old_ty, old_z;

	gf_sc_lock(compositor, GF_TRUE);
	old_tx = tx;
	old_ty = ty;
	old_z = compositor->zoom;

	if (zoom <= 0) zoom = FIX_ONE/1000;
	compositor->trans_x = tx;
	compositor->trans_y = ty;

	if (zoom != compositor->zoom) {
		ratio = gf_divfix(zoom, compositor->zoom);
		compositor->trans_x = gf_mulfix(compositor->trans_x, ratio);
		compositor->trans_y = gf_mulfix(compositor->trans_y, ratio);
		compositor->zoom = zoom;
		compositor->zoom_changed = GF_TRUE;

		/*recenter visual*/
		if (!compositor->visual->center_coords) {
			Fixed c_x, c_y, nc_x, nc_y;
			c_x = INT2FIX(compositor->display_width/2);
			c_y = INT2FIX(compositor->display_height/2);
			nc_x = gf_mulfix(c_x, ratio);
			nc_y = gf_mulfix(c_y, ratio);
			compositor->trans_x -= (nc_x-c_x);
			compositor->trans_y -= (nc_y-c_y);
		}
	}
	gf_mx2d_init(compositor->traverse_state->transform);

	switch (compositor->rotate_mode) {
	case 1:
		gf_mx2d_add_rotation(&compositor->traverse_state->transform, 0, 0, -GF_PI/2);
		break;
	case 2:
		gf_mx2d_add_scale(&compositor->traverse_state->transform, -1, -1);
		break;
	case 3:
		gf_mx2d_add_rotation(&compositor->traverse_state->transform, 0, 0, GF_PI/2);
		break;
	}

	gf_mx2d_add_scale(&compositor->traverse_state->transform, gf_mulfix(compositor->zoom,compositor->scale_x), gf_mulfix(compositor->zoom,compositor->scale_y));

	gf_mx2d_add_translation(&compositor->traverse_state->transform, compositor->trans_x, compositor->trans_y);
	if (compositor->rotation) gf_mx2d_add_rotation(&compositor->traverse_state->transform, 0, 0, compositor->rotation);

	if (!compositor->visual->center_coords) {
		gf_mx2d_add_translation(&compositor->traverse_state->transform, INT2FIX(compositor->vp_x), INT2FIX(compositor->vp_y));
	}

	GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor2D] Changing Zoom (%g) and Pan (%g %g)\n", FIX2FLT(compositor->zoom), FIX2FLT(compositor->trans_x) , FIX2FLT(compositor->trans_y)));


	gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME);
	compositor->traverse_state->invalidate_all = GF_TRUE;

	/*for zoom&pan, send the event right away. For resize/scroll, wait for the frame to be drawn before sending it
	otherwise viewport info of SVG nodes won't be correct*/
	if (!is_resize) compositor_send_resize_event(compositor, NULL, old_z, old_tx, old_ty, GF_FALSE);
	gf_sc_lock(compositor, GF_FALSE);
}



GF_Rect compositor_2d_update_clipper(GF_TraverseState *tr_state, GF_Rect this_clip, Bool *need_restore, GF_Rect *original, Bool for_layer)
{
	GF_Rect clip, orig;
	if (for_layer) {
		orig = tr_state->layer_clipper;
		*need_restore = tr_state->has_layer_clip;
	} else {
		orig = tr_state->clipper;
		*need_restore = tr_state->has_clip;
	}
	*original = orig;

	clip = this_clip;
	if (*need_restore) {
#ifndef GPAC_DISABLE_3D
		if (tr_state->visual->type_3d) {
			GF_Matrix mx;
			gf_mx_copy(mx, tr_state->model_matrix);
			gf_mx_inverse(&mx);
			gf_mx_apply_rect(&mx, &orig);
			gf_mx_apply_rect(&tr_state->layer_matrix, &orig);
		} else
#endif
		{
			GF_Matrix2D mx2d;
			gf_mx2d_copy(mx2d, tr_state->transform);
			gf_mx2d_inverse(&mx2d);
			gf_mx2d_apply_rect(&mx2d, &orig);
		}

		if (clip.x < orig.x) {
			clip.width -= (orig.x - clip.x);
			clip.x = orig.x;
		}
		if (clip.x + clip.width > orig.x + orig.width) {
			clip.width = orig.x + orig.width - clip.x;
		}
		if (clip.y > orig.y) {
			clip.height -= (clip.y - orig.y);
			clip.y = orig.y;
		}
		if (clip.y - clip.height < orig.y - orig.height) {
			clip.height = clip.y - orig.y + orig.height;
		}
	}
	if (for_layer) {
		tr_state->layer_clipper = clip;
		tr_state->has_layer_clip = GF_TRUE;
#ifndef GPAC_DISABLE_3D
		if (tr_state->visual->type_3d) {
			gf_mx_copy(tr_state->layer_matrix, tr_state->model_matrix);
		}
#endif
	} else {
		tr_state->clipper = clip;
#ifndef GPAC_DISABLE_3D
		if (tr_state->visual->type_3d) {
			/*retranslate to world coords*/
			gf_mx_apply_rect(&tr_state->model_matrix, &tr_state->clipper);
			/*if 2D, also update with user zoom and translation*/
			if (!tr_state->camera->is_3D)
				gf_mx_apply_rect(&tr_state->camera->modelview, &tr_state->clipper);
		} else
#endif

			gf_mx2d_apply_rect(&tr_state->transform, &tr_state->clipper);

		tr_state->has_clip = GF_TRUE;
	}
	return clip;
}


/*overlay management*/
Bool visual_2d_overlaps_overlay(GF_VisualManager *visual, DrawableContext *ctx, GF_TraverseState *tr_state)
{
	u32 res = 0;
	GF_OverlayStack *ol;
	GF_Compositor *compositor = visual->compositor;
	if (compositor->visual != visual) return GF_FALSE;

	ol = visual->overlays;
	while (ol) {
		u32 i;
		GF_IRect clip;
		if (ctx == ol->ctx) {
			ol = ol->next;
			continue;
		}
		clip = ctx->bi->clip;
		if (!ol->ra.count && !gf_irect_overlaps(&ol->ctx->bi->clip, &clip)) {
			ol = ol->next;
			continue;
		}
		/*check previsously drawn areas*/
		for (i=0; i<ol->ra.count; i++) {
			/*we have drawn something here, don't draw*/
			if (gf_irect_inside(&ol->ra.list[i].rect, &clip))
				break;
		}
		res++;
		if (i<ol->ra.count) {
			ol = ol->next;
			continue;
		}

		/*note that we add the entire cliper, not the intersection with the overlay one. This is a simple way
		to handle the case where Drawble2 overlaps Drawable1 overlaps Overlay but Drawable2 doesn't overlaps Overlay
		by adding the entire drawable cliper, we will postpone drawing of all interconnected regions touching the overlay*/
		ra_union_rect(&ol->ra, &clip);
		ol = ol->next;
	}
	return res ? GF_TRUE : GF_FALSE;
}

void visual_2d_flush_overlay_areas(GF_VisualManager *visual, GF_TraverseState *tr_state)
{
	DrawableContext *ctx;
	GF_OverlayStack *ol;
	GF_Compositor *compositor = visual->compositor;
	if (compositor->visual != visual) return;

	/*draw all overlays*/
	tr_state->traversing_mode = TRAVERSE_DRAW_2D;
	ol = visual->overlays;
	while (ol) {
		u32 i;
		Bool needs_draw = GF_TRUE;
		GF_IRect the_clip, vid_clip;

		ra_refresh(&ol->ra);

		for (i=0; i<ol->ra.count; i++) {
			the_clip = ol->ra.list[i].rect;

			/*draw all objects above this overlay*/
			ctx = ol->ctx->next;
			while (ctx && ctx->drawable) {
				if (gf_irect_overlaps(&ctx->bi->clip, &the_clip)) {
					GF_IRect prev_clip = ctx->bi->clip;

					if (needs_draw) {
						/*if first object above is not transparent and completely covers the overlay skip video redraw*/
						if ((ctx->flags & CTX_IS_TRANSPARENT) || !gf_irect_inside(&prev_clip, &the_clip)) {
							vid_clip = ol->ra.list[i].rect;
							gf_irect_intersect(&vid_clip, &ol->ctx->bi->clip);
							compositor_2d_draw_bitmap_ex(visual, ol->ctx->aspect.fill_texture, ol->ctx, &vid_clip, &ol->ctx->bi->unclip, 0xFF, tr_state, GF_TRUE);
						}
						needs_draw = GF_FALSE;
					}
					gf_irect_intersect(&ctx->bi->clip, &the_clip);
					tr_state->ctx = ctx;

					call_drawable_draw(ctx, tr_state, GF_FALSE);

					ctx->bi->clip = prev_clip;
				}
				ctx = ctx->next;
			}
		}
		ol = ol->next;
	}
}

void visual_2d_draw_overlays(GF_VisualManager *visual)
{
	GF_Err e;
	GF_TextureHandler *txh;
	GF_VideoSurface video_src;

	while (1) {
		GF_OverlayStack *ol = visual->overlays;
		if (!ol) return;
		visual->overlays = ol->next;

		txh = ol->ctx->aspect.fill_texture;
		memset(&video_src, 0, sizeof(GF_VideoSurface));
		video_src.height = txh->height;
		video_src.width = txh->width;
		video_src.pitch_x = 0;
		video_src.pitch_y = txh->stride;
		video_src.pixel_format = txh->pixelformat;
		video_src.video_buffer = txh->data;

		e = visual->compositor->video_out->Blit(visual->compositor->video_out, &video_src, &ol->src, &ol->dst, 2);
		if (e) GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Visual2D] Error %s during overlay update\n", gf_error_to_string(e) ));

		ra_del(&ol->ra);
		gf_free(ol);
	}
}

void compositor_2d_init_callbacks(GF_Compositor *compositor)
{
	compositor->visual->DrawBitmap = compositor_2d_draw_bitmap;
}

#endif //GPAC_DISABLE_COMPOSITOR
