The `crop` field was used by Blender Internal to do an overscan per tile and merge it back to the render result.
		
			
				
	
	
		
			1671 lines
		
	
	
		
			49 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1671 lines
		
	
	
		
			49 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * 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) 2006 Blender Foundation.
 | |
|  * All rights reserved.
 | |
|  */
 | |
| 
 | |
| /** \file
 | |
|  * \ingroup render
 | |
|  */
 | |
| 
 | |
| #include <errno.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| 
 | |
| #include "MEM_guardedalloc.h"
 | |
| 
 | |
| #include "BLI_ghash.h"
 | |
| #include "BLI_hash_md5.h"
 | |
| #include "BLI_listbase.h"
 | |
| #include "BLI_path_util.h"
 | |
| #include "BLI_rect.h"
 | |
| #include "BLI_string.h"
 | |
| #include "BLI_string_utils.h"
 | |
| #include "BLI_threads.h"
 | |
| #include "BLI_utildefines.h"
 | |
| 
 | |
| #include "BKE_appdir.h"
 | |
| #include "BKE_camera.h"
 | |
| #include "BKE_global.h"
 | |
| #include "BKE_image.h"
 | |
| #include "BKE_report.h"
 | |
| #include "BKE_scene.h"
 | |
| 
 | |
| #include "IMB_colormanagement.h"
 | |
| #include "IMB_imbuf.h"
 | |
| #include "IMB_imbuf_types.h"
 | |
| 
 | |
| #include "intern/openexr/openexr_multi.h"
 | |
| 
 | |
| #include "RE_engine.h"
 | |
| 
 | |
| #include "render_result.h"
 | |
| #include "render_types.h"
 | |
| 
 | |
| /********************************** Free *************************************/
 | |
| 
 | |
| static void render_result_views_free(RenderResult *rr)
 | |
| {
 | |
|   while (rr->views.first) {
 | |
|     RenderView *rv = rr->views.first;
 | |
|     BLI_remlink(&rr->views, rv);
 | |
| 
 | |
|     if (rv->rect32) {
 | |
|       MEM_freeN(rv->rect32);
 | |
|     }
 | |
| 
 | |
|     if (rv->rectz) {
 | |
|       MEM_freeN(rv->rectz);
 | |
|     }
 | |
| 
 | |
|     if (rv->rectf) {
 | |
|       MEM_freeN(rv->rectf);
 | |
|     }
 | |
| 
 | |
|     MEM_freeN(rv);
 | |
|   }
 | |
| 
 | |
|   rr->have_combined = false;
 | |
| }
 | |
| 
 | |
| void render_result_free(RenderResult *rr)
 | |
| {
 | |
|   if (rr == NULL) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   while (rr->layers.first) {
 | |
|     RenderLayer *rl = rr->layers.first;
 | |
| 
 | |
|     while (rl->passes.first) {
 | |
|       RenderPass *rpass = rl->passes.first;
 | |
|       if (rpass->rect) {
 | |
|         MEM_freeN(rpass->rect);
 | |
|       }
 | |
|       BLI_remlink(&rl->passes, rpass);
 | |
|       MEM_freeN(rpass);
 | |
|     }
 | |
|     BLI_remlink(&rr->layers, rl);
 | |
|     MEM_freeN(rl);
 | |
|   }
 | |
| 
 | |
|   render_result_views_free(rr);
 | |
| 
 | |
|   if (rr->rect32) {
 | |
|     MEM_freeN(rr->rect32);
 | |
|   }
 | |
|   if (rr->rectz) {
 | |
|     MEM_freeN(rr->rectz);
 | |
|   }
 | |
|   if (rr->rectf) {
 | |
|     MEM_freeN(rr->rectf);
 | |
|   }
 | |
|   if (rr->text) {
 | |
|     MEM_freeN(rr->text);
 | |
|   }
 | |
|   if (rr->error) {
 | |
|     MEM_freeN(rr->error);
 | |
|   }
 | |
| 
 | |
|   BKE_stamp_data_free(rr->stamp_data);
 | |
| 
 | |
|   MEM_freeN(rr);
 | |
| }
 | |
| 
 | |
| /* version that's compatible with fullsample buffers */
 | |
| void render_result_free_list(ListBase *lb, RenderResult *rr)
 | |
