Fix T57457: animated image sequences not working in Eevee.

The dependency graph now handles updating image users to point to the current
frame, and tags images to be refreshed on the GPU. The image editor user is
still updated outside of the dependency graph.

We still do not support multiple image users using a different current frame
in the same image, same as 2.7. This may require adding a GPU image texture
cache to keep memory usage under control. Things like rendering an animation
while the viewport stays fixed at the current frame works though.
This commit is contained in:
2019-02-18 13:23:49 +01:00
parent 286c34b4ab
commit 0e3a2acbfa
23 changed files with 170 additions and 62 deletions

View File

@@ -207,10 +207,13 @@ void BKE_image_verify_viewer_views(const struct RenderData *rd, struct Image *im
/* called on frame change or before render */
void BKE_image_user_frame_calc(struct ImageUser *iuser, int cfra);
void BKE_image_user_check_frame_calc(struct ImageUser *iuser, int cfra);
int BKE_image_user_frame_get(const struct ImageUser *iuser, int cfra, bool *r_is_in_range);
void BKE_image_user_file_path(struct ImageUser *iuser, struct Image *ima, char *path);
void BKE_image_update_frame(const struct Main *bmain, int cfra);
void BKE_image_editors_update_frame(const struct Main *bmain, int cfra);
/* dependency graph update for image user users */
bool BKE_image_user_id_has_animation(struct ID *id);
void BKE_image_user_id_eval_animation(struct Depsgraph *depsgrah, struct ID *id);
/* sets index offset for multilayer files */
struct RenderPass *BKE_image_multilayer_index(struct RenderResult *rr, struct ImageUser *iuser);

View File

@@ -88,6 +88,9 @@
#include "BLI_sys_types.h" // for intptr_t support
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
/* for image user iteration */
#include "DNA_node_types.h"
#include "DNA_space_types.h"
@@ -4418,26 +4421,72 @@ void BKE_image_user_frame_calc(ImageUser *iuser, int cfra)
}
}
void BKE_image_user_check_frame_calc(ImageUser *iuser, int cfra)
/* goes over all ImageUsers, and sets frame numbers if auto-refresh is set */
static void image_editors_update_frame(struct Image *UNUSED(ima), struct ImageUser *iuser, void *customdata)
{
if ((iuser->flag & IMA_ANIM_ALWAYS) || (iuser->flag & IMA_NEED_FRAME_RECALC)) {
BKE_image_user_frame_calc(iuser, cfra);
int cfra = *(int *)customdata;
if ((iuser->flag & IMA_ANIM_ALWAYS) ||
(iuser->flag & IMA_NEED_FRAME_RECALC))
{
BKE_image_user_frame_calc(iuser, cfra);
iuser->flag &= ~IMA_NEED_FRAME_RECALC;
}
}
/* goes over all ImageUsers, and sets frame numbers if auto-refresh is set */
static void image_update_frame(struct Image *UNUSED(ima), struct ImageUser *iuser, void *customdata)
void BKE_image_editors_update_frame(const Main *bmain, int cfra)
{
int cfra = *(int *)customdata;
BKE_image_user_check_frame_calc(iuser, cfra);
/* This only updates images used by the user interface. For others the
* dependency graph will call BKE_image_user_id_eval_animation. */
wmWindowManager *wm = bmain->wm.first;
BKE_image_walk_id_all_users(&wm->id, &cfra, image_editors_update_frame);
}
void BKE_image_update_frame(const Main *bmain, int cfra)
static void image_user_id_has_animation(struct Image *ima, struct ImageUser *UNUSED(iuser), void *customdata)
{
BKE_image_walk_all_users(bmain, &cfra, image_update_frame);
if (ima && BKE_image_is_animated(ima)) {
*(bool *)customdata = true;
}
}
bool BKE_image_user_id_has_animation(ID *id)
{
bool has_animation = false;
BKE_image_walk_id_all_users(id, &has_animation, image_user_id_has_animation);
return has_animation;
}
static void image_user_id_eval_animation(struct Image *ima, struct ImageUser *iuser, void *customdata)
{
if (ima && BKE_image_is_animated(ima)) {
Depsgraph *depsgraph = (Depsgraph *)customdata;
if ((iuser->flag & IMA_ANIM_ALWAYS) ||
(iuser->flag & IMA_NEED_FRAME_RECALC) ||
(DEG_get_mode(depsgraph) == DAG_EVAL_RENDER))
{
int framenr = iuser->framenr;
float cfra = DEG_get_ctime(depsgraph);
BKE_image_user_frame_calc(iuser, cfra);
iuser->flag &= ~IMA_NEED_FRAME_RECALC;
if (iuser->framenr != framenr) {
/* Note: a single texture and refresh doesn't really work when
* multiple image users may use different frames, this is to
* be improved with perhaps a GPU texture cache. */
ima->gpuflag |= IMA_GPU_REFRESH;
}
}
}
}
void BKE_image_user_id_eval_animation(Depsgraph *depsgraph, ID *id)
{
/* This is called from the dependency graph to update the image
* users in datablocks. It computes the current frame number
* and tags the image to be refreshed. */
BKE_image_walk_id_all_users(id, depsgraph, image_user_id_eval_animation);
}
void BKE_image_user_file_path(ImageUser *iuser, Image *ima, char *filepath)
@@ -4605,11 +4654,7 @@ bool BKE_image_has_packedfile(Image *ima)
return (BLI_listbase_is_empty(&ima->packedfiles) == false);
}
/**
* Checks the image buffer changes (not keyframed values)
*
* to see if we need to call #BKE_image_user_check_frame_calc
*/
/* Checks the image buffer changes with time (not keyframed values). */
bool BKE_image_is_animated(Image *image)
{
return ELEM(image->source, IMA_SRC_MOVIE, IMA_SRC_SEQUENCE);

View File

@@ -152,8 +152,6 @@ void BKE_object_handle_data_update(
Scene *scene,
Object *ob)
{
float ctime = BKE_scene_frame_get(scene);
DEG_debug_print_eval(depsgraph, __func__, ob->id.name, ob);
/* includes all keys and modifiers */
@@ -209,12 +207,6 @@ void BKE_object_handle_data_update(
case OB_LATTICE:
BKE_lattice_modifiers_calc(depsgraph, scene, ob);
break;
case OB_EMPTY:
if (ob->empty_drawtype == OB_EMPTY_IMAGE && ob->data)
if (BKE_image_is_animated(ob->data))
BKE_image_user_check_frame_calc(ob->iuser, (int)ctime);
break;
}
/* particles */

View File

@@ -1524,7 +1524,7 @@ void BKE_scene_graph_update_for_newframe(Depsgraph *depsgraph,
/* Update animated image textures for particles, modifiers, gpu, etc,
* call this at the start so modifiers with textures don't lag 1 frame.
*/
BKE_image_update_frame(bmain, scene->r.cfra);
BKE_image_editors_update_frame(bmain, scene->r.cfra);
BKE_sound_set_cfra(scene->r.cfra);
DEG_graph_relations_update(depsgraph, bmain, scene, view_layer);
/* Update animated cache files for modifiers.

View File

@@ -73,6 +73,7 @@ extern "C" {
#include "BKE_gpencil.h"
#include "BKE_gpencil_modifier.h"
#include "BKE_idcode.h"
#include "BKE_image.h"
#include "BKE_key.h"
#include "BKE_lattice.h"
#include "BKE_mask.h"
@@ -825,11 +826,13 @@ void DepsgraphNodeBuilder::build_object_pointcache(Object *object)
}
/**
* Build graph nodes for AnimData block
* Build graph nodes for AnimData block and any animated images used.
* \param id: ID-Block which hosts the AnimData
*/
void DepsgraphNodeBuilder::build_animdata(ID *id)
{
build_animation_images(id);
AnimData *adt = BKE_animdata_from_id(id);
if (adt == NULL) {
return;
@@ -886,6 +889,20 @@ void DepsgraphNodeBuilder::build_animdata_nlastrip_targets(ListBase *strips)
}
}
/**
* Build graph nodes to update the current frame in image users.
*/
void DepsgraphNodeBuilder::build_animation_images(ID *id)
{
if (BKE_image_user_id_has_animation(id)) {
ID *id_cow = get_cow_id(id);
add_operation_node(id,
NodeType::ANIMATION,
OperationCode::IMAGE_ANIMATION,
function_bind(BKE_image_user_id_eval_animation, _1, id_cow));
}
}
void DepsgraphNodeBuilder::build_action(bAction *action)
{
if (built_map_.checkIsBuiltAndTag(action)) {

View File

@@ -187,6 +187,7 @@ struct DepsgraphNodeBuilder {
void build_particle_settings(ParticleSettings *part);
void build_animdata(ID *id);
void build_animdata_nlastrip_targets(ListBase *strips);
void build_animation_images(ID *id);
void build_action(bAction *action);
void build_driver(ID *id, FCurve *fcurve, int driver_index);
void build_driver_variables(ID *id, FCurve *fcurve);

View File

@@ -72,6 +72,7 @@ extern "C" {
#include "BKE_effect.h"
#include "BKE_collision.h"
#include "BKE_fcurve.h"
#include "BKE_image.h"
#include "BKE_key.h"
#include "BKE_material.h"
#include "BKE_mball.h"
@@ -1199,6 +1200,8 @@ void DepsgraphRelationBuilder::build_constraints(ID *id,
void DepsgraphRelationBuilder::build_animdata(ID *id)
{
/* Images. */
build_animation_images(id);
/* Animation curves and NLA. */
build_animdata_curves(id);
/* Drivers. */
@@ -1391,6 +1394,18 @@ void DepsgraphRelationBuilder::build_animdata_drivers(ID *id)
}
}
void DepsgraphRelationBuilder::build_animation_images(ID *id)
{
/* TODO: can we check for existance of node for performance? */
if (BKE_image_user_id_has_animation(id)) {
OperationKey image_animation_key(id,
NodeType::ANIMATION,
OperationCode::IMAGE_ANIMATION);
TimeSourceKey time_src_key;
add_relation(time_src_key, image_animation_key, "TimeSrc -> Image Animation");
}
}
void DepsgraphRelationBuilder::build_action(bAction *action)
{
if (built_map_.checkIsBuiltAndTag(action)) {

View File

@@ -238,6 +238,7 @@ struct DepsgraphRelationBuilder
OperationNode *operation_from,
ListBase *strips);
void build_animdata_drivers(ID *id);
void build_animation_images(ID *id);
void build_action(bAction *action);
void build_driver(ID *id, FCurve *fcurve);
void build_driver_data(ID *id, FCurve *fcurve);

View File

@@ -119,6 +119,8 @@ const char *operationCodeAsString(OperationCode opcode)
case OperationCode::MOVIECLIP_EVAL: return "MOVIECLIP_EVAL";
case OperationCode::MOVIECLIP_SELECT_UPDATE:
return "MOVIECLIP_SELECT_UPDATE";
/* Image. */
case OperationCode::IMAGE_ANIMATION: return "IMAGE_ANIMATION";
/* Synchronization. */
case OperationCode::SYNCHRONIZE_TO_ORIGINAL:
return "SYNCHRONIZE_TO_ORIGINAL";

View File

@@ -173,6 +173,9 @@ enum class OperationCode {
MOVIECLIP_EVAL,
MOVIECLIP_SELECT_UPDATE,
/* Images. -------------------------------------------------------------- */
IMAGE_ANIMATION,
/* Synchronization clips. ----------------------------------------------- */
SYNCHRONIZE_TO_ORIGINAL,

View File

@@ -319,7 +319,7 @@ static DRWShadingGroup *DRW_gpencil_shgroup_fill_create(
BKE_image_release_ibuf(image, ibuf, NULL);
}
else {
GPUTexture *texture = GPU_texture_from_blender(gp_style->ima, &iuser, GL_TEXTURE_2D, true, 0.0);
GPUTexture *texture = GPU_texture_from_blender(gp_style->ima, &iuser, GL_TEXTURE_2D, true);
DRW_shgroup_uniform_texture(grp, "myTexture", texture);
stl->shgroups[id].texture_clamp = gp_style->flag & GP_STYLE_COLOR_TEX_CLAMP ? 1 : 0;
@@ -432,7 +432,7 @@ DRWShadingGroup *DRW_gpencil_shgroup_stroke_create(
BKE_image_release_ibuf(image, ibuf, NULL);
}
else {
GPUTexture *texture = GPU_texture_from_blender(gp_style->sima, &iuser, GL_TEXTURE_2D, true, 0.0f);
GPUTexture *texture = GPU_texture_from_blender(gp_style->sima, &iuser, GL_TEXTURE_2D, true);
DRW_shgroup_uniform_texture(grp, "myTexture", texture);
BKE_image_release_ibuf(image, ibuf, NULL);
@@ -525,7 +525,7 @@ static DRWShadingGroup *DRW_gpencil_shgroup_point_create(
BKE_image_release_ibuf(image, ibuf, NULL);
}
else {
GPUTexture *texture = GPU_texture_from_blender(gp_style->sima, &iuser, GL_TEXTURE_2D, true, 0.0f);
GPUTexture *texture = GPU_texture_from_blender(gp_style->sima, &iuser, GL_TEXTURE_2D, true);
DRW_shgroup_uniform_texture(grp, "myTexture", texture);
BKE_image_release_ibuf(image, ibuf, NULL);

View File

@@ -189,7 +189,7 @@ WORKBENCH_MaterialData *workbench_forward_get_or_create_material_data(
if (workbench_material_determine_color_type(wpd, material->ima, ob) == V3D_SHADING_TEXTURE_COLOR) {
material->shgrp_object_outline = DRW_shgroup_create(
e_data.object_outline_texture_sh, psl->object_outline_pass);
GPUTexture *tex = GPU_texture_from_blender(material->ima, NULL, GL_TEXTURE_2D, false, 0.0f);
GPUTexture *tex = GPU_texture_from_blender(material->ima, NULL, GL_TEXTURE_2D, false);
DRW_shgroup_uniform_texture(material->shgrp_object_outline, "image", tex);
}
else {

View File

@@ -247,7 +247,7 @@ void workbench_material_shgroup_uniform(
ImBuf *ibuf = BKE_image_acquire_ibuf(material->ima, NULL, NULL);
const bool do_color_correction = (ibuf && (ibuf->colormanage_flag & IMB_COLORMANAGE_IS_DATA) == 0);
BKE_image_release_ibuf(material->ima, ibuf, NULL);
GPUTexture *tex = GPU_texture_from_blender(material->ima, NULL, GL_TEXTURE_2D, false, 0.0f);
GPUTexture *tex = GPU_texture_from_blender(material->ima, NULL, GL_TEXTURE_2D, false);
DRW_shgroup_uniform_texture(grp, "image", tex);
DRW_shgroup_uniform_bool_copy(grp, "imageSrgb", do_color_correction);
DRW_shgroup_uniform_bool_copy(grp, "imageNearest", (interp == SHD_INTERP_CLOSEST));

View File

@@ -848,8 +848,7 @@ static DRWShadingGroup *drw_shgroup_material_inputs(DRWShadingGroup *grp, struct
GPUTexture *tex = NULL;
if (input->ima) {
double time = 0.0; /* TODO make time variable */
tex = GPU_texture_from_blender(input->ima, input->iuser, GL_TEXTURE_2D, input->image_isdata, time);
tex = GPU_texture_from_blender(input->ima, input->iuser, GL_TEXTURE_2D, input->image_isdata);
}
else {
/* Color Ramps */

View File

@@ -924,7 +924,7 @@ static void DRW_shgroup_empty_image(
GPUTexture *tex = NULL;
if (ob->data != NULL) {
tex = GPU_texture_from_blender(ob->data, ob->iuser, GL_TEXTURE_2D, false, 0.0f);
tex = GPU_texture_from_blender(ob->data, ob->iuser, GL_TEXTURE_2D, false);
if (tex) {
size[0] = GPU_texture_width(tex);
size[1] = GPU_texture_height(tex);

View File

@@ -193,7 +193,7 @@ static void PAINT_TEXTURE_cache_init(void *vedata)
Image *ima = (ma && ma->texpaintslot) ? ma->texpaintslot[ma->paint_active_slot].ima : NULL;
int interp = (ma && ma->texpaintslot) ? ma->texpaintslot[ma->paint_active_slot].interp : 0;
GPUTexture *tex = ima ?
GPU_texture_from_blender(ima, NULL, GL_TEXTURE_2D, false, 0.0f) : NULL;
GPU_texture_from_blender(ima, NULL, GL_TEXTURE_2D, false) : NULL;
if (tex) {
DRWShadingGroup *grp = DRW_shgroup_create(e_data.image_sh, psl->image_faces);
@@ -211,7 +211,7 @@ static void PAINT_TEXTURE_cache_init(void *vedata)
else {
Image *ima = imapaint->canvas;
GPUTexture *tex = ima ?
GPU_texture_from_blender(ima, NULL, GL_TEXTURE_2D, false, 0.0f) : NULL;
GPU_texture_from_blender(ima, NULL, GL_TEXTURE_2D, false) : NULL;
if (tex) {
DRWShadingGroup *grp = DRW_shgroup_create(e_data.image_sh, psl->image_faces);

View File

@@ -857,7 +857,7 @@ void uiTemplateImage(uiLayout *layout, bContext *C, PointerRNA *ptr, const char
ima = imaptr.data;
iuser = userptr->data;
BKE_image_user_check_frame_calc(iuser, (int)scene->r.cfra);
BKE_image_user_frame_calc(iuser, (int)scene->r.cfra);
cb = MEM_callocN(sizeof(RNAUpdateCb), "RNAUpdateCb");
cb->ptr = *ptr;
@@ -1242,6 +1242,7 @@ void uiTemplateImageLayers(uiLayout *layout, bContext *C, Image *ima, ImageUser
void uiTemplateImageInfo(uiLayout *layout, bContext *C, Image *ima, ImageUser *iuser)
{
Scene *scene = CTX_data_scene(C);
ImBuf *ibuf;
char str[MAX_IMAGE_INFO_LEN];
void *lock;
@@ -1251,7 +1252,8 @@ void uiTemplateImageInfo(uiLayout *layout, bContext *C, Image *ima, ImageUser *i
ibuf = BKE_image_acquire_ibuf(ima, iuser, &lock);
image_info(CTX_data_scene(C), iuser, ima, ibuf, str, MAX_IMAGE_INFO_LEN);
BKE_image_user_frame_calc(iuser, (int)scene->r.cfra);
image_info(scene, iuser, ima, ibuf, str, MAX_IMAGE_INFO_LEN);
BKE_image_release_ibuf(ima, ibuf, lock);
uiItemL(layout, str, ICON_NONE);
}

View File

@@ -335,7 +335,7 @@ static void image_refresh(const bContext *C, ScrArea *sa)
ima = ED_space_image(sima);
BKE_image_user_check_frame_calc(&sima->iuser, scene->r.cfra);
BKE_image_user_frame_calc(&sima->iuser, scene->r.cfra);
/* check if we have to set the image from the editmesh */
if (ima && (ima->source == IMA_SRC_VIEWER && sima->mode == SI_MODE_MASK)) {

View File

@@ -174,7 +174,7 @@ GPUTexture *GPU_texture_create_buffer(
GPUTexture *GPU_texture_from_bindcode(int textarget, int bindcode);
GPUTexture *GPU_texture_from_blender(
struct Image *ima, struct ImageUser *iuser, int textarget, bool is_data, double time);
struct Image *ima, struct ImageUser *iuser, int textarget, bool is_data);
GPUTexture *GPU_texture_from_preview(struct PreviewImage *prv, int mipmap);
void GPU_texture_add_mipmap(GPUTexture *tex, eGPUDataFormat gpu_data_format, int miplvl, const void *pixels);

View File

@@ -75,6 +75,8 @@
# include "smoke_API.h"
#endif
static void gpu_free_image_immediate(Image *ima);
//* Checking powers of two for images since OpenGL ES requires it */
#ifdef WITH_DDS
static bool is_power_of_2_resolution(int w, int h)
@@ -262,13 +264,18 @@ GPUTexture *GPU_texture_from_blender(
Image *ima,
ImageUser *iuser,
int textarget,
bool is_data,
double UNUSED(time))
bool is_data)
{
if (ima == NULL) {
return NULL;
}
/* currently, gpu refresh tagging is used by ima sequences */
if (ima->gpuflag & IMA_GPU_REFRESH) {
gpu_free_image_immediate(ima);
ima->gpuflag &= ~IMA_GPU_REFRESH;
}
/* Test if we already have a texture. */
GPUTexture **tex = gpu_get_image_gputexture(ima, textarget);
if (*tex) {
@@ -283,12 +290,6 @@ GPUTexture *GPU_texture_from_blender(
return *tex;
}
/* currently, tpage refresh is used by ima sequences */
if (ima->gpuflag & IMA_GPU_REFRESH) {
GPU_free_image(ima);
ima->gpuflag &= ~IMA_GPU_REFRESH;
}
/* check if we have a valid image buffer */
ImBuf *ibuf = BKE_image_acquire_ibuf(ima, iuser, NULL);
if (ibuf == NULL) {
@@ -1128,13 +1129,8 @@ void GPU_free_unused_buffers(Main *bmain)
BLI_thread_unlock(LOCK_OPENGL);
}
void GPU_free_image(Image *ima)
static void gpu_free_image_immediate(Image *ima)
{
if (!BLI_thread_is_main()) {
gpu_queue_image_for_free(ima);
return;
}
for (int i = 0; i < TEXTARGET_COUNT; i++) {
/* free glsl image binding */
if (ima->gputexture[i]) {
@@ -1146,6 +1142,16 @@ void GPU_free_image(Image *ima)
ima->gpuflag &= ~(IMA_GPU_MIPMAP_COMPLETE | IMA_GPU_IS_DATA);
}
void GPU_free_image(Image *ima)
{
if (!BLI_thread_is_main()) {
gpu_queue_image_for_free(ima);
return;
}
gpu_free_image_immediate(ima);
}
void GPU_free_images(Main *bmain)
{
if (bmain) {

View File

@@ -21,6 +21,7 @@
#include <stdlib.h>
#include "DNA_image_types.h"
#include "DNA_node_types.h"
#include "DNA_scene_types.h"
#include "BLI_utildefines.h"
@@ -66,6 +67,8 @@ static const EnumPropertyItem image_source_items[] = {
#include "IMB_imbuf.h"
#include "IMB_imbuf_types.h"
#include "ED_node.h"
static bool rna_Image_is_stereo_3d_get(PointerRNA *ptr)
{
return BKE_image_is_stereo((Image *)ptr->data);
@@ -136,16 +139,23 @@ static void rna_Image_views_format_update(Main *bmain, Scene *scene, PointerRNA
BKE_image_release_ibuf(ima, ibuf, lock);
}
static void rna_ImageUser_update(Main *UNUSED(bmain), Scene *scene, PointerRNA *ptr)
static void rna_ImageUser_update(Main *bmain, Scene *scene, PointerRNA *ptr)
{
ImageUser *iuser = ptr->data;
ID *id = ptr->id.data;
BKE_image_user_frame_calc(iuser, scene->r.cfra);
if (ptr->id.data) {
/* Update material or texture for render preview. */
DEG_id_tag_update(ptr->id.data, 0);
DEG_id_tag_update(ptr->id.data, ID_RECALC_EDITORS);
if (id) {
if (GS(id->name) == ID_NT) {
/* Special update for nodetrees to find parent datablock. */
ED_node_tag_update_nodetree(bmain, (bNodeTree *)id, NULL);
}
else {
/* Update material or texture for render preview. */
DEG_id_tag_update(id, 0);
DEG_id_tag_update(id, ID_RECALC_EDITORS);
}
}
}

View File

@@ -48,8 +48,14 @@ static void node_shader_init_tex_environment(bNodeTree *UNUSED(ntree), bNode *no
static int node_shader_gpu_tex_environment(GPUMaterial *mat, bNode *node, bNodeExecData *UNUSED(execdata), GPUNodeStack *in, GPUNodeStack *out)
{
Image *ima = (Image *)node->id;
ImageUser *iuser = NULL;
NodeTexEnvironment *tex = node->storage;
/* We get the image user from the original node, since GPU image keeps
* a pointer to it and the dependency refreshes the original. */
bNode *node_original = node->original ? node->original : node;
NodeTexImage *tex_original = node_original->storage;
ImageUser *iuser = &tex_original->iuser;
int isdata = tex->color_space == SHD_COLORSPACE_NONE;
GPUNodeLink *outalpha;

View File

@@ -67,8 +67,14 @@ static int node_shader_gpu_tex_image(GPUMaterial *mat, bNode *node, bNodeExecDat
};
Image *ima = (Image *)node->id;
ImageUser *iuser = NULL;
NodeTexImage *tex = node->storage;
/* We get the image user from the original node, since GPU image keeps
* a pointer to it and the dependency refreshes the original. */
bNode *node_original = node->original ? node->original : node;
NodeTexImage *tex_original = node_original->storage;
ImageUser *iuser = &tex_original->iuser;
const char *gpu_node_name = (tex->projection == SHD_PROJ_BOX)
? names_box[tex->interpolation]
: names[tex->interpolation];