Eevee Cryptomatte: Store hashes in render result meta data

Stores cryptomatte hashes as meta data to the render result. Compositors could
use this for lookup on names in stead of hashes.

Differential Revision: https://developer.blender.org/D9553
This commit is contained in:
Jeroen Bakker 2021-01-05 14:59:45 +01:00 committed by Jeroen Bakker
parent 7cd6f667e3
commit 1f41bdc6f3
16 changed files with 272 additions and 27 deletions

@ -24,19 +24,30 @@
#pragma once #pragma once
#include "BLI_sys_types.h" #include "BLI_sys_types.h"
#include "DNA_layer_types.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
struct Main; struct CryptomatteSession;
struct Material; struct Material;
struct ID;
struct Main;
struct Object; struct Object;
struct RenderResult;
struct CryptomatteSession *BKE_cryptomatte_init(void);
void BKE_cryptomatte_finish(struct CryptomatteSession *session);
void BKE_cryptomatte_free(struct CryptomatteSession *session);
uint32_t BKE_cryptomatte_hash(const char *name, int name_len); uint32_t BKE_cryptomatte_hash(const char *name, int name_len);
uint32_t BKE_cryptomatte_object_hash(const struct Object *object); uint32_t BKE_cryptomatte_object_hash(struct CryptomatteSession *session,
uint32_t BKE_cryptomatte_material_hash(const struct Material *material); const struct Object *object);
uint32_t BKE_cryptomatte_asset_hash(const struct Object *object); uint32_t BKE_cryptomatte_material_hash(struct CryptomatteSession *session,
const struct Material *material);
uint32_t BKE_cryptomatte_asset_hash(struct CryptomatteSession *session,
const struct Object *object);
float BKE_cryptomatte_hash_to_float(uint32_t cryptomatte_hash); float BKE_cryptomatte_hash_to_float(uint32_t cryptomatte_hash);
char *BKE_cryptomatte_entries_to_matte_id(struct NodeCryptomatte *node_storage); char *BKE_cryptomatte_entries_to_matte_id(struct NodeCryptomatte *node_storage);
@ -44,6 +55,12 @@ void BKE_cryptomatte_matte_id_to_entries(const struct Main *bmain,
struct NodeCryptomatte *node_storage, struct NodeCryptomatte *node_storage,
const char *matte_id); const char *matte_id);
void BKE_cryptomatte_store_metadata(struct CryptomatteSession *session,
struct RenderResult *render_result,
const ViewLayer *view_layer,
eViewLayerCryptomatteFlags cryptomatte_layer,
const char *cryptomatte_layer_name);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

