Skip to content

Commit

Permalink
Simplify default color handling in GLSL (#1452)
Browse files Browse the repository at this point in the history
This changelist simplifies default color handling in GLSL, making the renderer responsible for generating fallback textures when images are not found on disk, and removing associated dynamic branches in GLSL shader code.

Two advantages of this simplification are:
- Hydra Storm and other MaterialXGenGlsl integrations can now render materials that reference 1x1 images without needing special-case logic.
- The render performance of MaterialXGenGlsl shaders that reference images is slightly improved, as dynamic branches exact a performance cost in some situations.

This new logic doesn't yet handle color space differences between missing images and default colors, and we should address this across languages in a future improvement.
  • Loading branch information
jstone-lucasfilm authored Aug 18, 2023
1 parent b23de80 commit 79433cc
Show file tree
Hide file tree
Showing 16 changed files with 74 additions and 96 deletions.
11 changes: 2 additions & 9 deletions libraries/stdlib/genglsl/mx_image_color3.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@

void mx_image_color3(sampler2D tex_sampler, int layer, vec3 defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, vec2 uv_scale, vec2 uv_offset, out vec3 result)
{
if (textureSize(tex_sampler, 0).x > 1)
{
vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset);
result = texture(tex_sampler, uv).rgb;
}
else
{
result = defaultval;
}
vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset);
result = texture(tex_sampler, uv).rgb;
}
11 changes: 2 additions & 9 deletions libraries/stdlib/genglsl/mx_image_color4.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@

void mx_image_color4(sampler2D tex_sampler, int layer, vec4 defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, vec2 uv_scale, vec2 uv_offset, out vec4 result)
{
if (textureSize(tex_sampler, 0).x > 1)
{
vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset);
result = texture(tex_sampler, uv);
}
else
{
result = defaultval;
}
vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset);
result = texture(tex_sampler, uv);
}
11 changes: 2 additions & 9 deletions libraries/stdlib/genglsl/mx_image_float.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@

void mx_image_float(sampler2D tex_sampler, int layer, float defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, vec2 uv_scale, vec2 uv_offset, out float result)
{
if (textureSize(tex_sampler, 0).x > 1)
{
vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset);
result = texture(tex_sampler, uv).r;
}
else
{
result = defaultval;
}
vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset);
result = texture(tex_sampler, uv).r;
}
11 changes: 2 additions & 9 deletions libraries/stdlib/genglsl/mx_image_vector2.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@

void mx_image_vector2(sampler2D tex_sampler, int layer, vec2 defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, vec2 uv_scale, vec2 uv_offset, out vec2 result)
{
if (textureSize(tex_sampler, 0).x > 1)
{
vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset);
result = texture(tex_sampler, uv).rg;
}
else
{
result = defaultval;
}
vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset);
result = texture(tex_sampler, uv).rg;
}
11 changes: 2 additions & 9 deletions libraries/stdlib/genglsl/mx_image_vector3.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@

void mx_image_vector3(sampler2D tex_sampler, int layer, vec3 defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, vec2 uv_scale, vec2 uv_offset, out vec3 result)
{
if (textureSize(tex_sampler, 0).x > 1)
{
vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset);
result = texture(tex_sampler, uv).rgb;
}
else
{
result = defaultval;
}
vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset);
result = texture(tex_sampler, uv).rgb;
}
11 changes: 2 additions & 9 deletions libraries/stdlib/genglsl/mx_image_vector4.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@

