/*
 * ***** 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) 2008 Blender Foundation.
 * All rights reserved.
 *
 *
 * ***** END GPL LICENSE BLOCK *****
 */

/** \file blender/editors/render/render_opengl.c
 *  \ingroup edrend
 */


#include <math.h>
#include <string.h>
#include <stddef.h>

#include "MEM_guardedalloc.h"

#include "DNA_camera_types.h"
#include "BLI_math.h"
#include "BLI_math_color_blend.h"
#include "BLI_blenlib.h"
#include "BLI_utildefines.h"
#include "BLI_threads.h"
#include "BLI_task.h"

#include "DNA_scene_types.h"
#include "DNA_object_types.h"
#include "DNA_gpencil_types.h"

#include "BKE_camera.h"
#include "BKE_context.h"
#include "BKE_global.h"
#include "BKE_image.h"
#include "BKE_main.h"
#include "BKE_report.h"
#include "BKE_scene.h"
#include "BKE_sequencer.h"
#include "BKE_writeavi.h"

#include "WM_api.h"
#include "WM_types.h"

#include "ED_screen.h"
#include "ED_view3d.h"
#include "ED_gpencil.h"

#include "RE_pipeline.h"
#include "IMB_imbuf_types.h"
#include "IMB_imbuf.h"
#include "IMB_colormanagement.h"

#include "RNA_access.h"
#include "RNA_define.h"

#include "GPU_glew.h"
#include "GPU_compositing.h"
#include "GPU_framebuffer.h"

#include "render_intern.h"

/* Define this to get timing information. */
// #undef DEBUG_TIME

#ifdef DEBUG_TIME
#  include "PIL_time.h"
#endif

// TODO(sergey): Find better approximation of the scheduled frames.
// For really highres renders it might fail still.
#define MAX_SCHEDULED_FRAMES 8

typedef struct OGLRender {
	Main *bmain;
	Render *re;
	Scene *scene;

	View3D *v3d;
	RegionView3D *rv3d;
	ARegion *ar;

	ScrArea *prevsa;
	ARegion *prevar;

	int views_len;  /* multi-view views */

	bool is_sequencer;
	SpaceSeq *sseq;
	struct {
		ImBuf **ibufs_arr;
	} seq_data;


	Image *ima;
	ImageUser iuser;

	GPUOffScreen *ofs;
	int ofs_samples;
	bool ofs_full_samples;
	GPUFX *fx;
	int sizex, sizey;
	int write_still;

	ReportList *reports;
	bMovieHandle *mh;
	int cfrao, nfra;

	int totvideos;

	/* quick lookup */
	int view_id;

	/* wm vars for timer and progress cursor */
	wmWindowManager *wm;
	wmWindow *win;

	wmTimer *timer; /* use to check if running modal or not (invoke'd or exec'd)*/
	void **movie_ctx_arr;

	TaskScheduler *task_scheduler;
	TaskPool *task_pool;
	bool pool_ok;
	bool is_animation;
	SpinLock reports_lock;
	unsigned int num_scheduled_frames;
	ThreadMutex task_mutex;
	ThreadCondition task_condition;

#ifdef DEBUG_TIME
	double time_start;
#endif
} OGLRender;

/* added because v3d is not always valid */
static unsigned int screen_opengl_layers(OGLRender *oglrender)
{
	if (oglrender->v3d) {
		return oglrender->scene->lay | oglrender->v3d->lay;
	}
	else {
		return oglrender->scene->lay;
	}
}

static bool screen_opengl_is_multiview(OGLRender *oglrender)
{
	View3D *v3d = oglrender->v3d;
	RegionView3D *rv3d = oglrender->rv3d;
	RenderData *rd = &oglrender->scene->r;

	if ((rd == NULL) || ((!oglrender->is_sequencer) && ((rv3d == NULL) || (v3d == NULL))))
		return false;

	return (rd->scemode & R_MULTIVIEW) && ((oglrender->is_sequencer) || (rv3d->persp == RV3D_CAMOB && v3d->camera));
}

