This patch adds: * support for proxy building again (missing feature from Blender 2.49) additionally to the way, Blender 2.49 worked, you can select several strips at once and make Blender build proxies in the background (using the job system) Also a new thing: movie proxies are now build into AVI files, and the proxy system is moved into ImBuf-library, so that other parts of blender can also benefit from it. * Timecode support: to fix seeking issues with files, that have a) varying frame rates b) very large GOP lengths c) are broken inbetween d) use different time code tracks the proxy builder can now also build timecode indices, which are used (optionally) for seeking. For the first time, it is possible, to do frame exact seeking on all file types. * Support for different video-streams in one video file (can be selected in sequencer, other parts of blender can also use it, but UI has to be added accordingly) * IMPORTANT: this patch *requires* ffmpeg 0.7 or newer, since older versions don't support the pkt_pts field, that is essential for building timecode indices. Windows and Mac libs are already updated, Linux-users have to build their own ffmpeg verions until distros keep up.
510 lines
12 KiB
C
510 lines
12 KiB
C
/*
|
|
* $Id$
|
|
*
|
|
* ***** BEGIN GPL LICENSE BLOCK *****
|
|
*
|
|
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* The Original Code is Copyright (C) 2009 Blender Foundation.
|
|
* All rights reserved.
|
|
*
|
|
*
|
|
* Contributor(s): Blender Foundation
|
|
*
|
|
* ***** END GPL LICENSE BLOCK *****
|
|
*/
|
|
|
|
/** \file blender/windowmanager/intern/wm_jobs.c
|
|
* \ingroup wm
|
|
*/
|
|
|
|
|
|
#include <string.h>
|
|
|
|
#include "DNA_windowmanager_types.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_blenlib.h"
|
|
#include "BLI_threads.h"
|
|
|
|
#include "BKE_blender.h"
|
|
#include "BKE_context.h"
|
|
#include "BKE_idprop.h"
|
|
#include "BKE_global.h"
|
|
#include "BKE_library.h"
|
|
#include "BKE_main.h"
|
|
#include "BKE_report.h"
|
|
|
|
#include "WM_api.h"
|
|
#include "WM_types.h"
|
|
#include "wm_window.h"
|
|
#include "wm_event_system.h"
|
|
#include "wm_event_types.h"
|
|
#include "wm.h"
|
|
|
|
|
|
|
|
/* ********************** Threaded Jobs Manager ****************************** */
|
|
|
|
/*
|
|
Add new job
|
|
- register in WM
|
|
- configure callbacks
|
|
|
|
Start or re-run job
|
|
- if job running
|
|
- signal job to end
|
|
- add timer notifier to verify when it has ended, to start it
|
|
- else
|
|
- start job
|
|
- add timer notifier to handle progress
|
|
|
|
Stop job
|
|
- signal job to end
|
|
on end, job will tag itself as sleeping
|
|
|
|
Remove job
|
|
- signal job to end
|
|
on end, job will remove itself
|
|
|
|
When job is done:
|
|
- it puts timer to sleep (or removes?)
|
|
|
|
*/
|
|
|
|
struct wmJob {
|
|
struct wmJob *next, *prev;
|
|
|
|
/* job originating from, keep track of this when deleting windows */
|
|
wmWindow *win;
|
|
|
|
/* should store entire own context, for start, update, free */
|
|
void *customdata;
|
|
/* to prevent cpu overhead, use this one which only gets called when job really starts, not in thread */
|
|
void (*initjob)(void *);
|
|
/* this runs inside thread, and does full job */
|
|
void (*startjob)(void *, short *stop, short *do_update, float *progress);
|
|
/* update gets called if thread defines so, and max once per timerstep */
|
|
/* it runs outside thread, blocking blender, no drawing! */
|
|
void (*update)(void *);
|
|
/* free entire customdata, doesn't run in thread */
|
|
void (*free)(void *);
|
|
/* gets called when job is stopped, not in thread */
|
|
void (*endjob)(void *);
|
|
|
|
/* running jobs each have own timer */
|
|
double timestep;
|
|
wmTimer *wt;
|
|
/* the notifier event timers should send */
|
|
unsigned int note, endnote;
|
|
|
|
|
|
/* internal */
|
|
void *owner;
|
|
int flag;
|
|
short suspended, running, ready, do_update, stop;
|
|
float progress;
|
|
|
|
/* for display in header, identification */
|
|
char name[128];
|
|
|
|
/* once running, we store this separately */
|
|
void *run_customdata;
|
|
void (*run_free)(void *);
|
|
|
|
/* we use BLI_threads api, but per job only 1 thread runs */
|
|
ListBase threads;
|
|
|
|
};
|
|
|
|
/* finds:
|
|
* 1st priority: job with same owner and name
|
|
* 2nd priority: job with same owner
|
|
*/
|
|
static wmJob *wm_job_find(wmWindowManager *wm, void *owner, const char *name)
|
|
{
|
|
wmJob *steve, *found=NULL;
|
|
|
|
for(steve= wm->jobs.first; steve; steve= steve->next)
|
|
if(steve->owner==owner) {
|
|
found= steve;
|
|
if (name && strcmp(steve->name, name)==0)
|
|
return steve;
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
/* ******************* public API ***************** */
|
|
|
|
/* returns current or adds new job, but doesnt run it */
|
|
/* every owner only gets a single job, adding a new one will stop running stop and
|
|
when stopped it starts the new one */
|
|
wmJob *WM_jobs_get(wmWindowManager *wm, wmWindow *win, void *owner, const char *name, int flag)
|
|
{
|
|
wmJob *steve= wm_job_find(wm, owner, name);
|
|
|
|
if(steve==NULL) {
|
|
steve= MEM_callocN(sizeof(wmJob), "new job");
|
|
|
|
BLI_addtail(&wm->jobs, steve);
|
|
steve->win= win;
|
|
steve->owner= owner;
|
|
steve->flag= flag;
|
|
BLI_strncpy(steve->name, name, sizeof(steve->name));
|
|
}
|
|
|
|
return steve;
|
|
}
|
|
|
|
/* returns true if job runs, for UI (progress) indicators */
|
|
int WM_jobs_test(wmWindowManager *wm, void *owner)
|
|
{
|
|
wmJob *steve;
|
|
|
|
for(steve= wm->jobs.first; steve; steve= steve->next)
|
|
if(steve->owner==owner)
|
|
if(steve->running)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
float WM_jobs_progress(wmWindowManager *wm, void *owner)
|
|
{
|
|
wmJob *steve= wm_job_find(wm, owner, NULL);
|
|
|
|
if (steve && steve->flag & WM_JOB_PROGRESS)
|
|
return steve->progress;
|
|
|
|
return 0.0;
|
|
}
|
|
|
|
char *WM_jobs_name(wmWindowManager *wm, void *owner)
|
|
{
|
|
wmJob *steve= wm_job_find(wm, owner, NULL);
|
|
|
|
if (steve)
|
|
return steve->name;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int WM_jobs_is_running(wmJob *steve)
|
|
{
|
|
return steve->running;
|
|
}
|
|
|
|
void* WM_jobs_get_customdata(wmJob * steve)
|
|
{
|
|
if (!steve->customdata) {
|
|
return steve->run_customdata;
|
|
} else {
|
|
return steve->customdata;
|
|
}
|
|
}
|
|
|
|
void WM_jobs_customdata(wmJob *steve, void *customdata, void (*free)(void *))
|
|
{
|
|
/* pending job? just free */
|
|
if(steve->customdata)
|
|
steve->free(steve->customdata);
|
|
|
|
steve->customdata= customdata;
|
|
steve->free= free;
|
|
|
|
if(steve->running) {
|
|
/* signal job to end */
|
|
steve->stop= 1;
|
|
}
|
|
}
|
|
|
|
void WM_jobs_timer(wmJob *steve, double timestep, unsigned int note, unsigned int endnote)
|
|
{
|
|
steve->timestep = timestep;
|
|
steve->note = note;
|
|
steve->endnote = endnote;
|
|
}
|
|
|
|
void WM_jobs_callbacks(wmJob *steve,
|
|
void (*startjob)(void *, short *, short *, float *),
|
|
void (*initjob)(void *),
|
|
void (*update)(void *),
|
|
void (*endjob)(void *))
|
|
{
|
|
steve->startjob= startjob;
|
|
steve->initjob= initjob;
|
|
steve->update= update;
|
|
steve->endjob= endjob;
|
|
}
|
|
|
|
static void *do_job_thread(void *job_v)
|
|
{
|
|
wmJob *steve= job_v;
|
|
|
|
steve->startjob(steve->run_customdata, &steve->stop, &steve->do_update, &steve->progress);
|
|
steve->ready= 1;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* dont allow same startjob to be executed twice */
|
|
static void wm_jobs_test_suspend_stop(wmWindowManager *wm, wmJob *test)
|
|
{
|
|
wmJob *steve;
|
|
int suspend= 0;
|
|
|
|
/* job added with suspend flag, we wait 1 timer step before activating it */
|
|
if(test->flag & WM_JOB_SUSPEND) {
|
|
suspend= 1;
|
|
test->flag &= ~WM_JOB_SUSPEND;
|
|
}
|
|
else {
|
|
/* check other jobs */
|
|
for(steve= wm->jobs.first; steve; steve= steve->next) {
|
|
/* obvious case, no test needed */
|
|
if(steve==test || !steve->running) continue;
|
|
|
|
/* if new job is not render, then check for same startjob */
|
|
if(0==(test->flag & WM_JOB_EXCL_RENDER))
|
|
if(steve->startjob!=test->startjob)
|
|
continue;
|
|
|
|
/* if new job is render, any render job should be stopped */
|
|
if(test->flag & WM_JOB_EXCL_RENDER)
|
|
if(0==(steve->flag & WM_JOB_EXCL_RENDER))
|
|
continue;
|
|
|
|
suspend= 1;
|
|
|
|
/* if this job has higher priority, stop others */
|
|
if(test->flag & WM_JOB_PRIORITY) {
|
|
steve->stop= 1;
|
|
// printf("job stopped: %s\n", steve->name);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* possible suspend ourselfs, waiting for other jobs, or de-suspend */
|
|
test->suspended= suspend;
|
|
// if(suspend) printf("job suspended: %s\n", test->name);
|
|
}
|
|
|
|
/* if job running, the same owner gave it a new job */
|
|
/* if different owner starts existing startjob, it suspends itself */
|
|
void WM_jobs_start(wmWindowManager *wm, wmJob *steve)
|
|
{
|
|
if(steve->running) {
|
|
/* signal job to end and restart */
|
|
steve->stop= 1;
|
|
// printf("job started a running job, ending... %s\n", steve->name);
|
|
}
|
|
else {
|
|
|
|
if(steve->customdata && steve->startjob) {
|
|
|
|
wm_jobs_test_suspend_stop(wm, steve);
|
|
|
|
if(steve->suspended==0) {
|
|
/* copy to ensure proper free in end */
|
|
steve->run_customdata= steve->customdata;
|
|
steve->run_free= steve->free;
|
|
steve->free= NULL;
|
|
steve->customdata= NULL;
|
|
steve->running= 1;
|
|
|
|
if(steve->initjob)
|
|
steve->initjob(steve->run_customdata);
|
|
|
|
steve->stop= 0;
|
|
steve->ready= 0;
|
|
steve->progress= 0.0;
|
|
|
|
// printf("job started: %s\n", steve->name);
|
|
|
|
BLI_init_threads(&steve->threads, do_job_thread, 1);
|
|
BLI_insert_thread(&steve->threads, steve);
|
|
}
|
|
|
|
/* restarted job has timer already */
|
|
if(steve->wt==NULL)
|
|
steve->wt= WM_event_add_timer(wm, steve->win, TIMERJOBS, steve->timestep);
|
|
}
|
|
else printf("job fails, not initialized\n");
|
|
}
|
|
}
|
|
|
|
/* stop job, free data completely */
|
|
static void wm_jobs_kill_job(wmWindowManager *wm, wmJob *steve)
|
|
{
|
|
if(steve->running) {
|
|
/* signal job to end */
|
|
steve->stop= 1;
|
|
BLI_end_threads(&steve->threads);
|
|
|
|
if(steve->endjob)
|
|
steve->endjob(steve->run_customdata);
|
|
}
|
|
|
|
if(steve->wt)
|
|
WM_event_remove_timer(wm, steve->win, steve->wt);
|
|
if(steve->customdata)
|
|
steve->free(steve->customdata);
|
|
if(steve->run_customdata)
|
|
steve->run_free(steve->run_customdata);
|
|
|
|
/* remove steve */
|
|
BLI_remlink(&wm->jobs, steve);
|
|
MEM_freeN(steve);
|
|
|
|
}
|
|
|
|
void WM_jobs_stop_all(wmWindowManager *wm)
|
|
{
|
|
wmJob *steve;
|
|
|
|
while((steve= wm->jobs.first))
|
|
wm_jobs_kill_job(wm, steve);
|
|
|
|
}
|
|
|
|
/* signal job(s) from this owner or callback to stop, timer is required to get handled */
|
|
void WM_jobs_stop(wmWindowManager *wm, void *owner, void *startjob)
|
|
{
|
|
wmJob *steve;
|
|
|
|
for(steve= wm->jobs.first; steve; steve= steve->next)
|
|
if(steve->owner==owner || steve->startjob==startjob)
|
|
if(steve->running)
|
|
steve->stop= 1;
|
|
}
|
|
|
|
/* actually terminate thread and job timer */
|
|
void WM_jobs_kill(wmWindowManager *wm, void *owner, void (*startjob)(void *, short int *, short int *, float *))
|
|
{
|
|
wmJob *steve;
|
|
|
|
steve= wm->jobs.first;
|
|
while(steve) {
|
|
if(steve->owner==owner || steve->startjob==startjob) {
|
|
wmJob* bill = steve;
|
|
steve= steve->next;
|
|
wm_jobs_kill_job(wm, bill);
|
|
} else {
|
|
steve= steve->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* kill job entirely, also removes timer itself */
|
|
void wm_jobs_timer_ended(wmWindowManager *wm, wmTimer *wt)
|
|
{
|
|
wmJob *steve;
|
|
|
|
for(steve= wm->jobs.first; steve; steve= steve->next) {
|
|
if(steve->wt==wt) {
|
|
wm_jobs_kill_job(wm, steve);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* hardcoded to event TIMERJOBS */
|
|
void wm_jobs_timer(const bContext *C, wmWindowManager *wm, wmTimer *wt)
|
|
{
|
|
wmJob *steve= wm->jobs.first, *stevenext;
|
|
float total_progress= 0.f;
|
|
float jobs_progress=0;
|
|
|
|
|
|
for(; steve; steve= stevenext) {
|
|
stevenext= steve->next;
|
|
|
|
if(steve->wt==wt) {
|
|
|
|
/* running threads */
|
|
if(steve->threads.first) {
|
|
|
|
/* always call note and update when ready */
|
|
if(steve->do_update || steve->ready) {
|
|
if(steve->update)
|
|
steve->update(steve->run_customdata);
|
|
if(steve->note)
|
|
WM_event_add_notifier(C, steve->note, NULL);
|
|
|
|
if (steve->flag & WM_JOB_PROGRESS)
|
|
WM_event_add_notifier(C, NC_WM|ND_JOB, NULL);
|
|
steve->do_update= 0;
|
|
}
|
|
|
|
if(steve->ready) {
|
|
if(steve->endjob)
|
|
steve->endjob(steve->run_customdata);
|
|
|
|
/* free own data */
|
|
steve->run_free(steve->run_customdata);
|
|
steve->run_customdata= NULL;
|
|
steve->run_free= NULL;
|
|
|
|
// if(steve->stop) printf("job ready but stopped %s\n", steve->name);
|
|
// else printf("job finished %s\n", steve->name);
|
|
|
|
steve->running= 0;
|
|
BLI_end_threads(&steve->threads);
|
|
|
|
if(steve->endnote)
|
|
WM_event_add_notifier(C, steve->endnote, NULL);
|
|
|
|
WM_event_add_notifier(C, NC_WM|ND_JOB, NULL);
|
|
|
|
/* new job added for steve? */
|
|
if(steve->customdata) {
|
|
// printf("job restarted with new data %s\n", steve->name);
|
|
WM_jobs_start(wm, steve);
|
|
}
|
|
else {
|
|
WM_event_remove_timer(wm, steve->win, steve->wt);
|
|
steve->wt= NULL;
|
|
|
|
/* remove steve */
|
|
BLI_remlink(&wm->jobs, steve);
|
|
MEM_freeN(steve);
|
|
}
|
|
} else if (steve->flag & WM_JOB_PROGRESS) {
|
|
/* accumulate global progress for running jobs */
|
|
jobs_progress++;
|
|
total_progress += steve->progress;
|
|
}
|
|
}
|
|
else if(steve->suspended) {
|
|
WM_jobs_start(wm, steve);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* on file load 'winactive' can be NULL, possibly it should not happen but for now do a NULL check - campbell */
|
|
if(wm->winactive) {
|
|
/* if there are running jobs, set the global progress indicator */
|
|
if (jobs_progress > 0) {
|
|
float progress = total_progress / (float)jobs_progress;
|
|
WM_progress_set(wm->winactive, progress);
|
|
} else {
|
|
WM_progress_clear(wm->winactive);
|
|
}
|
|
}
|
|
}
|
|
|