@ -22,8 +22,10 @@
*/ */
#include "BKE_cryptomatte.h" #include "BKE_cryptomatte.h"
#include "BKE_image.h"
#include "BKE_main.h" #include "BKE_main.h"
#include "DNA_layer_types.h"
#include "DNA_material_types.h" #include "DNA_material_types.h"
#include "DNA_node_types.h" #include "DNA_node_types.h"
#include "DNA_object_types.h" #include "DNA_object_types.h"
@ -32,48 +34,142 @@
#include "BLI_dynstr.h" #include "BLI_dynstr.h"
#include "BLI_hash_mm3.h" #include "BLI_hash_mm3.h"
#include "BLI_listbase.h" #include "BLI_listbase.h"
#include "BLI_set.hh"
#include "BLI_string.h" #include "BLI_string.h"
#include "MEM_guardedalloc.h" #include "MEM_guardedalloc.h"
#include <cstring> #include <cstring>
#include <iomanip>
#include <sstream> #include <sstream>
#include <string> #include <string>
static uint32_t cryptomatte_hash(const ID *id) enum CryptomatteLayerState {
EMPTY,
FILLED,
CLOSED,
};
struct CryptomatteLayer {
CryptomatteLayerState state = CryptomatteLayerState::EMPTY;
blender::Set<std::string> names;
std::stringstream manifest;
#ifdef WITH_CXX_GUARDEDALLOC
MEM_CXX_CLASS_ALLOC_FUNCS("cryptomatte:CryptomatteLayer")
#endif
void add_hash(std::string name, uint32_t cryptomatte_hash)
{ {
const char *name = &id->name[2]; BLI_assert(state != CryptomatteLayerState::CLOSED);
const int name_len = BLI_strnlen(name, MAX_NAME); const bool first_item = names.is_empty();
uint32_t cryptohash_int = BKE_cryptomatte_hash(name, name_len); if (!names.add(name)) {
return cryptohash_int; return;
} }
uint32_t BKE_cryptomatte_hash(const char *name, int name_len) if (first_item) {
state = CryptomatteLayerState::FILLED;
manifest << "{";
}
else {
manifest << ",";
}
manifest << quoted(name) << ":\"";
manifest << std::setfill('0') << std::setw(sizeof(uint32_t) * 2) << std::hex
<< cryptomatte_hash;
manifest << "\"";
}
void close_manifest()
{
BLI_assert(state != CryptomatteLayerState::CLOSED);
if (state == CryptomatteLayerState::FILLED) {
manifest << "}";
}
state = CryptomatteLayerState::CLOSED;
}
std::string manifest_get_string()
{
BLI_assert(state == CryptomatteLayerState::CLOSED);
return manifest.str();
}
};
struct CryptomatteSession {
CryptomatteLayer objects;
CryptomatteLayer assets;
CryptomatteLayer materials;
#ifdef WITH_CXX_GUARDEDALLOC
MEM_CXX_CLASS_ALLOC_FUNCS("cryptomatte:CryptomatteSession")
#endif
void finish()
{
objects.close_manifest();
materials.close_manifest();
assets.close_manifest();
}
};
CryptomatteSession *BKE_cryptomatte_init(void)
{
CryptomatteSession *session = new CryptomatteSession();
return session;
}
void BKE_cryptomatte_finish(CryptomatteSession *session)
{
BLI_assert(session != NULL);
session->finish();
}
void BKE_cryptomatte_free(CryptomatteSession *session)
{
BLI_assert(session != NULL);
delete session;
}
uint32_t BKE_cryptomatte_hash(const char *name, const int name_len)
{ {
uint32_t cryptohash_int = BLI_hash_mm3((const unsigned char *)name, name_len, 0); uint32_t cryptohash_int = BLI_hash_mm3((const unsigned char *)name, name_len, 0);
return cryptohash_int; return cryptohash_int;
} }
uint32_t BKE_cryptomatte_object_hash(const Object *object) static uint32_t cryptomatte_hash(CryptomatteLayer *layer, const ID *id)
{ {
return cryptomatte_hash(&object->id); const char *name = &id->name[2];
const int name_len = BLI_strnlen(name, MAX_NAME - 2);
uint32_t cryptohash_int = BKE_cryptomatte_hash(name, name_len);
if (layer != nullptr) {
layer->add_hash(std::string(name, name_len), cryptohash_int);
} }
uint32_t BKE_cryptomatte_material_hash(const Material *material) return cryptohash_int;
}
uint32_t BKE_cryptomatte_object_hash(CryptomatteSession *session, const Object *object)
{ {
if (material == nullptr) { return cryptomatte_hash(&session->objects, &object->id);
}
uint32_t BKE_cryptomatte_material_hash(CryptomatteSession *session, const Material *material)
{
if (material == NULL) {
return 0.0f; return 0.0f;
} }
return cryptomatte_hash(&material->id); return cryptomatte_hash(&session->materials, &material->id);
} }
uint32_t BKE_cryptomatte_asset_hash(const Object *object) uint32_t BKE_cryptomatte_asset_hash(CryptomatteSession *session, const Object *object)
{ {
const Object *asset_object = object; const Object *asset_object = object;
while (asset_object->parent != nullptr) { while (asset_object->parent != NULL) {
asset_object = asset_object->parent; asset_object = asset_object->parent;
} }
return cryptomatte_hash(&asset_object->id); return cryptomatte_hash(&session->assets, &asset_object->id);
} }
/* Convert a cryptomatte hash to a float. /* Convert a cryptomatte hash to a float.
@ -188,3 +284,63 @@ void BKE_cryptomatte_matte_id_to_entries(const Main *bmain,
} }
} }
} }
static std::string cryptomatte_determine_name(const ViewLayer *view_layer,
const std::string cryptomatte_layer_name)
{
std::stringstream stream;
const size_t view_layer_name_len = BLI_strnlen(view_layer->name, sizeof(view_layer->name));
stream << std::string(view_layer->name, view_layer_name_len) << "." << cryptomatte_layer_name;
return stream.str();
}
static uint32_t cryptomatte_determine_identifier(const std::string name)
{
return BLI_hash_mm3(reinterpret_cast<const unsigned char *>(name.c_str()), name.length(), 0);
}
static std::string cryptomatte_determine_prefix(const std::string name)
{
std::stringstream stream;
const uint32_t render_pass_identifier = cryptomatte_determine_identifier(name);
stream << "cryptomatte/";
stream << std::setfill('0') << std::setw(sizeof(uint32_t) * 2) << std::hex
<< render_pass_identifier;
stream << "/";
return stream.str();
}
void BKE_cryptomatte_store_metadata(struct CryptomatteSession *session,
RenderResult *render_result,
const ViewLayer *view_layer,
eViewLayerCryptomatteFlags cryptomatte_layer,
const char *cryptomatte_layer_name)
{
/* Create Manifest. */
CryptomatteLayer *layer = nullptr;
switch (cryptomatte_layer) {
case VIEW_LAYER_CRYPTOMATTE_OBJECT:
layer = &session->objects;
break;
case VIEW_LAYER_CRYPTOMATTE_MATERIAL:
layer = &session->materials;
break;
case VIEW_LAYER_CRYPTOMATTE_ASSET:
layer = &session->assets;
break;
default:
BLI_assert(!"Incorrect cryptomatte layer");
break;
}
const std::string manifest = layer->manifest_get_string();
const std::string name = cryptomatte_determine_name(view_layer, cryptomatte_layer_name);
const std::string prefix = cryptomatte_determine_prefix(name);
/* Store the meta data into the render result. */
BKE_render_result_stamp_data(render_result, (prefix + "name").c_str(), name.c_str());
BKE_render_result_stamp_data(render_result, (prefix + "hash").c_str(), "MurmurHash3_32");
BKE_render_result_stamp_data(
render_result, (prefix + "conversion").c_str(), "uint32_to_float32");
BKE_render_result_stamp_data(render_result, (prefix + "manifest").c_str(), manifest.c_str());
}