static void screen_opengl_views_setup(OGLRender *oglrender)
{
	RenderResult *rr;
	RenderView *rv;
	SceneRenderView *srv;
	bool is_multiview;
	Object *camera;
	View3D *v3d = oglrender->v3d;

	RenderData *rd = &oglrender->scene->r;

	rr = RE_AcquireResultWrite(oglrender->re);

	is_multiview = screen_opengl_is_multiview(oglrender);

	if (!is_multiview) {
		/* we only have one view when multiview is off */
		rv = rr->views.first;

		if (rv == NULL) {
			rv = MEM_callocN(sizeof(RenderView), "new opengl render view");
			BLI_addtail(&rr->views, rv);
		}

		while (rv->next) {
			RenderView *rv_del = rv->next;
			BLI_remlink(&rr->views, rv_del);

			if (rv_del->rectf)
				MEM_freeN(rv_del->rectf);

			if (rv_del->rectz)
				MEM_freeN(rv_del->rectz);

			if (rv_del->rect32)
				MEM_freeN(rv_del->rect32);

			MEM_freeN(rv_del);
		}
	}
	else {
		if (!oglrender->is_sequencer)
			RE_SetOverrideCamera(oglrender->re, V3D_CAMERA_SCENE(oglrender->scene, v3d));

		/* remove all the views that are not needed */
		rv = rr->views.last;
		while (rv) {
			srv = BLI_findstring(&rd->views, rv->name, offsetof(SceneRenderView, name));
			if (BKE_scene_multiview_is_render_view_active(rd, srv)) {
				rv = rv->prev;
			}
			else {
				RenderView *rv_del = rv;
				rv  = rv_del->prev;

				BLI_remlink(&rr->views, rv_del);

				if (rv_del->rectf)
					MEM_freeN(rv_del->rectf);

				if (rv_del->rectz)
					MEM_freeN(rv_del->rectz);

				if (rv_del->rect32)
					MEM_freeN(rv_del->rect32);

				MEM_freeN(rv_del);
			}
		}

		/* create all the views that are needed */
		for (srv = rd->views.first; srv; srv = srv->next) {
			if (BKE_scene_multiview_is_render_view_active(rd, srv) == false)
				continue;

			rv = BLI_findstring(&rr->views, srv->name, offsetof(SceneRenderView, name));

			if (rv == NULL) {
				rv = MEM_callocN(sizeof(RenderView), "new opengl render view");
				BLI_strncpy(rv->name, srv->name, sizeof(rv->name));
				BLI_addtail(&rr->views, rv);
			}
		}
	}

	if (!(is_multiview && BKE_scene_multiview_is_stereo3d(rd)))
		oglrender->iuser.flag &= ~IMA_SHOW_STEREO;

	/* will only work for non multiview correctly */
	if (v3d) {
		camera = BKE_camera_multiview_render(oglrender->scene, v3d->camera, "new opengl render view");
		BKE_render_result_stamp_info(oglrender->scene, camera, rr, false);
	}
	else {
		BKE_render_result_stamp_info(oglrender->scene, oglrender->scene->camera, rr, false);
	}

	RE_ReleaseResult(oglrender->re);
}

static void screen_opengl_render_doit(OGLRender *oglrender, RenderResult *rr)
{
	Scene *scene = oglrender->scene;
	ARegion *ar = oglrender->ar;
	View3D *v3d = oglrender->v3d;
	RegionView3D *rv3d = oglrender->rv3d;
	Object *camera = NULL;
	int sizex = oglrender->sizex;
	int sizey = oglrender->sizey;
	const short view_context = (v3d != NULL);
	bool draw_bgpic = true;
	bool draw_sky = (scene->r.alphamode == R_ADDSKY);
	unsigned char *rect = NULL;
	const char *viewname = RE_GetActiveRenderView(oglrender->re);
	ImBuf *ibuf_result = NULL;

	if (oglrender->is_sequencer) {
		SpaceSeq *sseq = oglrender->sseq;
		struct bGPdata *gpd = (sseq && (sseq->flag & SEQ_SHOW_GPENCIL)) ? sseq->gpd : NULL;

		/* use pre-calculated ImBuf (avoids deadlock), see: */
		ImBuf *ibuf = oglrender->seq_data.ibufs_arr[oglrender->view_id];

		if (ibuf) {
			ImBuf *out = IMB_dupImBuf(ibuf);
			IMB_freeImBuf(ibuf);
			/* OpenGL render is considered to be preview and should be
			 * as fast as possible. So currently we're making sure sequencer
			 * result is always byte to simplify color management pipeline.
			 *
			 * TODO(sergey): In the case of output to float container (EXR)
			 * it actually makes sense to keep float buffer instead.
			 */
			if (out->rect_float != NULL) {
				IMB_rect_from_float(out);
				imb_freerectfloatImBuf(out);
			}
			BLI_assert((oglrender->sizex == ibuf->x) && (oglrender->sizey == ibuf->y));
			RE_render_result_rect_from_ibuf(rr, &scene->r, out, oglrender->view_id);
			IMB_freeImBuf(out);
		}
		else if (gpd) {
			/* If there are no strips, Grease Pencil still needs a buffer to draw on */
			ImBuf *out = IMB_allocImBuf(oglrender->sizex, oglrender->sizey, 32, IB_rect);
			RE_render_result_rect_from_ibuf(rr, &scene->r, out, oglrender->view_id);
			IMB_freeImBuf(out);
		}

		if (gpd) {
			int i;
			unsigned char *gp_rect;
			unsigned char *render_rect = (unsigned char *)RE_RenderViewGetById(rr, oglrender->view_id)->rect32;

			GPU_offscreen_bind(oglrender->ofs, true);

			glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

			wmOrtho2(0, sizex, 0, sizey);
			glTranslatef(sizex / 2, sizey / 2, 0.0f);

			G.f |= G_RENDER_OGL;
			ED_gpencil_draw_ex(scene, gpd, sizex, sizey, scene->r.cfra, SPACE_SEQ);
			G.f &= ~G_RENDER_OGL;

			gp_rect = MEM_mallocN(sizex * sizey * sizeof(unsigned char) * 4, "offscreen rect");
			GPU_offscreen_read_pixels(oglrender->ofs, GL_UNSIGNED_BYTE, gp_rect);

			for (i = 0; i < sizex * sizey * 4; i += 4) {
				blend_color_mix_byte(&render_rect[i], &render_rect[i], &gp_rect[i]);
			}
			GPU_offscreen_unbind(oglrender->ofs, true);

			MEM_freeN(gp_rect);
		}
	}
	else {
		/* shouldnt suddenly give errors mid-render but possible */
		char err_out[256] = "unknown";
		ImBuf *ibuf_view;
		const int alpha_mode = (draw_sky) ? R_ADDSKY : R_ALPHAPREMUL;

		if (view_context) {
			ibuf_view = ED_view3d_draw_offscreen_imbuf(
			       scene, v3d, ar, sizex, sizey,
			       IB_rect, draw_bgpic,
			       alpha_mode, oglrender->ofs_samples, oglrender->ofs_full_samples, viewname,
			       oglrender->fx, oglrender->ofs, err_out);

			/* for stamp only */
			if (rv3d->persp == RV3D_CAMOB && v3d->camera) {
				camera = BKE_camera_multiview_render(oglrender->scene, v3d->camera, viewname);
			}
		}
		else {
			ibuf_view = ED_view3d_draw_offscreen_imbuf_simple(
			        scene, scene->camera, oglrender->sizex, oglrender->sizey,
			        IB_rect, OB_SOLID, false, true, true,
			        alpha_mode, oglrender->ofs_samples, oglrender->ofs_full_samples, viewname,
			        oglrender->fx, oglrender->ofs, err_out);
			camera = scene->camera;
		}

		if (ibuf_view) {
			ibuf_result = ibuf_view;
			rect = (unsigned char *)ibuf_view->rect;
		}
		else {
			fprintf(stderr, "%s: failed to get buffer, %s\n", __func__, err_out);
		}
	}

	if (ibuf_result != NULL) {
		if ((scene->r.stamp & R_STAMP_ALL) && (scene->r.stamp & R_STAMP_DRAW)) {
			BKE_image_stamp_buf(scene, camera, NULL, rect, NULL, rr->rectx, rr->recty, 4);
		}
		RE_render_result_rect_from_ibuf(rr, &scene->r, ibuf_result, oglrender->view_id);
		IMB_freeImBuf(ibuf_result);
	}
}

