
/*
 * CINELERRA
 * Copyright (C) 2009-2013 Adam Williams <broadcast at earthling dot net>
 * Copyright (C) 2003-2016 Cinelerra CV contributors
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include "asset.h"
#include "bchash.h"
#include "bcpbuffer.h"
#include "bcsignals.h"
#include "cache.h"
#include "clip.h"
#include "commonrender.h"
#include "edits.h"
#include "edl.h"
#include "edlsession.h"
#include "file.h"
#include "filexml.h"
#include "floatautos.h"
#include "mainerror.h"
#include "maskauto.h"
#include "maskautos.h"
#include "mwindow.h"
#include "overlayframe.h"
#include "patch.h"
#include "pluginarray.h"
#include "preferences.h"
#include "renderengine.h"
#include "sharedlocation.h"
#include "tracks.h"
#include "transition.h"
#include "transportque.h"
#include "units.h"
#include "vattachmentpoint.h"
#include "vdevicex11.h"
#include "vedit.h"
#include "vframe.h"
#include "videodevice.h"
#include "virtualvconsole.h"
#include "vmodule.h"
#include "vrender.h"
#include "vplugin.h"
#include "vtrack.h"
#include <string.h>
#include "maskengine.h"
#include "automation.h"

VModule::VModule(RenderEngine *renderengine,
	CommonRender *commonrender,
	PluginArray *plugin_array,
	Track *track)
 : Module(renderengine, commonrender, plugin_array, track)
{
	data_type = TRACK_VIDEO;
	overlay_temp = 0;
	input_temp = 0;
	transition_temp = 0;
	masker = 0;
}

VModule::~VModule()
{
	if( overlay_temp ) delete overlay_temp;
	if( input_temp ) delete input_temp;
	if( transition_temp ) delete transition_temp;
	delete masker;
}


AttachmentPoint* VModule::new_attachment(Plugin *plugin)
{
	return new VAttachmentPoint(renderengine, plugin);
}

int VModule::get_buffer_size()
{
	return 1;
}

CICache* VModule::get_cache()
{
	if( renderengine )
		return renderengine->get_vcache();
	else
		return cache;
}


int VModule::import_frame(VFrame *output, VEdit *current_edit,
	int64_t input_position, double frame_rate, int direction, int use_opengl)
{
	int64_t direction_position=0;
// Translation of edit
	float in_x=0.0, in_y=0.0, in_w=0.0, in_h=0.0;
	float out_x=0.0, out_y=0.0, out_w=0.0, out_h=0.0;
	int result = 0;
	const int debug = 0;
	double edl_rate = get_edl()->session->frame_rate;

	int64_t input_position_project = Units::to_int64(input_position *
		edl_rate / frame_rate + 0.001);

	if( !output ) printf("VModule::import_frame %d output=%p\n", __LINE__, output);
	//output->dump_params();

	if( debug ) printf("VModule::import_frame %d this=%p input_position=%lld direction=%d\n",
		__LINE__, this, (long long)input_position, direction);

// Convert to position corrected for direction
	direction_position = input_position;
	if( direction == PLAY_REVERSE ) {
		if( direction_position > 0 ) direction_position--;
		if( input_position_project > 0 ) input_position_project--;
	}
	if( !output ) printf("VModule::import_frame %d output=%p\n", __LINE__, output);

	VDeviceX11 *x11_device = 0;
	if( use_opengl ) {
		if( renderengine && renderengine->video ) {
			x11_device = (VDeviceX11*)renderengine->video->get_output_base();
			output->set_opengl_state(VFrame::RAM);
			if( !x11_device ) use_opengl = 0;
		}
	}

	if( !output ) printf("VModule::import_frame %d output=%p x11_device=%p nested_edl=%p\n",
		__LINE__, output, x11_device, nested_edl);

	if( debug ) printf("VModule::import_frame %d current_edit=%p\n",
		__LINE__, current_edit);

// Load frame into output

// Create objects for nested EDL
	if( current_edit && current_edit->nested_edl ) {
		int command;
		if( debug ) printf("VModule::import_frame %d nested_edl=%p current_edit->nested_edl=%p\n",
			__LINE__, nested_edl, current_edit->nested_edl);

// Convert requested direction to command
		if( renderengine->command->command == CURRENT_FRAME ||
		    renderengine->command->command == LAST_FRAME ) {
			command = renderengine->command->command;
		}
		else
		if( direction == PLAY_REVERSE ) {
			if( renderengine->command->single_frame() )
				command = SINGLE_FRAME_REWIND;
			else
				command = NORMAL_REWIND;
		}
		else {
			if( renderengine->command->single_frame() )
				command = SINGLE_FRAME_FWD;
			else
				command = NORMAL_FWD;
		}

		if( !nested_edl || nested_edl->id != current_edit->nested_edl->id ) {
			nested_edl = current_edit->nested_edl;
			if( nested_renderengine ) {
				delete nested_renderengine;
				nested_renderengine = 0;
			}
			if( !nested_command )
				nested_command = new TransportCommand(get_preferences());
			nested_command->command = command;
			nested_command->get_edl()->copy_all(nested_edl);
			nested_command->change_type = CHANGE_ALL;
			nested_command->realtime = renderengine->command->realtime;
			nested_renderengine = new RenderEngine(0, get_preferences(), 0, 1);
			nested_renderengine->set_vcache(get_cache());
			nested_renderengine->arm_command(nested_command);
		}
		else {

// Update nested command
			nested_renderengine->command->command = command;
			nested_command->realtime = renderengine->command->realtime;
		}

// Update nested video driver for opengl
		nested_renderengine->video = renderengine->video;
	}
	else {
		nested_edl = 0;
	}
	if( debug ) printf("VModule::import_frame %d\n", __LINE__);

	if( output ) {
		if( use_opengl )
			x11_device->clear_input(output);
		else
			output->clear_frame();
	}
	else
		printf("VModule::import_frame %d output=%p\n", __LINE__, output);

	if( current_edit &&
		(current_edit->asset ||
		(current_edit->nested_edl && nested_renderengine->vrender)) ) {
		File *file = 0;

//printf("VModule::import_frame %d cache=%p\n", __LINE__, get_cache());
		if( current_edit->asset ) {
			get_cache()->age();
			file = get_cache()->check_out(current_edit->asset, get_edl());
//			get_cache()->dump();
		}

// File found
		if( file || nested_edl ) {
			int64_t nested_position = 0;
// Source position going forward, in edl framerate
			int64_t pos = Units::to_int64((double)direction_position / frame_rate * edl_rate);
			int64_t len = pos - current_edit->startproject;
			FloatAutos *speed_autos = !track->has_speed() ? 0 :
				(FloatAutos*)track->automation->autos[AUTOMATION_SPEED];
			if( speed_autos && len > 0 )
				len = speed_autos->automation_integral(current_edit->startproject, len, PLAY_FORWARD);
			pos = current_edit->startsource + len;
// Make positions based on requested frame rate.
			int64_t position = Units::to_int64((double)pos * frame_rate / edl_rate);

			int64_t video_length;
			int asset_w, asset_h;
			if( file ) {
				asset_w = current_edit->asset->width;
				asset_h = current_edit->asset->height;
				video_length = file->get_video_length();
			}
			else {
				asset_w = nested_edl->session->output_w;
				asset_h = nested_edl->session->output_h;
				video_length = nested_edl->tracks->total_length();
			}

			VFrame *&input = commonrender ?
				((VRender*)commonrender)->input_temp : // Realtime playback
				input_temp ; // Menu effect
			VFrame::get_temp(input, asset_w, asset_h, get_edl()->session->color_model);

			int use_cache = renderengine->command->single_frame() ? 1 :
				renderengine->command->get_direction() == PLAY_REVERSE ? -1 : 0;

//			int use_asynchronous = !use_cache &&
//				renderengine &&
// Try to make rendering go faster.
// But converts some formats to YUV420, which may degrade input format.
////				renderengine->command->realtime &&
//				renderengine->get_edl()->session->video_asynchronous;

			if( file ) {
				if( debug ) printf("VModule::import_frame %d\n", __LINE__);
//				if( use_asynchronous )
//					file->start_video_decode_thread();
//				else
					file->stop_video_thread();

				VEdit *vnext = (VEdit *)current_edit->next;
				pos = Units::to_int64((double)input_position / frame_rate * edl_rate);
				if( renderengine->preferences->cache_transitions && !use_cache &&
// cache transitions, not caching and inside transition 
				    vnext && vnext->transition && vnext->transition->on &&
				    file->get_video_length() >= 0 && pos >= vnext->startproject &&
				    pos < vnext->startproject + vnext->transition->length ) {
					file->set_cache_frames(0);
					file->set_layer(current_edit->channel);
					VEdit *vnext = (VEdit *)current_edit->next;
					Track *track = current_edit->track;
					FloatAutos *speed_autos = (FloatAutos*)(track->has_speed() ?
						track->automation->autos[AUTOMATION_SPEED] : 0);
					int64_t end = vnext->startproject + vnext->transition->length;
					int first_frame = 1;
					int count = renderengine->preferences->cache_size /
						input->get_data_size() / 2;  // try to burn only 1/2 of cache
					while( !result && pos < end && count > 0 ) {
						int64_t curr_pos = pos - current_edit->startproject;
						if( curr_pos > 0 && speed_autos )
							curr_pos = speed_autos->automation_integral(
								current_edit->startproject, curr_pos, PLAY_FORWARD);
						curr_pos += current_edit->startsource;
						int64_t norm_pos = Units::to_int64((double)curr_pos *
							current_edit->asset->frame_rate / edl_rate);
						if( norm_pos < 0 || video_length < 0 )
							norm_pos = 0;
						else if( norm_pos >= video_length )
							norm_pos = video_length-1;
						if( first_frame ) {
							if( file->get_cache_frame(input, norm_pos) )
								break;  // if inside a cache run
							first_frame = 0;
							file->purge_cache(); // start new run
						}
						file->set_cache_frames(1);
						file->set_video_position(norm_pos, 0);
						result = file->read_frame(input);
						++pos;  --count;
					}
					use_cache = 1;
				}

				int64_t normalized_position = Units::to_int64((double)position *
					current_edit->asset->frame_rate / frame_rate);
				if( normalized_position < 0 || video_length < 0 )
					normalized_position = 0;
				else if( normalized_position >= video_length )
					normalized_position = video_length-1;
//printf("VModule::import_frame %d %lld %lld\n", __LINE__, position, normalized_position);
				file->set_layer(current_edit->channel);
				file->set_video_position(normalized_position, 0);
			}
			else {
				if( debug ) printf("VModule::import_frame %d\n", __LINE__);
// Get source position in nested frame rate in direction of playback.
				nested_position = Units::to_int64(position *
					nested_edl->session->frame_rate /
					frame_rate);
				if( direction == PLAY_REVERSE )
					nested_position++;
			}


// Auto scale if required
			if( output->get_params()->get("AUTOSCALE", 0) ) {
				float autoscale_w = output->get_params()->get("AUTOSCALE_W", 1024);
				float autoscale_h = output->get_params()->get("AUTOSCALE_H", 1024);
				float x_scale = autoscale_w / asset_w;
				float y_scale = autoscale_h / asset_h;

// Overriding camera
				in_x = 0;
				in_y = 0;
				in_w = asset_w;
				in_h = asset_h;

				if( x_scale < y_scale ) {
					out_w = in_w * x_scale;
					out_h = in_h * x_scale;
				}
				else {
					out_w = in_w * y_scale;
					out_h = in_h * y_scale;
				}

				out_x = track->track_w / 2 - out_w / 2;
				out_y = track->track_h / 2 - out_h / 2;
			}
			else {
// Apply camera
				((VTrack*)track)->calculate_input_transfer(asset_w,
					asset_h, input_position_project, direction,
					in_x, in_y, in_w, in_h,
					out_x, out_y, out_w, out_h);
			}

// printf("VModule::import_frame %d %f %d %f %d\n",
// __LINE__, in_w, asset_w, in_h, asset_h);

// file -> temp -> output
			if( !EQUIV(in_x, 0) ||
				!EQUIV(in_y, 0) ||
				!EQUIV(in_w, track->track_w) ||
				!EQUIV(in_h, track->track_h) ||
				!EQUIV(out_x, 0) ||
				!EQUIV(out_y, 0) ||
				!EQUIV(out_w, track->track_w) ||
				!EQUIV(out_h, track->track_h) ||
				!EQUIV(in_w, asset_w) ||
				!EQUIV(in_h, asset_h)) {
//printf("VModule::import_frame %d file -> temp -> output\n", __LINE__);
// Get temporary input buffer
				VFrame **input = commonrender ?  // Realtime playback
					&((VRender*)commonrender)->input_temp :
					&input_temp ; // Menu effect
				VFrame::get_temp(*input, asset_w, asset_h,
						get_edl()->session->color_model);
				(*input)->copy_stacks(output);
// file -> temp
// Cache for single frame, reverse playback
				if( file ) {
					if( debug ) printf("VModule::import_frame %d this=%p file=%s\n",
						__LINE__,
						this,
						current_edit->asset->path);
					if( use_cache )
						file->set_cache_frames(use_cache);
					result = file->read_frame((*input));
					if( use_cache )
						file->set_cache_frames(0);
					(*input)->set_opengl_state(VFrame::RAM);
				}
				else
				if( nested_edl ) {
// If the colormodels differ, change input to nested colormodel
					int nested_cmodel = nested_renderengine->get_edl()->session->color_model;
					int current_cmodel = output->get_color_model();
					int output_w = output->get_w();
					int output_h = output->get_h();
					VFrame *input2 = (*input);

					if( nested_cmodel != current_cmodel ) {
// If opengl, input -> input -> output
						if( use_opengl ) { }
						else {
// If software, input2 -> input -> output
// Use output as a temporary.
							input2 = output;
						}

						if( debug ) printf("VModule::import_frame %d this=%p nested_cmodel=%d\n",
							__LINE__,
							this,
							nested_cmodel);
						input2->dump();
						input2->reallocate(0, -1, 0, 0, 0,
							(*input)->get_w(), (*input)->get_h(),
							nested_cmodel, -1);
						input2->dump();
					}


					if( debug ) printf("VModule::import_frame %d this=%p nested_edl=%s input2=%p\n",
						__LINE__, this, nested_edl->path, input2);

					result = nested_renderengine->vrender->process_buffer(
						input2, nested_position, use_opengl);

					if( debug ) printf("VModule::import_frame %d this=%p nested_edl=%s\n",
						__LINE__,
						this,
						nested_edl->path);

					if( nested_cmodel != current_cmodel ) {
						if( debug ) printf("VModule::import_frame %d\n", __LINE__);
						if( use_opengl ) {
// Change colormodel in hardware.
							if( debug ) printf("VModule::import_frame %d\n", __LINE__);
							x11_device->convert_cmodel(input2,
								current_cmodel);

// The converted color model is now in hardware, so return the input2 buffer
// to the expected color model.
							input2->reallocate(0, -1, 0, 0, 0,
								(*input)->get_w(), (*input)->get_h(),
								current_cmodel, -1);
						}
						else {
// Transfer from input2 to input
if( debug ) printf("VModule::import_frame %d nested_cmodel=%d current_cmodel=%d input2=%p input=%p output=%p\n",
  __LINE__, nested_cmodel, current_cmodel, input2, (*input), output);
							BC_CModels::transfer((*input)->get_rows(), input2->get_rows(),
								0, 0, 0, 0, 0, 0,
								0, 0, input2->get_w(), input2->get_h(),
								0, 0, (*input)->get_w(), (*input)->get_h(),
								nested_cmodel, current_cmodel, 0,
								input2->get_w(), (*input)->get_w());
//printf("VModule::import_frame %d\n", __LINE__);

// input2 was the output buffer, so it must be restored
						input2->reallocate(0, -1, 0, 0, 0,
							output_w, output_h, current_cmodel, -1);
//printf("VModule::import_frame %d\n", __LINE__);
						}
					}

				}

// Find an overlayer object to perform the camera transformation
				OverlayFrame *overlayer = 0;

// OpenGL playback uses hardware
				if( use_opengl ) {
//printf("VModule::import_frame %d\n", __LINE__);
				}
				else if( commonrender ) {
// Realtime playback
					VRender *vrender = (VRender*)commonrender;
					overlayer = vrender->overlayer;
				}
				else {
// Menu effect
					if( !plugin_array )
						printf("VModule::import_frame neither plugin_array nor commonrender is defined.\n");
					if( !overlay_temp ) {
						overlay_temp = new OverlayFrame(plugin_array->mwindow->preferences->processors);
					}

					overlayer = overlay_temp;
				}
// printf("VModule::import_frame 1 %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f\n",
//   in_x, in_y, in_w, in_h, out_x, out_y, out_w, out_h);

// temp -> output
// for( int j = 0; j < output->get_w() * 3 * 5; j++ )
// 	output->get_rows()[0][j] = 255;

				if( use_opengl ) {
					x11_device->do_camera(output, (*input),
						in_x, in_y, in_x + in_w, in_y + in_h,
						out_x, out_y, out_x + out_w, out_y + out_h);
if( debug ) printf("VModule::import_frame %d %d %d\n",
 __LINE__, output->get_opengl_state(), (*input)->get_opengl_state());
				}
				else {
					output->clear_frame();
// get_cache()->check_in(current_edit->asset);
// return;

// TRANSFER_REPLACE is the fastest transfer mode but it has the disadvantage
// of producing green borders in floating point translation of YUV
					int mode = TRANSFER_REPLACE;
					if( get_edl()->session->interpolation_type != NEAREST_NEIGHBOR &&
						BC_CModels::is_yuv(output->get_color_model()) )
						mode = TRANSFER_NORMAL;

					if( debug ) printf("VModule::import_frame %d temp -> output\n", __LINE__);
					overlayer->overlay(output, (*input),
						in_x, in_y, in_x + in_w, in_y + in_h,
						out_x, out_y, out_x + out_w, out_y + out_h,
						1, mode, get_edl()->session->interpolation_type);
				}
				result = 1;
				
				output->copy_stacks((*input));
				
				
//printf("VModule::import_frame %d\n", __LINE__); 
//(*input)->dump_params();
//output->dump_params();
			}
			else {
// file -> output
				if( debug ) printf("VModule::import_frame %d file -> output nested_edl=%p file=%p\n",
					__LINE__, nested_edl, file);
				if( nested_edl ) {
					VFrame **input = &output;
// If colormodels differ, reallocate output in nested colormodel.
					int nested_cmodel = nested_renderengine->get_edl()->session->color_model;
					int current_cmodel = output->get_color_model();

if( debug ) printf("VModule::import_frame %d nested_cmodel=%d current_cmodel=%d\n",
__LINE__,
nested_cmodel,
current_cmodel);

					if( nested_cmodel != current_cmodel ) {
						if( use_opengl ) { }
						else {
							if( commonrender ) {
								input = &((VRender*)commonrender)->input_temp;
							}
							else {
								input = &input_temp;
							}

							if( !(*input) ) (*input) = new VFrame;
						}

						(*input)->reallocate(0, -1, 0, 0, 0,
							output->get_w(), output->get_h(),
							nested_cmodel, -1);
if( debug ) printf("VModule::import_frame %d\n", __LINE__);
//(*input)->dump();
//(*input)->clear_frame();
					}

if( debug ) printf("VModule::import_frame %d %p %p\n",
 __LINE__, (*input)->get_rows(), (*input));
					result = nested_renderengine->vrender->process_buffer(
						(*input), nested_position, use_opengl);
if( debug ) printf("VModule::import_frame %d\n",
__LINE__);

// If colormodels differ, change colormodels in opengl if possible.
// Swap output for temp if not possible.
					if( nested_cmodel != current_cmodel ) {
						if( use_opengl ) {
							x11_device->convert_cmodel(output,
								current_cmodel);

// The color model was changed in place, so return output buffer
							output->reallocate(0, -1, 0, 0, 0,
								output->get_w(), output->get_h(),
								current_cmodel, -1);
						}
						else {
// Transfer from temporary to output
if( debug ) printf("VModule::import_frame %d %d %d %d %d %d %d\n",
  __LINE__, (*input)->get_w(), (*input)->get_h(),
  output->get_w(), output->get_h(), nested_cmodel, current_cmodel);
							BC_CModels::transfer(output->get_rows(), (*input)->get_rows(),
								0, 0, 0, 0, 0, 0,
								0, 0, (*input)->get_w(), (*input)->get_h(),
								0, 0, output->get_w(), output->get_h(),
								nested_cmodel, current_cmodel, 0,
								(*input)->get_w(), output->get_w());
						}

					}
				}
				else if( file ) {
// Cache single frames
//memset(output->get_rows()[0], 0xff, 1024);
					if( use_cache )
						file->set_cache_frames(use_cache);
					result = file->read_frame(output);
					if( use_cache )
						file->set_cache_frames(0);
					output->set_opengl_state(VFrame::RAM);
				}
			}

			if( file ) {
				get_cache()->check_in(current_edit->asset);
				file = 0;
			}
		}
		else {
// Source not found
			result = 1;
		}

// 		printf("VModule::import_frame %d cache=%p\n", 
// 			__LINE__,
// 			get_cache());

	}
	else {
// Source is silence
		if( debug ) printf("VModule::import_frame %d\n", __LINE__);
		if( use_opengl ) {
			x11_device->clear_input(output);
		}
		else {
			output->clear_frame();
		}
	}

	if( debug ) printf("VModule::import_frame %d done\n", __LINE__);

	return result;
}


int VModule::render(VFrame *output,
	int64_t start_position, int direction, double frame_rate,
	int use_nudge, int debug_render, int use_opengl)
{
	int result = 0;
	double edl_rate = get_edl()->session->frame_rate;

//printf("VModule::render %d %ld\n", __LINE__, start_position);

	if( use_nudge ) start_position += Units::to_int64(track->nudge *
		frame_rate / edl_rate);

	int64_t start_position_project = Units::to_int64(start_position *
		edl_rate / frame_rate + 0.5);

	update_transition(start_position_project, direction);

	VEdit* current_edit = (VEdit*)track->edits->editof(start_position_project,
		direction, 0);
	VEdit* previous_edit = 0;
//printf("VModule::render %d %p %ld %d\n", __LINE__, current_edit, start_position_project, direction);

	Plugin *transition = get_edl()->tracks->plugin_exists(transition_id);
	if( debug_render )
		printf("    VModule::render %d %d %jd %s transition=%p opengl=%d current_edit=%p output=%p\n",
			__LINE__, use_nudge, start_position_project, track->title,
			transition, use_opengl, current_edit, output);

	if( !current_edit ) {
		output->clear_frame();
		// We do not apply mask here, since alpha is 0, and neither substracting nor multypling changes it
		// Another mask mode - "addition" should be added to be able to create mask from empty frames
		// in this case we would call masking here too...
		return 0;
	}




// Process transition
	if( transition && transition->on ) {

// Get temporary buffer
		VFrame **transition_input = 0;
		if( commonrender ) {
			VRender *vrender = (VRender*)commonrender;
			transition_input = &vrender->transition_temp;
		}
		else {
			transition_input = &transition_temp;
		}

		if( (*transition_input) &&
			((*transition_input)->get_w() != track->track_w ||
			(*transition_input)->get_h() != track->track_h) ) {
			delete (*transition_input);
			(*transition_input) = 0;
		}

// Load incoming frame
		if( !(*transition_input) ) {
			(*transition_input) =
				new VFrame(track->track_w, track->track_h,
					get_edl()->session->color_model);
		}

		(*transition_input)->copy_stacks(output);

//printf("VModule::render %d\n", __LINE__);
		result = import_frame((*transition_input),
			current_edit, start_position, frame_rate,
			direction, use_opengl);


// Load transition buffer
		previous_edit = (VEdit*)current_edit->previous;

		result |= import_frame(output,
			previous_edit, start_position, frame_rate,
			direction, use_opengl);
//printf("VModule::render %d %p %p %p %p\n", __LINE__,
// (*transition_input), (*transition_input)->get_pbuffer(),
// output, output->get_pbuffer());

		if( transition_server ) {
// Execute plugin with transition_input and output here
			if( renderengine )
				transition_server->set_use_opengl(use_opengl, renderengine->video);
			transition_server->process_transition((*transition_input), output,
				(direction == PLAY_FORWARD) ?
					(start_position_project - current_edit->startproject) :
					(start_position_project - current_edit->startproject - 1),
				transition->length);
		}
		else
			eprintf("missing transition plugin: %s\n", transition->title);
	}
	else {
// Load output buffer
		result = import_frame(output,
			current_edit, start_position, frame_rate,
			direction, use_opengl);
	}

	Auto *current = 0;
	MaskAutos *keyframe_set =
		(MaskAutos*)track->automation->autos[AUTOMATION_MASK];
	int64_t mask_position = !renderengine ? start_position :
		renderengine->vrender->current_position;
       	MaskAuto *keyframe =
		(MaskAuto*)keyframe_set->get_prev_auto(mask_position, direction, current);

	if( keyframe->apply_before_plugins && keyframe->has_active_mask() ) {
		VDeviceX11 *x11_device = 0;
		if( use_opengl && renderengine && renderengine->video ) {
			x11_device = (VDeviceX11*)renderengine->video->get_output_base();
			if( !x11_device->can_mask(mask_position, keyframe_set) ) {
				if( output->get_opengl_state() != VFrame::RAM ) {
					int w = output->get_w(), h = output->get_h();
					x11_device->do_camera(output, output,
						0,0,w,h, 0,0,w,h); // copy to ram
				}
				use_opengl = 0;
			}
		}
		if( use_opengl && x11_device ) {
			x11_device->do_mask(output, mask_position, keyframe_set,
					keyframe, keyframe);
		}
		else {
			if( !masker ) {
				int cpus = renderengine ?
					renderengine->preferences->processors :
					plugin_array->mwindow->preferences->processors;
				masker = new MaskEngine(cpus);
			}
			masker->do_mask(output, mask_position, keyframe_set, keyframe, keyframe);
		}
	}

	return result;
}


void VModule::create_objects()
{
	Module::create_objects();
}