| {
 | |
|   RenderResult *rrnext;
 | |
| 
 | |
|   for (; rr; rr = rrnext) {
 | |
|     rrnext = rr->next;
 | |
| 
 | |
|     if (lb && lb->first) {
 | |
|       BLI_remlink(lb, rr);
 | |
|     }
 | |
| 
 | |
|     render_result_free(rr);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /********************************* multiview *************************************/
 | |
| 
 | |
| /* create a new views Listbase in rr without duplicating the memory pointers */
 | |
| void render_result_views_shallowcopy(RenderResult *dst, RenderResult *src)
 | |
| {
 | |
|   RenderView *rview;
 | |
| 
 | |
|   if (dst == NULL || src == NULL) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   for (rview = src->views.first; rview; rview = rview->next) {
 | |
|     RenderView *rv;
 | |
| 
 | |
|     rv = MEM_mallocN(sizeof(RenderView), "new render view");
 | |
|     BLI_addtail(&dst->views, rv);
 | |
| 
 | |
|     BLI_strncpy(rv->name, rview->name, sizeof(rv->name));
 | |
|     rv->rectf = rview->rectf;
 | |
|     rv->rectz = rview->rectz;
 | |
|     rv->rect32 = rview->rect32;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* free the views created temporarily */
 | |
| void render_result_views_shallowdelete(RenderResult *rr)
 | |
| {
 | |
|   if (rr == NULL) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   while (rr->views.first) {
 | |
|     RenderView *rv = rr->views.first;
 | |
|     BLI_remlink(&rr->views, rv);
 | |
|     MEM_freeN(rv);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static char *set_pass_name(char *outname, const char *name, int channel, const char *chan_id)
 | |
| {
 | |
|   const char *strings[2];
 | |
|   int strings_len = 0;
 | |
|   strings[strings_len++] = name;
 | |
|   char token[2];
 | |
|   if (channel >= 0) {
 | |
|     ARRAY_SET_ITEMS(token, chan_id[channel], '\0');
 | |
|     strings[strings_len++] = token;
 | |
|   }
 | |
|   BLI_string_join_array_by_sep_char(outname, EXR_PASS_MAXNAME, '.', strings, strings_len);
 | |
|   return outname;
 | |
| }
 | |
| 
 | |
| static void set_pass_full_name(
 | |
|     char *fullname, const char *name, int channel, const char *view, const char *chan_id)
 | |
| {
 | |
|   const char *strings[3];
 | |
|   int strings_len = 0;
 | |
|   strings[strings_len++] = name;
 | |
|   if (view && view[0]) {
 | |
|     strings[strings_len++] = view;
 | |
|   }
 | |
|   char token[2];
 | |
|   if (channel >= 0) {
 | |
|     ARRAY_SET_ITEMS(token, chan_id[channel], '\0');
 | |
|     strings[strings_len++] = token;
 | |
|   }
 | |
|   BLI_string_join_array_by_sep_char(fullname, EXR_PASS_MAXNAME, '.', strings, strings_len);
 | |
| }
 | |
| 
 | |
| /********************************** New **************************************/
 | |
| 
 | |
| RenderPass *render_layer_add_pass(RenderResult *rr,
 | |
|                                   RenderLayer *rl,
 | |
|                                   int channels,
 | |
|                                   const char *name,
 | |
|                                   const char *viewname,
 | |
|                                   const char *chan_id)
 | |
| {
 | |
|   const int view_id = BLI_findstringindex(&rr->views, viewname, offsetof(RenderView, name));
 | |
|   RenderPass *rpass = MEM_callocN(sizeof(RenderPass), name);
 | |
|   size_t rectsize = ((size_t)rr->rectx) * rr->recty * channels;
 | |
| 
 | |
|   rpass->channels = channels;
 | |
|   rpass->rectx = rl->rectx;
 | |
|   rpass->recty = rl->recty;
 | |
|   rpass->view_id = view_id;
 | |
| 
 | |
|   BLI_strncpy(rpass->name, name, sizeof(rpass->name));
 | |
|   BLI_strncpy(rpass->chan_id, chan_id, sizeof(rpass->chan_id));
 | |
|   BLI_strncpy(rpass->view, viewname, sizeof(rpass->view));
 | |
|   set_pass_full_name(rpass->fullname, rpass->name, -1, rpass->view, rpass->chan_id);
 | |
| 
 | |
|   if (rl->exrhandle) {
 | |
|     int a;
 | |
|     for (a = 0; a < channels; a++) {
 | |
|       char passname[EXR_PASS_MAXNAME];
 | |
|       IMB_exr_add_channel(rl->exrhandle,
 | |
|                           rl->name,
 | |
|                           set_pass_name(passname, rpass->name, a, rpass->chan_id),
 | |
|                           viewname,
 | |
|                           0,
 | |
|                           0,
 | |
|                           NULL,
 | |
|                           false);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* Always allocate combined for display, in case of save buffers
 | |
|    * other passes are not allocated and only saved to the EXR file. */
 | |
|   if (rl->exrhandle == NULL || STREQ(rpass->name, RE_PASSNAME_COMBINED)) {
 | |
|     float *rect;
 | |
|     int x;
 | |
| 
 | |
|     rpass->rect = MEM_callocN(sizeof(float) * rectsize, name);
 | |
|     if (rpass->rect == NULL) {
 | |
|       MEM_freeN(rpass);
 | |
|       return NULL;
 | |
|     }
 | |
| 
 | |
|     if (STREQ(rpass->name, RE_PASSNAME_VECTOR)) {
 | |
|       /* initialize to max speed */
 | |
|       rect = rpass->rect;
 | |
|       for (x = rectsize - 1; x >= 0; x--) {
 | |
|         rect[x] = PASS_VECTOR_MAX;
 | |
|       }
 | |
|     }
 | |
|     else if (STREQ(rpass->name, RE_PASSNAME_Z)) {
 | |
|       rect = rpass->rect;
 | |
|       for (x = rectsize - 1; x >= 0; x--) {
 | |
|         rect[x] = 10e10;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   BLI_addtail(&rl->passes, rpass);
 | |
| 
 | |
|   return rpass;
 | |
| }
 | |
| 
 | |
| /* called by main render as well for parts */
 | |
| /* will read info from Render *re to define layers */
 | |
| /* called in threads */
 | |
| /* re->winx,winy is coordinate space of entire image, partrct the part within */
 | |
| RenderResult *render_result_new(
 | |
|     Render *re, rcti *partrct, int savebuffers, const char *layername, const char *viewname)
 | |
| {
 | |
|   RenderResult *rr;
 | |
|   RenderLayer *rl;
 | |
|   RenderView *rv;
 | |
|   int rectx, recty;
 | |
| 
 | |
|   rectx = BLI_rcti_size_x(partrct);
 | |
|   recty = BLI_rcti_size_y(partrct);
 | |
| 
 | |
|   if (rectx <= 0 || recty <= 0) {
 | |
|     return NULL;
 | |
|   }
 | |
| 
 | |
|   rr = MEM_callocN(sizeof(RenderResult), "new render result");
 | |
|   rr->rectx = rectx;
 | |
|   rr->recty = recty;
 | |
|   rr->renrect.xmin = 0;
 | |
|   rr->renrect.xmax = rectx;
 | |
| 
 | |
|   /* tilerect is relative coordinates within render disprect. do not subtract crop yet */
 | |
|   rr->tilerect.xmin = partrct->xmin - re->disprect.xmin;
 | |
|   rr->tilerect.xmax = partrct->xmax - re->disprect.xmin;
 | |
|   rr->tilerect.ymin = partrct->ymin - re->disprect.ymin;
 | |
|   rr->tilerect.ymax = partrct->ymax - re->disprect.ymin;
 | |
| 
 | |
|   if (savebuffers) {
 | |
|     rr->do_exr_tile = true;
 | |
|   }
 | |
| 
 | |
|   render_result_views_new(rr, &re->r);
 | |
| 
 | |
|   /* check renderdata for amount of layers */
 | |
|   FOREACH_VIEW_LAYER_TO_RENDER_BEGIN (re, view_layer) {
 | |
|     if (layername && layername[0]) {
 | |
|       if (!STREQ(view_layer->name, layername)) {
 | |
|         continue;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     rl = MEM_callocN(sizeof(RenderLayer), "new render layer");
 | |
|     BLI_addtail(&rr->layers, rl);
 | |
| 
 | |
|     BLI_strncpy(rl->name, view_layer->name, sizeof(rl->name));
 | |
|     rl->layflag = view_layer->layflag;
 | |
| 
 | |
|     /* for debugging: view_layer->passflag | SCE_PASS_RAYHITS; */
 | |
|     rl->passflag = view_layer->passflag;
 | |
| 
 | |
|     rl->rectx = rectx;
 | |
|     rl->recty = recty;
 | |
| 
 | |
|     if (rr->do_exr_tile) {
 | |
|       rl->exrhandle = IMB_exr_get_handle();
 | |
|     }
 | |
| 
 | |
|     for (rv = rr->views.first; rv; rv = rv->next) {
 | |
|       const char *view = rv->name;
 | |
| 
 | |
|       if (viewname && viewname[0]) {
 | |
|         if (!STREQ(view, viewname)) {
 | |
|           continue;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (rr->do_exr_tile) {
 | |
|         IMB_exr_add_view(rl->exrhandle, view);
 | |
|       }
 | |
| 
 | |
| #define RENDER_LAYER_ADD_PASS_SAFE(rr, rl, channels, name, viewname, chan_id) \
 | |
|   do { \
 | |
|     if (render_layer_add_pass(rr, rl, channels, name, viewname, chan_id) == NULL) { \
 | |
|       render_result_free(rr); \
 | |
|       return NULL; \
 | |
|     } \
 | |
|   } while (false)
 | |
| 
 | |
|       /* a renderlayer should always have a Combined pass*/
 | |
|       render_layer_add_pass(rr, rl, 4, "Combined", view, "RGBA");
 | |
| 
 | |
|       if (view_layer->passflag & SCE_PASS_Z) {
 | |
|         RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 1, RE_PASSNAME_Z, view, "Z");
 | |
|       }
 | |
|       if (view_layer->passflag & SCE_PASS_VECTOR) {
 | |
|         RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 4, RE_PASSNAME_VECTOR, view, "XYZW");
 | |
|       }
 | |
|       if (view_layer->passflag & SCE_PASS_NORMAL) {
 | |
|         RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 3, RE_PASSNAME_NORMAL, view, "XYZ");
 | |
|       }
 | |
|       if (view_layer->passflag & SCE_PASS_UV) {
 | |
|         RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 3, RE_PASSNAME_UV, view, "UVA");
 | |
|       }
 | |
|       if (view_layer->passflag & SCE_PASS_EMIT) {
 | |
|         RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 3, RE_PASSNAME_EMIT, view, "RGB");
 | |
|       }
 | |
|       if (view_layer->passflag & SCE_PASS_AO) {
 | |
|         RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 3, RE_PASSNAME_AO, view, "RGB");
 | |
|       }
 | |
|       if (view_layer->passflag & SCE_PASS_ENVIRONMENT) {
 | |
|         RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 3, RE_PASSNAME_ENVIRONMENT, view, "RGB");
 | |
|       }
 | |
|       if (view_layer->passflag & SCE_PASS_SHADOW) {
 | |
|         RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 3, RE_PASSNAME_SHADOW, view, "RGB");
 | |
|       }
 | |
|       if (view_layer->passflag & SCE_PASS_INDEXOB) {
 | |
|         RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 1, RE_PASSNAME_INDEXOB, view, "X");
 | |
|       }
 | |
|       if (view_layer->passflag & SCE_PASS_INDEXMA) {
 | |
|         RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 1, RE_PASSNAME_INDEXMA, view, "X");
 | |
|       }
 | |
|       if (view_layer->passflag & SCE_PASS_MIST) {
 | |
|         RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 1, RE_PASSNAME_MIST, view, "Z");
 | |
|       }
 | |
|       if (rl->passflag & SCE_PASS_RAYHITS) {
 | |
|         RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 4, RE_PASSNAME_RAYHITS, view, "RGB");
 | |
|       }
 | |
|       if (view_layer->passflag & SCE_PASS_DIFFUSE_DIRECT) {
 | |
|         RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 3, RE_PASSNAME_DIFFUSE_DIRECT, view, "RGB");
 | |
|       }
 | |
|       if (view_layer->passflag & SCE_PASS_DIFFUSE_INDIRECT) {
 | |
|         RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 3, RE_PASSNAME_DIFFUSE_INDIRECT, view, "RGB");
 | |
|       }
 | |
|       if (view_layer->passflag & SCE_PASS_DIFFUSE_COLOR) {
 | |
|         RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 3, RE_PASSNAME_DIFFUSE_COLOR, view, "RGB");
 | |
|       }
 | |
|       if (view_layer->passflag & SCE_PASS_GLOSSY_DIRECT) {
 | |
|         RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 3, RE_PASSNAME_GLOSSY_DIRECT, view, "RGB");
 | |
|       }
 | |
|       if (view_layer->passflag & SCE_PASS_GLOSSY_INDIRECT) {
 | |
|         RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 3, RE_PASSNAME_GLOSSY_INDIRECT, view, "RGB");
 | |
|       }
 | |
|       if (view_layer->passflag & SCE_PASS_GLOSSY_COLOR) {
 | |
|         RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 3, RE_PASSNAME_GLOSSY_COLOR, view, "RGB");
 | |
|       }
 | |
|       if (view_layer->passflag & SCE_PASS_TRANSM_DIRECT) {
 | |
|         RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 3, RE_PASSNAME_TRANSM_DIRECT, view, "RGB");
 | |
|       }
 | |