static void screen_opengl_render_write(OGLRender *oglrender)
{
	Scene *scene = oglrender->scene;
	RenderResult *rr;
	bool ok;
	char name[FILE_MAX];

	rr = RE_AcquireResultRead(oglrender->re);

	BKE_image_path_from_imformat(
	        name, scene->r.pic, oglrender->bmain->name, scene->r.cfra,
	        &scene->r.im_format, (scene->r.scemode & R_EXTENSION) != 0, false, NULL);

	/* write images as individual images or stereo */
	BKE_render_result_stamp_info(scene, scene->camera, rr, false);
	ok = RE_WriteRenderViewsImage(oglrender->reports, rr, scene, false, name);

	RE_ReleaseResultImage(oglrender->re);

	if (ok) printf("OpenGL Render written to '%s'\n", name);
	else printf("OpenGL Render failed to write '%s'\n", name);
}

static void addAlphaOverFloat(float dest[4], const float source[4])
{
	/* d = s + (1-alpha_s)d*/
	float mul;

	mul = 1.0f - source[3];

	dest[0] = (mul * dest[0]) + source[0];
	dest[1] = (mul * dest[1]) + source[1];
	dest[2] = (mul * dest[2]) + source[2];
	dest[3] = (mul * dest[3]) + source[3];

}

/* add renderlayer and renderpass for each grease pencil layer for using in composition */
static void add_gpencil_renderpass(OGLRender *oglrender, RenderResult *rr, RenderView *rv)
{
	bGPdata *gpd = oglrender->scene->gpd;
	Scene *scene = oglrender->scene;

	/* sanity checks */
	if (gpd == NULL) {
		return;
	}
	if (scene == NULL) {
		return;
	}
	if (BLI_listbase_is_empty(&gpd->layers)) {
		return;
	}
	if (oglrender->v3d != NULL && (oglrender->v3d->flag2 & V3D_SHOW_GPENCIL) == 0) {
		return;
	}

	/* save old alpha mode */
	short oldalphamode = scene->r.alphamode;
	/* set alpha transparent for gp */
	scene->r.alphamode = R_ALPHAPREMUL;
	
	/* saves layer status */
	short *oldsts = MEM_mallocN(BLI_listbase_count(&gpd->layers) * sizeof(short), "temp_gplayers_flag");
	int i = 0;
	for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) {
		oldsts[i] = gpl->flag;
		++i;
	}
	/* loop all layers to create separate render */
	for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) {
		/* dont draw layer if hidden */
		if (gpl->flag & GP_LAYER_HIDE)
			continue;
		/* hide all layer except current */
		for (bGPDlayer *gph = gpd->layers.first; gph; gph = gph->next) {
			if (gpl != gph) {
				gph->flag |= GP_LAYER_HIDE;
			}
		}

		/* render this gp layer */
		screen_opengl_render_doit(oglrender, rr);
		
		/* add RendePass composite */
		RenderPass *rp = RE_create_gp_pass(rr, gpl->info, rv->name);

		/* copy image data from rectf */
		// XXX: Needs conversion.
		unsigned char *src = (unsigned char *)RE_RenderViewGetById(rr, oglrender->view_id)->rect32;
		if (src != NULL) {
			float *dest = rp->rect;

			int x, y, rectx, recty;
			rectx = rr->rectx;
			recty = rr->recty;
			for (y = 0; y < recty; y++) {
				for (x = 0; x < rectx; x++) {
					unsigned char *pixSrc = src + 4 * (rectx * y + x);
					if (pixSrc[3] > 0) {
						float *pixDest = dest + 4 * (rectx * y + x);
						float float_src[4];
						srgb_to_linearrgb_uchar4(float_src, pixSrc);
						addAlphaOverFloat(pixDest, float_src);
					}
				}
			}
		}
		/* back layer status */
		i = 0;
		for (bGPDlayer *gph = gpd->layers.first; gph; gph = gph->next) {
			gph->flag = oldsts[i];
			++i;
		}
	}
	/* free memory */
	MEM_freeN(oldsts);

	/* back default alpha mode */
	scene->r.alphamode = oldalphamode;
}