void mx_image_vector4(sampler2D tex_sampler, int layer, vec4 defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, vec2 uv_scale, vec2 uv_offset, out vec4 result)
{
if (textureSize(tex_sampler, 0).x > 1)
{
vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset);
result = texture(tex_sampler, uv);
}
else
{
result = defaultval;
}
vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset);
result = texture(tex_sampler, uv);
}
36 changes: 36 additions & 0 deletions resources/Materials/TestSuite/stdlib/texture/image_default.mtlx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0"?>
<materialx version="1.38" colorspace="lin_rec709">
<!--
Validation of default color fallbacks for missing image files.
-->
<image name="image_color4" type="color4">
<input name="file" type="filename" value="resources/Images/invalid.png" />
<input name="default" type="color4" value="0.8, 0.2, 0.2, 1.0" />
</image>
<output name="image_color4_output" type="color4" nodename="image_color4" />
<image name="image_color3" type="color3">
<input name="file" type="filename" value="resources/Images/invalid.png" />
<input name="default" type="color3" value="0.8, 0.2, 0.2" />
</image>
<output name="image_color3_output" type="color3" nodename="image_color3" />
<image name="image_vector4" type="vector4">
<input name="file" type="filename" value="resources/Images/invalid.png" />
<input name="default" type="vector4" value="0.8, 0.2, 0.2, 1.0" />
</image>
<output name="image_vector4_output" type="vector4" nodename="image_vector4" />
<image name="image_vector3" type="vector3">
<input name="file" type="filename" value="resources/Images/invalid.png" />
<input name="default" type="vector3" value="0.8, 0.2, 0.2" />
</image>
<output name="image_vector3_output" type="vector3" nodename="image_vector3" />
<image name="image_vector2" type="vector2">
<input name="file" type="filename" value="resources/Images/invalid.png" />
<input name="default" type="vector2" value="0.8, 0.2" />
</image>
<output name="image_vector2_output" type="vector2" nodename="image_vector2" />
<image name="image_float" type="float">
<input name="file" type="filename" value="resources/Images/invalid.png" />
<input name="default" type="float" value="0.8" />
</image>
<output name="image_float_output" type="float" nodename="image_float" />
</materialx>
2 changes: 1 addition & 1 deletion source/MaterialXGraphEditor/RenderView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -720,7 +720,7 @@ void RenderView::loadEnvironmentLight()
envIrradianceMap = _imageHandler->acquireImage(envIrradiancePath);

// If not found, then generate an irradiance map via spherical harmonics.
if (envIrradianceMap == _imageHandler->getInvalidImage())
if (envIrradianceMap == _imageHandler->getZeroImage())
{
mx::Sh3ColorCoeffs shIrradiance = mx::projectEnvironment(envRadianceMap, true);
envIrradianceMap = mx::renderEnvironment(shIrradiance, IRRADIANCE_MAP_WIDTH, IRRADIANCE_MAP_HEIGHT);
Expand Down
40 changes: 16 additions & 24 deletions source/MaterialXRender/ImageHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,6 @@ ImageHandler::ImageHandler(ImageLoaderPtr imageLoader)
{
addLoader(imageLoader);
_zeroImage = createUniformImage(2, 2, 4, Image::BaseType::UINT8, Color4(0.0f));

// Generated shaders interpret 1x1 textures as invalid images.
_invalidImage = createUniformImage(1, 1, 4, Image::BaseType::UINT8, Color4(0.0f));
}

void ImageHandler::addLoader(ImageLoaderPtr loader)
Expand Down Expand Up @@ -118,7 +115,7 @@ bool ImageHandler::saveImage(const FilePath& filePath,
return false;
}

ImagePtr ImageHandler::acquireImage(const FilePath& filePath)
ImagePtr ImageHandler::acquireImage(const FilePath& filePath, const Color4& defaultColor)
{
// Resolve the input filepath.
FilePath resolvedFilePath = filePath;
Expand All @@ -142,9 +139,12 @@ ImagePtr ImageHandler::acquireImage(const FilePath& filePath)
return image;
}

// No valid image was found, so cache the sentinel invalid image.
cacheImage(resolvedFilePath, _invalidImage);
return _invalidImage;
// No valid image was found, so generate a uniform texture with the given default color.
// TODO: This step assumes that the missing image and its default color are in the same
// color space, which is not always the case.
ImagePtr defaultImage = createUniformImage(1, 1, 4, Image::BaseType::UINT8, defaultColor);
cacheImage(resolvedFilePath, defaultImage);
return defaultImage;
}