@ -260,6 +260,7 @@ DrawEngineType draw_engine_basic_type = {
NULL, NULL,
NULL, NULL,
NULL, NULL,
NULL,
}; };
#undef BASIC_ENGINE #undef BASIC_ENGINE

@ -125,6 +125,7 @@ void EEVEE_cryptomatte_renderpasses_init(EEVEE_Data *vedata)
return; return;
} }
if (eevee_cryptomatte_active_layers(view_layer) != 0) { if (eevee_cryptomatte_active_layers(view_layer) != 0) {
g_data->cryptomatte_session = BKE_cryptomatte_init();
g_data->render_passes |= EEVEE_RENDER_PASS_CRYPTOMATTE | EEVEE_RENDER_PASS_VOLUME_LIGHT; g_data->render_passes |= EEVEE_RENDER_PASS_CRYPTOMATTE | EEVEE_RENDER_PASS_VOLUME_LIGHT;
g_data->cryptomatte_accurate_mode = (view_layer->cryptomatte_flag & g_data->cryptomatte_accurate_mode = (view_layer->cryptomatte_flag &
VIEW_LAYER_CRYPTOMATTE_ACCURATE) != 0; VIEW_LAYER_CRYPTOMATTE_ACCURATE) != 0;
@ -193,24 +194,26 @@ static DRWShadingGroup *eevee_cryptomatte_shading_group_create(EEVEE_Data *vedat
const ViewLayer *view_layer = draw_ctx->view_layer; const ViewLayer *view_layer = draw_ctx->view_layer;
const eViewLayerCryptomatteFlags cryptomatte_layers = eevee_cryptomatte_active_layers( const eViewLayerCryptomatteFlags cryptomatte_layers = eevee_cryptomatte_active_layers(
view_layer); view_layer);
EEVEE_PrivateData *g_data = vedata->stl->g_data;
float cryptohash[4] = {0.0f}; float cryptohash[4] = {0.0f};
EEVEE_PassList *psl = vedata->psl; EEVEE_PassList *psl = vedata->psl;
int layer_offset = 0; int layer_offset = 0;
if ((cryptomatte_layers & VIEW_LAYER_CRYPTOMATTE_OBJECT) != 0) { if ((cryptomatte_layers & VIEW_LAYER_CRYPTOMATTE_OBJECT) != 0) {
uint32_t cryptomatte_hash = BKE_cryptomatte_object_hash(ob); uint32_t cryptomatte_hash = BKE_cryptomatte_object_hash(g_data->cryptomatte_session, ob);
float cryptomatte_color_value = BKE_cryptomatte_hash_to_float(cryptomatte_hash); float cryptomatte_color_value = BKE_cryptomatte_hash_to_float(cryptomatte_hash);
cryptohash[layer_offset] = cryptomatte_color_value; cryptohash[layer_offset] = cryptomatte_color_value;
layer_offset++; layer_offset++;
} }
if ((cryptomatte_layers & VIEW_LAYER_CRYPTOMATTE_MATERIAL) != 0) { if ((cryptomatte_layers & VIEW_LAYER_CRYPTOMATTE_MATERIAL) != 0) {
uint32_t cryptomatte_hash = BKE_cryptomatte_material_hash(material); uint32_t cryptomatte_hash = BKE_cryptomatte_material_hash(g_data->cryptomatte_session,
material);
float cryptomatte_color_value = BKE_cryptomatte_hash_to_float(cryptomatte_hash); float cryptomatte_color_value = BKE_cryptomatte_hash_to_float(cryptomatte_hash);
cryptohash[layer_offset] = cryptomatte_color_value; cryptohash[layer_offset] = cryptomatte_color_value;
layer_offset++; layer_offset++;
} }
if ((cryptomatte_layers & VIEW_LAYER_CRYPTOMATTE_ASSET) != 0) { if ((cryptomatte_layers & VIEW_LAYER_CRYPTOMATTE_ASSET) != 0) {
uint32_t cryptomatte_hash = BKE_cryptomatte_asset_hash(ob); uint32_t cryptomatte_hash = BKE_cryptomatte_asset_hash(g_data->cryptomatte_session, ob);
float cryptomatte_color_value = BKE_cryptomatte_hash_to_float(cryptomatte_hash); float cryptomatte_color_value = BKE_cryptomatte_hash_to_float(cryptomatte_hash);
cryptohash[layer_offset] = cryptomatte_color_value; cryptohash[layer_offset] = cryptomatte_color_value;
layer_offset++; layer_offset++;
@ -310,6 +313,12 @@ void EEVEE_cryptomatte_cache_populate(EEVEE_Data *vedata, EEVEE_ViewLayerData *s
} }
} }
void EEVEE_cryptomatte_cache_finish(EEVEE_ViewLayerData *UNUSED(sldata), EEVEE_Data *vedata)
{
EEVEE_PrivateData *g_data = vedata->stl->g_data;
BKE_cryptomatte_finish(g_data->cryptomatte_session);
}
/** \} */ /** \} */
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
@ -677,6 +686,37 @@ void EEVEE_cryptomatte_render_result(RenderLayer *rl,
} }
} }
void EEVEE_cryptomatte_store_metadata(EEVEE_Data *vedata, RenderResult *render_result)
{
EEVEE_PrivateData *g_data = vedata->stl->g_data;
const DRWContextState *draw_ctx = DRW_context_state_get();
const ViewLayer *view_layer = draw_ctx->view_layer;
const eViewLayerCryptomatteFlags cryptomatte_layers = view_layer->cryptomatte_flag &
VIEW_LAYER_CRYPTOMATTE_ALL;
BLI_assert(g_data->cryptomatte_session);
if ((cryptomatte_layers & VIEW_LAYER_CRYPTOMATTE_OBJECT) != 0) {
BKE_cryptomatte_store_metadata(g_data->cryptomatte_session,
render_result,
view_layer,
VIEW_LAYER_CRYPTOMATTE_OBJECT,
"CryptoObject");
}
if ((cryptomatte_layers & VIEW_LAYER_CRYPTOMATTE_MATERIAL) != 0) {
BKE_cryptomatte_store_metadata(g_data->cryptomatte_session,
render_result,
view_layer,
VIEW_LAYER_CRYPTOMATTE_MATERIAL,
"CryptoMaterial");
}
if ((cryptomatte_layers & VIEW_LAYER_CRYPTOMATTE_ASSET) != 0) {
BKE_cryptomatte_store_metadata(g_data->cryptomatte_session,
render_result,
view_layer,
VIEW_LAYER_CRYPTOMATTE_ASSET,
"CryptoAsset");
}
}
/** \} */ /** \} */
void EEVEE_cryptomatte_free(EEVEE_Data *vedata) void EEVEE_cryptomatte_free(EEVEE_Data *vedata)
@ -684,4 +724,8 @@ void EEVEE_cryptomatte_free(EEVEE_Data *vedata)
EEVEE_PrivateData *g_data = vedata->stl->g_data; EEVEE_PrivateData *g_data = vedata->stl->g_data;
MEM_SAFE_FREE(g_data->cryptomatte_accum_buffer); MEM_SAFE_FREE(g_data->cryptomatte_accum_buffer);
MEM_SAFE_FREE(g_data->cryptomatte_download_buffer); MEM_SAFE_FREE(g_data->cryptomatte_download_buffer);
if (g_data->cryptomatte_session) {
BKE_cryptomatte_free(g_data->cryptomatte_session);
g_data->cryptomatte_session = NULL;
}
} }