static void screen_opengl_render_apply(OGLRender *oglrender)
{
	RenderResult *rr;
	RenderView *rv;
	int view_id;
	ImBuf *ibuf;
	void *lock;

	if (oglrender->is_sequencer) {
		Scene *scene = oglrender->scene;

		SeqRenderData context;
		SpaceSeq *sseq = oglrender->sseq;
		int chanshown = sseq ? sseq->chanshown : 0;

		BKE_sequencer_new_render_data(
		        oglrender->bmain->eval_ctx, oglrender->bmain, scene,
		        oglrender->sizex, oglrender->sizey, 100.0f,
		        &context);

		for (view_id = 0; view_id < oglrender->views_len; view_id++) {
			context.view_id = view_id;
			context.gpu_offscreen = oglrender->ofs;
			context.gpu_fx = oglrender->fx;
			context.gpu_full_samples = oglrender->ofs_full_samples;

			oglrender->seq_data.ibufs_arr[view_id] = BKE_sequencer_give_ibuf(&context, CFRA, chanshown);
		}
	}

	rr = RE_AcquireResultRead(oglrender->re);
	for (rv = rr->views.first, view_id = 0; rv; rv = rv->next, view_id++) {
		BLI_assert(view_id < oglrender->views_len);
		RE_SetActiveRenderView(oglrender->re, rv->name);
		oglrender->view_id = view_id;
		/* add grease pencil passes. For sequencer, the render does not include renderpasses
		 * TODO: The sequencer render of grease pencil should be rethought */
		if (!oglrender->is_sequencer) {
			add_gpencil_renderpass(oglrender, rr, rv);
		}
		/* render composite */
		screen_opengl_render_doit(oglrender, rr);
	}

	RE_ReleaseResult(oglrender->re);

	ibuf = BKE_image_acquire_ibuf(oglrender->ima, &oglrender->iuser, &lock);
	if (ibuf) {
		ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID;
	}
	BKE_image_release_ibuf(oglrender->ima, ibuf, lock);

	if (oglrender->write_still) {
		screen_opengl_render_write(oglrender);
	}
}