bool ImageHandler::bindImage(ImagePtr, const ImageSamplingProperties&)
Expand Down Expand Up @@ -189,7 +189,7 @@ ImageVec ImageHandler::getReferencedImages(ConstDocumentPtr doc)
if (file)
{
ImagePtr image = acquireImage(file->getResolvedValueString());
if (image && image != _invalidImage)
if (image)
{
imageVec.push_back(image);
}
Expand All @@ -214,14 +214,6 @@ ImagePtr ImageHandler::loadImage(const FilePath& filePath)
}
if (image)
{
// Generated shaders interpret 1x1 textures as invalid images, so valid 1x1
// images must be resized.
if (image->getWidth() == 1 && image->getHeight() == 1)
{
image = createUniformImage(2, 2, image->getChannelCount(),
image->getBaseType(), image->getTexelColor(0, 0));
}

return image;
}
}
Expand Down Expand Up @@ -288,23 +280,23 @@ void ImageSamplingProperties::setProperties(const string& fileNameUniform,
root = root.substr(0, pos);
}

const string uaddressmodeStr = root + UADDRESS_MODE_SUFFIX;
const ShaderPort* port = uniformBlock.find(uaddressmodeStr);
const ShaderPort* port = uniformBlock.find(root + UADDRESS_MODE_SUFFIX);
ValuePtr intValue = port ? port->getValue() : nullptr;
uaddressMode = ImageSamplingProperties::AddressMode(intValue && intValue->isA<int>() ? intValue->asA<int>() : INVALID_MAPPED_INT_VALUE);

const string vaddressmodeStr = root + VADDRESS_MODE_SUFFIX;
port = uniformBlock.find(vaddressmodeStr);
port = uniformBlock.find(root + VADDRESS_MODE_SUFFIX);
intValue = port ? port->getValue() : nullptr;
vaddressMode = ImageSamplingProperties::AddressMode(intValue && intValue->isA<int>() ? intValue->asA<int>() : INVALID_MAPPED_INT_VALUE);

const string filtertypeStr = root + FILTER_TYPE_SUFFIX;
port = uniformBlock.find(filtertypeStr);
port = uniformBlock.find(root + FILTER_TYPE_SUFFIX);
intValue = port ? port->getValue() : nullptr;
filterType = ImageSamplingProperties::FilterType(intValue && intValue->isA<int>() ? intValue->asA<int>() : INVALID_MAPPED_INT_VALUE);

const string defaultColorStr = root + DEFAULT_COLOR_SUFFIX;
port = uniformBlock.find(defaultColorStr);
port = uniformBlock.find(root + DEFAULT_COLOR_SUFFIX);
if (!port)
{
port = uniformBlock.find(root + DEFAULT_COLOR_SUFFIX + "_cm_in");
}
ValuePtr colorValue = port ? port->getValue() : nullptr;
if (colorValue)
{
Expand Down
10 changes: 1 addition & 9 deletions source/MaterialXRender/ImageHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ class MX_RENDER_API ImageHandler
/// found in the cache, then each image loader will be applied in turn.
/// @param filePath File path of the image.
/// @return On success, a shared pointer to the acquired image.
ImagePtr acquireImage(const FilePath& filePath);
ImagePtr acquireImage(const FilePath& filePath, const Color4& defaultColor = Color4(0.0f));

/// Bind an image for rendering.
/// @param image The image to bind.
Expand Down Expand Up @@ -247,13 +247,6 @@ class MX_RENDER_API ImageHandler
return _zeroImage;
}

/// Return the sentinel invalid image, representing images that cannot be loaded
/// and should be replaced with their declared default value.
ImagePtr getInvalidImage() const
{
return _invalidImage;
}

/// Acquire all images referenced by the given document, and return the
/// images in a vector.
ImageVec getReferencedImages(ConstDocumentPtr doc);
Expand All @@ -278,7 +271,6 @@ class MX_RENDER_API ImageHandler
FileSearchPath _searchPath;
StringResolverPtr _resolver;
ImagePtr _zeroImage;
ImagePtr _invalidImage;
};

