The idea is to only allocate pixel storage only when there is an actual data to be written to them. This moves the code forward a better support of high-res rendering when pixel storage is not allocated until render engine is ready to provide pixel data. Is expected to be no functional changes for neither users no external engines. The only difference is that the motion and depth passes will be displayed as transparent for until render engine provides any tile result (at which point the pixels will be allocated and initialized to infinite depth). Differential Revision: https://developer.blender.org/D12195
1680 lines
49 KiB
C
1680 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 full-sample 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);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
rr->passes_allocated = false;
|
|
|
|
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;
|
|
|
|
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 (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;
|
|
|
|
/* Preview-render 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 = SCE_LAY_FLAG_DEFAULT;
|
|
rl->passflag = SCE_PASS_COMBINED;
|
|
|
|
re->active_view_layer = 0;
|
|
}
|
|
|
|
/* Border render; calculate offset for use in compositor. compo is centralized coords. */
|
|
/* XXX(ton): obsolete? I now use it for drawing border render offset. */
|
|
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_passes_allocated_ensure(RenderResult *rr)
|
|
{
|
|
LISTBASE_FOREACH (RenderLayer *, rl, &rr->layers) {
|
|
LISTBASE_FOREACH (RenderPass *, rp, &rl->passes) {
|
|
if (rl->exrhandle != NULL && !STREQ(rp->name, RE_PASSNAME_COMBINED)) {
|
|
continue;
|
|
}
|
|
|
|
if (rp->rect != NULL) {
|
|
continue;
|
|
}
|
|
|
|
const size_t rectsize = ((size_t)rr->rectx) * rr->recty * rp->channels;
|
|
rp->rect = MEM_callocN(sizeof(float) * rectsize, rp->name);
|
|
|
|
if (STREQ(rp->name, RE_PASSNAME_VECTOR)) {
|
|
/* initialize to max speed */
|
|
float *rect = rp->rect;
|
|
for (int x = rectsize - 1; x >= 0; x--) {
|
|
rect[x] = PASS_VECTOR_MAX;
|
|
}
|
|
}
|
|
else if (STREQ(rp->name, RE_PASSNAME_Z)) {
|
|
float *rect = rp->rect;
|
|
for (int x = rectsize - 1; x >= 0; x--) {
|
|
rect[x] = 10e10;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
rr->passes_allocated = true;
|
|
}
|
|
|
|
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(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(const RenderResult *rr)
|
|
{
|
|
const 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)
|
|
{
|
|
/* Preserve stamp data. */
|
|
struct StampData *stamp_data = re->result->stamp_data;
|
|
re->result->stamp_data = NULL;
|
|
|
|
/* 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);
|
|
re->result->stamp_data = stamp_data;
|
|
render_result_passes_allocated_ensure(re->result);
|
|
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;
|
|
}
|