static bool screen_opengl_render_init(bContext *C, wmOperator *op)
{
	/* new render clears all callbacks */
	wmWindowManager *wm = CTX_wm_manager(C);
	wmWindow *win = CTX_wm_window(C);

	Scene *scene = CTX_data_scene(C);
	ScrArea *prevsa = CTX_wm_area(C);
	ARegion *prevar = CTX_wm_region(C);
	GPUOffScreen *ofs;
	OGLRender *oglrender;
	int sizex, sizey;
	const int samples = (scene->r.mode & R_OSA) ? scene->r.osa : 0;
	const bool full_samples = (samples != 0) && (scene->r.scemode & R_FULL_SAMPLE);
	bool is_view_context = RNA_boolean_get(op->ptr, "view_context");
	const bool is_animation = RNA_boolean_get(op->ptr, "animation");
	const bool is_sequencer = RNA_boolean_get(op->ptr, "sequencer");
	const bool is_write_still = RNA_boolean_get(op->ptr, "write_still");
	char err_out[256] = "unknown";

	if (G.background) {
		BKE_report(op->reports, RPT_ERROR, "Cannot use OpenGL render in background mode (no opengl context)");
		return false;
	}

	/* only one render job at a time */
	if (WM_jobs_test(wm, scene, WM_JOB_TYPE_RENDER))
		return false;

	if (is_sequencer) {
		is_view_context = false;
	}
	else {
		/* ensure we have a 3d view */
		if (!ED_view3d_context_activate(C)) {
			RNA_boolean_set(op->ptr, "view_context", false);
			is_view_context = false;
		}

		if (!is_view_context && scene->camera == NULL) {
			BKE_report(op->reports, RPT_ERROR, "Scene has no camera");
			return false;
		}
	}

	if (!is_animation && is_write_still && BKE_imtype_is_movie(scene->r.im_format.imtype)) {
		BKE_report(op->reports, RPT_ERROR, "Cannot write a single file with an animation format selected");
		return false;
	}

	/* stop all running jobs, except screen one. currently previews frustrate Render */
	WM_jobs_kill_all_except(wm, CTX_wm_screen(C));

	/* create offscreen buffer */
	sizex = (scene->r.size * scene->r.xsch) / 100;
	sizey = (scene->r.size * scene->r.ysch) / 100;

	/* corrects render size with actual size, not every card supports non-power-of-two dimensions */
	ofs = GPU_offscreen_create(sizex, sizey, full_samples ? 0 : samples, err_out);

	if (!ofs) {
		BKE_reportf(op->reports, RPT_ERROR, "Failed to create OpenGL off-screen buffer, %s", err_out);
		return false;
	}

	/* allocate opengl render */
	oglrender = MEM_callocN(sizeof(OGLRender), "OGLRender");
	op->customdata = oglrender;

	oglrender->ofs = ofs;
	oglrender->ofs_samples = samples;
	oglrender->ofs_full_samples = full_samples;
	oglrender->sizex = sizex;
	oglrender->sizey = sizey;
	oglrender->bmain = CTX_data_main(C);
	oglrender->scene = scene;
	oglrender->cfrao = scene->r.cfra;

	oglrender->write_still = is_write_still && !is_animation;
	oglrender->is_animation = is_animation;

	oglrender->views_len = BKE_scene_multiview_num_views_get(&scene->r);

	oglrender->is_sequencer = is_sequencer;
	if (is_sequencer) {
		oglrender->sseq = CTX_wm_space_seq(C);
		ImBuf **ibufs_arr = MEM_callocN(sizeof(*ibufs_arr) * oglrender->views_len, __func__);
		oglrender->seq_data.ibufs_arr = ibufs_arr;
	}

	oglrender->prevsa = prevsa;
	oglrender->prevar = prevar;

	if (is_view_context) {
		ED_view3d_context_user_region(C, &oglrender->v3d, &oglrender->ar); /* so quad view renders camera */
		oglrender->rv3d = oglrender->ar->regiondata;

		/* MUST be cleared on exit */
		oglrender->scene->customdata_mask_modal = ED_view3d_datamask(oglrender->scene, oglrender->v3d);

		/* apply immediately in case we're rendering from a script,
		 * running notifiers again will overwrite */
		oglrender->scene->customdata_mask |= oglrender->scene->customdata_mask_modal;

		if (oglrender->v3d->fx_settings.fx_flag & (GPU_FX_FLAG_DOF | GPU_FX_FLAG_SSAO)) {
			oglrender->fx = GPU_fx_compositor_create();
		}
	}

	/* create render */
	oglrender->re = RE_NewRender(scene->id.name);

	/* create image and image user */
	oglrender->ima = BKE_image_verify_viewer(IMA_TYPE_R_RESULT, "Render Result");
	BKE_image_signal(oglrender->ima, NULL, IMA_SIGNAL_FREE);
	BKE_image_backup_render(oglrender->scene, oglrender->ima, true);

	oglrender->iuser.scene = scene;
	oglrender->iuser.ok = 1;

	/* create render result */
	RE_InitState(oglrender->re, NULL, &scene->r, NULL, sizex, sizey, NULL);

	/* create render views */
	screen_opengl_views_setup(oglrender);

	/* wm vars */
	oglrender->wm = wm;
	oglrender->win = win;

	oglrender->totvideos = 0;
	oglrender->mh = NULL;
	oglrender->movie_ctx_arr = NULL;

	if (is_animation) {
		TaskScheduler *task_scheduler = BLI_task_scheduler_get();
		if (BKE_imtype_is_movie(scene->r.im_format.imtype)) {
			task_scheduler = BLI_task_scheduler_create(1);
			oglrender->task_scheduler = task_scheduler;
			oglrender->task_pool = BLI_task_pool_create_background(task_scheduler,
			                                                       oglrender);
		}
		else {
			oglrender->task_scheduler = NULL;
			oglrender->task_pool = BLI_task_pool_create(task_scheduler,
			                                            oglrender);
		}
		oglrender->pool_ok = true;
		BLI_spin_init(&oglrender->reports_lock);
	}
	else {
		oglrender->task_scheduler = NULL;
		oglrender->task_pool = NULL;
	}
	oglrender->num_scheduled_frames = 0;
	BLI_mutex_init(&oglrender->task_mutex);
	BLI_condition_init(&oglrender->task_condition);

#ifdef DEBUG_TIME
	oglrender->time_start = PIL_check_seconds_timer();
#endif

	return true;
}