|       if (view_layer->passflag & SCE_PASS_TRANSM_INDIRECT) {
 | |
|         RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 3, RE_PASSNAME_TRANSM_INDIRECT, view, "RGB");
 | |
|       }
 | |
|       if (view_layer->passflag & SCE_PASS_TRANSM_COLOR) {
 | |
|         RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 3, RE_PASSNAME_TRANSM_COLOR, view, "RGB");
 | |
|       }
 | |
|       if (view_layer->passflag & SCE_PASS_SUBSURFACE_DIRECT) {
 | |
|         RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 3, RE_PASSNAME_SUBSURFACE_DIRECT, view, "RGB");
 | |
|       }
 | |
|       if (view_layer->passflag & SCE_PASS_SUBSURFACE_INDIRECT) {
 | |
|         RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 3, RE_PASSNAME_SUBSURFACE_INDIRECT, view, "RGB");
 | |
|       }
 | |
|       if (view_layer->passflag & SCE_PASS_SUBSURFACE_COLOR) {
 | |
|         RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 3, RE_PASSNAME_SUBSURFACE_COLOR, view, "RGB");
 | |
|       }
 | |
| #undef RENDER_LAYER_ADD_PASS_SAFE
 | |
|     }
 | |
|   }
 | |
|   FOREACH_VIEW_LAYER_TO_RENDER_END;
 | |
| 
 | |
|   /* previewrender doesn't do layers, so we make a default one */
 | |