@ -570,8 +570,6 @@ static void eevee_render_to_image(void *vedata,
EEVEE_motion_blur_data_free(&ved->stl->effects->motion_blur); EEVEE_motion_blur_data_free(&ved->stl->effects->motion_blur);
if (RE_engine_test_break(engine)) { if (RE_engine_test_break(engine)) {
/* Cryptomatte buffers are freed during render_read_result */
EEVEE_cryptomatte_free(vedata);
return; return;
} }
@ -586,6 +584,16 @@ static void eevee_render_to_image(void *vedata,
} }
} }
static void eevee_store_metadata(void *vedata, struct RenderResult *render_result)
{
EEVEE_Data *ved = (EEVEE_Data *)vedata;
EEVEE_PrivateData *g_data = ved->stl->g_data;
if (g_data->render_passes & EEVEE_RENDER_PASS_CRYPTOMATTE) {
EEVEE_cryptomatte_store_metadata(ved, render_result);
EEVEE_cryptomatte_free(ved);
}
}
static void eevee_engine_free(void) static void eevee_engine_free(void)
{ {
EEVEE_shaders_free(); EEVEE_shaders_free();
@ -611,6 +619,7 @@ DrawEngineType draw_engine_eevee_type = {
&eevee_view_update, &eevee_view_update,
&eevee_id_update, &eevee_id_update,
&eevee_render_to_image, &eevee_render_to_image,
&eevee_store_metadata,
}; };
RenderEngineType DRW_engine_viewport_eevee_type = { RenderEngineType DRW_engine_viewport_eevee_type = {

@ -977,6 +977,7 @@ typedef struct EEVEE_PrivateData {
eViewLayerEEVEEPassType render_passes; eViewLayerEEVEEPassType render_passes;
int aov_hash; int aov_hash;
int num_aovs_used; int num_aovs_used;
struct CryptomatteSession *cryptomatte_session;
bool cryptomatte_accurate_mode; bool cryptomatte_accurate_mode;
EEVEE_CryptomatteSample *cryptomatte_accum_buffer; EEVEE_CryptomatteSample *cryptomatte_accum_buffer;
float *cryptomatte_download_buffer; float *cryptomatte_download_buffer;
@ -1246,6 +1247,7 @@ void EEVEE_cryptomatte_output_init(EEVEE_ViewLayerData *sldata,
int tot_samples); int tot_samples);
void EEVEE_cryptomatte_cache_init(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata); void EEVEE_cryptomatte_cache_init(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata);
void EEVEE_cryptomatte_cache_populate(EEVEE_Data *vedata, EEVEE_ViewLayerData *sldata, Object *ob); void EEVEE_cryptomatte_cache_populate(EEVEE_Data *vedata, EEVEE_ViewLayerData *sldata, Object *ob);
void EEVEE_cryptomatte_cache_finish(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata);
void EEVEE_cryptomatte_particle_hair_cache_populate(EEVEE_Data *vedata, void EEVEE_cryptomatte_particle_hair_cache_populate(EEVEE_Data *vedata,
EEVEE_ViewLayerData *sldata, EEVEE_ViewLayerData *sldata,
Object *ob); Object *ob);
@ -1261,6 +1263,7 @@ void EEVEE_cryptomatte_render_result(struct RenderLayer *rl,
const rcti *rect, const rcti *rect,
EEVEE_Data *vedata, EEVEE_Data *vedata,
EEVEE_ViewLayerData *sldata); EEVEE_ViewLayerData *sldata);
void EEVEE_cryptomatte_store_metadata(EEVEE_Data *vedata, struct RenderResult *render_result);
void EEVEE_cryptomatte_free(EEVEE_Data *vedata); void EEVEE_cryptomatte_free(EEVEE_Data *vedata);
/* eevee_occlusion.c */ /* eevee_occlusion.c */

@ -510,7 +510,6 @@ static void eevee_render_result_cryptomatte(RenderLayer *rl,
if ((vedata->stl->g_data->render_passes & EEVEE_RENDER_PASS_CRYPTOMATTE) != 0) { if ((vedata->stl->g_data->render_passes & EEVEE_RENDER_PASS_CRYPTOMATTE) != 0) {
EEVEE_cryptomatte_render_result(rl, viewname, rect, vedata, sldata); EEVEE_cryptomatte_render_result(rl, viewname, rect, vedata, sldata);
} }
EEVEE_cryptomatte_free(vedata);
} }
static void eevee_render_draw_background(EEVEE_Data *vedata) static void eevee_render_draw_background(EEVEE_Data *vedata)

@ -255,6 +255,10 @@ void EEVEE_renderpasses_cache_finish(EEVEE_ViewLayerData *sldata, EEVEE_Data *ve
else { else {
psl->renderpass_pass = NULL; psl->renderpass_pass = NULL;
} }
if ((g_data->render_passes & (EEVEE_RENDER_PASS_CRYPTOMATTE)) != 0) {
EEVEE_cryptomatte_cache_finish(sldata, vedata);
}
} }
/* Post-process data to construct a specific render-pass /* Post-process data to construct a specific render-pass

@ -311,6 +311,7 @@ static DrawEngineType draw_engine_external_type = {
NULL, NULL,
NULL, NULL,
NULL, NULL,
NULL,
}; };
/* Note: currently unused, /* Note: currently unused,

@ -977,4 +977,5 @@ DrawEngineType draw_engine_gpencil_type = {
NULL, NULL,
NULL, NULL,
&GPENCIL_render_to_image, &GPENCIL_render_to_image,
NULL,
}; };

@ -413,4 +413,5 @@ DrawEngineType draw_engine_image_type = {
NULL, /* view_update */ NULL, /* view_update */
NULL, /* id_update */ NULL, /* id_update */
NULL, /* render_to_image */ NULL, /* render_to_image */
NULL, /* store_metadata */
}; };