static void screen_opengl_render_end(bContext *C, OGLRender *oglrender)
{
	Main *bmain = CTX_data_main(C);
	Scene *scene = oglrender->scene;
	int i;

	if (oglrender->is_animation) {
		/* Trickery part for movie output:
		 *
		 * We MUST write frames in an exact order, so we only let background
		 * thread to work on that, and main thread is simply waits for that
		 * thread to do all the dirty work.
		 *
		 * After this loop is done work_and_wait() will have nothing to do,
		 * so we don't run into wrong order of frames written to the stream.
		 */
		if (BKE_imtype_is_movie(scene->r.im_format.imtype)) {
			BLI_mutex_lock(&oglrender->task_mutex);
			while (oglrender->num_scheduled_frames > 0) {
				BLI_condition_wait(&oglrender->task_condition,
				                   &oglrender->task_mutex);
			}
			BLI_mutex_unlock(&oglrender->task_mutex);
		}
		BLI_task_pool_work_and_wait(oglrender->task_pool);
		BLI_task_pool_free(oglrender->task_pool);
		/* Depending on various things we might or might not use global scheduler. */
		if (oglrender->task_scheduler != NULL) {
			BLI_task_scheduler_free(oglrender->task_scheduler);
		}
		BLI_spin_end(&oglrender->reports_lock);
	}
	BLI_mutex_end(&oglrender->task_mutex);
	BLI_condition_end(&oglrender->task_condition);

#ifdef DEBUG_TIME
	printf("Total render time: %f\n", PIL_check_seconds_timer() - oglrender->time_start);
#endif

	if (oglrender->mh) {
		if (BKE_imtype_is_movie(scene->r.im_format.imtype)) {
			for (i = 0; i < oglrender->totvideos; i++) {
				oglrender->mh->end_movie(oglrender->movie_ctx_arr[i]);
				oglrender->mh->context_free(oglrender->movie_ctx_arr[i]);
			}
		}

		if (oglrender->movie_ctx_arr) {
			MEM_freeN(oglrender->movie_ctx_arr);
		}
	}

	if (oglrender->timer) { /* exec will not have a timer */
		scene->r.cfra = oglrender->cfrao;
		BKE_scene_update_for_newframe(bmain->eval_ctx, bmain, scene, screen_opengl_layers(oglrender));

		WM_event_remove_timer(oglrender->wm, oglrender->win, oglrender->timer);
	}

	WM_cursor_modal_restore(oglrender->win);

	WM_event_add_notifier(C, NC_SCENE | ND_RENDER_RESULT, oglrender->scene);

	if (oglrender->fx)
		GPU_fx_compositor_destroy(oglrender->fx);

	GPU_offscreen_free(oglrender->ofs);

	if (oglrender->is_sequencer) {
		MEM_freeN(oglrender->seq_data.ibufs_arr);
	}

	oglrender->scene->customdata_mask_modal = 0;

	CTX_wm_area_set(C, oglrender->prevsa);
	CTX_wm_region_set(C, oglrender->prevar);

	MEM_freeN(oglrender);
}

static void screen_opengl_render_cancel(bContext *C, wmOperator *op)
{
	screen_opengl_render_end(C, op->customdata);
}

/* share between invoke and exec */
static bool screen_opengl_render_anim_initialize(bContext *C, wmOperator *op)
{
	/* initialize animation */
	OGLRender *oglrender;
	Scene *scene;

	oglrender = op->customdata;
	scene = oglrender->scene;
	oglrender->totvideos = BKE_scene_multiview_num_videos_get(&scene->r);

	oglrender->reports = op->reports;

	if (BKE_imtype_is_movie(scene->r.im_format.imtype)) {
		size_t width, height;
		int i;

		BKE_scene_multiview_videos_dimensions_get(&scene->r, oglrender->sizex, oglrender->sizey, &width, &height);
		oglrender->mh = BKE_movie_handle_get(scene->r.im_format.imtype);

		if (oglrender->mh == NULL) {
			BKE_report(oglrender->reports, RPT_ERROR, "Movie format unsupported");
			screen_opengl_render_end(C, oglrender);
			return false;
		}

		oglrender->movie_ctx_arr = MEM_mallocN(sizeof(void *) * oglrender->totvideos, "Movies");

		for (i = 0; i < oglrender->totvideos; i++) {
			const char *suffix = BKE_scene_multiview_view_id_suffix_get(&scene->r, i);

			oglrender->movie_ctx_arr[i] = oglrender->mh->context_create();
			if (!oglrender->mh->start_movie(oglrender->movie_ctx_arr[i], scene, &scene->r, oglrender->sizex,
			                                oglrender->sizey, oglrender->reports, PRVRANGEON != 0, suffix))
			{
				screen_opengl_render_end(C, oglrender);
				return false;
			}
		}
	}

	oglrender->cfrao = scene->r.cfra;
	oglrender->nfra = PSFRA;
	scene->r.cfra = PSFRA;

	return true;
}

typedef struct WriteTaskData {
	RenderResult *rr;
	Scene tmp_scene;
} WriteTaskData;

