/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation * * SPDX-License-Identifier: Apache-2.0 */ #include "device/device.h" #include "scene/background.h" #include "scene/camera.h" #include "scene/colorspace.h" #include "scene/integrator.h" #include "scene/light.h" #include "scene/mesh.h" #include "scene/object.h" #include "scene/osl.h" #include "scene/procedural.h" #include "scene/scene.h" #include "scene/shader.h" #include "scene/shader_graph.h" #include "scene/shader_nodes.h" #include "scene/svm.h" #include "scene/tables.h" #include "util/foreach.h" #include "util/murmurhash.h" #include "util/task.h" #include "util/transform.h" #ifdef WITH_OCIO # include namespace OCIO = OCIO_NAMESPACE; #endif #include "scene/shader.tables" CCL_NAMESPACE_BEGIN thread_mutex ShaderManager::lookup_table_mutex; /* Shader */ NODE_DEFINE(Shader) { NodeType *type = NodeType::add("shader", create); static NodeEnum emission_sampling_method_enum; emission_sampling_method_enum.insert("none", EMISSION_SAMPLING_NONE); emission_sampling_method_enum.insert("auto", EMISSION_SAMPLING_AUTO); emission_sampling_method_enum.insert("front", EMISSION_SAMPLING_FRONT); emission_sampling_method_enum.insert("back", EMISSION_SAMPLING_BACK); emission_sampling_method_enum.insert("front_back", EMISSION_SAMPLING_FRONT_BACK); SOCKET_ENUM(emission_sampling_method, "Emission Sampling Method", emission_sampling_method_enum, EMISSION_SAMPLING_AUTO); SOCKET_BOOLEAN(use_transparent_shadow, "Use Transparent Shadow", true); SOCKET_BOOLEAN(heterogeneous_volume, "Heterogeneous Volume", true); static NodeEnum volume_sampling_method_enum; volume_sampling_method_enum.insert("distance", VOLUME_SAMPLING_DISTANCE); volume_sampling_method_enum.insert("equiangular", VOLUME_SAMPLING_EQUIANGULAR); volume_sampling_method_enum.insert("multiple_importance", VOLUME_SAMPLING_MULTIPLE_IMPORTANCE); SOCKET_ENUM(volume_sampling_method, "Volume Sampling Method", volume_sampling_method_enum, VOLUME_SAMPLING_MULTIPLE_IMPORTANCE); static NodeEnum volume_interpolation_method_enum; volume_interpolation_method_enum.insert("linear", VOLUME_INTERPOLATION_LINEAR); volume_interpolation_method_enum.insert("cubic", VOLUME_INTERPOLATION_CUBIC); SOCKET_ENUM(volume_interpolation_method, "Volume Interpolation Method", volume_interpolation_method_enum, VOLUME_INTERPOLATION_LINEAR); SOCKET_FLOAT(volume_step_rate, "Volume Step Rate", 1.0f); static NodeEnum displacement_method_enum; displacement_method_enum.insert("bump", DISPLACE_BUMP); displacement_method_enum.insert("true", DISPLACE_TRUE); displacement_method_enum.insert("both", DISPLACE_BOTH); SOCKET_ENUM(displacement_method, "Displacement Method", displacement_method_enum, DISPLACE_BUMP); SOCKET_INT(pass_id, "Pass ID", 0); return type; } Shader::Shader() : Node(get_node_type()) { pass_id = 0; graph = NULL; has_surface = false; has_surface_transparent = false; has_surface_raytrace = false; has_surface_bssrdf = false; has_volume = false; has_displacement = false; has_bump = false; has_bssrdf_bump = false; has_surface_spatial_varying = false; has_volume_spatial_varying = false; has_volume_attribute_dependency = false; has_integrator_dependency = false; has_volume_connected = false; prev_volume_step_rate = 0.0f; emission_estimate = zero_float3(); emission_sampling = EMISSION_SAMPLING_NONE; emission_is_constant = true; displacement_method = DISPLACE_BUMP; id = -1; need_update_uvs = true; need_update_attribute = true; need_update_displacement = true; } Shader::~Shader() { delete graph; } static float3 output_estimate_emission(ShaderOutput *output, bool &is_constant) { /* Only supports a few nodes for now, not arbitrary shader graphs. */ ShaderNode *node = (output) ? output->parent : nullptr; if (node == nullptr) { return zero_float3(); } else if (node->type == EmissionNode::get_node_type() || node->type == BackgroundNode::get_node_type()) { /* Emission and Background node. */ ShaderInput *color_in = node->input("Color"); ShaderInput *strength_in = node->input("Strength"); float3 estimate = one_float3(); if (color_in->link) { is_constant = false; } else { estimate *= node->get_float3(color_in->socket_type); } if (strength_in->link) { is_constant = false; estimate *= output_estimate_emission(strength_in->link, is_constant); } else { estimate *= node->get_float(strength_in->socket_type); } /* Lower importance of emission nodes from automatic value/color to shader * conversion, as these are likely used for previewing and can be slow to * build a light tree for on dense meshes. */ if (node->type == EmissionNode::get_node_type()) { EmissionNode *emission_node = static_cast(node); if (emission_node->from_auto_conversion) { estimate *= 0.1f; } } return estimate; } else if (node->type == LightFalloffNode::get_node_type() || node->type == IESLightNode::get_node_type()) { /* Get strength from Light Falloff and IES texture node. */ ShaderInput *strength_in = node->input("Strength"); is_constant = false; return (strength_in->link) ? output_estimate_emission(strength_in->link, is_constant) : make_float3(node->get_float(strength_in->socket_type)); } else if (node->type == AddClosureNode::get_node_type()) { /* Add Closure. */ ShaderInput *closure1_in = node->input("Closure1"); ShaderInput *closure2_in = node->input("Closure2"); const float3 estimate1 = (closure1_in->link) ? output_estimate_emission(closure1_in->link, is_constant) : zero_float3(); const float3 estimate2 = (closure2_in->link) ? output_estimate_emission(closure2_in->link, is_constant) : zero_float3(); return estimate1 + estimate2; } else if (node->type == MixClosureNode::get_node_type()) { /* Mix Closure. */ ShaderInput *fac_in = node->input("Fac"); ShaderInput *closure1_in = node->input("Closure1"); ShaderInput *closure2_in = node->input("Closure2"); const float3 estimate1 = (closure1_in->link) ? output_estimate_emission(closure1_in->link, is_constant) : zero_float3(); const float3 estimate2 = (closure2_in->link) ? output_estimate_emission(closure2_in->link, is_constant) : zero_float3(); if (fac_in->link) { is_constant = false; return estimate1 + estimate2; } else { const float fac = node->get_float(fac_in->socket_type); return (1.0f - fac) * estimate1 + fac * estimate2; } } else { /* Other nodes, potentially OSL nodes with arbitrary code for which all we can * determine is if it has emission or not. */ const bool has_emission = node->has_surface_emission(); float3 estimate; if (output->type() == SocketType::CLOSURE) { if (has_emission) { estimate = one_float3(); is_constant = false; } else { estimate = zero_float3(); } foreach (const ShaderInput *in, node->inputs) { if (in->type() == SocketType::CLOSURE && in->link) { estimate += output_estimate_emission(in->link, is_constant); } } } else { estimate = one_float3(); is_constant = false; } return estimate; } } void Shader::estimate_emission() { /* If the shader has AOVs, they need to be evaluated, so we can't skip the shader. */ emission_is_constant = true; foreach (ShaderNode *node, graph->nodes) { if (node->special_type == SHADER_SPECIAL_TYPE_OUTPUT_AOV) { emission_is_constant = false; } } ShaderInput *surf = graph->output()->input("Surface"); emission_estimate = fabs(output_estimate_emission(surf->link, emission_is_constant)); if (is_zero(emission_estimate)) { emission_sampling = EMISSION_SAMPLING_NONE; } else if (emission_sampling_method == EMISSION_SAMPLING_AUTO) { /* Automatically disable MIS when emission is low, to avoid weakly emitting * using a lot of memory in the light tree and potentially wasting samples * where indirect light samples are sufficient. * Possible optimization: estimate front and back emission separately. */ emission_sampling = (reduce_max(emission_estimate) > 0.5f) ? EMISSION_SAMPLING_FRONT_BACK : EMISSION_SAMPLING_NONE; } else { emission_sampling = emission_sampling_method; } } void Shader::set_graph(ShaderGraph *graph_) { /* do this here already so that we can detect if mesh or object attributes * are needed, since the node attribute callbacks check if their sockets * are connected but proxy nodes should not count */ if (graph_) { graph_->remove_proxy_nodes(); if (displacement_method != DISPLACE_BUMP) { graph_->compute_displacement_hash(); } } /* update geometry if displacement changed */ if (displacement_method != DISPLACE_BUMP) { const char *old_hash = (graph) ? graph->displacement_hash.c_str() : ""; const char *new_hash = (graph_) ? graph_->displacement_hash.c_str() : ""; if (strcmp(old_hash, new_hash) != 0) { need_update_displacement = true; } } /* assign graph */ delete graph; graph = graph_; /* Store info here before graph optimization to make sure that * nodes that get optimized away still count. */ has_volume_connected = (graph->output()->input("Volume")->link != NULL); } void Shader::tag_update(Scene *scene) { /* update tag */ tag_modified(); scene->shader_manager->tag_update(scene, ShaderManager::SHADER_MODIFIED); /* if the shader previously was emissive, update light distribution, * if the new shader is emissive, a light manager update tag will be * done in the shader manager device update. */ if (emission_sampling != EMISSION_SAMPLING_NONE) scene->light_manager->tag_update(scene, LightManager::SHADER_MODIFIED); /* Special handle of background MIS light for now: for some reason it * has use_mis set to false. We are quite close to release now, so * better to be safe. */ if (this == scene->background->get_shader(scene)) { scene->light_manager->need_update_background = true; if (scene->light_manager->has_background_light(scene)) { scene->light_manager->tag_update(scene, LightManager::SHADER_MODIFIED); } } /* quick detection of which kind of shaders we have to avoid loading * e.g. surface attributes when there is only a volume shader. this could * be more fine grained but it's better than nothing */ OutputNode *output = graph->output(); bool prev_has_volume = has_volume; has_surface = has_surface || output->input("Surface")->link; has_volume = has_volume || output->input("Volume")->link; has_displacement = has_displacement || output->input("Displacement")->link; /* get requested attributes. this could be optimized by pruning unused * nodes here already, but that's the job of the shader manager currently, * and may not be so great for interactive rendering where you temporarily * disconnect a node */ AttributeRequestSet prev_attributes = attributes; attributes.clear(); foreach (ShaderNode *node, graph->nodes) node->attributes(this, &attributes); if (has_displacement) { if (displacement_method == DISPLACE_BOTH) { attributes.add(ATTR_STD_POSITION_UNDISPLACED); } if (displacement_method_is_modified()) { need_update_displacement = true; scene->geometry_manager->tag_update(scene, GeometryManager::SHADER_DISPLACEMENT_MODIFIED); scene->object_manager->need_flags_update = true; } } /* compare if the attributes changed, mesh manager will check * need_update_attribute, update the relevant meshes and clear it. */ if (attributes.modified(prev_attributes)) { need_update_attribute = true; scene->geometry_manager->tag_update(scene, GeometryManager::SHADER_ATTRIBUTE_MODIFIED); scene->procedural_manager->tag_update(); } if (has_volume != prev_has_volume || volume_step_rate != prev_volume_step_rate) { scene->geometry_manager->need_flags_update = true; scene->object_manager->need_flags_update = true; prev_volume_step_rate = volume_step_rate; } } void Shader::tag_used(Scene *scene) { /* if an unused shader suddenly gets used somewhere, it needs to be * recompiled because it was skipped for compilation before */ if (!reference_count()) { tag_modified(); /* We do not reference here as the shader will be referenced when added to a socket. */ scene->shader_manager->tag_update(scene, ShaderManager::SHADER_MODIFIED); } } bool Shader::need_update_geometry() const { return need_update_uvs || need_update_attribute || need_update_displacement; } /* Shader Manager */ ShaderManager::ShaderManager() { update_flags = UPDATE_ALL; init_xyz_transforms(); } ShaderManager::~ShaderManager() {} ShaderManager *ShaderManager::create(int shadingsystem, Device *device) { ShaderManager *manager; (void)shadingsystem; /* Ignored when built without OSL. */ (void)device; #ifdef WITH_OSL if (shadingsystem == SHADINGSYSTEM_OSL) { manager = new OSLShaderManager(device); } else #endif { manager = new SVMShaderManager(); } return manager; } uint64_t ShaderManager::get_attribute_id(ustring name) { thread_scoped_spin_lock lock(attribute_lock_); /* get a unique id for each name, for SVM attribute lookup */ AttributeIDMap::iterator it = unique_attribute_id.find(name); if (it != unique_attribute_id.end()) return it->second; uint64_t id = ATTR_STD_NUM + unique_attribute_id.size(); unique_attribute_id[name] = id; return id; } uint64_t ShaderManager::get_attribute_id(AttributeStandard std) { return (uint64_t)std; } int ShaderManager::get_shader_id(Shader *shader, bool smooth) { /* get a shader id to pass to the kernel */ int id = shader->id; /* smooth flag */ if (smooth) id |= SHADER_SMOOTH_NORMAL; /* default flags */ id |= SHADER_CAST_SHADOW | SHADER_AREA_LIGHT; return id; } void ShaderManager::device_update(Device *device, DeviceScene *dscene, Scene *scene, Progress &progress) { if (!need_update()) { return; } uint id = 0; foreach (Shader *shader, scene->shaders) { shader->id = id++; } /* Those shaders should always be compiled as they are used as fallback if a shader cannot be * found, e.g. bad shader index for the triangle shaders on a Mesh. */ assert(scene->default_surface->reference_count() != 0); assert(scene->default_light->reference_count() != 0); assert(scene->default_background->reference_count() != 0); assert(scene->default_empty->reference_count() != 0); device_update_specific(device, dscene, scene, progress); } void ShaderManager::device_update_common(Device * /*device*/, DeviceScene *dscene, Scene *scene, Progress & /*progress*/) { dscene->shaders.free(); if (scene->shaders.size() == 0) return; KernelShader *kshader = dscene->shaders.alloc(scene->shaders.size()); bool has_volumes = false; bool has_transparent_shadow = false; foreach (Shader *shader, scene->shaders) { uint flag = 0; if (shader->emission_sampling == EMISSION_SAMPLING_FRONT) { flag |= SD_MIS_FRONT; } else if (shader->emission_sampling == EMISSION_SAMPLING_BACK) { flag |= SD_MIS_BACK; } else if (shader->emission_sampling == EMISSION_SAMPLING_FRONT_BACK) { flag |= SD_MIS_FRONT | SD_MIS_BACK; } if (!is_zero(shader->emission_estimate)) flag |= SD_HAS_EMISSION; if (shader->has_surface_transparent && shader->get_use_transparent_shadow()) flag |= SD_HAS_TRANSPARENT_SHADOW; if (shader->has_surface_raytrace) flag |= SD_HAS_RAYTRACE; if (shader->has_volume) { flag |= SD_HAS_VOLUME; has_volumes = true; /* todo: this could check more fine grained, to skip useless volumes * enclosed inside an opaque bsdf. */ flag |= SD_HAS_TRANSPARENT_SHADOW; } /* in this case we can assume transparent surface */ if (shader->has_volume_connected && !shader->has_surface) flag |= SD_HAS_ONLY_VOLUME; if (shader->has_volume) { if (shader->get_heterogeneous_volume() && shader->has_volume_spatial_varying) flag |= SD_HETEROGENEOUS_VOLUME; } if (shader->has_volume_attribute_dependency) flag |= SD_NEED_VOLUME_ATTRIBUTES; if (shader->has_bssrdf_bump) flag |= SD_HAS_BSSRDF_BUMP; if (shader->get_volume_sampling_method() == VOLUME_SAMPLING_EQUIANGULAR) flag |= SD_VOLUME_EQUIANGULAR; if (shader->get_volume_sampling_method() == VOLUME_SAMPLING_MULTIPLE_IMPORTANCE) flag |= SD_VOLUME_MIS; if (shader->get_volume_interpolation_method() == VOLUME_INTERPOLATION_CUBIC) flag |= SD_VOLUME_CUBIC; if (shader->has_bump) flag |= SD_HAS_BUMP; if (shader->get_displacement_method() != DISPLACE_BUMP) flag |= SD_HAS_DISPLACEMENT; /* constant emission check */ if (shader->emission_is_constant) flag |= SD_HAS_CONSTANT_EMISSION; uint32_t cryptomatte_id = util_murmur_hash3(shader->name.c_str(), shader->name.length(), 0); /* regular shader */ kshader->flags = flag; kshader->pass_id = shader->get_pass_id(); kshader->constant_emission[0] = shader->emission_estimate.x; kshader->constant_emission[1] = shader->emission_estimate.y; kshader->constant_emission[2] = shader->emission_estimate.z; kshader->cryptomatte_id = util_hash_to_float(cryptomatte_id); kshader++; has_transparent_shadow |= (flag & SD_HAS_TRANSPARENT_SHADOW) != 0; } dscene->shaders.copy_to_device(); /* lookup tables */ KernelTables *ktables = &dscene->data.tables; ktables->ggx_E = ensure_bsdf_table(dscene, scene, table_ggx_E); ktables->ggx_Eavg = ensure_bsdf_table(dscene, scene, table_ggx_Eavg); ktables->ggx_glass_E = ensure_bsdf_table(dscene, scene, table_ggx_glass_E); ktables->ggx_glass_Eavg = ensure_bsdf_table(dscene, scene, table_ggx_glass_Eavg); ktables->ggx_glass_inv_E = ensure_bsdf_table(dscene, scene, table_ggx_glass_inv_E); ktables->ggx_glass_inv_Eavg = ensure_bsdf_table(dscene, scene, table_ggx_glass_inv_Eavg); /* integrator */ KernelIntegrator *kintegrator = &dscene->data.integrator; kintegrator->use_volumes = has_volumes; /* TODO(sergey): De-duplicate with flags set in integrator.cpp. */ kintegrator->transparent_shadows = has_transparent_shadow; /* film */ KernelFilm *kfilm = &dscene->data.film; /* color space, needs to be here because e.g. displacement shaders could depend on it */ kfilm->xyz_to_r = float3_to_float4(xyz_to_r); kfilm->xyz_to_g = float3_to_float4(xyz_to_g); kfilm->xyz_to_b = float3_to_float4(xyz_to_b); kfilm->rgb_to_y = float3_to_float4(rgb_to_y); kfilm->rec709_to_r = float3_to_float4(rec709_to_r); kfilm->rec709_to_g = float3_to_float4(rec709_to_g); kfilm->rec709_to_b = float3_to_float4(rec709_to_b); kfilm->is_rec709 = is_rec709; } void ShaderManager::device_free_common(Device * /*device*/, DeviceScene *dscene, Scene *scene) { for (auto &entry : bsdf_tables) { scene->lookup_tables->remove_table(&entry.second); } bsdf_tables.clear(); dscene->shaders.free(); } void ShaderManager::add_default(Scene *scene) { /* default surface */ { ShaderGraph *graph = new ShaderGraph(); DiffuseBsdfNode *diffuse = graph->create_node(); diffuse->set_color(make_float3(0.8f, 0.8f, 0.8f)); graph->add(diffuse); graph->connect(diffuse->output("BSDF"), graph->output()->input("Surface")); Shader *shader = scene->create_node(); shader->name = "default_surface"; shader->set_graph(graph); shader->reference(); scene->default_surface = shader; shader->tag_update(scene); } /* default volume */ { ShaderGraph *graph = new ShaderGraph(); PrincipledVolumeNode *principled = graph->create_node(); graph->add(principled); graph->connect(principled->output("Volume"), graph->output()->input("Volume")); Shader *shader = scene->create_node(); shader->name = "default_volume"; shader->set_graph(graph); scene->default_volume = shader; shader->tag_update(scene); /* No default reference for the volume to avoid compiling volume kernels if there are no * actual volumes in the scene */ } /* default light */ { ShaderGraph *graph = new ShaderGraph(); EmissionNode *emission = graph->create_node(); emission->set_color(make_float3(0.8f, 0.8f, 0.8f)); emission->set_strength(0.0f); graph->add(emission); graph->connect(emission->output("Emission"), graph->output()->input("Surface")); Shader *shader = scene->create_node(); shader->name = "default_light"; shader->set_graph(graph); shader->reference(); scene->default_light = shader; shader->tag_update(scene); } /* default background */ { ShaderGraph *graph = new ShaderGraph(); Shader *shader = scene->create_node(); shader->name = "default_background"; shader->set_graph(graph); shader->reference(); scene->default_background = shader; shader->tag_update(scene); } /* default empty */ { ShaderGraph *graph = new ShaderGraph(); Shader *shader = scene->create_node(); shader->name = "default_empty"; shader->set_graph(graph); shader->reference(); scene->default_empty = shader; shader->tag_update(scene); } } uint ShaderManager::get_graph_kernel_features(ShaderGraph *graph) { uint kernel_features = 0; foreach (ShaderNode *node, graph->nodes) { kernel_features |= node->get_feature(); if (node->special_type == SHADER_SPECIAL_TYPE_CLOSURE) { BsdfBaseNode *bsdf_node = static_cast(node); if (CLOSURE_IS_VOLUME(bsdf_node->get_closure_type())) { kernel_features |= KERNEL_FEATURE_NODE_VOLUME; } } if (node->has_surface_bssrdf()) { kernel_features |= KERNEL_FEATURE_SUBSURFACE; } if (node->has_surface_transparent()) { kernel_features |= KERNEL_FEATURE_TRANSPARENT; } } return kernel_features; } uint ShaderManager::get_kernel_features(Scene *scene) { uint kernel_features = KERNEL_FEATURE_NODE_BSDF | KERNEL_FEATURE_NODE_EMISSION; for (int i = 0; i < scene->shaders.size(); i++) { Shader *shader = scene->shaders[i]; if (!shader->reference_count()) { continue; } /* Gather requested features from all the nodes from the graph nodes. */ kernel_features |= get_graph_kernel_features(shader->graph); ShaderNode *output_node = shader->graph->output(); if (output_node->input("Displacement")->link != NULL) { kernel_features |= KERNEL_FEATURE_NODE_BUMP; if (shader->get_displacement_method() == DISPLACE_BOTH) { kernel_features |= KERNEL_FEATURE_NODE_BUMP_STATE; } } /* On top of volume nodes, also check if we need volume sampling because * e.g. an Emission node would slip through the KERNEL_FEATURE_NODE_VOLUME check */ if (shader->has_volume_connected) { kernel_features |= KERNEL_FEATURE_VOLUME; } } if (use_osl()) { kernel_features |= KERNEL_FEATURE_OSL; } return kernel_features; } void ShaderManager::free_memory() { #ifdef WITH_OSL OSLShaderManager::free_memory(); #endif ColorSpaceManager::free_memory(); } float ShaderManager::linear_rgb_to_gray(float3 c) { return dot(c, rgb_to_y); } float3 ShaderManager::rec709_to_scene_linear(float3 c) { return make_float3(dot(rec709_to_r, c), dot(rec709_to_g, c), dot(rec709_to_b, c)); } string ShaderManager::get_cryptomatte_materials(Scene *scene) { string manifest = "{"; unordered_set materials; foreach (Shader *shader, scene->shaders) { if (materials.count(shader->name)) { continue; } materials.insert(shader->name); uint32_t cryptomatte_id = util_murmur_hash3(shader->name.c_str(), shader->name.length(), 0); manifest += string_printf("\"%s\":\"%08x\",", shader->name.c_str(), cryptomatte_id); } manifest[manifest.size() - 1] = '}'; return manifest; } void ShaderManager::tag_update(Scene * /*scene*/, uint32_t /*flag*/) { /* update everything for now */ update_flags = ShaderManager::UPDATE_ALL; } bool ShaderManager::need_update() const { return update_flags != UPDATE_NONE; } #ifdef WITH_OCIO static bool to_scene_linear_transform(OCIO::ConstConfigRcPtr &config, const char *colorspace, Transform &to_scene_linear) { OCIO::ConstProcessorRcPtr processor; try { processor = config->getProcessor("scene_linear", colorspace); } catch (OCIO::Exception &) { return false; } if (!processor) { return false; } OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor(); if (!device_processor) { return false; } to_scene_linear = transform_identity(); device_processor->applyRGB(&to_scene_linear.x.x); device_processor->applyRGB(&to_scene_linear.y.x); device_processor->applyRGB(&to_scene_linear.z.x); to_scene_linear = transform_transposed_inverse(to_scene_linear); return true; } #endif void ShaderManager::init_xyz_transforms() { /* Default to ITU-BT.709 in case no appropriate transform found. * Note XYZ here is defined as having a D65 white point. */ const Transform xyz_to_rec709 = make_transform(3.2404542f, -1.5371385f, -0.4985314f, 0.0f, -0.9692660f, 1.8760108f, 0.0415560f, 0.0f, 0.0556434f, -0.2040259f, 1.0572252f, 0.0f); xyz_to_r = float4_to_float3(xyz_to_rec709.x); xyz_to_g = float4_to_float3(xyz_to_rec709.y); xyz_to_b = float4_to_float3(xyz_to_rec709.z); rgb_to_y = make_float3(0.2126729f, 0.7151522f, 0.0721750f); rec709_to_r = make_float3(1.0f, 0.0f, 0.0f); rec709_to_g = make_float3(0.0f, 1.0f, 0.0f); rec709_to_b = make_float3(0.0f, 0.0f, 1.0f); is_rec709 = true; #ifdef WITH_OCIO /* Get from OpenColorO config if it has the required roles. */ OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig(); if (!(config && config->hasRole("scene_linear"))) { return; } Transform xyz_to_rgb; if (config->hasRole("aces_interchange")) { /* Standard OpenColorIO role, defined as ACES AP0 (ACES2065-1). */ Transform aces_to_rgb; if (!to_scene_linear_transform(config, "aces_interchange", aces_to_rgb)) { return; } /* This is the OpenColorIO builtin transform: * UTILITY - ACES-AP0_to_CIE-XYZ-D65_BFD. */ const Transform ACES_AP0_to_xyz_D65 = make_transform(0.938280f, -0.004451f, 0.016628f, 0.000000f, 0.337369f, 0.729522f, -0.066890f, 0.000000f, 0.001174f, -0.003711f, 1.091595f, 0.000000f); const Transform xyz_to_aces = transform_inverse(ACES_AP0_to_xyz_D65); xyz_to_rgb = aces_to_rgb * xyz_to_aces; } else if (config->hasRole("XYZ")) { /* Custom role used before the standard existed. */ if (!to_scene_linear_transform(config, "XYZ", xyz_to_rgb)) { return; } } else { /* No reference role found to determine XYZ. */ return; } xyz_to_r = float4_to_float3(xyz_to_rgb.x); xyz_to_g = float4_to_float3(xyz_to_rgb.y); xyz_to_b = float4_to_float3(xyz_to_rgb.z); const Transform rgb_to_xyz = transform_inverse(xyz_to_rgb); rgb_to_y = float4_to_float3(rgb_to_xyz.y); const Transform rec709_to_rgb = xyz_to_rgb * transform_inverse(xyz_to_rec709); rec709_to_r = float4_to_float3(rec709_to_rgb.x); rec709_to_g = float4_to_float3(rec709_to_rgb.y); rec709_to_b = float4_to_float3(rec709_to_rgb.z); is_rec709 = transform_equal_threshold(xyz_to_rgb, xyz_to_rec709, 0.0001f); #endif } size_t ShaderManager::ensure_bsdf_table_impl(DeviceScene *dscene, Scene *scene, const float *table, size_t n) { /* Since the BSDF tables are static arrays, we can use their address to identify them. */ if (!(bsdf_tables.count(table))) { vector entries(table, table + n); bsdf_tables[table] = scene->lookup_tables->add_table(dscene, entries); } return bsdf_tables[table]; } CCL_NAMESPACE_END