MATERIALX_NAMESPACE_END
Expand Down
2 changes: 1 addition & 1 deletion source/MaterialXRenderGlsl/GlslMaterial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ ImagePtr GlslMaterial::bindImage(const FilePath& filePath, const std::string& un
imageHandler->setFilenameResolver(resolver);

// Acquire the given image.
ImagePtr image = imageHandler->acquireImage(filePath);
ImagePtr image = imageHandler->acquireImage(filePath, samplingProperties.defaultColor);
if (!image)
{
return nullptr;
Expand Down
2 changes: 1 addition & 1 deletion source/MaterialXRenderGlsl/GlslProgram.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ ImagePtr GlslProgram::bindTexture(unsigned int uniformType, int uniformLocation,
uniformType >= GL_SAMPLER_1D && uniformType <= GL_SAMPLER_CUBE)
{
// Acquire the image.
ImagePtr image = imageHandler->acquireImage(filePath);
ImagePtr image = imageHandler->acquireImage(filePath, samplingProperties.defaultColor);
if (imageHandler->bindImage(image, samplingProperties))
{
GLTextureHandlerPtr textureHandler = std::static_pointer_cast<GLTextureHandler>(imageHandler);
Expand Down
2 changes: 1 addition & 1 deletion source/MaterialXRenderMsl/MslMaterial.mm
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@
imageHandler->setFilenameResolver(resolver);

// Acquire the given image.
return imageHandler->acquireImage(filePath);
return imageHandler->acquireImage(filePath, samplingProperties.defaultColor);
}

void MslMaterial::bindLighting(LightHandlerPtr lightHandler,
Expand Down
2 changes: 1 addition & 1 deletion source/MaterialXRenderMsl/MslPipelineStateObject.mm
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ int GetStrideOfMetalType(MTLDataType type)
{
// Acquire the image.
string error;
ImagePtr image = imageHandler->acquireImage(filePath);
ImagePtr image = imageHandler->acquireImage(filePath, samplingProperties.defaultColor);
imageHandler->bindImage(image, samplingProperties);
return bindTexture(renderCmdEncoder, uniformLocation, image, imageHandler);
}
Expand Down
4 changes: 2 additions & 2 deletions source/MaterialXView/Viewer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -474,15 +474,15 @@ void Viewer::loadEnvironmentLight()
}

// Look for an irradiance map using an expected filename convention.
mx::ImagePtr envIrradianceMap = _imageHandler->getInvalidImage();
mx::ImagePtr envIrradianceMap = _imageHandler->getZeroImage();
if (!_normalizeEnvironment && !_splitDirectLight)
{
mx::FilePath envIrradiancePath = _envRadianceFilename.getParentPath() / IRRADIANCE_MAP_FOLDER / _envRadianceFilename.getBaseName();
envIrradianceMap = _imageHandler->acquireImage(envIrradiancePath);
}

// If not found, then generate an irradiance map via spherical harmonics.
if (envIrradianceMap == _imageHandler->getInvalidImage())
if (envIrradianceMap == _imageHandler->getZeroImage())
{
if (_generateReferenceIrradiance)
{
Expand Down
4 changes: 2 additions & 2 deletions source/PyMaterialX/PyMaterialXRender/PyImageHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ void bindPyImageHandler(py::module& mod)
.def("addLoader", &mx::ImageHandler::addLoader)
.def("saveImage", &mx::ImageHandler::saveImage,
py::arg("filePath"), py::arg("image"), py::arg("verticalFlip") = false)
.def("acquireImage", &mx::ImageHandler::acquireImage)
.def("acquireImage", &mx::ImageHandler::acquireImage,
py::arg("filePath"), py::arg("defaultColor") = mx::Color4(0.0f))
.def("bindImage", &mx::ImageHandler::bindImage)
.def("unbindImage", &mx::ImageHandler::unbindImage)
.def("unbindImages", &mx::ImageHandler::unbindImages)
Expand All @@ -54,6 +55,5 @@ void bindPyImageHandler(py::module& mod)
py::arg("image") = nullptr)
.def("clearImageCache", &mx::ImageHandler::clearImageCache)
.def("getZeroImage", &mx::ImageHandler::getZeroImage)
.def("getInvalidImage", &mx::ImageHandler::getInvalidImage)
.def("getReferencedImages", &mx::ImageHandler::getReferencedImages);
}

0 comments on commit 79433cc

Please sign in to comment.