static void write_result_func(TaskPool * __restrict pool,
                              void *task_data_v,
                              int UNUSED(thread_id))
{
	OGLRender *oglrender = (OGLRender *) BLI_task_pool_userdata(pool);
	WriteTaskData *task_data = (WriteTaskData *) task_data_v;
	Scene *scene = &task_data->tmp_scene;
	RenderResult *rr = task_data->rr;
	const bool is_movie = BKE_imtype_is_movie(scene->r.im_format.imtype);
	const int cfra = scene->r.cfra;
	bool ok;
	/* Don't attempt to write if we've got an error. */
	if (!oglrender->pool_ok) {
		RE_FreeRenderResult(rr);
		BLI_mutex_lock(&oglrender->task_mutex);
		oglrender->num_scheduled_frames--;
		BLI_condition_notify_all(&oglrender->task_condition);
		BLI_mutex_unlock(&oglrender->task_mutex);
		return;
	}
	/* Construct local thread0safe copy of reports structure which we can
	 * safely pass to the underlying functions.
	 */
	ReportList reports;
	BKE_reports_init(&reports, oglrender->reports->flag & ~RPT_PRINT);
	/* Do actual save logic here, depending on the file format.
	 *
	 * NOTE: We have to construct temporary scene with proper scene->r.cfra.
	 * This is because underlying calls do not use r.cfra but use scene
	 * for that.
	 */
	if (is_movie) {
		ok = RE_WriteRenderViewsMovie(&reports,
		                              rr,
		                              scene,
		                              &scene->r,
		                              oglrender->mh,
		                              oglrender->movie_ctx_arr,
		                              oglrender->totvideos,
		                              PRVRANGEON != 0);
	}
	else {
		/* TODO(sergey): We can in theory save some CPU ticks here because we
		 * calculate file name again here.
		 */
		char name[FILE_MAX];
		BKE_image_path_from_imformat(name,
		                             scene->r.pic,
		                             oglrender->bmain->name,
		                             cfra,
		                             &scene->r.im_format,
		                             (scene->r.scemode & R_EXTENSION) != 0,
		                             true,
		                             NULL);

		BKE_render_result_stamp_info(scene, scene->camera, rr, false);
		ok = RE_WriteRenderViewsImage(NULL, rr, scene, true, name);
		if (!ok) {
			BKE_reportf(&reports,
			            RPT_ERROR,
			            "Write error: cannot save %s",
			            name);
		}
	}
	if (reports.list.first != NULL) {
		BLI_spin_lock(&oglrender->reports_lock);
		for (Report *report = reports.list.first;
		     report != NULL;
		     report = report->next)
		{
			BKE_report(oglrender->reports,
			           report->type,
			           report->message);
		}
		BLI_spin_unlock(&oglrender->reports_lock);
	}
	if (!ok) {
		oglrender->pool_ok = false;
	}
	RE_FreeRenderResult(rr);
	BLI_mutex_lock(&oglrender->task_mutex);
	oglrender->num_scheduled_frames--;
	BLI_condition_notify_all(&oglrender->task_condition);
	BLI_mutex_unlock(&oglrender->task_mutex);
}

static bool schedule_write_result(OGLRender *oglrender, RenderResult *rr)
{
	if (!oglrender->pool_ok) {
		RE_FreeRenderResult(rr);
		return false;
	}
	Scene *scene = oglrender->scene;
	WriteTaskData *task_data = MEM_mallocN(sizeof(WriteTaskData), "write task data");
	task_data->rr = rr;
	task_data->tmp_scene = *scene;
	BLI_mutex_lock(&oglrender->task_mutex);
	oglrender->num_scheduled_frames++;
	if (oglrender->num_scheduled_frames > MAX_SCHEDULED_FRAMES) {
		BLI_condition_wait(&oglrender->task_condition,
		                   &oglrender->task_mutex);
	}
	BLI_mutex_unlock(&oglrender->task_mutex);
	BLI_task_pool_push(oglrender->task_pool,
	                   write_result_func,
	                   task_data,
	                   true,
	                   TASK_PRIORITY_LOW);
	return true;
}

static bool screen_opengl_render_anim_step(bContext *C, wmOperator *op)
{
	Main *bmain = CTX_data_main(C);
	OGLRender *oglrender = op->customdata;
	Scene *scene = oglrender->scene;
	char name[FILE_MAX];
	bool ok = false;
	const bool view_context = (oglrender->v3d != NULL);
	bool is_movie;
	RenderResult *rr;

	/* go to next frame */
	if (CFRA < oglrender->nfra)
		CFRA++;
	while (CFRA < oglrender->nfra) {
		unsigned int lay = screen_opengl_layers(oglrender);

		if (lay & 0xFF000000)
			lay &= 0xFF000000;

		BKE_scene_update_for_newframe(bmain->eval_ctx, bmain, scene, lay);
		CFRA++;
	}

	is_movie = BKE_imtype_is_movie(scene->r.im_format.imtype);

	if (!is_movie) {
		BKE_image_path_from_imformat(
		        name, scene->r.pic, oglrender->bmain->name, scene->r.cfra,
		        &scene->r.im_format, (scene->r.scemode & R_EXTENSION) != 0, true, NULL);

		if ((scene->r.mode & R_NO_OVERWRITE) && BLI_exists(name)) {
			BLI_spin_lock(&oglrender->reports_lock);
			BKE_reportf(op->reports, RPT_INFO, "Skipping existing frame \"%s\"", name);
			BLI_spin_unlock(&oglrender->reports_lock);
			ok = true;
			goto finally;
		}
	}

	WM_cursor_time(oglrender->win, scene->r.cfra);

	BKE_scene_update_for_newframe(bmain->eval_ctx, bmain, scene, screen_opengl_layers(oglrender));

	if (view_context) {
		if (oglrender->rv3d->persp == RV3D_CAMOB && oglrender->v3d->camera && oglrender->v3d->scenelock) {
			/* since BKE_scene_update_for_newframe() is used rather
			 * then ED_update_for_newframe() the camera needs to be set */
			if (BKE_scene_camera_switch_update(scene)) {
				oglrender->v3d->camera = scene->camera;
			}
		}
	}
	else {
		BKE_scene_camera_switch_update(scene);
	}

	/* render into offscreen buffer */
	screen_opengl_render_apply(oglrender);

	/* save to disk */
	rr = RE_AcquireResultRead(oglrender->re);
	RenderResult *new_rr = RE_DuplicateRenderResult(rr);
	RE_ReleaseResult(oglrender->re);

	ok = schedule_write_result(oglrender, new_rr);

finally:  /* Step the frame and bail early if needed */

	/* go to next frame */
	oglrender->nfra += scene->r.frame_step;

	/* stop at the end or on error */
	if (CFRA >= PEFRA || !ok) {
		screen_opengl_render_end(C, op->customdata);
		return 0;
	}

	return 1;
}