|   if (BLI_listbase_is_empty(&rr->layers) && !(layername && layername[0])) {
 | |
|     rl = MEM_callocN(sizeof(RenderLayer), "new render layer");
 | |
|     BLI_addtail(&rr->layers, rl);
 | |
| 
 | |
|     rl->rectx = rectx;
 | |
|     rl->recty = recty;
 | |
| 
 | |
|     /* duplicate code... */
 | |
|     if (rr->do_exr_tile) {
 | |
|       rl->exrhandle = IMB_exr_get_handle();
 | |
|     }
 | |
| 
 | |
|     for (rv = rr->views.first; rv; rv = rv->next) {
 | |
|       const char *view = rv->name;
 | |
| 
 | |
|       if (viewname && viewname[0]) {
 | |
|         if (!STREQ(view, viewname)) {
 | |
|           continue;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (rr->do_exr_tile) {
 | |
|         IMB_exr_add_view(rl->exrhandle, view);
 | |
|       }
 | |
| 
 | |
|       /* a renderlayer should always have a Combined pass */
 | |
|       render_layer_add_pass(rr, rl, 4, RE_PASSNAME_COMBINED, view, "RGBA");
 | |
|     }
 | |
| 
 | |
|     /* note, this has to be in sync with scene.c */
 | |
|     rl->layflag = 0x7FFF; /* solid ztra halo strand */
 | |
|     rl->passflag = SCE_PASS_COMBINED;
 | |
| 
 | |
|     re->active_view_layer = 0;
 | |
|   }
 | |
| 
 | |
|   /* border render; calculate offset for use in compositor. compo is centralized coords */
 | |
|   /* XXX obsolete? I now use it for drawing border render offset (ton) */
 | |
|   rr->xof = re->disprect.xmin + BLI_rcti_cent_x(&re->disprect) - (re->winx / 2);
 | |
|   rr->yof = re->disprect.ymin + BLI_rcti_cent_y(&re->disprect) - (re->winy / 2);
 | |
| 
 | |
|   return rr;
 | |
| }
 | |
| 
 | |
| void render_result_clone_passes(Render *re, RenderResult *rr, const char *viewname)
 | |
| {
 | |
|   RenderLayer *rl;
 | |
|   RenderPass *main_rp;
 | |
| 
 | |
|   for (rl = rr->layers.first; rl; rl = rl->next) {
 | |
|     RenderLayer *main_rl = BLI_findstring(
 | |
|         &re->result->layers, rl->name, offsetof(RenderLayer, name));
 | |
|     if (!main_rl) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     for (main_rp = main_rl->passes.first; main_rp; main_rp = main_rp->next) {
 | |
|       if (viewname && viewname[0] && !STREQ(main_rp->view, viewname)) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       /* Compare fullname to make sure that the view also is equal. */
 | |
|       RenderPass *rp = BLI_findstring(
 | |
|           &rl->passes, main_rp->fullname, offsetof(RenderPass, fullname));
 | |
|       if (!rp) {
 | |
|         render_layer_add_pass(
 | |
|             rr, rl, main_rp->channels, main_rp->name, main_rp->view, main_rp->chan_id);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void RE_create_render_pass(RenderResult *rr,
 | |
|                            const char *name,
 | |
|                            int channels,
 | |
|                            const char *chan_id,
 | |
|                            const char *layername,
 | |
|                            const char *viewname)
 | |
| {
 | |
|   RenderLayer *rl;
 | |
|   RenderPass *rp;
 | |
|   RenderView *rv;
 | |
| 
 | |
|   for (rl = rr->layers.first; rl; rl = rl->next) {
 | |
|     if (layername && layername[0] && !STREQ(rl->name, layername)) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     for (rv = rr->views.first; rv; rv = rv->next) {
 | |
|       const char *view = rv->name;
 | |
| 
 | |
|       if (viewname && viewname[0] && !STREQ(view, viewname)) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       /* Ensure that the pass doesn't exist yet. */
 | |
|       for (rp = rl->passes.first; rp; rp = rp->next) {
 | |
|         if (!STREQ(rp->name, name)) {
 | |
|           continue;
 | |
|         }
 | |
|         if (!STREQ(rp->view, view)) {
 | |
|           continue;
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       if (!rp) {
 | |
|         render_layer_add_pass(rr, rl, channels, name, view, chan_id);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static int passtype_from_name(const char *name)
 | |
| {
 | |
|   const char delim[] = {'.', '\0'};
 | |
|   const char *sep, *suf;
 | |
|   int len = BLI_str_partition(name, delim, &sep, &suf);
 | |
| 
 | |
| #define CHECK_PASS(NAME) \
 | |
|   if (STREQLEN(name, RE_PASSNAME_##NAME, len)) { \
 | |
|     return SCE_PASS_##NAME; \
 | |
|   } \
 | |
|   ((void)0)
 | |
| 
 | |
|   CHECK_PASS(COMBINED);
 | |
|   CHECK_PASS(Z);
 | |
|   CHECK_PASS(VECTOR);
 | |
|   CHECK_PASS(NORMAL);
 | |
|   CHECK_PASS(UV);
 | |
|   CHECK_PASS(EMIT);
 | |
|   CHECK_PASS(SHADOW);
 | |
|   CHECK_PASS(AO);
 | |
|   CHECK_PASS(ENVIRONMENT);
 | |
|   CHECK_PASS(INDEXOB);
 | |
|   CHECK_PASS(INDEXMA);
 | |
|   CHECK_PASS(MIST);
 | |
|   CHECK_PASS(RAYHITS);
 | |
|   CHECK_PASS(DIFFUSE_DIRECT);
 | |
|   CHECK_PASS(DIFFUSE_INDIRECT);
 | |
|   CHECK_PASS(DIFFUSE_COLOR);
 | |
|   CHECK_PASS(GLOSSY_DIRECT);
 | |
|   CHECK_PASS(GLOSSY_INDIRECT);
 | |
|   CHECK_PASS(GLOSSY_COLOR);
 | |
|   CHECK_PASS(TRANSM_DIRECT);
 | |
|   CHECK_PASS(TRANSM_INDIRECT);
 | |
|   CHECK_PASS(TRANSM_COLOR);
 | |
|   CHECK_PASS(SUBSURFACE_DIRECT);
 | |
|   CHECK_PASS(SUBSURFACE_INDIRECT);
 | |
|   CHECK_PASS(SUBSURFACE_COLOR);
 | |
| 
 | |
| #undef CHECK_PASS
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /* callbacks for render_result_new_from_exr */
 | |
| static void *ml_addlayer_cb(void *base, const char *str)
 | |
| {
 | |
|   RenderResult *rr = base;
 | |
|   RenderLayer *rl;
 | |
| 
 | |
|   rl = MEM_callocN(sizeof(RenderLayer), "new render layer");
 | |
|   BLI_addtail(&rr->layers, rl);
 | |
| 
 | |
|   BLI_strncpy(rl->name, str, EXR_LAY_MAXNAME);
 | |
|   return rl;
 | |
| }
 | |
| 
 | |
| static void ml_addpass_cb(void *base,
 | |
|                           void *lay,
 | |
|                           const char *name,
 | |
|                           float *rect,
 | |
|                           int totchan,
 | |
|                           const char *chan_id,
 | |
|                           const char *view)
 | |
| {
 | |
|   RenderResult *rr = base;
 | |
|   RenderLayer *rl = lay;
 | |
|   RenderPass *rpass = MEM_callocN(sizeof(RenderPass), "loaded pass");
 | |
| 
 | |
|   BLI_addtail(&rl->passes, rpass);
 | |
|   rpass->channels = totchan;
 | |
|   rl->passflag |= passtype_from_name(name);
 | |
| 
 | |
|   /* channel id chars */
 | |
|   BLI_strncpy(rpass->chan_id, chan_id, sizeof(rpass->chan_id));
 | |
| 
 | |
|   rpass->rect = rect;
 | |
|   BLI_strncpy(rpass->name, name, EXR_PASS_MAXNAME);
 | |
|   BLI_strncpy(rpass->view, view, sizeof(rpass->view));
 | |
|   set_pass_full_name(rpass->fullname, name, -1, view, rpass->chan_id);
 | |
| 
 | |
|   if (view[0] != '\0') {
 | |
|     rpass->view_id = BLI_findstringindex(&rr->views, view, offsetof(RenderView, name));
 | |
|   }
 | |
|   else {
 | |
|     rpass->view_id = 0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void *ml_addview_cb(void *base, const char *str)
 | |
| {
 | |
|   RenderResult *rr = base;
 | |
|   RenderView *rv;
 | |
| 
 | |
|   rv = MEM_callocN(sizeof(RenderView), "new render view");
 | |
|   BLI_strncpy(rv->name, str, EXR_VIEW_MAXNAME);
 | |
| 
 | |
|   /* For stereo drawing we need to ensure:
 | |
|    * STEREO_LEFT_NAME  == STEREO_LEFT_ID and
 | |
|    * STEREO_RIGHT_NAME == STEREO_RIGHT_ID */
 | |
| 
 | |
|   if (STREQ(str, STEREO_LEFT_NAME)) {
 | |
|     BLI_addhead(&rr->views, rv);
 | |
|   }
 | |
|   else if (STREQ(str, STEREO_RIGHT_NAME)) {
 | |
|     RenderView *left_rv = BLI_findstring(&rr->views, STEREO_LEFT_NAME, offsetof(RenderView, name));
 | |
| 
 | |
|     if (left_rv == NULL) {
 | |
|       BLI_addhead(&rr->views, rv);
 | |
|     }
 | |
|     else {
 | |
|       BLI_insertlinkafter(&rr->views, left_rv, rv);
 | |
|     }
 | |
|   }
 | |
|   else {
 | |
|     BLI_addtail(&rr->views, rv);
 | |
|   }
 | |
| 
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| static int order_render_passes(const void *a, const void *b)
 | |
| {
 | |
|   // 1 if a is after b
 | |
|   RenderPass *rpa = (RenderPass *)a;
 | |
|   RenderPass *rpb = (RenderPass *)b;
 | |
|   unsigned int passtype_a = passtype_from_name(rpa->name);
 | |
|   unsigned int passtype_b = passtype_from_name(rpb->name);
 | |
| 
 | |
|   /* Render passes with default type always go first. */
 | |
|   if (passtype_b && !passtype_a) {
 | |
|     return 1;
 | |
|   }
 | |
|   if (passtype_a && !passtype_b) {
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   if (passtype_a && passtype_b) {
 | |
|     if (passtype_a > passtype_b) {
 | |
|       return 1;
 | |
|     }
 | |
|     if (passtype_a < passtype_b) {
 | |
|       return 0;
 | |
|     }
 | |
|   }
 | |
|   else {
 | |
|     int cmp = strncmp(rpa->name, rpb->name, EXR_PASS_MAXNAME);
 | |
|     if (cmp > 0) {
 | |
|       return 1;
 | |
|     }
 | |
|     if (cmp < 0) {
 | |
|       return 0;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* they have the same type */
 | |
|   /* left first */
 | |
|   if (STREQ(rpa->view, STEREO_LEFT_NAME)) {
 | |
|     return 0;
 | |
|   }
 | |
|   if (STREQ(rpb->view, STEREO_LEFT_NAME)) {
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   /* right second */
 | |
|   if (STREQ(rpa->view, STEREO_RIGHT_NAME)) {
 | |
|     return 0;
 | |
|   }
 | |
|   if (STREQ(rpb->view, STEREO_RIGHT_NAME)) {
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   /* remaining in ascending id order */
 | |
|   return (rpa->view_id < rpb->view_id);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * From imbuf, if a handle was returned and
 | |
|  * it's not a single-layer multi-view we convert this to render result.
 | |
|  */
 | |
| RenderResult *render_result_new_from_exr(
 | |
|     void *exrhandle, const char *colorspace, bool predivide, int rectx, int recty)
 | |
| {
 | |
|   RenderResult *rr = MEM_callocN(sizeof(RenderResult), __func__);
 | |
|   RenderLayer *rl;
 | |
|   RenderPass *rpass;
 | |
|   const char *to_colorspace = IMB_colormanagement_role_colorspace_name_get(
 | |
|       COLOR_ROLE_SCENE_LINEAR);
 | |
| 
 | |
|   rr->rectx = rectx;
 | |
|   rr->recty = recty;
 | |
| 
 | |
|   IMB_exr_multilayer_convert(exrhandle, rr, ml_addview_cb, ml_addlayer_cb, ml_addpass_cb);
 | |
| 
 | |
|   for (rl = rr->layers.first; rl; rl = rl->next) {
 | |
|     rl->rectx = rectx;
 | |
|     rl->recty = recty;
 | |
| 
 | |
|     BLI_listbase_sort(&rl->passes, order_render_passes);
 | |
| 
 | |
|     for (rpass = rl->passes.first; rpass; rpass = rpass->next) {
 | |
|       rpass->rectx = rectx;
 | |
|       rpass->recty = recty;
 | |
| 
 | |
|       if (rpass->channels >= 3) {
 | |
|         IMB_colormanagement_transform(rpass->rect,
 | |
|                                       rpass->rectx,
 | |
|                                       rpass->recty,
 | |
|                                       rpass->channels,
 | |
|                                       colorspace,
 | |
|                                       to_colorspace,
 | |
|                                       predivide);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return rr;
 | |
| }
 | |
| 
 | |
| void render_result_view_new(RenderResult *rr, const char *viewname)
 | |
| {
 | |
|   RenderView *rv = MEM_callocN(sizeof(RenderView), "new render view");
 | |
|   BLI_addtail(&rr->views, rv);
 | |
|   BLI_strncpy(rv->name, viewname, sizeof(rv->name));
 | |
| }
 | |
| 
 | |
| void render_result_views_new(RenderResult *rr, const RenderData *rd)
 | |
| {
 | |
|   SceneRenderView *srv;
 | |
| 
 | |
|   /* clear previously existing views - for sequencer */
 | |
|   render_result_views_free(rr);
 | |
| 
 | |
|   /* check renderdata for amount of views */
 | |
|   if ((rd->scemode & R_MULTIVIEW)) {
 | |
|     for (srv = rd->views.first; srv; srv = srv->next) {
 | |
|       if (BKE_scene_multiview_is_render_view_active(rd, srv) == false) {
 | |
|         continue;
 | |
|       }
 | |
|       render_result_view_new(rr, srv->name);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* we always need at least one view */
 | |
|   if (BLI_listbase_count_at_most(&rr->views, 1) == 0) {
 | |
|     render_result_view_new(rr, "");
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool render_result_has_views(RenderResult *rr)
 | |
| {
 | |
|   RenderView *rv = rr->views.first;
 | |
|   return (rv && (rv->next || rv->name[0]));
 | |
| }
 | |
| 
 | |
| /*********************************** Merge ***********************************/
 | |
| 
 | |
| static void do_merge_tile(
 | |
|     RenderResult *rr, RenderResult *rrpart, float *target, float *tile, int pixsize)
 | |
| {
 | |
|   int y, tilex, tiley;
 | |
|   size_t ofs, copylen;
 | |
| 
 | |
|   copylen = tilex = rrpart->rectx;
 | |
|   tiley = rrpart->recty;
 | |
| 
 | |
|   ofs = (((size_t)rrpart->tilerect.ymin) * rr->rectx + rrpart->tilerect.xmin);
 | |
|   target += pixsize * ofs;
 | |
| 
 | |
|   copylen *= sizeof(float) * pixsize;
 | |
|   tilex *= pixsize;
 | |
|   ofs = pixsize * rr->rectx;
 | |
| 
 | |
|   for (y = 0; y < tiley; y++) {
 | |
|     memcpy(target, tile, copylen);
 | |
|     target += ofs;
 | |
|     tile += tilex;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* used when rendering to a full buffer, or when reading the exr part-layer-pass file */
 | |
| /* no test happens here if it fits... we also assume layers are in sync */
 | |
| /* is used within threads */
 | |
| void render_result_merge(RenderResult *rr, RenderResult *rrpart)
 | |
| {
 | |
|   RenderLayer *rl, *rlp;
 | |
|   RenderPass *rpass, *rpassp;
 | |
| 
 | |
|   for (rl = rr->layers.first; rl; rl = rl->next) {
 | |
|     rlp = RE_GetRenderLayer(rrpart, rl->name);
 | |
|     if (rlp) {
 | |
|       /* Passes are allocated in sync. */
 | |
|       for (rpass = rl->passes.first, rpassp = rlp->passes.first; rpass && rpassp;
 | |
|            rpass = rpass->next) {
 | |
|         /* For save buffers, skip any passes that are only saved to disk. */
 | |
|         if (rpass->rect == NULL || rpassp->rect == NULL) {
 | |
|           continue;
 | |
|         }
 | |
|         /* Renderresult have all passes, renderpart only the active view's passes. */
 | |
|         if (!STREQ(rpassp->fullname, rpass->fullname)) {
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         do_merge_tile(rr, rrpart, rpass->rect, rpassp->rect, rpass->channels);
 | |
| 
 | |
|         /* manually get next render pass */
 | |
|         rpassp = rpassp->next;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* Called from the UI and render pipeline, to save multilayer and multiview
 | |
|  * images, optionally isolating a specific, view, layer or RGBA/Z pass. */
 | |
| bool RE_WriteRenderResult(ReportList *reports,
 | |
|                           RenderResult *rr,
 | |
|                           const char *filename,
 | |
|                           ImageFormatData *imf,
 | |
|                           const char *view,
 | |
|                           int layer)
 | |
| {
 | |
|   void *exrhandle = IMB_exr_get_handle();
 | |
|   const bool half_float = (imf && imf->depth == R_IMF_CHAN_DEPTH_16);
 | |
|   const bool multi_layer = !(imf && imf->imtype == R_IMF_IMTYPE_OPENEXR);
 | |
|   const bool write_z = !multi_layer && (imf && (imf->flag & R_IMF_FLAG_ZBUF));
 | |
| 
 | |
|   /* Write first layer if not multilayer and no layer was specified. */
 | |
|   if (!multi_layer && layer == -1) {
 | |
|     layer = 0;
 | |
|   }
 | |
| 
 | |
|   /* First add views since IMB_exr_add_channel checks number of views. */
 | |
|   if (render_result_has_views(rr)) {
 | |
|     LISTBASE_FOREACH (RenderView *, rview, &rr->views) {
 | |
|       if (!view || STREQ(view, rview->name)) {
 | |
|         IMB_exr_add_view(exrhandle, rview->name);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* Compositing result. */
 | |
|   if (rr->have_combined) {
 | |
|     LISTBASE_FOREACH (RenderView *, rview, &rr->views) {
 | |
|       if (!rview->rectf) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       const char *viewname = rview->name;
 | |
|       if (view) {
 | |
|         if (!STREQ(view, viewname)) {
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         viewname = "";
 | |
|       }
 | |
| 
 | |
|       /* Skip compositing if only a single other layer is requested. */
 | |
|       if (!multi_layer && layer != 0) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       for (int a = 0; a < 4; a++) {
 | |
|         char passname[EXR_PASS_MAXNAME];
 | |
|         char layname[EXR_PASS_MAXNAME];
 | |
|         const char *chan_id = "RGBA";
 | |
| 
 | |
|         if (multi_layer) {
 | |
|           set_pass_name(passname, "Combined", a, chan_id);
 | |
|           BLI_strncpy(layname, "Composite", sizeof(layname));
 | |
|         }
 | |
|         else {
 | |
|           passname[0] = chan_id[a];
 | |
|           passname[1] = '\0';
 | |
|           layname[0] = '\0';
 | |
|         }
 | |
| 
 | |
|         IMB_exr_add_channel(exrhandle,
 | |
|                             layname,
 | |
|                             passname,
 | |
|                             viewname,
 | |
|                             4,
 | |
|                             4 * rr->rectx,
 | |
|                             rview->rectf + a,
 | |
|                             half_float);
 | |
|       }
 | |
| 
 | |
|       if (write_z && rview->rectz) {
 | |
|         const char *layname = (multi_layer) ? "Composite" : "";
 | |
|         IMB_exr_add_channel(exrhandle, layname, "Z", viewname, 1, rr->rectx, rview->rectz, false);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* Other render layers. */
 | |
|   int nr = (rr->have_combined) ? 1 : 0;
 | |
|   for (RenderLayer *rl = rr->layers.first; rl; rl = rl->next, nr++) {
 | |
|     /* Skip other render layers if requested. */
 | |
|     if (!multi_layer && nr != layer) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     LISTBASE_FOREACH (RenderPass *, rp, &rl->passes) {
 | |
|       /* Skip non-RGBA and Z passes if not using multi layer. */
 | |
|       if (!multi_layer && !(STREQ(rp->name, RE_PASSNAME_COMBINED) || STREQ(rp->name, "") ||
 | |
|                             (STREQ(rp->name, RE_PASSNAME_Z) && write_z))) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       /* Skip pass if it does not match the requested view(s). */
 | |
|       const char *viewname = rp->view;
 | |
|       if (view) {
 | |
|         if (!STREQ(view, viewname)) {
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         viewname = "";
 | |
|       }
 | |
| 
 | |
|       /* We only store RGBA passes as half float, for
 | |
|        * others precision loss can be problematic. */
 | |
|       bool pass_half_float = half_float &&
 | |
|                              (STR_ELEM(rp->chan_id, "RGB", "RGBA", "R", "G", "B", "A"));
 | |
| 
 | |
|       for (int a = 0; a < rp->channels; a++) {
 | |
|         /* Save Combined as RGBA if single layer save. */
 | |
|         char passname[EXR_PASS_MAXNAME];
 | |
|         char layname[EXR_PASS_MAXNAME];
 | |
| 
 | |
|         if (multi_layer) {
 | |
|           set_pass_name(passname, rp->name, a, rp->chan_id);
 | |
|           BLI_strncpy(layname, rl->name, sizeof(layname));
 | |
|         }
 | |
|         else {
 | |
|           passname[0] = rp->chan_id[a];
 | |
|           passname[1] = '\0';
 | |
|           layname[0] = '\0';
 | |
|         }
 | |
| 
 | |
|         IMB_exr_add_channel(exrhandle,
 | |
|                             layname,
 | |
|                             passname,
 | |
|                             viewname,
 | |
|                             rp->channels,
 | |
|                             rp->channels * rr->rectx,
 | |
|                             rp->rect + a,
 | |
|                             pass_half_float);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   errno = 0;
 | |
| 
 | |
|   BLI_make_existing_file(filename);
 | |
| 
 | |
|   int compress = (imf ? imf->exr_codec : 0);
 | |
|   bool success = IMB_exr_begin_write(
 | |
|       exrhandle, filename, rr->rectx, rr->recty, compress, rr->stamp_data);
 | |
|   if (success) {
 | |
|     IMB_exr_write_channels(exrhandle);
 | |
|   }
 | |
|   else {
 | |
|     /* TODO, get the error from openexr's exception */
 | |
|     BKE_reportf(
 | |
|         reports, RPT_ERROR, "Error writing render result, %s (see console)", strerror(errno));
 | |
|   }
 | |
| 
 | |
|   IMB_exr_close(exrhandle);
 | |
|   return success;
 | |
| }
 | |
| 
 | |
| /**************************** Single Layer Rendering *************************/
 | |
| 
 | |
| void render_result_single_layer_begin(Render *re)
 | |
| {
 | |
|   /* all layers except the active one get temporally pushed away */
 | |
| 
 | |
|   /* officially pushed result should be NULL... error can happen with do_seq */
 | |
|   RE_FreeRenderResult(re->pushedresult);
 | |
| 
 | |
|   re->pushedresult = re->result;
 | |
|   re->result = NULL;
 | |
| }
 | |
| 
 | |
| /* if scemode is R_SINGLE_LAYER, at end of rendering, merge the both render results */
 | |
| void render_result_single_layer_end(Render *re)
 | |
| {
 | |
|   ViewLayer *view_layer;
 | |
|   RenderLayer *rlpush;
 | |
|   RenderLayer *rl;
 | |
|   int nr;
 | |
| 
 | |
|   if (re->result == NULL) {
 | |
|     printf("pop render result error; no current result!\n");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!re->pushedresult) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (re->pushedresult->rectx == re->result->rectx &&
 | |
|       re->pushedresult->recty == re->result->recty) {
 | |
|     /* find which layer in re->pushedresult should be replaced */
 | |
|     rl = re->result->layers.first;
 | |
| 
 | |
|     /* render result should be empty after this */
 | |
|     BLI_remlink(&re->result->layers, rl);
 | |
| 
 | |
|     /* reconstruct render result layers */
 | |
|     for (nr = 0, view_layer = re->view_layers.first; view_layer;
 | |
|          view_layer = view_layer->next, nr++) {
 | |
|       if (nr == re->active_view_layer) {
 | |
|         BLI_addtail(&re->result->layers, rl);
 | |
|       }
 | |
|       else {
 | |
|         rlpush = RE_GetRenderLayer(re->pushedresult, view_layer->name);
 | |
|         if (rlpush) {
 | |
|           BLI_remlink(&re->pushedresult->layers, rlpush);
 | |
|           BLI_addtail(&re->result->layers, rlpush);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   RE_FreeRenderResult(re->pushedresult);
 | |
|   re->pushedresult = NULL;
 | |
| }
 | |
| 
 | |
| /************************* EXR Tile File Rendering ***************************/
 | |
| 
 | |
| static void save_render_result_tile(RenderResult *rr, RenderResult *rrpart, const char *viewname)
 | |
| {
 | |
|   RenderLayer *rlp, *rl;
 | |
|   RenderPass *rpassp;
 | |
|   int partx, party;
 | |
| 
 | |
|   BLI_thread_lock(LOCK_IMAGE);
 | |
| 
 | |
|   for (rlp = rrpart->layers.first; rlp; rlp = rlp->next) {
 | |
|     rl = RE_GetRenderLayer(rr, rlp->name);
 | |
| 
 | |
|     /* should never happen but prevents crash if it does */
 | |
|     BLI_assert(rl);
 | |
|     if (UNLIKELY(rl == NULL)) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     /* passes are allocated in sync */
 | |
|     for (rpassp = rlp->passes.first; rpassp; rpassp = rpassp->next) {
 | |
|       const int xstride = rpassp->channels;
 | |
|       int a;
 | |
|       char fullname[EXR_PASS_MAXNAME];
 | |
| 
 | |
|       for (a = 0; a < xstride; a++) {
 | |
|         set_pass_full_name(fullname, rpassp->name, a, viewname, rpassp->chan_id);
 | |
| 
 | |
|         IMB_exr_set_channel(rl->exrhandle,
 | |
|                             rlp->name,
 | |
|                             fullname,
 | |
|                             xstride,
 | |
|                             xstride * rrpart->rectx,
 | |
|                             rpassp->rect + a);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   party = rrpart->tilerect.ymin;
 | |
|   partx = rrpart->tilerect.xmin;
 | |
| 
 | |
|   for (rlp = rrpart->layers.first; rlp; rlp = rlp->next) {
 | |
|     rl = RE_GetRenderLayer(rr, rlp->name);
 | |
| 
 | |
|     /* should never happen but prevents crash if it does */
 | |
|     BLI_assert(rl);
 | |
|     if (UNLIKELY(rl == NULL)) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     IMB_exrtile_write_channels(rl->exrhandle, partx, party, 0, viewname, false);
 | |
|   }
 | |
| 
 | |
|   BLI_thread_unlock(LOCK_IMAGE);
 | |
| }
 | |
| 
 | |
| void render_result_save_empty_result_tiles(Render *re)
 | |
| {
 | |
|   RenderResult *rr;
 | |
|   RenderLayer *rl;
 | |
| 
 | |
|   for (rr = re->result; rr; rr = rr->next) {
 | |
|     for (rl = rr->layers.first; rl; rl = rl->next) {
 | |
|       GHashIterator pa_iter;
 | |
|       GHASH_ITER (pa_iter, re->parts) {
 | |
|         RenderPart *pa = BLI_ghashIterator_getValue(&pa_iter);
 | |
|         if (pa->status != PART_STATUS_MERGED) {
 | |
|           int party = pa->disprect.ymin - re->disprect.ymin;
 | |
|           int partx = pa->disprect.xmin - re->disprect.xmin;
 | |
|           IMB_exrtile_write_channels(rl->exrhandle, partx, party, 0, re->viewname, true);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* Compute list of passes needed by render engine. */
 | |
| static void templates_register_pass_cb(void *userdata,
 | |
|                                        Scene *UNUSED(scene),
 | |
|                                        ViewLayer *UNUSED(view_layer),
 | |
|                                        const char *name,
 | |
|                                        int channels,
 | |
|                                        const char *chan_id,
 | |
|                                        eNodeSocketDatatype UNUSED(type))
 | |
| {
 | |
|   ListBase *templates = userdata;
 | |
|   RenderPass *pass = MEM_callocN(sizeof(RenderPass), "RenderPassTemplate");
 | |
| 
 | |
|   pass->channels = channels;
 | |
|   BLI_strncpy(pass->name, name, sizeof(pass->name));
 | |
|   BLI_strncpy(pass->chan_id, chan_id, sizeof(pass->chan_id));
 | |
| 
 | |
|   BLI_addtail(templates, pass);
 | |
| }
 | |
| 
 | |
| static void render_result_get_pass_templates(RenderEngine *engine,
 | |
|                                              Render *re,
 | |
|                                              RenderLayer *rl,
 | |
|                                              ListBase *templates)
 | |
| {
 | |
|   BLI_listbase_clear(templates);
 | |
| 
 | |
|   if (engine && engine->type->update_render_passes) {
 | |
|     ViewLayer *view_layer = BLI_findstring(&re->view_layers, rl->name, offsetof(ViewLayer, name));
 | |
|     if (view_layer) {
 | |
|       RE_engine_update_render_passes(
 | |
|           engine, re->scene, view_layer, templates_register_pass_cb, templates);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* begin write of exr tile file */
 | |
| void render_result_exr_file_begin(Render *re, RenderEngine *engine)
 | |
| {
 | |
|   char str[FILE_MAX];
 | |
| 
 | |
|   for (RenderResult *rr = re->result; rr; rr = rr->next) {
 | |
|     LISTBASE_FOREACH (RenderLayer *, rl, &rr->layers) {
 | |
|       /* Get passes needed by engine. Normally we would wait for the
 | |
|        * engine to create them, but for EXR file we need to know in
 | |
|        * advance. */
 | |
|       ListBase templates;
 | |
|       render_result_get_pass_templates(engine, re, rl, &templates);
 | |
| 
 | |
|       /* Create render passes requested by engine. Only this part is
 | |
|        * mutex locked to avoid deadlock with Python GIL. */
 | |
|       BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE);
 | |
|       LISTBASE_FOREACH (RenderPass *, pass, &templates) {
 | |
|         RE_create_render_pass(
 | |
|             re->result, pass->name, pass->channels, pass->chan_id, rl->name, NULL);
 | |
|       }
 | |
|       BLI_rw_mutex_unlock(&re->resultmutex);
 | |
| 
 | |
|       BLI_freelistN(&templates);
 | |
| 
 | |
|       /* Open EXR file for writing. */
 | |
|       render_result_exr_file_path(re->scene, rl->name, rr->sample_nr, str);
 | |
|       printf("write exr tmp file, %dx%d, %s\n", rr->rectx, rr->recty, str);
 | |
|       IMB_exrtile_begin_write(rl->exrhandle, str, 0, rr->rectx, rr->recty, re->partx, re->party);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* end write of exr tile file, read back first sample */
 | |
| void render_result_exr_file_end(Render *re, RenderEngine *engine)
 | |
| {
 | |
|   /* Close EXR files. */
 | |
|   for (RenderResult *rr = re->result; rr; rr = rr->next) {
 | |
|     LISTBASE_FOREACH (RenderLayer *, rl, &rr->layers) {
 | |
|       IMB_exr_close(rl->exrhandle);
 | |
|       rl->exrhandle = NULL;
 | |
|     }
 | |
| 
 | |
|     rr->do_exr_tile = false;
 | |
|   }
 | |
| 
 | |
|   /* Create new render result in memory instead of on disk. */
 | |
|   BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE);
 | |
|   render_result_free_list(&re->fullresult, re->result);
 | |
|   re->result = render_result_new(re, &re->disprect, RR_USE_MEM, RR_ALL_LAYERS, RR_ALL_VIEWS);
 | |
|   BLI_rw_mutex_unlock(&re->resultmutex);
 | |
| 
 | |
|   LISTBASE_FOREACH (RenderLayer *, rl, &re->result->layers) {
 | |
|     /* Get passes needed by engine. */
 | |
|     ListBase templates;
 | |
|     render_result_get_pass_templates(engine, re, rl, &templates);
 | |
| 
 | |
|     /* Create render passes requested by engine. Only this part is
 | |
|      * mutex locked to avoid deadlock with Python GIL. */
 | |
|     BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE);
 | |
|     LISTBASE_FOREACH (RenderPass *, pass, &templates) {
 | |
|       RE_create_render_pass(re->result, pass->name, pass->channels, pass->chan_id, rl->name, NULL);
 | |
|     }
 | |
| 
 | |
|     BLI_freelistN(&templates);
 | |
| 
 | |
|     /* Render passes contents from file. */
 | |
|     char str[FILE_MAXFILE + MAX_ID_NAME + MAX_ID_NAME + 100] = "";
 | |
|     render_result_exr_file_path(re->scene, rl->name, 0, str);
 | |
|     printf("read exr tmp file: %s\n", str);
 | |
| 
 | |
|     if (!render_result_exr_file_read_path(re->result, rl, str)) {
 | |
|       printf("cannot read: %s\n", str);
 | |
|     }
 | |
|     BLI_rw_mutex_unlock(&re->resultmutex);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* save part into exr file */
 | |
| void render_result_exr_file_merge(RenderResult *rr, RenderResult *rrpart, const char *viewname)
 | |
| {
 | |
|   for (; rr && rrpart; rr = rr->next, rrpart = rrpart->next) {
 | |
|     save_render_result_tile(rr, rrpart, viewname);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* path to temporary exr file */
 | |
| void render_result_exr_file_path(Scene *scene, const char *layname, int sample, char *filepath)
 | |
| {
 | |
|   char name[FILE_MAXFILE + MAX_ID_NAME + MAX_ID_NAME + 100];
 | |
|   const char *fi = BLI_path_basename(BKE_main_blendfile_path_from_global());
 | |
| 
 | |
|   if (sample == 0) {
 | |
|     BLI_snprintf(name, sizeof(name), "%s_%s_%s.exr", fi, scene->id.name + 2, layname);
 | |
|   }
 | |
|   else {
 | |
|     BLI_snprintf(name, sizeof(name), "%s_%s_%s%d.exr", fi, scene->id.name + 2, layname, sample);
 | |
|   }
 | |
| 
 | |
|   /* Make name safe for paths, see T43275. */
 | |
|   BLI_filename_make_safe(name);
 | |
| 
 | |
|   BLI_join_dirfile(filepath, FILE_MAX, BKE_tempdir_session(), name);
 | |
| }
 | |
| 
 | |
| /* called for reading temp files, and for external engines */
 | |
| int render_result_exr_file_read_path(RenderResult *rr,
 | |
|                                      RenderLayer *rl_single,
 | |
|                                      const char *filepath)
 | |
| {
 | |
|   RenderLayer *rl;
 | |
|   RenderPass *rpass;
 | |
|   void *exrhandle = IMB_exr_get_handle();
 | |
|   int rectx, recty;
 | |
| 
 | |
|   if (IMB_exr_begin_read(exrhandle, filepath, &rectx, &recty) == 0) {
 | |
|     printf("failed being read %s\n", filepath);
 | |
|     IMB_exr_close(exrhandle);
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   if (rr == NULL || rectx != rr->rectx || recty != rr->recty) {
 | |
|     if (rr) {
 | |
|       printf("error in reading render result: dimensions don't match\n");
 | |
|     }
 | |
|     else {
 | |
|       printf("error in reading render result: NULL result pointer\n");
 | |
|     }
 | |
|     IMB_exr_close(exrhandle);
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   for (rl = rr->layers.first; rl; rl = rl->next) {
 | |
|     if (rl_single && rl_single != rl) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     /* passes are allocated in sync */
 | |
|     for (rpass = rl->passes.first; rpass; rpass = rpass->next) {
 | |
|       const int xstride = rpass->channels;
 | |
|       int a;
 | |
|       char fullname[EXR_PASS_MAXNAME];
 | |
| 
 | |
|       for (a = 0; a < xstride; a++) {
 | |
|         set_pass_full_name(fullname, rpass->name, a, rpass->view, rpass->chan_id);
 | |
|         IMB_exr_set_channel(
 | |
|             exrhandle, rl->name, fullname, xstride, xstride * rectx, rpass->rect + a);
 | |
|       }
 | |
| 
 | |
|       set_pass_full_name(rpass->fullname, rpass->name, -1, rpass->view, rpass->chan_id);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   IMB_exr_read_channels(exrhandle);
 | |
|   IMB_exr_close(exrhandle);
 | |
| 
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| static void render_result_exr_file_cache_path(Scene *sce, const char *root, char *r_path)
 | |
| {
 | |
|   char filename_full[FILE_MAX + MAX_ID_NAME + 100], filename[FILE_MAXFILE], dirname[FILE_MAXDIR];
 | |
|   char path_digest[16] = {0};
 | |
|   char path_hexdigest[33];
 | |
| 
 | |
|   /* If root is relative, use either current .blend file dir, or temp one if not saved. */
 | |
|   const char *blendfile_path = BKE_main_blendfile_path_from_global();
 | |
|   if (blendfile_path[0] != '\0') {
 | |
|     BLI_split_dirfile(blendfile_path, dirname, filename, sizeof(dirname), sizeof(filename));
 | |
|     BLI_path_extension_replace(filename, sizeof(filename), ""); /* strip '.blend' */
 | |
|     BLI_hash_md5_buffer(blendfile_path, strlen(blendfile_path), path_digest);
 | |
|   }
 | |
|   else {
 | |
|     BLI_strncpy(dirname, BKE_tempdir_base(), sizeof(dirname));
 | |
|     BLI_strncpy(filename, "UNSAVED", sizeof(filename));
 | |
|   }
 | |
|   BLI_hash_md5_to_hexdigest(path_digest, path_hexdigest);
 | |
| 
 | |
|   /* Default to *non-volatile* tmp dir. */
 | |
|   if (*root == '\0') {
 | |
|     root = BKE_tempdir_base();
 | |
|   }
 | |
| 
 | |
|   BLI_snprintf(filename_full,
 | |
|                sizeof(filename_full),
 | |
|                "cached_RR_%s_%s_%s.exr",
 | |
|                filename,
 | |
|                sce->id.name + 2,
 | |
|                path_hexdigest);
 | |
|   BLI_make_file_string(dirname, r_path, root, filename_full);
 | |
| }
 | |
| 
 | |
| void render_result_exr_file_cache_write(Render *re)
 | |
| {
 | |
|   RenderResult *rr = re->result;
 | |
|   char str[FILE_MAXFILE + FILE_MAXFILE + MAX_ID_NAME + 100];
 | |
|   char *root = U.render_cachedir;
 | |
| 
 | |
|   render_result_exr_file_cache_path(re->scene, root, str);
 | |
|   printf("Caching exr file, %dx%d, %s\n", rr->rectx, rr->recty, str);
 | |
| 
 | |
|   RE_WriteRenderResult(NULL, rr, str, NULL, NULL, -1);
 | |
| }
 | |
| 
 | |
| /* For cache, makes exact copy of render result */
 | |
| bool render_result_exr_file_cache_read(Render *re)
 | |
| {
 | |
|   char str[FILE_MAXFILE + MAX_ID_NAME + MAX_ID_NAME + 100] = "";
 | |
|   char *root = U.render_cachedir;
 | |
| 
 | |
|   RE_FreeRenderResult(re->result);
 | |
|   re->result = render_result_new(re, &re->disprect, RR_USE_MEM, RR_ALL_LAYERS, RR_ALL_VIEWS);
 | |
| 
 | |
|   /* First try cache. */
 | |
|   render_result_exr_file_cache_path(re->scene, root, str);
 | |
| 
 | |
|   printf("read exr cache file: %s\n", str);
 | |
|   if (!render_result_exr_file_read_path(re->result, NULL, str)) {
 | |
|     printf("cannot read: %s\n", str);
 | |
|     return false;
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /*************************** Combined Pixel Rect *****************************/
 | |
| 
 | |
| ImBuf *render_result_rect_to_ibuf(RenderResult *rr, const RenderData *rd, const int view_id)
 | |
| {
 | |
|   ImBuf *ibuf = IMB_allocImBuf(rr->rectx, rr->recty, rd->im_format.planes, 0);
 | |
|   RenderView *rv = RE_RenderViewGetById(rr, view_id);
 | |
| 
 | |
|   /* if not exists, BKE_imbuf_write makes one */
 | |
|   ibuf->rect = (unsigned int *)rv->rect32;
 | |
|   ibuf->rect_float = rv->rectf;
 | |
|   ibuf->zbuf_float = rv->rectz;
 | |
| 
 | |
|   /* float factor for random dither, imbuf takes care of it */
 | |
|   ibuf->dither = rd->dither_intensity;
 | |
| 
 | |
|   /* prepare to gamma correct to sRGB color space
 | |
|    * note that sequence editor can generate 8bpc render buffers
 | |
|    */
 | |
|   if (ibuf->rect) {
 | |
|     if (BKE_imtype_valid_depths(rd->im_format.imtype) &
 | |
|         (R_IMF_CHAN_DEPTH_12 | R_IMF_CHAN_DEPTH_16 | R_IMF_CHAN_DEPTH_24 | R_IMF_CHAN_DEPTH_32)) {
 | |
|       if (rd->im_format.depth == R_IMF_CHAN_DEPTH_8) {
 | |
|         /* Higher depth bits are supported but not needed for current file output. */
 | |
|         ibuf->rect_float = NULL;
 | |
|       }
 | |
|       else {
 | |
|         IMB_float_from_rect(ibuf);
 | |
|       }
 | |
|     }
 | |
|     else {
 | |
|       /* ensure no float buffer remained from previous frame */
 | |
|       ibuf->rect_float = NULL;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* color -> grayscale */
 | |
|   /* editing directly would alter the render view */
 | |
|   if (rd->im_format.planes == R_IMF_PLANES_BW) {
 | |
|     ImBuf *ibuf_bw = IMB_dupImBuf(ibuf);
 | |
|     IMB_color_to_bw(ibuf_bw);
 | |
|     IMB_freeImBuf(ibuf);
 | |
|     ibuf = ibuf_bw;
 | |
|   }
 | |
| 
 | |
|   return ibuf;
 | |
| }
 | |
| 
 | |
| void RE_render_result_rect_from_ibuf(RenderResult *rr,
 | |
|                                      RenderData *UNUSED(rd),
 | |
|                                      ImBuf *ibuf,
 | |
|                                      const int view_id)
 | |
| {
 | |
|   RenderView *rv = RE_RenderViewGetById(rr, view_id);
 | |
| 
 | |
|   if (ibuf->rect_float) {
 | |
|     rr->have_combined = true;
 | |
| 
 | |
|     if (!rv->rectf) {
 | |
|       rv->rectf = MEM_mallocN(sizeof(float[4]) * rr->rectx * rr->recty, "render_seq rectf");
 | |
|     }
 | |
| 
 | |
|     memcpy(rv->rectf, ibuf->rect_float, sizeof(float[4]) * rr->rectx * rr->recty);
 | |
| 
 | |
|     /* TSK! Since sequence render doesn't free the *rr render result, the old rect32
 | |
|      * can hang around when sequence render has rendered a 32 bits one before */
 | |
|     MEM_SAFE_FREE(rv->rect32);
 | |
|   }
 | |
|   else if (ibuf->rect) {
 | |
|     rr->have_combined = true;
 | |
| 
 | |
|     if (!rv->rect32) {
 | |
|       rv->rect32 = MEM_mallocN(sizeof(int) * rr->rectx * rr->recty, "render_seq rect");
 | |
|     }
 | |
| 
 | |
|     memcpy(rv->rect32, ibuf->rect, 4 * rr->rectx * rr->recty);
 | |
| 
 | |
|     /* Same things as above, old rectf can hang around from previous render. */
 | |
|     MEM_SAFE_FREE(rv->rectf);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void render_result_rect_fill_zero(RenderResult *rr, const int view_id)
 | |
| {
 | |
|   RenderView *rv = RE_RenderViewGetById(rr, view_id);
 | |
| 
 | |
|   if (rv->rectf) {
 | |
|     memset(rv->rectf, 0, sizeof(float[4]) * rr->rectx * rr->recty);
 | |
|   }
 | |
|   else if (rv->rect32) {
 | |
|     memset(rv->rect32, 0, 4 * rr->rectx * rr->recty);
 | |
|   }
 | |
|   else {
 | |
|     rv->rect32 = MEM_callocN(sizeof(int) * rr->rectx * rr->recty, "render_seq rect");
 | |
|   }
 | |
| }
 | |
| 
 | |
| void render_result_rect_get_pixels(RenderResult *rr,
 | |
|                                    unsigned int *rect,
 | |
|                                    int rectx,
 | |
|                                    int recty,
 | |
|                                    const ColorManagedViewSettings *view_settings,
 | |
|                                    const ColorManagedDisplaySettings *display_settings,
 | |
|                                    const int view_id)
 | |
| {
 | |
|   RenderView *rv = RE_RenderViewGetById(rr, view_id);
 | |
| 
 | |
|   if (rv && rv->rect32) {
 | |
|     memcpy(rect, rv->rect32, sizeof(int) * rr->rectx * rr->recty);
 | |
|   }
 | |
|   else if (rv && rv->rectf) {
 | |
|     IMB_display_buffer_transform_apply((unsigned char *)rect,
 | |
|                                        rv->rectf,
 | |
|                                        rr->rectx,
 | |
|                                        rr->recty,
 | |
|                                        4,
 | |
|                                        view_settings,
 | |
|                                        display_settings,
 | |
|                                        true);
 | |
|   }
 | |
|   else {
 | |
|     /* else fill with black */
 | |
|     memset(rect, 0, sizeof(int) * rectx * recty);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*************************** multiview functions *****************************/
 | |
| 
 | |
| bool RE_HasCombinedLayer(RenderResult *rr)
 | |
| {
 | |
|   RenderView *rv;
 | |
| 
 | |
|   if (rr == NULL) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   rv = rr->views.first;
 | |
|   if (rv == NULL) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return (rv->rect32 || rv->rectf);
 | |
| }
 | |
| 
 | |
| bool RE_HasFloatPixels(RenderResult *rr)
 | |
| {
 | |
|   RenderView *rview;
 | |
| 
 | |
|   for (rview = rr->views.first; rview; rview = rview->next) {
 | |
|     if (rview->rect32 && !rview->rectf) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool RE_RenderResult_is_stereo(RenderResult *rr)
 | |
| {
 | |
|   if (!BLI_findstring(&rr->views, STEREO_LEFT_NAME, offsetof(RenderView, name))) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (!BLI_findstring(&rr->views, STEREO_RIGHT_NAME, offsetof(RenderView, name))) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| RenderView *RE_RenderViewGetById(RenderResult *rr, const int view_id)
 | |
| {
 | |
|   RenderView *rv = BLI_findlink(&rr->views, view_id);
 | |
|   BLI_assert(rr->views.first);
 | |
|   return rv ? rv : rr->views.first;
 | |
| }
 | |
| 
 | |
| RenderView *RE_RenderViewGetByName(RenderResult *rr, const char *viewname)
 | |
| {
 | |
|   RenderView *rv = BLI_findstring(&rr->views, viewname, offsetof(RenderView, name));
 | |
|   BLI_assert(rr->views.first);
 | |
|   return rv ? rv : rr->views.first;
 | |
| }
 | |
| 
 | |
| static RenderPass *duplicate_render_pass(RenderPass *rpass)
 | |
| {
 | |
|   RenderPass *new_rpass = MEM_mallocN(sizeof(RenderPass), "new render pass");
 | |
|   *new_rpass = *rpass;
 | |
|   new_rpass->next = new_rpass->prev = NULL;
 | |
|   if (new_rpass->rect != NULL) {
 | |
|     new_rpass->rect = MEM_dupallocN(new_rpass->rect);
 | |
|   }
 | |
|   return new_rpass;
 | |
| }
 | |
| 
 | |
| static RenderLayer *duplicate_render_layer(RenderLayer *rl)
 | |
| {
 | |
|   RenderLayer *new_rl = MEM_mallocN(sizeof(RenderLayer), "new render layer");
 | |
|   *new_rl = *rl;
 | |
|   new_rl->next = new_rl->prev = NULL;
 | |
|   new_rl->passes.first = new_rl->passes.last = NULL;
 | |
|   new_rl->exrhandle = NULL;
 | |
|   for (RenderPass *rpass = rl->passes.first; rpass != NULL; rpass = rpass->next) {
 | |
|     RenderPass *new_rpass = duplicate_render_pass(rpass);
 | |
|     BLI_addtail(&new_rl->passes, new_rpass);
 | |
|   }
 | |
|   return new_rl;
 | |
| }
 | |
| 
 | |
| static RenderView *duplicate_render_view(RenderView *rview)
 | |
| {
 | |
|   RenderView *new_rview = MEM_mallocN(sizeof(RenderView), "new render view");
 | |
|   *new_rview = *rview;
 | |
|   if (new_rview->rectf != NULL) {
 | |
|     new_rview->rectf = MEM_dupallocN(new_rview->rectf);
 | |
|   }
 | |
|   if (new_rview->rectz != NULL) {
 | |
|     new_rview->rectz = MEM_dupallocN(new_rview->rectz);
 | |
|   }
 | |
|   if (new_rview->rect32 != NULL) {
 | |
|     new_rview->rect32 = MEM_dupallocN(new_rview->rect32);
 | |
|   }
 | |
|   return new_rview;
 | |
| }
 | |
| 
 | |
| RenderResult *RE_DuplicateRenderResult(RenderResult *rr)
 | |
| {
 | |
|   RenderResult *new_rr = MEM_mallocN(sizeof(RenderResult), "new duplicated render result");
 | |
|   *new_rr = *rr;
 | |
|   new_rr->next = new_rr->prev = NULL;
 | |
|   new_rr->layers.first = new_rr->layers.last = NULL;
 | |
|   new_rr->views.first = new_rr->views.last = NULL;
 | |
|   for (RenderLayer *rl = rr->layers.first; rl != NULL; rl = rl->next) {
 | |
|     RenderLayer *new_rl = duplicate_render_layer(rl);
 | |
|     BLI_addtail(&new_rr->layers, new_rl);
 | |
|   }
 | |
|   for (RenderView *rview = rr->views.first; rview != NULL; rview = rview->next) {
 | |
|     RenderView *new_rview = duplicate_render_view(rview);
 | |
|     BLI_addtail(&new_rr->views, new_rview);
 | |
|   }
 | |
|   if (new_rr->rect32 != NULL) {
 | |
|     new_rr->rect32 = MEM_dupallocN(new_rr->rect32);
 | |
|   }
 | |
|   if (new_rr->rectf != NULL) {
 | |
|     new_rr->rectf = MEM_dupallocN(new_rr->rectf);
 | |
|   }
 | |
|   if (new_rr->rectz != NULL) {
 | |
|     new_rr->rectz = MEM_dupallocN(new_rr->rectz);
 | |
|   }
 | |
|   new_rr->stamp_data = BKE_stamp_data_copy(new_rr->stamp_data);
 | |
|   return new_rr;
 | |
| }
 |