diff --git a/scripts/startup/bl_ui/properties_render.py b/scripts/startup/bl_ui/properties_render.py index 78f56565429..b4430ee48fd 100644 --- a/scripts/startup/bl_ui/properties_render.py +++ b/scripts/startup/bl_ui/properties_render.py @@ -85,7 +85,34 @@ class RENDER_PT_color_management(RenderButtonsPanel, Panel): col.prop(view, "exposure") col.prop(view, "gamma") - col.separator() + +class RENDER_PT_color_management_working_space(RenderButtonsPanel, Panel): + bl_label = "Working Space" + bl_parent_id = "RENDER_PT_color_management" + bl_options = {'DEFAULT_CLOSED'} + COMPAT_ENGINES = { + 'BLENDER_RENDER', + 'BLENDER_EEVEE', + 'BLENDER_WORKBENCH', + } + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + + scene = context.scene + blend_colorspace = context.blend_data.colorspace + + flow = layout.grid_flow(row_major=True, columns=0, even_columns=False, even_rows=False, align=True) + + col = flow.column() + + split = col.split(factor=0.4) + row = split.row() + row.label(text="File") + row.alignment = 'RIGHT' + split.operator_menu_enum("wm.set_working_color_space", "working_space", text=blend_colorspace.working_space) col.prop(scene.sequencer_colorspace_settings, "name", text="Sequencer") @@ -1112,6 +1139,7 @@ classes = ( RENDER_PT_opengl_film, RENDER_PT_hydra_debug, RENDER_PT_color_management, + RENDER_PT_color_management_working_space, RENDER_PT_color_management_curves, RENDER_PT_color_management_white_balance_presets, RENDER_PT_color_management_white_balance, diff --git a/source/blender/blenkernel/BKE_library.hh b/source/blender/blenkernel/BKE_library.hh index 006976e7fc1..5452786da78 100644 --- a/source/blender/blenkernel/BKE_library.hh +++ b/source/blender/blenkernel/BKE_library.hh @@ -11,6 +11,8 @@ #include "BLI_string_ref.hh" +#include "BKE_main.hh" + struct FileData; struct Library; struct ListBase; @@ -55,6 +57,9 @@ struct LibraryRuntime { /** See BLENDER_FILE_VERSION, BLENDER_FILE_SUBVERSION, needed for do_versions. */ short versionfile = 0; short subversionfile = 0; + + /* Colorspace information. */ + MainColorspace colorspace; }; /** diff --git a/source/blender/blenkernel/BKE_main.hh b/source/blender/blenkernel/BKE_main.hh index 4b30e2835b9..93298d435c0 100644 --- a/source/blender/blenkernel/BKE_main.hh +++ b/source/blender/blenkernel/BKE_main.hh @@ -25,6 +25,7 @@ #include "DNA_listBase.h" #include "BLI_compiler_attrs.h" +#include "BLI_math_matrix_types.hh" #include "BLI_sys_types.h" #include "BLI_utility_mixins.hh" #include "BLI_vector_set.hh" @@ -141,6 +142,14 @@ enum { }; struct MainColorspace { + /* + * File working colorspace for all scene linear colors. + * The name is only for the user interface and is not a unique identifier, the matrix is + * the XYZ colorspace is the source of truth. + * */ + char scene_linear_name[64 /*MAX_COLORSPACE_NAME*/] = ""; + blender::float3x3 scene_linear_to_xyz = blender::float3x3::zero(); + /* * A colorspace, view or display was not found, which likely means the OpenColorIO config * used to create this blend file is missing. diff --git a/source/blender/blenkernel/intern/blendfile.cc b/source/blender/blenkernel/intern/blendfile.cc index 9a8e451b419..47987082054 100644 --- a/source/blender/blenkernel/intern/blendfile.cc +++ b/source/blender/blenkernel/intern/blendfile.cc @@ -982,6 +982,9 @@ static void setup_app_data(bContext *C, reuse_data.old_bmain = bmain; reuse_data.wm_setup_data = wm_setup_data; + const bool reuse_editable_assets = mode != LOAD_UNDO && !params->is_factory_settings && + reuse_editable_asset_needed(&reuse_data); + if (mode != LOAD_UNDO) { const short ui_id_codes[]{ID_WS, ID_SCR}; @@ -1008,7 +1011,7 @@ static void setup_app_data(bContext *C, BKE_main_idmap_destroy(reuse_data.id_map); - if (!params->is_factory_settings && reuse_editable_asset_needed(&reuse_data)) { + if (reuse_editable_assets) { unpin_file_local_grease_pencil_brush_materials(&reuse_data); /* Keep linked brush asset data, similar to UI data. Only does a known * subset know. Could do everything, but that risks dragging along more @@ -1229,9 +1232,8 @@ static void setup_app_data(bContext *C, /* Setting scene might require having a dependency graph, with copy-on-eval * we need to make sure we ensure scene has correct color management before * constructing dependency graph. */ - if (mode != LOAD_UNDO) { - IMB_colormanagement_check_file_config(bmain); - } + IMB_colormanagement_working_space_check(bmain, mode == LOAD_UNDO, reuse_editable_assets); + IMB_colormanagement_check_file_config(bmain); BKE_scene_set_background(bmain, curscene); diff --git a/source/blender/blenkernel/intern/main.cc b/source/blender/blenkernel/intern/main.cc index c46d80b0de4..29e3d387f79 100644 --- a/source/blender/blenkernel/intern/main.cc +++ b/source/blender/blenkernel/intern/main.cc @@ -38,6 +38,7 @@ #include "BKE_main_namemap.hh" #include "BKE_report.hh" +#include "IMB_colormanagement.hh" #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" @@ -88,6 +89,7 @@ Main::~Main() Main *BKE_main_new() { Main *bmain = MEM_new
(__func__); + IMB_colormanagement_working_space_init(bmain); return bmain; } diff --git a/source/blender/blenloader/intern/readfile.cc b/source/blender/blenloader/intern/readfile.cc index f10a17bebd6..7f9c8690c9f 100644 --- a/source/blender/blenloader/intern/readfile.cc +++ b/source/blender/blenloader/intern/readfile.cc @@ -112,6 +112,8 @@ #include "SEQ_sequencer.hh" #include "SEQ_utils.hh" +#include "IMB_colormanagement.hh" + #include "readfile.hh" #include "versioning_common.hh" @@ -444,6 +446,7 @@ void blo_split_main(Main *bmain) libmain->has_forward_compatibility_issues = !MAIN_VERSION_FILE_OLDER_OR_EQUAL( libmain, BLENDER_FILE_VERSION, BLENDER_FILE_SUBVERSION); libmain->is_asset_edit_file = (lib->runtime->tag & LIBRARY_IS_ASSET_EDIT_FILE) != 0; + libmain->colorspace = lib->runtime->colorspace; bmain->split_mains->add_new(libmain); libmain->split_mains = bmain->split_mains; lib->runtime->temp_index = i; @@ -464,7 +467,7 @@ void blo_split_main(Main *bmain) MEM_freeN(lib_main_array); } -static void read_file_version(FileData *fd, Main *main) +static void read_file_version_and_colorspace(FileData *fd, Main *main) { BHead *bhead; @@ -485,6 +488,9 @@ static void read_file_version(FileData *fd, Main *main) main->has_forward_compatibility_issues = !MAIN_VERSION_FILE_OLDER_OR_EQUAL( main, BLENDER_FILE_VERSION, BLENDER_FILE_SUBVERSION); main->is_asset_edit_file = (fg->fileflags & G_FILE_ASSET_EDIT_FILE) != 0; + STRNCPY(main->colorspace.scene_linear_name, fg->colorspace_scene_linear_name); + main->colorspace.scene_linear_to_xyz = blender::float3x3( + fg->colorspace_scene_linear_to_xyz); MEM_freeN(fg); } else if (bhead->code == BLO_CODE_ENDB) { @@ -497,6 +503,7 @@ static void read_file_version(FileData *fd, Main *main) main->curlib->runtime->subversionfile = main->subversionfile; SET_FLAG_FROM_TEST( main->curlib->runtime->tag, main->is_asset_edit_file, LIBRARY_IS_ASSET_EDIT_FILE); + main->curlib->runtime->colorspace = main->colorspace; } } @@ -588,7 +595,7 @@ static Main *blo_find_main(FileData *fd, const char *filepath, const char *relab m->curlib = lib; - read_file_version(fd, m); + read_file_version_and_colorspace(fd, m); if (G.debug & G_DEBUG) { CLOG_DEBUG(&LOG, "Added new lib %s", filepath); @@ -3228,6 +3235,10 @@ static BHead *read_global(BlendFileData *bfd, FileData *fd, BHead *bhead) STRNCPY(bfd->main->build_hash, fg->build_hash); bfd->main->is_asset_edit_file = (fg->fileflags & G_FILE_ASSET_EDIT_FILE) != 0; + STRNCPY(bfd->main->colorspace.scene_linear_name, fg->colorspace_scene_linear_name); + bfd->main->colorspace.scene_linear_to_xyz = blender::float3x3( + fg->colorspace_scene_linear_to_xyz); + bfd->fileflags = fg->fileflags; bfd->globalf = fg->globalf; @@ -4004,6 +4015,7 @@ BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath) mainvar->curlib->runtime->filedata : fd, mainvar); + IMB_colormanagement_working_space_convert(mainvar, bfd->main); } blo_join_main(bfd->main); @@ -4597,7 +4609,7 @@ static Main *library_link_begin(Main *mainvar, /* needed for do_version */ mainl->versionfile = short(fd->fileversion); - read_file_version(fd, mainl); + read_file_version_and_colorspace(fd, mainl); read_file_bhead_idname_map_create(fd); return mainl; @@ -4646,6 +4658,7 @@ static void split_main_newid(Main *mainptr, Main *main_newid) main_newid->subversionfile = mainptr->subversionfile; STRNCPY(main_newid->filepath, mainptr->filepath); main_newid->curlib = mainptr->curlib; + main_newid->colorspace = mainptr->colorspace; MainListsArray lbarray = BKE_main_lists_get(*mainptr); MainListsArray lbarray_newid = BKE_main_lists_get(*main_newid); @@ -4728,6 +4741,7 @@ static void library_link_end(Main *mainl, FileData **fd, const int flag, ReportL main_newid->curlib->runtime->filedata : *fd, main_newid); + IMB_colormanagement_working_space_convert(main_newid, mainvar); add_main_to_main(mainlib, main_newid); @@ -5037,7 +5051,7 @@ static FileData *read_library_file_data(FileData *basefd, Main *bmain, Main *lib lib_bmain->versionfile = fd->fileversion; /* subversion */ - read_file_version(fd, lib_bmain); + read_file_version_and_colorspace(fd, lib_bmain); read_file_bhead_idname_map_create(fd); } else { @@ -5047,6 +5061,7 @@ static FileData *read_library_file_data(FileData *basefd, Main *bmain, Main *lib /* Set lib version to current main one... Makes assert later happy. */ lib_bmain->versionfile = lib_bmain->curlib->runtime->versionfile = bmain->versionfile; lib_bmain->subversionfile = lib_bmain->curlib->runtime->subversionfile = bmain->subversionfile; + lib_bmain->colorspace = lib_bmain->curlib->runtime->colorspace = bmain->colorspace; } if (fd == nullptr) { diff --git a/source/blender/blenloader/intern/writefile.cc b/source/blender/blenloader/intern/writefile.cc index daa05b17120..bdab6285cc4 100644 --- a/source/blender/blenloader/intern/writefile.cc +++ b/source/blender/blenloader/intern/writefile.cc @@ -97,6 +97,7 @@ #include "BLI_fileops.hh" #include "BLI_implicit_sharing.hh" #include "BLI_math_base.h" +#include "BLI_math_matrix.h" #include "BLI_multi_value_map.hh" #include "BLI_path_utils.hh" #include "BLI_set.hh" @@ -1241,6 +1242,9 @@ static void write_global(WriteData *wd, const int fileflags, Main *mainvar) fg.curscene = scene; fg.cur_view_layer = view_layer; + STRNCPY(fg.colorspace_scene_linear_name, mainvar->colorspace.scene_linear_name); + copy_m3_m3(fg.colorspace_scene_linear_to_xyz, mainvar->colorspace.scene_linear_to_xyz.ptr()); + /* Prevent to save this, is not good convention, and feature with concerns. */ fg.fileflags = (fileflags & ~G_FILE_FLAG_ALL_RUNTIME); diff --git a/source/blender/imbuf/CMakeLists.txt b/source/blender/imbuf/CMakeLists.txt index 889dae26faa..4315da62e85 100644 --- a/source/blender/imbuf/CMakeLists.txt +++ b/source/blender/imbuf/CMakeLists.txt @@ -73,6 +73,7 @@ set(LIB PRIVATE bf::blenkernel PRIVATE bf::blenlib PRIVATE bf::blenloader + PRIVATE bf::depsgraph PRIVATE bf::dna PRIVATE bf::gpu bf_imbuf_openimageio diff --git a/source/blender/imbuf/IMB_colormanagement.hh b/source/blender/imbuf/IMB_colormanagement.hh index d61f55d8b97..8a0178c1ba3 100644 --- a/source/blender/imbuf/IMB_colormanagement.hh +++ b/source/blender/imbuf/IMB_colormanagement.hh @@ -19,6 +19,7 @@ struct ColorManagedColorspaceSettings; struct ColorManagedDisplaySettings; struct ColorManagedViewSettings; struct ColormanageProcessor; +struct ID; struct EnumPropertyItem; struct ImBuf; struct ImageFormatData; @@ -392,6 +393,37 @@ void IMB_colormanagement_colorspace_from_ibuf_ftype( /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Working Space Functions + * \{ */ + +const char *IMB_colormanagement_working_space_get_default(); +const char *IMB_colormanagement_working_space_get(); + +bool IMB_colormanagement_working_space_set_from_name(const char *name); +bool IMB_colormanagement_working_space_set_from_matrix( + const char *name, const blender::float3x3 &scene_linear_to_xyz); + +void IMB_colormanagement_working_space_check(Main *bmain, + const bool for_undo, + const bool have_editable_assets); + +void IMB_colormanagement_working_space_init(Main *bmain); +void IMB_colormanagement_working_space_convert( + Main *bmain, + const blender::float3x3 ¤t_scene_linear_to_xyz, + const blender::float3x3 &new_xyz_to_scene_linear, + const bool depsgraph_tag = false, + const bool linked_only = false, + const bool editable_assets_only = false); +void IMB_colormanagement_working_space_convert(Main *bmain, const Main *reference_bmain); + +int IMB_colormanagement_working_space_get_named_index(const char *name); +const char *IMB_colormanagement_working_space_get_indexed_name(int index); +void IMB_colormanagement_working_space_items_add(EnumPropertyItem **items, int *totitem); + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name RNA Helper Functions * \{ */ diff --git a/source/blender/imbuf/intern/colormanagement.cc b/source/blender/imbuf/intern/colormanagement.cc index 82f3b768bd2..e58fcd342b4 100644 --- a/source/blender/imbuf/intern/colormanagement.cc +++ b/source/blender/imbuf/intern/colormanagement.cc @@ -12,6 +12,7 @@ #include #include +#include "DNA_ID.h" #include "DNA_color_types.h" #include "DNA_image_types.h" #include "DNA_movieclip_types.h" @@ -29,6 +30,7 @@ #include "MEM_guardedalloc.h" +#include "BLI_color.hh" #include "BLI_colorspace.hh" #include "BLI_fileops.hh" #include "BLI_listbase.h" @@ -49,8 +51,11 @@ #include "BKE_colortools.hh" #include "BKE_context.hh" #include "BKE_global.hh" +#include "BKE_idtype.hh" #include "BKE_image_format.hh" +#include "BKE_library.hh" #include "BKE_main.hh" +#include "BKE_node.hh" #include "BKE_node_legacy_types.hh" #include "GPU_capabilities.hh" @@ -59,6 +64,8 @@ #include "SEQ_iterator.hh" +#include "DEG_depsgraph.hh" + #include "CLG_log.h" #include "OCIO_api.hh" @@ -92,6 +99,12 @@ static char global_role_default_float[MAX_COLORSPACE_NAME]; static char global_role_default_sequencer[MAX_COLORSPACE_NAME]; static char global_role_aces_interchange[MAX_COLORSPACE_NAME]; +/* Defaults from the config that never change with working space. */ +static char global_role_scene_linear_default[MAX_COLORSPACE_NAME]; +static char global_role_default_float_default[MAX_COLORSPACE_NAME]; + +float3x3 global_scene_linear_to_xyz_default = float3x3::zero(); + /* lock used by pre-cached processors getters, so processor wouldn't * be created several times * LOCK_COLORMANAGE can not be used since this mutex could be needed to @@ -587,6 +600,11 @@ static bool colormanage_load_config(ocio::Config &config) colormanage_update_matrices(); + /* Defaults that don't change with file working space. */ + STRNCPY(global_role_scene_linear_default, global_role_scene_linear); + STRNCPY(global_role_default_float_default, global_role_default_float); + global_scene_linear_to_xyz_default = blender::colorspace::scene_linear_to_xyz; + return ok; } @@ -3069,6 +3087,391 @@ const char *IMB_colormanagement_look_validate_for_view(const char *view_name, /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Working Space Functions + * \{ */ + +/* Should have enough bits of precision, and this can be reasonably high assuming + * that if colorspaces are really this close, no point converting anyway. */ +static const float imb_working_space_compare_threshold = 0.001f; + +const char *IMB_colormanagement_working_space_get_default() +{ + return global_role_scene_linear_default; +} + +int IMB_colormanagement_working_space_get_named_index(const char *name) +{ + return IMB_colormanagement_colorspace_get_named_index(name); +} + +const char *IMB_colormanagement_working_space_get_indexed_name(int index) +{ + return IMB_colormanagement_colorspace_get_indexed_name(index); +} + +void IMB_colormanagement_working_space_items_add(EnumPropertyItem **items, int *totitem) +{ + const ColorSpace *scene_linear = g_config->get_color_space(OCIO_ROLE_SCENE_LINEAR); + + blender::Vector working_spaces = { + IMB_colormanagement_space_from_interop_id("lin_rec709_scene"), + IMB_colormanagement_space_from_interop_id("lin_rec2020_scene"), + IMB_colormanagement_space_from_interop_id("lin_ap1_scene")}; + + if (!working_spaces.contains(scene_linear)) { + working_spaces.prepend(scene_linear); + } + + for (const ColorSpace *colorspace : working_spaces) { + if (colorspace == nullptr) { + continue; + } + + EnumPropertyItem item; + + item.value = colorspace->index; + item.name = colorspace->name().c_str(); + item.identifier = colorspace->name().c_str(); + item.icon = 0; + item.description = colorspace->description().c_str(); + + RNA_enum_item_add(items, totitem, &item); + } +} + +const char *IMB_colormanagement_working_space_get() +{ + return global_role_scene_linear; +} + +bool IMB_colormanagement_working_space_set_from_name(const char *name) +{ + if (STREQ(global_role_scene_linear, name)) { + return false; + } + + CLOG_DEBUG(&LOG, "Setting blend file working color space to '%s'", name); + + /* Change default float along with working space for convenience, if it was the same. */ + if (STREQ(global_role_default_float_default, global_role_scene_linear_default)) { + STRNCPY(global_role_default_float, name); + } + else { + STRNCPY(global_role_default_float, global_role_default_float_default); + } + + STRNCPY(global_role_scene_linear, name); + g_config->set_scene_linear_role(name); + + colormanage_update_matrices(); + return true; +} + +bool IMB_colormanagement_working_space_set_from_matrix( + const char *name, const blender::float3x3 &scene_linear_to_xyz) +{ + StringRefNull interop_id; + + /* Check if we match the working space defined by the config. */ + if (blender::math::is_equal(scene_linear_to_xyz, + global_scene_linear_to_xyz_default, + imb_working_space_compare_threshold)) + { + return IMB_colormanagement_working_space_set_from_name(global_role_scene_linear_default); + } + + /* Check if we match a common known working space, that hopefully exists in the config. */ + if (blender::math::is_equal( + scene_linear_to_xyz, ocio::ACES_TO_XYZ, imb_working_space_compare_threshold)) + { + interop_id = "lin_ap0_scene"; + } + else if (blender::math::is_equal( + scene_linear_to_xyz, ocio::ACESCG_TO_XYZ, imb_working_space_compare_threshold)) + { + interop_id = "lin_ap1_scene"; + } + else if (blender::math::is_equal(scene_linear_to_xyz, + blender::math::invert(ocio::XYZ_TO_REC709), + imb_working_space_compare_threshold)) + { + interop_id = "lin_rec709_scene"; + } + else if (blender::math::is_equal(scene_linear_to_xyz, + blender::math::invert(ocio::XYZ_TO_REC2020), + imb_working_space_compare_threshold)) + { + interop_id = "lin_rec2020_scene"; + } + + const ColorSpace *colorspace = g_config->get_color_space_by_interop_id(interop_id); + if (colorspace) { + return IMB_colormanagement_working_space_set_from_name(colorspace->name().c_str()); + } + + CLOG_ERROR( + &LOG, "Unknown scene linear working space '%s'. Missing OpenColorIO configuration?", name); + return IMB_colormanagement_working_space_set_from_name(global_role_scene_linear_default); +} + +void IMB_colormanagement_working_space_check(Main *bmain, + const bool for_undo, + const bool have_editable_assets) +{ + /* For old files without info, assume current OpenColorIO config. */ + if (blender::math::is_zero(bmain->colorspace.scene_linear_to_xyz)) { + STRNCPY(bmain->colorspace.scene_linear_name, global_role_scene_linear_default); + bmain->colorspace.scene_linear_to_xyz = global_scene_linear_to_xyz_default; + CLOG_DEBUG(&LOG, + "Blend file has unknown scene linear working color space, setting to default"); + } + + const blender::float3x3 current_scene_linear_to_xyz = blender::colorspace::scene_linear_to_xyz; + + /* Change the working space to the one from the blend file. */ + const bool working_space_changed = IMB_colormanagement_working_space_set_from_matrix( + bmain->colorspace.scene_linear_name, bmain->colorspace.scene_linear_to_xyz); + if (!working_space_changed) { + return; + } + + /* For undo, we need to convert the linked datablocks as they were left unchanged by undo. + * For file load, we need to convert editable assets that came from the previous main. */ + if (!(for_undo || have_editable_assets)) { + return; + } + + IMB_colormanagement_working_space_convert( + bmain, + current_scene_linear_to_xyz, + blender::math::invert(bmain->colorspace.scene_linear_to_xyz), + for_undo, + for_undo, + !for_undo && have_editable_assets); +} + +static blender::float3 imb_working_space_convert(const blender::float3x3 &m, + const bool is_smaller_gamut, + const blender::float3 in_rgb) +{ + blender::float3 rgb = m * in_rgb; + + for (int i = 0; i < 3; i++) { + /* Round to nicer fractions. */ + rgb[i] = 1e-5f * roundf(rgb[i] * 1e5f); + /* Snap to 0 and 1. */ + if (fabsf(rgb[i]) < 5e-5) { + rgb[i] = 0.0f; + } + else if (fabsf(1.0f - rgb[i]) < 5e-5) { + rgb[i] = 1.0f; + } + /* Clamp when goig to smaller gamut. We can't really distinguish + * between HDR and out of gamut colors. */ + if (is_smaller_gamut) { + rgb[i] = blender::math::clamp(rgb[i], 0.0f, 1.0f); + } + } + + return rgb; +} + +static blender::ColorGeometry4f imb_working_space_convert(const blender::float3x3 &m, + const bool is_smaller_gamut, + const blender::ColorGeometry4f color) +{ + using namespace blender; + const float3 in_rgb = float3(color::unpremultiply_alpha(color)); + const float3 rgb = imb_working_space_convert(m, is_smaller_gamut, in_rgb); + return color::premultiply_alpha(ColorGeometry4f(rgb[0], rgb[1], rgb[2], color[3])); +} + +void IMB_colormanagement_working_space_convert( + Main *bmain, + const blender::float3x3 ¤t_scene_linear_to_xyz, + const blender::float3x3 &new_xyz_to_scene_linear, + const bool depsgraph_tag, + const bool linked_only, + const bool editable_assets_only) +{ + using namespace blender; + /* If unknown, assume it's the OpenColorIO config scene linear space. */ + float3x3 bmain_scene_linear_to_xyz = (math::is_zero(current_scene_linear_to_xyz)) ? + global_scene_linear_to_xyz_default : + current_scene_linear_to_xyz; + + float3x3 M = new_xyz_to_scene_linear * bmain_scene_linear_to_xyz; + + /* Already in the same space? */ + if (math::is_equal(M, float3x3::identity(), imb_working_space_compare_threshold)) { + return; + } + + if (math::determinant(M) == 0.0f) { + CLOG_ERROR(&LOG, "Working space conversion matrix is not invertible"); + return; + } + + /* Determine if we are going to a smaller gamut and need to clamp. We prefer not to, + * to preserve HDR colors, although they should not be common in properties. */ + bool is_smaller_gamut = false; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + if (M[i][j] < 0.0f) { + is_smaller_gamut = true; + } + } + } + + /* Single color. */ + const auto single = [&M, is_smaller_gamut](float rgb[3]) { + copy_v3_v3(rgb, imb_working_space_convert(M, is_smaller_gamut, float3(rgb))); + }; + + /* Array with implicit sharing. + * + * We store references to all color arrays, so we can efficiently preserve implicit + * sharing and write in place when possible. */ + struct ColorArrayInfo { + Vector data_ptrs; + Vector *> sharing_info_ptrs; + /* Though it is unlikely, the same data array could be used among multiple geometries with + * different domain sizes, so keep track of the maximum size among all users. */ + size_t max_size; + }; + Map color_array_map; + + const auto implicit_sharing_array = + [&](ImplicitSharingPtr<> &sharing_info, ColorGeometry4f *&data, size_t size) { + /* No data? */ + if (!sharing_info) { + BLI_assert(size == 0); + return; + } + color_array_map.add_or_modify( + sharing_info.get(), + [&](ColorArrayInfo *value) { + new (value) ColorArrayInfo(); + value->data_ptrs.append(&data); + value->sharing_info_ptrs.append(&sharing_info); + value->max_size = size; + }, + [&](ColorArrayInfo *value) { + BLI_assert(data == *value->data_ptrs.last()); + value->data_ptrs.append(&data); + value->sharing_info_ptrs.append(&sharing_info); + value->max_size = std::max(value->max_size, size); + }); + }; + + IDTypeForeachColorFunctionCallback fn = {single, implicit_sharing_array}; + + /* Iterate over IDs and embedded IDs. No need to do it for master collections + * though, they don't have colors. */ + /* TODO: Multithreading over IDs? */ + ID *id_iter; + FOREACH_MAIN_ID_BEGIN (bmain, id_iter) { + if (linked_only) { + if (!id_iter->lib) { + continue; + } + } + if (editable_assets_only) { + if (!(id_iter->lib && (id_iter->lib->runtime->tag & LIBRARY_ASSET_EDITABLE))) { + continue; + } + } + + const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id_iter); + if (id_type->foreach_working_space_color) { + id_type->foreach_working_space_color(id_iter, fn); + if (depsgraph_tag) { + DEG_id_tag_update(id_iter, ID_RECALC_ALL); + } + } + + if (bNodeTree *node_tree = bke::node_tree_from_id(id_iter)) { + const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(&node_tree->id); + if (id_type->foreach_working_space_color) { + id_type->foreach_working_space_color(&node_tree->id, fn); + } + } + } + FOREACH_MAIN_ID_END; + + /* Handle implicit sharing arrays. */ + Vector::Item> color_array_items( + color_array_map.items().begin(), color_array_map.items().end()); + + threading::parallel_for(color_array_items.index_range(), 64, [&](const IndexRange range) { + for (const int item_index : range) { + const auto &item = color_array_items[item_index]; + + if (item.value.data_ptrs.size() == item.key->strong_users()) { + /* All of the users of the array data are from the main we're converting, so we can change + * the data array in place without allocating a new version. */ + item.key->tag_ensured_mutable(); + MutableSpan data(*item.value.data_ptrs.first(), item.value.max_size); + threading::parallel_for(data.index_range(), 1024, [&](const IndexRange range) { + for (const int64_t i : range) { + data[i] = imb_working_space_convert(M, is_smaller_gamut, data[i]); + } + }); + } + else { + /* Somehow the data is used by something outside of the Main we're currently converting, it + * has to be duplicated before being converted to avoid changing the original. */ + const Span src_data(*item.value.data_ptrs.first(), item.value.max_size); + + auto *dst_data = MEM_malloc_arrayN( + src_data.size(), "IMB_colormanagement_working_space_convert"); + const ImplicitSharingPtr<> sharing_ptr(implicit_sharing::info_for_mem_free(dst_data)); + + threading::parallel_for(src_data.index_range(), 1024, [&](const IndexRange range) { + for (const int64_t i : range) { + dst_data[i] = imb_working_space_convert(M, is_smaller_gamut, src_data[i]); + } + }); + + /* Replace the data pointer and the sharing info pointer with the new data in all of the + * users from the main data-base. The sharing pointer assignment adds a user. */ + for (ColorGeometry4f **pointer : item.value.data_ptrs) { + *pointer = dst_data; + } + for (ImplicitSharingPtr<> *pointer : item.value.sharing_info_ptrs) { + *pointer = sharing_ptr; + } + } + } + }); +} + +void IMB_colormanagement_working_space_convert(Main *bmain, const Main *reference_bmain) +{ + /* If unknown, assume it's the OpenColorIO config scene linear space. */ + float3x3 reference_scene_linear_to_xyz = blender::math::is_zero( + reference_bmain->colorspace.scene_linear_to_xyz) ? + global_scene_linear_to_xyz_default : + reference_bmain->colorspace.scene_linear_to_xyz; + + IMB_colormanagement_working_space_convert(bmain, + bmain->colorspace.scene_linear_to_xyz, + blender::math::invert(reference_scene_linear_to_xyz), + false); + + STRNCPY(bmain->colorspace.scene_linear_name, reference_bmain->colorspace.scene_linear_name); + bmain->colorspace.scene_linear_to_xyz = reference_bmain->colorspace.scene_linear_to_xyz; +} + +void IMB_colormanagement_working_space_init(Main *bmain) +{ + STRNCPY(bmain->colorspace.scene_linear_name, global_role_scene_linear_default); + bmain->colorspace.scene_linear_to_xyz = global_scene_linear_to_xyz_default; +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name RNA Helper Functions * \{ */ diff --git a/source/blender/imbuf/opencolorio/OCIO_config.hh b/source/blender/imbuf/opencolorio/OCIO_config.hh index 626bb83dd0f..ac6c4462132 100644 --- a/source/blender/imbuf/opencolorio/OCIO_config.hh +++ b/source/blender/imbuf/opencolorio/OCIO_config.hh @@ -9,6 +9,7 @@ #include "BLI_math_matrix_types.hh" #include "BLI_math_vector_types.hh" #include "BLI_string_ref.hh" +#include "DNA_windowmanager_types.h" namespace blender::ocio { diff --git a/source/blender/imbuf/opencolorio/intern/libocio/libocio_colorspace.cc b/source/blender/imbuf/opencolorio/intern/libocio/libocio_colorspace.cc index 37ddcc2c345..10f0c7b5b34 100644 --- a/source/blender/imbuf/opencolorio/intern/libocio/libocio_colorspace.cc +++ b/source/blender/imbuf/opencolorio/intern/libocio/libocio_colorspace.cc @@ -3,6 +3,8 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "libocio_colorspace.hh" +#include "OCIO_cpu_processor.hh" +#include "intern/cpu_processor_cache.hh" #if defined(WITH_OPENCOLORIO) diff --git a/source/blender/makesdna/DNA_fileglobal_types.h b/source/blender/makesdna/DNA_fileglobal_types.h index 21239315a75..68d71c0d1c7 100644 --- a/source/blender/makesdna/DNA_fileglobal_types.h +++ b/source/blender/makesdna/DNA_fileglobal_types.h @@ -34,6 +34,12 @@ typedef struct FileGlobal { char build_hash[16]; /** File path where this was saved, for recover. */ char filepath[/*FILE_MAX*/ 1024]; + + /* Working colorspace, for automatic conversion. Note the matrix is + * the source of truth, the name is only for user interface and diagnosis. */ + char colorspace_scene_linear_name[/*MAX_COLORSPACE_NAME*/ 64]; + float colorspace_scene_linear_to_xyz[3][3]; + int _pad2[3]; } FileGlobal; /* minversion: in file, the oldest past blender version you can use compliant */ diff --git a/source/blender/makesrna/intern/rna_main.cc b/source/blender/makesrna/intern/rna_main.cc index dac5df28d79..1d4320fa5f0 100644 --- a/source/blender/makesrna/intern/rna_main.cc +++ b/source/blender/makesrna/intern/rna_main.cc @@ -12,11 +12,14 @@ #include "BLI_path_utils.hh" #include "RNA_define.hh" +#include "RNA_enum_types.hh" #include "rna_internal.hh" #ifdef RNA_RUNTIME +# include "IMB_colormanagement.hh" + # include "DNA_windowmanager_types.h" # include "BKE_global.hh" @@ -88,6 +91,28 @@ static PointerRNA rna_Main_colorspace_get(PointerRNA *ptr) return PointerRNA(nullptr, &RNA_BlendFileColorspace, &bmain->colorspace); } +static int rna_MainColorspace_working_space_get(PointerRNA *ptr) +{ + MainColorspace *colorspace = ptr->data_as(); + return IMB_colormanagement_working_space_get_named_index(colorspace->scene_linear_name); +} + +static const EnumPropertyItem *rna_MainColorspace_working_space_itemf(bContext * /*C*/, + PointerRNA * /*ptr*/, + PropertyRNA * /*prop*/, + bool *r_free) +{ + EnumPropertyItem *items = nullptr; + int totitem = 0; + + IMB_colormanagement_working_space_items_add(&items, &totitem); + RNA_enum_item_end(&items, &totitem); + + *r_free = true; + + return items; +} + static bool rna_MainColorspace_is_missing_opencolorio_config_get(PointerRNA *ptr) { MainColorspace *colorspace = ptr->data_as(); @@ -185,7 +210,20 @@ static void rna_def_main_colorspace(BlenderRNA *brna) srna = RNA_def_struct(brna, "BlendFileColorspace", nullptr); RNA_def_struct_ui_text(srna, "Blend-File Color Space", - "Information about the color space used for datablocks in a blend file"); + "Information about the color space used for data-blocks in a blend file"); + + prop = RNA_def_property(srna, "working_space", PROP_ENUM, PROP_NONE); + RNA_def_property_flag(prop, PROP_ENUM_NO_CONTEXT); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_enum_items(prop, rna_enum_dummy_NULL_items); + RNA_def_property_ui_text(prop, + "Working Space", + "Color space used for all scene linear colors in this file, and " + "for compositing, shader and geometry nodes processing"); + RNA_def_property_enum_funcs(prop, + "rna_MainColorspace_working_space_get", + nullptr, + "rna_MainColorspace_working_space_itemf"); prop = RNA_def_property(srna, "is_missing_opencolorio_config", PROP_BOOLEAN, PROP_NONE); RNA_def_property_clear_flag(prop, PROP_EDITABLE); @@ -511,7 +549,7 @@ void RNA_def_main(BlenderRNA *brna) RNA_def_property_ui_text( prop, "Color Space", - "Information about the color space used for datablocks in a blend file"); + "Information about the color space used for data-blocks in a blend file"); RNA_api_main(srna); diff --git a/source/blender/windowmanager/CMakeLists.txt b/source/blender/windowmanager/CMakeLists.txt index cf6b6b6261d..dd867aaf50c 100644 --- a/source/blender/windowmanager/CMakeLists.txt +++ b/source/blender/windowmanager/CMakeLists.txt @@ -26,6 +26,7 @@ set(SRC intern/wm_event_system.cc intern/wm_files.cc intern/wm_files_link.cc + intern/wm_files_colorspace.cc intern/wm_gesture.cc intern/wm_gesture_ops.cc intern/wm_init_exit.cc diff --git a/source/blender/windowmanager/intern/wm_files_colorspace.cc b/source/blender/windowmanager/intern/wm_files_colorspace.cc new file mode 100644 index 00000000000..5a0318ee448 --- /dev/null +++ b/source/blender/windowmanager/intern/wm_files_colorspace.cc @@ -0,0 +1,206 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup wm + * + * Functions for handling file colorspaces. + */ + +#include "BLI_colorspace.hh" +#include "BLI_listbase.h" +#include "BLI_string.h" + +#include "BKE_context.hh" +#include "BKE_image.hh" +#include "BKE_main.hh" +#include "BKE_movieclip.h" +#include "BKE_report.hh" + +#include "DNA_windowmanager_enums.h" +#include "DNA_windowmanager_types.h" +#include "RNA_access.hh" +#include "RNA_define.hh" +#include "RNA_enum_types.hh" + +#include "IMB_colormanagement.hh" + +#include "DEG_depsgraph.hh" + +#include "UI_interface_c.hh" +#include "UI_interface_icons.hh" +#include "UI_interface_layout.hh" + +#include "BLT_translation.hh" + +#include "ED_image.hh" +#include "ED_render.hh" + +#include "RE_pipeline.h" + +#include "SEQ_prefetch.hh" +#include "SEQ_relations.hh" + +#include "WM_api.hh" +#include "WM_types.hh" + +#include "wm_files.hh" + +/* -------------------------------------------------------------------- */ +/** \name Set Working Color Space Operator + * \{ */ + +static const EnumPropertyItem *working_space_itemf(bContext * /*C*/, + PointerRNA * /*ptr*/, + PropertyRNA * /*prop*/, + bool *r_free) +{ + EnumPropertyItem *item = nullptr; + int totitem = 0; + IMB_colormanagement_working_space_items_add(&item, &totitem); + RNA_enum_item_end(&item, &totitem); + *r_free = true; + return item; +} + +static bool wm_set_working_space_check_safe(bContext *C, wmOperator *op) +{ + const wmWindowManager *wm = CTX_wm_manager(C); + const Main *bmain = CTX_data_main(C); + const Scene *scene = CTX_data_scene(C); + + if (WM_jobs_test(wm, scene, WM_JOB_TYPE_ANY)) { + BKE_report( + op->reports, RPT_WARNING, RPT_("Can't change working space while josb are running")); + return false; + } + + if (ED_image_should_save_modified(bmain)) { + BKE_report(op->reports, + RPT_WARNING, + RPT_("Can't change working space with modified images, save them first")); + return false; + } + + return true; +} + +static wmOperatorStatus wm_set_working_color_space_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + const bool convert_colors = RNA_boolean_get(op->ptr, "convert_colors"); + const int working_space_index = RNA_enum_get(op->ptr, "working_space"); + const char *working_space = IMB_colormanagement_working_space_get_indexed_name( + working_space_index); + + if (!wm_set_working_space_check_safe(C, op)) { + return OPERATOR_CANCELLED; + } + + if (working_space[0] == '\0' || STREQ(working_space, bmain->colorspace.scene_linear_name)) { + return OPERATOR_CANCELLED; + } + + /* Stop all viewport renders. */ + ED_render_engine_changed(bmain, true); + RE_FreeAllPersistentData(); + + /* Change working space. */ + IMB_colormanagement_working_space_set_from_name(working_space); + + if (convert_colors) { + const bool depsgraph_tag = true; + IMB_colormanagement_working_space_convert(bmain, + bmain->colorspace.scene_linear_to_xyz, + blender::colorspace::xyz_to_scene_linear, + depsgraph_tag); + } + + STRNCPY(bmain->colorspace.scene_linear_name, working_space); + bmain->colorspace.scene_linear_to_xyz = blender::colorspace::scene_linear_to_xyz; + + /* Free all render, compositor and sequencer caches. */ + RE_FreeAllRenderResults(); + RE_FreeInteractiveCompositorRenders(); + blender::seq::prefetch_stop_all(); + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + blender::seq::cache_cleanup(scene); + } + + /* Free all images, they may have scene linear float buffers. */ + LISTBASE_FOREACH (Image *, image, &bmain->images) { + DEG_id_tag_update(&image->id, ID_RECALC_SOURCE); + BKE_image_signal(bmain, image, nullptr, IMA_SIGNAL_COLORMANAGE); + BKE_image_partial_update_mark_full_update(image); + } + LISTBASE_FOREACH (MovieClip *, clip, &bmain->movieclips) { + BKE_movieclip_clear_cache(clip); + BKE_movieclip_free_gputexture(clip); + DEG_id_tag_update(&clip->id, ID_RECALC_SOURCE); + } + + /* Redraw everything. */ + WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, nullptr); + WM_main_add_notifier(NC_SCENE | ND_RENDER_OPTIONS, nullptr); + WM_main_add_notifier(NC_SCENE | ND_NODES, nullptr); + WM_main_add_notifier(NC_WINDOW, nullptr); + + return OPERATOR_FINISHED; +} + +static wmOperatorStatus wm_set_working_color_space_invoke(bContext *C, + wmOperator *op, + const wmEvent *event) +{ + if (!wm_set_working_space_check_safe(C, op)) { + return OPERATOR_CANCELLED; + } + + if (RNA_enum_get(op->ptr, "working_space") == -1) { + RNA_enum_set(op->ptr, + "working_space", + IMB_colormanagement_working_space_get_named_index( + IMB_colormanagement_working_space_get_default())); + } + + return WM_operator_props_popup_confirm_ex( + C, + op, + event, + std::nullopt, + IFACE_("Apply"), + false, + IFACE_("To match renders with the previous working space as closely as possible,\n" + "colors in all materials, lights and geometry must be converted.\n\n" + "Some nodes graphs cannot be converted accurately and may need manual fix-ups.")); +} + +void WM_OT_set_working_color_space(wmOperatorType *ot) +{ + ot->name = "Set Blend File Working Color Space"; + ot->idname = "WM_OT_set_working_color_space"; + ot->description = "Change the working color space of all colors in this blend file"; + + ot->exec = wm_set_working_color_space_exec; + ot->invoke = wm_set_working_color_space_invoke; + + ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; + + RNA_def_boolean(ot->srna, + "convert_colors", + true, + "Convert Colors in All Data-blocks", + "Change colors in all data-blocks to the new working space"); + PropertyRNA *prop = RNA_def_enum(ot->srna, + "working_space", + rna_enum_dummy_NULL_items, + -1, + "Working Space", + "Color space to set"); + RNA_def_enum_funcs(prop, working_space_itemf); + + ot->prop = prop; +} + +/** \} */ diff --git a/source/blender/windowmanager/intern/wm_operators.cc b/source/blender/windowmanager/intern/wm_operators.cc index 8374424c1c3..1046a74b24f 100644 --- a/source/blender/windowmanager/intern/wm_operators.cc +++ b/source/blender/windowmanager/intern/wm_operators.cc @@ -4242,6 +4242,7 @@ void wm_operatortypes_register() WM_operatortype_append(WM_OT_previews_ensure); WM_operatortype_append(WM_OT_previews_clear); WM_operatortype_append(WM_OT_doc_view_manual_ui_context); + WM_operatortype_append(WM_OT_set_working_color_space); #ifdef WITH_XR_OPENXR wm_xr_operatortypes_register(); diff --git a/source/blender/windowmanager/wm_files.hh b/source/blender/windowmanager/wm_files.hh index 979bae258c2..4e280f128b2 100644 --- a/source/blender/windowmanager/wm_files.hh +++ b/source/blender/windowmanager/wm_files.hh @@ -131,3 +131,7 @@ void WM_OT_id_linked_relocate(wmOperatorType *ot); void WM_OT_lib_relocate(wmOperatorType *ot); void WM_OT_lib_reload(wmOperatorType *ot); + +/* `wm_files_colorspace.cc` */ + +void WM_OT_set_working_color_space(wmOperatorType *ot); diff --git a/tests/files/render/colorspace/acescg_blackbody.blend b/tests/files/render/colorspace/acescg_blackbody.blend new file mode 100644 index 00000000000..5dce8380891 --- /dev/null +++ b/tests/files/render/colorspace/acescg_blackbody.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f91f8958a63882e5b177ae7655145d93ae117f054968638d9fe9dd43fc54f81 +size 121924 diff --git a/tests/files/render/colorspace/cycles_renders/acescg_blackbody.png b/tests/files/render/colorspace/cycles_renders/acescg_blackbody.png new file mode 100644 index 00000000000..3f8d686a85b --- /dev/null +++ b/tests/files/render/colorspace/cycles_renders/acescg_blackbody.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3133976c8379549caa892fb8e3a79d58dabc859f6d1264a1faebeb0bc921e5f +size 25822 diff --git a/tests/files/render/colorspace/cycles_renders/rec2020_lights.png b/tests/files/render/colorspace/cycles_renders/rec2020_lights.png new file mode 100644 index 00000000000..3e269003791 --- /dev/null +++ b/tests/files/render/colorspace/cycles_renders/rec2020_lights.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:174df43a29954ec6997a9efd8f8b77c1a5f95bc0097cc8b5a86a41c657e136d5 +size 21898 diff --git a/tests/files/render/colorspace/eevee_renders/acescg_blackbody.png b/tests/files/render/colorspace/eevee_renders/acescg_blackbody.png new file mode 100644 index 00000000000..d3d8a249be3 --- /dev/null +++ b/tests/files/render/colorspace/eevee_renders/acescg_blackbody.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ffa1579348545727211aa698be87962aa2561ee7737ba0e1f192d056fd405e2 +size 15610 diff --git a/tests/files/render/colorspace/eevee_renders/rec2020_lights.png b/tests/files/render/colorspace/eevee_renders/rec2020_lights.png new file mode 100644 index 00000000000..8ab721364f8 --- /dev/null +++ b/tests/files/render/colorspace/eevee_renders/rec2020_lights.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:55af108f03621705c0d565e92ed6a46ec1c70f8aa67a5670436bc45bc47a77f4 +size 14921 diff --git a/tests/files/render/colorspace/rec2020_lights.blend b/tests/files/render/colorspace/rec2020_lights.blend new file mode 100644 index 00000000000..4287bb32f84 --- /dev/null +++ b/tests/files/render/colorspace/rec2020_lights.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8afbe84d5c92c296f5ac1e969a43f3c428e2b02c25eda7fe91053dc686774c42 +size 101757 diff --git a/tests/files/render/colorspace/storm_hydra_renders/acescg_blackbody.png b/tests/files/render/colorspace/storm_hydra_renders/acescg_blackbody.png new file mode 100644 index 00000000000..be0b8d1da99 --- /dev/null +++ b/tests/files/render/colorspace/storm_hydra_renders/acescg_blackbody.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e04cddc2c61422289bc24c46cdf7a3902919eb04ed63d77dfbd25bd2ab56de8 +size 8225 diff --git a/tests/files/render/colorspace/storm_hydra_renders/rec2020_lights.png b/tests/files/render/colorspace/storm_hydra_renders/rec2020_lights.png new file mode 100644 index 00000000000..5a45d3f6def --- /dev/null +++ b/tests/files/render/colorspace/storm_hydra_renders/rec2020_lights.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2694a8533cc2028db32851b1983104f238bf7c7ce4a9a86b38ca8d991f174f69 +size 9786 diff --git a/tests/files/render/colorspace/storm_usd_renders/acescg_blackbody.png b/tests/files/render/colorspace/storm_usd_renders/acescg_blackbody.png new file mode 100644 index 00000000000..6bc09229f36 --- /dev/null +++ b/tests/files/render/colorspace/storm_usd_renders/acescg_blackbody.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62110068e7090cce17e9d583a0101c9e43a6c1e3c740f6e89af72bfa93a4dd6b +size 8237 diff --git a/tests/files/render/colorspace/storm_usd_renders/rec2020_lights.png b/tests/files/render/colorspace/storm_usd_renders/rec2020_lights.png new file mode 100644 index 00000000000..fb502b23e8d --- /dev/null +++ b/tests/files/render/colorspace/storm_usd_renders/rec2020_lights.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed5115beb9d5e07a3ff3806eca38d86c64f3bc9a67b3ff3159c914b5a1976c96 +size 9760 diff --git a/tests/files/render/colorspace/workbench_renders/acescg_blackbody.png b/tests/files/render/colorspace/workbench_renders/acescg_blackbody.png new file mode 100644 index 00000000000..1f8dc4bd4c6 --- /dev/null +++ b/tests/files/render/colorspace/workbench_renders/acescg_blackbody.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10f1c20a20dde3b70d7714d3621067778059fbbea6fbf3806f45e76be869ed0c +size 11083 diff --git a/tests/files/render/colorspace/workbench_renders/rec2020_lights.png b/tests/files/render/colorspace/workbench_renders/rec2020_lights.png new file mode 100644 index 00000000000..eb4744ec380 --- /dev/null +++ b/tests/files/render/colorspace/workbench_renders/rec2020_lights.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ade71dcdfb8b47d556eed577bd874967984450177817cc6a7c4e75caf4ac3b3 +size 4524 diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index 5190c75dc77..5996818825e 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -669,6 +669,7 @@ if((WITH_CYCLES OR WITH_GPU_RENDER_TESTS) AND TEST_SRC_DIR_EXISTS) set(render_tests attributes camera + colorspace bsdf hair image_colorspace diff --git a/tests/python/cycles_render_tests.py b/tests/python/cycles_render_tests.py index 07e82cf8a2d..1f8b69007c0 100644 --- a/tests/python/cycles_render_tests.py +++ b/tests/python/cycles_render_tests.py @@ -280,6 +280,9 @@ def main(): if (test_dir_name in {'volume', 'openvdb'}): report.set_fail_threshold(0.048) report.set_fail_percent(3) + # OSL blackbody output is a little different. + if (test_dir_name in {'colorspace'}): + report.set_fail_threshold(0.05) ok = report.run(args.testdir, args.blender, get_arguments, batch=args.batch)