static int screen_opengl_render_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
	OGLRender *oglrender = op->customdata;
	const bool anim = RNA_boolean_get(op->ptr, "animation");
	bool ret;

	switch (event->type) {
		case ESCKEY:
			/* cancel */
			oglrender->pool_ok = false;  /* Flag pool for cancel. */
			screen_opengl_render_end(C, op->customdata);
			return OPERATOR_FINISHED;
		case TIMER:
			/* render frame? */
			if (oglrender->timer == event->customdata)
				break;
			ATTR_FALLTHROUGH;
		default:
			/* nothing to do */
			return OPERATOR_RUNNING_MODAL;
	}

	/* run first because screen_opengl_render_anim_step can free oglrender */
	WM_event_add_notifier(C, NC_SCENE | ND_RENDER_RESULT, oglrender->scene);
	
	if (anim == 0) {
		screen_opengl_render_apply(op->customdata);
		screen_opengl_render_end(C, op->customdata);
		return OPERATOR_FINISHED;
	}
	else {
		ret = screen_opengl_render_anim_step(C, op);
	}

	/* stop at the end or on error */
	if (ret == false) {
		return OPERATOR_FINISHED;
	}

	return OPERATOR_RUNNING_MODAL;
}

static int screen_opengl_render_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
	OGLRender *oglrender;
	const bool anim = RNA_boolean_get(op->ptr, "animation");

	if (!screen_opengl_render_init(C, op))
		return OPERATOR_CANCELLED;

	if (anim) {
		if (!screen_opengl_render_anim_initialize(C, op))
			return OPERATOR_CANCELLED;
	}
	
	oglrender = op->customdata;
	render_view_open(C, event->x, event->y, op->reports);
	
	/* view may be changed above (R_OUTPUT_WINDOW) */
	oglrender->win = CTX_wm_window(C);

	WM_event_add_modal_handler(C, op);
	oglrender->timer = WM_event_add_timer(oglrender->wm, oglrender->win, TIMER, 0.01f);
	
	return OPERATOR_RUNNING_MODAL;
}

/* executes blocking render */
static int screen_opengl_render_exec(bContext *C, wmOperator *op)
{
	const bool is_animation = RNA_boolean_get(op->ptr, "animation");

	if (!screen_opengl_render_init(C, op))
		return OPERATOR_CANCELLED;

	if (!is_animation) { /* same as invoke */
		/* render image */
		screen_opengl_render_apply(op->customdata);
		screen_opengl_render_end(C, op->customdata);

		return OPERATOR_FINISHED;
	}
	else {
		bool ret = true;

		if (!screen_opengl_render_anim_initialize(C, op))
			return OPERATOR_CANCELLED;

		while (ret) {
			ret = screen_opengl_render_anim_step(C, op);
		}
	}

	/* no redraw needed, we leave state as we entered it */
//	ED_update_for_newframe(C, 1);
	WM_event_add_notifier(C, NC_SCENE | ND_RENDER_RESULT, CTX_data_scene(C));

	return OPERATOR_FINISHED;
}

void RENDER_OT_opengl(wmOperatorType *ot)
{
	PropertyRNA *prop;

	/* identifiers */
	ot->name = "OpenGL Render";
	ot->description = "OpenGL render active viewport";
	ot->idname = "RENDER_OT_opengl";

	/* api callbacks */
	ot->invoke = screen_opengl_render_invoke;
	ot->exec = screen_opengl_render_exec; /* blocking */
	ot->modal = screen_opengl_render_modal;
	ot->cancel = screen_opengl_render_cancel;

	ot->poll = ED_operator_screenactive;

	prop = RNA_def_boolean(ot->srna, "animation", 0, "Animation", "Render files from the animation range of this scene");
	RNA_def_property_flag(prop, PROP_SKIP_SAVE);
	prop = RNA_def_boolean(ot->srna, "sequencer", 0, "Sequencer", "Render using the sequencer's OpenGL display");
	RNA_def_property_flag(prop, PROP_SKIP_SAVE);
	prop = RNA_def_boolean(ot->srna, "write_still", 0, "Write Image", "Save rendered the image to the output path (used only when animation is disabled)");
	RNA_def_property_flag(prop, PROP_SKIP_SAVE);
	prop = RNA_def_boolean(ot->srna, "view_context", 1, "View Context", "Use the current 3D view for rendering, else use scene settings");
	RNA_def_property_flag(prop, PROP_SKIP_SAVE);

}

/* function for getting an opengl buffer from a View3D, used by sequencer */
// extern void *sequencer_view3d_cb;
