Cycles: float texture support. Due to GPU limitations there are now 95 byte,

and 5 float image textures. For CPU render this limit will be lifted later
on with image cache support. Patch by Mike Farnsworth.

Also changed color space option in image/environment texture node, to show
options Color and Non-Color Data, instead of sRGB and Linear, this is more
descriptive, and it was not really correct to equate Non-Color Data with
Linear.
This commit is contained in:
Brecht Van Lommel
2012-03-07 12:27:18 +00:00
parent bdf731f03d
commit 9fba458a7f
12 changed files with 360 additions and 98 deletions

View File

@@ -84,6 +84,25 @@ void kernel_tex_copy(KernelGlobals *kg, const char *name, device_ptr mem, size_t
#define KERNEL_IMAGE_TEX(type, ttype, tname)
#include "kernel_textures.h"
else if(strstr(name, "__tex_image_float")) {
texture_image_float4 *tex = NULL;
int id = atoi(name + strlen("__tex_image_float_"));
switch(id) {
case 95: tex = &kg->__tex_image_float_095; break;
case 96: tex = &kg->__tex_image_float_096; break;
case 97: tex = &kg->__tex_image_float_097; break;
case 98: tex = &kg->__tex_image_float_098; break;
case 99: tex = &kg->__tex_image_float_099; break;
default: break;
}
if(tex) {
tex->data = (float4*)mem;
tex->width = width;
tex->height = height;
}
}
else if(strstr(name, "__tex_image")) {
texture_image_uchar4 *tex = NULL;
int id = atoi(name + strlen("__tex_image_"));
@@ -184,11 +203,6 @@ void kernel_tex_copy(KernelGlobals *kg, const char *name, device_ptr mem, size_t
case 92: tex = &kg->__tex_image_092; break;
case 93: tex = &kg->__tex_image_093; break;
case 94: tex = &kg->__tex_image_094; break;
case 95: tex = &kg->__tex_image_095; break;
case 96: tex = &kg->__tex_image_096; break;
case 97: tex = &kg->__tex_image_097; break;
case 98: tex = &kg->__tex_image_098; break;
case 99: tex = &kg->__tex_image_099; break;
default: break;
}

View File

@@ -142,11 +142,13 @@ KERNEL_IMAGE_TEX(uchar4, texture_image_uchar4, __tex_image_091)
KERNEL_IMAGE_TEX(uchar4, texture_image_uchar4, __tex_image_092)
KERNEL_IMAGE_TEX(uchar4, texture_image_uchar4, __tex_image_093)
KERNEL_IMAGE_TEX(uchar4, texture_image_uchar4, __tex_image_094)
KERNEL_IMAGE_TEX(uchar4, texture_image_uchar4, __tex_image_095)
KERNEL_IMAGE_TEX(uchar4, texture_image_uchar4, __tex_image_096)
KERNEL_IMAGE_TEX(uchar4, texture_image_uchar4, __tex_image_097)
KERNEL_IMAGE_TEX(uchar4, texture_image_uchar4, __tex_image_098)
KERNEL_IMAGE_TEX(uchar4, texture_image_uchar4, __tex_image_099)
/* full-float image */
KERNEL_IMAGE_TEX(float4, texture_image_float4, __tex_image_float_095)
KERNEL_IMAGE_TEX(float4, texture_image_float4, __tex_image_float_096)
KERNEL_IMAGE_TEX(float4, texture_image_float4, __tex_image_float_097)
KERNEL_IMAGE_TEX(float4, texture_image_float4, __tex_image_float_098)
KERNEL_IMAGE_TEX(float4, texture_image_float4, __tex_image_float_099)
#undef KERNEL_TEX
#undef KERNEL_IMAGE_TEX

View File

@@ -130,11 +130,11 @@ __device float4 svm_image_texture(KernelGlobals *kg, int id, float x, float y)
case 92: r = kernel_tex_image_interp(__tex_image_092, x, y); break;
case 93: r = kernel_tex_image_interp(__tex_image_093, x, y); break;
case 94: r = kernel_tex_image_interp(__tex_image_094, x, y); break;
case 95: r = kernel_tex_image_interp(__tex_image_095, x, y); break;
case 96: r = kernel_tex_image_interp(__tex_image_096, x, y); break;
case 97: r = kernel_tex_image_interp(__tex_image_097, x, y); break;
case 98: r = kernel_tex_image_interp(__tex_image_098, x, y); break;
case 99: r = kernel_tex_image_interp(__tex_image_099, x, y); break;
case 95: r = kernel_tex_image_interp(__tex_image_float_095, x, y); break;
case 96: r = kernel_tex_image_interp(__tex_image_float_096, x, y); break;
case 97: r = kernel_tex_image_interp(__tex_image_float_097, x, y); break;
case 98: r = kernel_tex_image_interp(__tex_image_float_098, x, y); break;
case 99: r = kernel_tex_image_interp(__tex_image_float_099, x, y); break;
default:
kernel_assert(0);
return make_float4(0.0f, 0.0f, 0.0f, 0.0f);

View File

@@ -39,9 +39,10 @@ ImageManager::ImageManager()
ImageManager::~ImageManager()
{
for(size_t slot = 0; slot < images.size(); slot++) {
for(size_t slot = 0; slot < images.size(); slot++)
assert(!images[slot]);
}
for(size_t slot = 0; slot < float_images.size(); slot++)
assert(!float_images[slot]);
}
void ImageManager::set_osl_texture_system(void *texture_system)
@@ -49,39 +50,111 @@ void ImageManager::set_osl_texture_system(void *texture_system)
osl_texture_system = texture_system;
}
int ImageManager::add_image(const string& filename)
static bool is_float_image(const string& filename)
{
ImageInput *in = ImageInput::create(filename);
bool is_float = false;
if(in) {
ImageSpec spec;
if(in->open(filename, spec)) {
/* check the main format, and channel formats;
if any are non-integer, we'll need a float texture slot */
if(spec.format == TypeDesc::HALF ||
spec.format == TypeDesc::FLOAT ||
spec.format == TypeDesc::DOUBLE) {
is_float = true;
}
for(size_t channel = 0; channel < spec.channelformats.size(); channel++) {
if(spec.channelformats[channel] == TypeDesc::HALF ||
spec.channelformats[channel] == TypeDesc::FLOAT ||
spec.channelformats[channel] == TypeDesc::DOUBLE) {
is_float = true;
}
}
in->close();
}
delete in;
}
return is_float;
}
int ImageManager::add_image(const string& filename, bool& is_float)
{
Image *img;
size_t slot;
/* find existing image */
for(slot = 0; slot < images.size(); slot++) {
if(images[slot] && images[slot]->filename == filename) {
images[slot]->users++;
return slot;
/* load image info and find out if we need a float texture */
is_float = is_float_image(filename);
if(is_float) {
/* find existing image */
for(slot = 0; slot < float_images.size(); slot++) {
if(float_images[slot] && float_images[slot]->filename == filename) {
float_images[slot]->users++;
return slot+TEX_IMAGE_FLOAT_START;
}
}
/* find free slot */
for(slot = 0; slot < float_images.size(); slot++) {
if(!float_images[slot])
break;
}
if(slot == float_images.size()) {
/* max images limit reached */
if(float_images.size() == TEX_NUM_FLOAT_IMAGES)
return -1;
float_images.resize(float_images.size() + 1);
}
/* add new image */
img = new Image();
img->filename = filename;
img->need_load = true;
img->users = 1;
float_images[slot] = img;
/* report slot out of total set of textures */
slot += TEX_IMAGE_FLOAT_START;
}
else {
for(slot = 0; slot < images.size(); slot++) {
if(images[slot] && images[slot]->filename == filename) {
images[slot]->users++;
return slot;
}
}
/* find free slot */
for(slot = 0; slot < images.size(); slot++)
if(!images[slot])
break;
if(slot == images.size()) {
/* max images limit reached */
if(images.size() == TEX_IMAGE_MAX)
return -1;
/* find free slot */
for(slot = 0; slot < images.size(); slot++) {
if(!images[slot])
break;
}
images.resize(images.size() + 1);
if(slot == images.size()) {
/* max images limit reached */
if(images.size() == TEX_NUM_IMAGES)
return -1;
images.resize(images.size() + 1);
}
/* add new image */
img = new Image();
img->filename = filename;
img->need_load = true;
img->users = 1;
images[slot] = img;
}
/* add new image */
img = new Image();
img->filename = filename;
img->need_load = true;
img->users = 1;
images[slot] = img;
need_update = true;
return slot;
@@ -91,24 +164,40 @@ void ImageManager::remove_image(const string& filename)
{
size_t slot;
for(slot = 0; slot < images.size(); slot++)
if(images[slot] && images[slot]->filename == filename)
for(slot = 0; slot < images.size(); slot++) {
if(images[slot] && images[slot]->filename == filename) {
/* decrement user count */
images[slot]->users--;
assert(images[slot]->users >= 0);
/* don't remove immediately, rather do it all together later on. one of
the reasons for this is that on shader changes we add and remove nodes
that use them, but we do not want to reload the image all the time. */
if(images[slot]->users == 0)
need_update = true;
break;
if(slot == images.size())
return;
}
}
assert(images[slot]);
if(slot == images.size()) {
/* see if it's in a float texture slot */
for(slot = 0; slot < float_images.size(); slot++) {
if(float_images[slot] && float_images[slot]->filename == filename) {
/* decrement user count */
float_images[slot]->users--;
assert(float_images[slot]->users >= 0);
/* decrement user count */
images[slot]->users--;
assert(images[slot]->users >= 0);
/* don't remove immediately, rather do it all together later on. one of
the reasons for this is that on shader changes we add and remove nodes
that use them, but we do not want to reload the image all the time. */
if(images[slot]->users == 0)
need_update = true;
/* don't remove immediately, rather do it all together later on. one of
the reasons for this is that on shader changes we add and remove nodes
that use them, but we do not want to reload the image all the time. */
if(float_images[slot]->users == 0)
need_update = true;
break;
}
}
}
}
bool ImageManager::file_load_image(Image *img, device_vector<uchar4>& tex_img)
@@ -173,51 +262,168 @@ bool ImageManager::file_load_image(Image *img, device_vector<uchar4>& tex_img)
return true;
}
bool ImageManager::file_load_float_image(Image *img, device_vector<float4>& tex_img)
{
if(img->filename == "")
return false;
/* load image from file through OIIO */
ImageInput *in = ImageInput::create(img->filename);
if(!in)
return false;
ImageSpec spec;
if(!in->open(img->filename, spec)) {
delete in;
return false;
}
/* we only handle certain number of components */
int width = spec.width;
int height = spec.height;
int components = spec.nchannels;
if(!(components == 1 || components == 3 || components == 4)) {
in->close();
delete in;
return false;
}
/* read RGBA pixels */
float *pixels = (float*)tex_img.resize(width, height);
int scanlinesize = width*components*sizeof(float);
in->read_image(TypeDesc::FLOAT,
(uchar*)pixels + (height-1)*scanlinesize,
AutoStride,
-scanlinesize,
AutoStride);
in->close();
delete in;
if(components == 3) {
for(int i = width*height-1; i >= 0; i--) {
pixels[i*4+3] = 1.0f;
pixels[i*4+2] = pixels[i*3+2];
pixels[i*4+1] = pixels[i*3+1];
pixels[i*4+0] = pixels[i*3+0];
}
}
else if(components == 1) {
for(int i = width*height-1; i >= 0; i--) {
pixels[i*4+3] = 1.0f;
pixels[i*4+2] = pixels[i];
pixels[i*4+1] = pixels[i];
pixels[i*4+0] = pixels[i];
}
}
return true;
}
void ImageManager::device_load_image(Device *device, DeviceScene *dscene, int slot)
{
if(osl_texture_system)
return;
Image *img = images[slot];
device_vector<uchar4>& tex_img = dscene->tex_image[slot];
Image *img;
bool is_float;
if(tex_img.device_pointer)
device->tex_free(tex_img);
if(!file_load_image(img, tex_img)) {
/* on failure to load, we set a 1x1 pixels black image */
uchar *pixels = (uchar*)tex_img.resize(1, 1);
pixels[0] = 0;
pixels[1] = 0;
pixels[2] = 0;
pixels[3] = 0;
if(slot < TEX_IMAGE_FLOAT_START) {
img = images[slot];
is_float = false;
}
else {
img = float_images[slot - TEX_IMAGE_FLOAT_START];
is_float = true;
}
string name;
if(is_float) {
device_vector<float4>& tex_img = dscene->tex_float_image[slot - TEX_IMAGE_FLOAT_START];
if(slot >= 10) name = string_printf("__tex_image_0%d", slot);
else name = string_printf("__tex_image_00%d", slot);
if(tex_img.device_pointer)
device->tex_free(tex_img);
device->tex_alloc(name.c_str(), tex_img, true, true);
if(!file_load_float_image(img, tex_img)) {
/* on failure to load, we set a 1x1 pixels black image */
float *pixels = (float*)tex_img.resize(1, 1);
pixels[0] = 0.0f;
pixels[1] = 0.0f;
pixels[2] = 0.0f;
pixels[3] = 0.0f;
}
string name;
if(slot >= 10) name = string_printf("__tex_image_float_0%d", slot);
else name = string_printf("__tex_image_float_00%d", slot);
device->tex_alloc(name.c_str(), tex_img, true, true);
}
else {
device_vector<uchar4>& tex_img = dscene->tex_image[slot];
if(tex_img.device_pointer)
device->tex_free(tex_img);
if(!file_load_image(img, tex_img)) {
/* on failure to load, we set a 1x1 pixels black image */
uchar *pixels = (uchar*)tex_img.resize(1, 1);
pixels[0] = 0;
pixels[1] = 0;
pixels[2] = 0;
pixels[3] = 0;
}
string name;
if(slot >= 10) name = string_printf("__tex_image_0%d", slot);
else name = string_printf("__tex_image_00%d", slot);
device->tex_alloc(name.c_str(), tex_img, true, true);
}
}
void ImageManager::device_free_image(Device *device, DeviceScene *dscene, int slot)
{
if(images[slot]) {
Image *img;
bool is_float;
if(slot < TEX_IMAGE_FLOAT_START) {
img = images[slot];
is_float = false;
}
else {
img = float_images[slot - TEX_IMAGE_FLOAT_START];
is_float = true;
}
if(img) {
if(osl_texture_system) {
#ifdef WITH_OSL
ustring filename(images[slot]->filename);
((OSL::TextureSystem*)osl_texture_system)->invalidate(filename);
#endif
}
else if(is_float) {
device->tex_free(dscene->tex_float_image[slot - TEX_IMAGE_FLOAT_START]);
dscene->tex_float_image[slot - TEX_IMAGE_FLOAT_START].clear();
delete float_images[slot - TEX_IMAGE_FLOAT_START];
float_images[slot - TEX_IMAGE_FLOAT_START] = NULL;
}
else {
device->tex_free(dscene->tex_image[slot]);
dscene->tex_image[slot].clear();
}
delete images[slot];
images[slot] = NULL;
delete images[slot];
images[slot] = NULL;
}
}
}
@@ -242,6 +448,22 @@ void ImageManager::device_update(Device *device, DeviceScene *dscene, Progress&
}
}
for(size_t slot = 0; slot < float_images.size(); slot++) {
if(float_images[slot]) {
if(float_images[slot]->users == 0) {
device_free_image(device, dscene, slot + TEX_IMAGE_FLOAT_START);
}
else if(float_images[slot]->need_load) {
string name = path_filename(float_images[slot]->filename);
progress.set_status("Updating Images", "Loading " + name);
device_load_image(device, dscene, slot + TEX_IMAGE_FLOAT_START);
float_images[slot]->need_load = false;
}
if(progress.get_cancel()) return;
}
}
need_update = false;
}
@@ -249,8 +471,11 @@ void ImageManager::device_free(Device *device, DeviceScene *dscene)
{
for(size_t slot = 0; slot < images.size(); slot++)
device_free_image(device, dscene, slot);
for(size_t slot = 0; slot < float_images.size(); slot++)
device_free_image(device, dscene, slot + TEX_IMAGE_FLOAT_START);
images.clear();
float_images.clear();
}
CCL_NAMESPACE_END

View File

@@ -26,7 +26,10 @@
CCL_NAMESPACE_BEGIN
#define TEX_IMAGE_MAX 100
#define TEX_NUM_FLOAT_IMAGES 5
#define TEX_NUM_IMAGES 95
#define TEX_IMAGE_MAX (TEX_NUM_IMAGES + TEX_NUM_FLOAT_IMAGES)
#define TEX_IMAGE_FLOAT_START TEX_NUM_IMAGES
class Device;
class DeviceScene;
@@ -37,7 +40,7 @@ public:
ImageManager();
~ImageManager();
int add_image(const string& filename);
int add_image(const string& filename, bool& is_float);
void remove_image(const string& filename);
void device_update(Device *device, DeviceScene *dscene, Progress& progress);
@@ -56,9 +59,11 @@ private:
};
vector<Image*> images;
vector<Image*> float_images;
void *osl_texture_system;
bool file_load_image(Image *img, device_vector<uchar4>& tex_img);
bool file_load_float_image(Image *img, device_vector<float4>& tex_img);
void device_load_image(Device *device, DeviceScene *dscene, int slot);
void device_free_image(Device *device, DeviceScene *dscene, int slot);

View File

@@ -93,8 +93,8 @@ static ShaderEnum color_space_init()
{
ShaderEnum enm;
enm.insert("Linear", 0);
enm.insert("sRGB", 1);
enm.insert("None", 0);
enm.insert("Color", 1);
return enm;
}
@@ -106,8 +106,9 @@ ImageTextureNode::ImageTextureNode()
{
image_manager = NULL;
slot = -1;
is_float = false;
filename = "";
color_space = ustring("sRGB");
color_space = ustring("Color");
add_input("Vector", SHADER_SOCKET_POINT, ShaderInput::TEXTURE_UV);
add_output("Color", SHADER_SOCKET_COLOR);
@@ -125,6 +126,7 @@ ShaderNode *ImageTextureNode::clone() const
ImageTextureNode *node = new ImageTextureNode(*this);
node->image_manager = NULL;
node->slot = -1;
node->is_float = false;
return node;
}
@@ -136,7 +138,7 @@ void ImageTextureNode::compile(SVMCompiler& compiler)
image_manager = compiler.image_manager;
if(slot == -1)
slot = image_manager->add_image(filename);
slot = image_manager->add_image(filename, is_float);
if(!color_out->links.empty())
compiler.stack_assign(color_out);
@@ -144,6 +146,7 @@ void ImageTextureNode::compile(SVMCompiler& compiler)
compiler.stack_assign(alpha_out);
if(slot != -1) {
int srgb = (is_float || color_space != "Color")? 0: 1;
compiler.stack_assign(vector_in);
if(!tex_mapping.skip())
@@ -155,7 +158,7 @@ void ImageTextureNode::compile(SVMCompiler& compiler)
vector_in->stack_offset,
color_out->stack_offset,
alpha_out->stack_offset,
color_space_enum[color_space]));
srgb));
}
else {
/* image not found */
@@ -171,7 +174,10 @@ void ImageTextureNode::compile(SVMCompiler& compiler)
void ImageTextureNode::compile(OSLCompiler& compiler)
{
compiler.parameter("filename", filename.c_str());
compiler.parameter("color_space", color_space.c_str());
if(is_float || color_space != "Color")
compiler.parameter("color_space", "Linear");
else
compiler.parameter("color_space", "sRGB");
compiler.add(this, "node_image_texture");
}
@@ -184,8 +190,9 @@ EnvironmentTextureNode::EnvironmentTextureNode()
{
image_manager = NULL;
slot = -1;
is_float = false;
filename = "";
color_space = ustring("sRGB");
color_space = ustring("Color");
add_input("Vector", SHADER_SOCKET_VECTOR, ShaderInput::POSITION);
add_output("Color", SHADER_SOCKET_COLOR);
@@ -203,6 +210,7 @@ ShaderNode *EnvironmentTextureNode::clone() const
EnvironmentTextureNode *node = new EnvironmentTextureNode(*this);
node->image_manager = NULL;
node->slot = -1;
node->is_float = false;
return node;
}
@@ -214,7 +222,7 @@ void EnvironmentTextureNode::compile(SVMCompiler& compiler)
image_manager = compiler.image_manager;
if(slot == -1)
slot = image_manager->add_image(filename);
slot = image_manager->add_image(filename, is_float);
if(!color_out->links.empty())
compiler.stack_assign(color_out);
@@ -222,6 +230,8 @@ void EnvironmentTextureNode::compile(SVMCompiler& compiler)
compiler.stack_assign(alpha_out);
if(slot != -1) {
int srgb = (is_float || color_space != "Color")? 0: 1;
compiler.stack_assign(vector_in);
if(!tex_mapping.skip())
@@ -233,7 +243,7 @@ void EnvironmentTextureNode::compile(SVMCompiler& compiler)
vector_in->stack_offset,
color_out->stack_offset,
alpha_out->stack_offset,
color_space_enum[color_space]));
srgb));
}
else {
/* image not found */
@@ -249,7 +259,10 @@ void EnvironmentTextureNode::compile(SVMCompiler& compiler)
void EnvironmentTextureNode::compile(OSLCompiler& compiler)
{
compiler.parameter("filename", filename.c_str());
compiler.parameter("color_space", color_space.c_str());
if(is_float || color_space != "Color")
compiler.parameter("color_space", "Linear");
else
compiler.parameter("color_space", "sRGB");
compiler.add(this, "node_environment_texture");
}

View File

@@ -64,6 +64,7 @@ public:
ImageManager *image_manager;
int slot;
bool is_float;
string filename;
ustring color_space;
@@ -78,6 +79,7 @@ public:
ImageManager *image_manager;
int slot;
bool is_float;
string filename;
ustring color_space;

View File

@@ -92,7 +92,8 @@ public:
device_vector<uint> sobol_directions;
/* images */
device_vector<uchar4> tex_image[TEX_IMAGE_MAX];
device_vector<uchar4> tex_image[TEX_NUM_IMAGES];
device_vector<float4> tex_float_image[TEX_NUM_FLOAT_IMAGES];
KernelData data;
};