@ -695,6 +695,7 @@ DrawEngineType draw_engine_overlay_type = {
NULL, NULL,
NULL, NULL,
NULL, NULL,
NULL,
}; };
/** \} */ /** \} */

@ -370,6 +370,7 @@ DrawEngineType draw_engine_select_type = {
NULL, NULL,
NULL, NULL,
NULL, NULL,
NULL,
}; };
/* Note: currently unused, we may want to register so we can see this when debugging the view. */ /* Note: currently unused, we may want to register so we can see this when debugging the view. */

@ -641,6 +641,7 @@ DrawEngineType draw_engine_workbench = {
&workbench_view_update, &workbench_view_update,
&workbench_id_update, &workbench_id_update,
&workbench_render, &workbench_render,
NULL,
}; };
RenderEngineType DRW_engine_viewport_workbench_type = { RenderEngineType DRW_engine_viewport_workbench_type = {

@ -131,6 +131,7 @@ typedef struct DrawEngineType {
struct RenderEngine *engine, struct RenderEngine *engine,
struct RenderLayer *layer, struct RenderLayer *layer,
const struct rcti *rect); const struct rcti *rect);
void (*store_metadata)(void *vedata, struct RenderResult *render_result);
} DrawEngineType; } DrawEngineType;
/* Textures */ /* Textures */

@ -1911,6 +1911,11 @@ void DRW_render_to_image(RenderEngine *engine, struct Depsgraph *depsgraph)
RE_engine_end_result(engine, render_result, false, false, false); RE_engine_end_result(engine, render_result, false, false, false);
if (engine_type->draw_engine->store_metadata) {
RenderResult *final_render_result = RE_engine_get_result(engine);
engine_type->draw_engine->store_metadata(data, final_render_result);
}
/* Force cache to reset. */ /* Force cache to reset. */
drw_viewport_cache_resize(); drw_viewport_cache_resize();