View File

@@ -582,8 +582,8 @@ typedef struct TexNodeOutput {
#define SHD_WAVE_TRI 2
/* image/environment texture */
#define SHD_COLORSPACE_LINEAR 0
#define SHD_COLORSPACE_SRGB 1
#define SHD_COLORSPACE_NONE 0
#define SHD_COLORSPACE_COLOR 1
/* blur node */
#define CMP_NODE_BLUR_ASPECT_NONE 0

View File

@@ -1264,8 +1264,8 @@ static void def_sh_tex_sky(StructRNA *srna)
static void def_sh_tex_environment(StructRNA *srna)
{
static const EnumPropertyItem prop_color_space_items[] = {
{SHD_COLORSPACE_SRGB, "SRGB", 0, "sRGB", "Image is in sRGB color space"},
{SHD_COLORSPACE_LINEAR, "LINEAR", 0, "Linear", "Image is in scene linear color space"},
{SHD_COLORSPACE_COLOR, "COLOR", 0, "Color", "Image contains color data, and will be converted to linear color for rendering"},
{SHD_COLORSPACE_NONE, "NONE", 0, "Non-Color Data", "Image contains non-color data, for example a displacement or normal map, and will not be converted"},
{0, NULL, 0, NULL, NULL}};
PropertyRNA *prop;
@@ -1289,8 +1289,8 @@ static void def_sh_tex_environment(StructRNA *srna)
static void def_sh_tex_image(StructRNA *srna)
{
static const EnumPropertyItem prop_color_space_items[] = {
{SHD_COLORSPACE_LINEAR, "LINEAR", 0, "Linear", "Image is in scene linear color space"},
{SHD_COLORSPACE_SRGB, "SRGB", 0, "sRGB", "Image is in sRGB color space"},
{SHD_COLORSPACE_COLOR, "COLOR", 0, "Color", "Image contains color data, and will be converted to linear color for rendering"},
{SHD_COLORSPACE_NONE, "NONE", 0, "Non-Color Data", "Image contains non-color data, for example a displacement or normal map, and will not be converted"},
{0, NULL, 0, NULL, NULL}};
PropertyRNA *prop;

View File

@@ -44,7 +44,7 @@ static void node_shader_init_tex_environment(bNodeTree *UNUSED(ntree), bNode* no
NodeTexEnvironment *tex = MEM_callocN(sizeof(NodeTexEnvironment), "NodeTexEnvironment");
default_tex_mapping(&tex->base.tex_mapping);
default_color_mapping(&tex->base.color_mapping);
tex->color_space = SHD_COLORSPACE_SRGB;
tex->color_space = SHD_COLORSPACE_COLOR;
node->storage = tex;
}

View File

@@ -44,7 +44,7 @@ static void node_shader_init_tex_image(bNodeTree *UNUSED(ntree), bNode* node, bN
NodeTexImage *tex = MEM_callocN(sizeof(NodeTexImage), "NodeTexImage");
default_tex_mapping(&tex->base.tex_mapping);
default_color_mapping(&tex->base.color_mapping);
tex->color_space = SHD_COLORSPACE_SRGB;
tex->color_space = SHD_COLORSPACE_COLOR;
node->storage = tex;
}