diff --git a/include/OpenColorIO/OpenColorIO.h b/include/OpenColorIO/OpenColorIO.h index a7d073f788..ec2a8bb871 100644 --- a/include/OpenColorIO/OpenColorIO.h +++ b/include/OpenColorIO/OpenColorIO.h @@ -125,9 +125,11 @@ class OCIOEXPORT ExceptionMissingFile : public Exception * Under normal usage, this is not necessary, but it can be helpful in particular instances, * such as designing OCIO profiles, and wanting to re-read luts without restarting. * - * \note The method does not apply to instance specific caches such as the processor cache in a - * config instance or the GPU and CPU processor caches in a processor instance. Here deleting the - * instance flushes the cache. + * \note + * This method does not apply to instance-specific caches such as the Processor cache in + * a Config instance or the GPU and CPU Processor caches in a Processor instance. So in cases + * where you still have a Config instance after calling ClearAllCaches, you should also call + * the Config's clearProcessorCache method. */ extern OCIOEXPORT void ClearAllCaches(); @@ -1319,6 +1321,20 @@ class OCIOEXPORT Config const char * dstColorSpaceName, const char * dstInterchangeName); + /// Control the caching of processors in the config instance. By default, caching is on. + /// The flags allow turning caching off entirely or only turning it off if dynamic + /// properties are being used by the processor. + void setProcessorCacheFlags(ProcessorCacheFlags flags) noexcept; + + /** + * \brief Clears this config's cache of Processor, CPUProcessor, and GPUProcessor instances. + * + * This must be done if any of the LUT files used by these Processors have been modified. + * Note that setProcessorCacheFlags(PROCESSOR_CACHE_OFF) turns off caching but does not clear + * any existing cache. + */ + void clearProcessorCache() noexcept; + /// Set the ConfigIOProxy object used to provision the config and LUTs from somewhere other /// than the file system. (This is set on the config's embedded Context object.) void setConfigIOProxy(ConfigIOProxyRcPtr ciop); @@ -1381,11 +1397,6 @@ class OCIOEXPORT Config /// Do not use (needed only for pybind11). ~Config(); - /// Control the caching of processors in the config instance. By default, caching is on. - /// The flags allow turning caching off entirely or only turning it off if dynamic - /// properties are being used by the processor. - void setProcessorCacheFlags(ProcessorCacheFlags flags) noexcept; - private: Config(); diff --git a/src/OpenColorIO/Caching.h b/src/OpenColorIO/Caching.h index 6dd60f31b9..9d4c595f89 100644 --- a/src/OpenColorIO/Caching.h +++ b/src/OpenColorIO/Caching.h @@ -54,11 +54,6 @@ class GenericCache AutoMutex lock(m_mutex); m_enabled = enable; - - if (!isEnabled()) - { - m_entries.clear(); - } } inline bool isEnabled() const noexcept { return !m_envDisableAllCaches && m_enabled; } diff --git a/src/OpenColorIO/Config.cpp b/src/OpenColorIO/Config.cpp index c8f2a4bb83..69a1dd6c72 100644 --- a/src/OpenColorIO/Config.cpp +++ b/src/OpenColorIO/Config.cpp @@ -5069,6 +5069,10 @@ void Config::setProcessorCacheFlags(ProcessorCacheFlags flags) noexcept getImpl()->setProcessorCacheFlags(flags); } +void Config::clearProcessorCache() noexcept +{ + getImpl()->m_processorCache.clear(); +} /////////////////////////////////////////////////////////////////////////// // Config::Impl diff --git a/src/bindings/python/PyConfig.cpp b/src/bindings/python/PyConfig.cpp index 48270f2eca..b90d6e8b5d 100644 --- a/src/bindings/python/PyConfig.cpp +++ b/src/bindings/python/PyConfig.cpp @@ -777,6 +777,8 @@ void bindPyConfig(py::module & m) DOC(Config, GetProcessorFromConfigs, 4)) .def("setProcessorCacheFlags", &Config::setProcessorCacheFlags, "flags"_a, DOC(Config, setProcessorCacheFlags)) + .def("clearProcessorCache", &Config::clearProcessorCache, + DOC(Config, setProcessorCacheFlags)) // Archiving .def("isArchivable", &Config::isArchivable, DOC(Config, isArchivable)) diff --git a/tests/cpu/Caching_tests.cpp b/tests/cpu/Caching_tests.cpp index 27841fa965..94f93ba238 100644 --- a/tests/cpu/Caching_tests.cpp +++ b/tests/cpu/Caching_tests.cpp @@ -5,6 +5,7 @@ #include "Caching.cpp" #include "testutils/UnitTest.h" +#include "UnitTestUtils.h" namespace OCIO = OCIO_NAMESPACE; @@ -115,6 +116,25 @@ OCIO_ADD_TEST(Caching, processor_cache) OCIO_CHECK_ASSERT(cache.isEnabled()); } + // Test that the content of the cache stays the same after disabling and enabling the cache. + { + OCIO::ProcessorCache cache; + OCIO_CHECK_ASSERT(cache.isEnabled()); + + DataRcPtr entry1 = std::make_shared(); + cache["entry1"] = entry1; + + cache.enable(false); + + // Expecting failure because the cache is disabled. + OCIO_CHECK_ASSERT(!cache.exists("entry1")); + + cache.enable(true); + + // The data with the key "entry1" still exists after enabling the cache. + OCIO_CHECK_ASSERT(cache.exists("entry1")); + } + { // Disable all the caches. Guard guard; @@ -137,5 +157,91 @@ OCIO_ADD_TEST(Caching, processor_cache) OCIO::GenericCache cache2; OCIO_CHECK_ASSERT(cache2.isEnabled()); } -} + // Test the processor cache reset. + { + static const std::string CONFIG = + "ocio_profile_version: 2\n" + "\n" + "search_path: " + OCIO::GetTestFilesDir() + "\n" + "\n" + "environment: {CS3: lut1d_green.ctf}\n" + "\n" + "roles:\n" + " default: cs1\n" + "\n" + "displays:\n" + " disp1:\n" + " - ! {name: view1, colorspace: cs3}\n" + "\n" + "colorspaces:\n" + " - !\n" + " name: cs1\n" + "\n" + " - !\n" + " name: cs2\n" + " from_scene_reference: ! {offset: [0.11, 0.12, 0.13, 0]}\n" + "\n" + " - !\n" + " name: cs3\n" + " from_scene_reference: ! {src: $CS3}\n"; + + std::istringstream iss; + iss.str(CONFIG); + + OCIO::ConstConfigRcPtr config; + OCIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(iss)); + + // Creating a editable config to clear the processor cache later in the test. + OCIO::ConfigRcPtr cfg = config->createEditableCopy(); + + { + // Test that clearProcessorCache clears the Processor cache. + + // Create two processors and confirm that it is the same object as expected. + OCIO::ConstProcessorRcPtr procA = cfg->getProcessor("cs3", + "disp1", + "view1", + OCIO::TRANSFORM_DIR_FORWARD); + OCIO::ConstProcessorRcPtr procB = cfg->getProcessor("cs3", + "disp1", + "view1", + OCIO::TRANSFORM_DIR_FORWARD); + + // Comparing the address of both Processor objects to confirm if they are the same or not. + OCIO_CHECK_EQUAL(procA, procB); + + cfg->clearProcessorCache(); + + // Create a third processor and confirm that it is different from the previous two as the + // the processor cache was cleared. + OCIO::ConstProcessorRcPtr procC = cfg->getProcessor("cs3", + "disp1", + "view1", + OCIO::TRANSFORM_DIR_FORWARD); + + OCIO_CHECK_NE(procC, procA); + } + + { + // Test that disable and re-enable the cache, using setProcessorCacheFlags, does not + // clear the Processor cache. + + OCIO::ConstProcessorRcPtr procA = cfg->getProcessor("cs3", + "disp1", + "view1", + OCIO::TRANSFORM_DIR_FORWARD); + + // Disable and re-enable the processor cache. + cfg->setProcessorCacheFlags(OCIO::PROCESSOR_CACHE_OFF); + cfg->setProcessorCacheFlags(OCIO::PROCESSOR_CACHE_ENABLED); + + // Confirm that the processor is the same. + OCIO::ConstProcessorRcPtr procB = cfg->getProcessor("cs3", + "disp1", + "view1", + OCIO::TRANSFORM_DIR_FORWARD); + OCIO_CHECK_EQUAL(procA, procB); + } + } +} \ No newline at end of file diff --git a/tests/python/OpenColorIOTestSuite.py b/tests/python/OpenColorIOTestSuite.py index 995706f0a4..159d5eca81 100755 --- a/tests/python/OpenColorIOTestSuite.py +++ b/tests/python/OpenColorIOTestSuite.py @@ -80,6 +80,7 @@ import NamedTransformTest import OCIOZArchiveTest import OpenColorIOTest +import ProcessorCacheTest import ProcessorTest import RangeTransformTest import TransformsTest @@ -137,6 +138,7 @@ def suite(): suite.addTest(loader.loadTestsFromModule(NamedTransformTest)) suite.addTest(loader.loadTestsFromModule(OCIOZArchiveTest)) suite.addTest(loader.loadTestsFromModule(OpenColorIOTest)) + suite.addTest(loader.loadTestsFromModule(ProcessorCacheTest)) suite.addTest(loader.loadTestsFromModule(ProcessorTest)) suite.addTest(loader.loadTestsFromModule(RangeTransformTest)) suite.addTest(loader.loadTestsFromModule(TransformsTest)) diff --git a/tests/python/ProcessorCacheTest.py b/tests/python/ProcessorCacheTest.py new file mode 100644 index 0000000000..4f65a1232d --- /dev/null +++ b/tests/python/ProcessorCacheTest.py @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright Contributors to the OpenColorIO Project. + +import unittest, os, sys +import PyOpenColorIO as OCIO + +from UnitTestUtils import (TEST_DATAFILES_DIR) + +class ProcessorCacheTest(unittest.TestCase): + def test_processor_cache(self): + CONFIG = """ocio_profile_version: 2 + +search_path: """ + TEST_DATAFILES_DIR + """ +strictparsing: true +luma: [0.2126, 0.7152, 0.0722] + +environment: {CS3: lut1d_green.ctf} + +roles: + default: cs1 + +displays: + disp1: + - ! {name: view1, colorspace: cs3} + +colorspaces: + - ! + name: cs1 + + - ! + name: cs2 + from_scene_reference: ! {offset: [0.11, 0.12, 0.13, 0]} + + - ! + name: cs3 + from_scene_reference: ! {src: $CS3} +""" + + cfg = OCIO.Config.CreateFromStream(CONFIG) + cfg.validate() + + # Test that clearProcessorCache clears the Processor cache. + + # Create two processors and confirm that it is the same object as expected + procA = cfg.getProcessor("cs3", "disp1", "view1", OCIO.TRANSFORM_DIR_FORWARD) + procB = cfg.getProcessor("cs3", "disp1", "view1", OCIO.TRANSFORM_DIR_FORWARD) + + # Comparing the address of both Processor objects to confirm if they are the same or not. + self.assertEqual(procA, procB) + + cfg.clearProcessorCache() + + # Create a third processor and confirm that it is different from the previous two as the + # the processor cache was cleared. + procC = cfg.getProcessor("cs3", "disp1", "view1", OCIO.TRANSFORM_DIR_FORWARD) + + self.assertNotEqual(procC, procA) + + + # Test that disable and re-enable the cache, using setProcessorCacheFlags, does not + # clear the Processor cache. + + procD = cfg.getProcessor("cs3", "disp1", "view1", OCIO.TRANSFORM_DIR_FORWARD) + + # Disable and re-enable the processor cache. + cfg.setProcessorCacheFlags(OCIO.PROCESSOR_CACHE_OFF) + cfg.setProcessorCacheFlags(OCIO.PROCESSOR_CACHE_ENABLED) + + # Confirm that the processor is the same. + procE = cfg.getProcessor("cs3", "disp1", "view1", OCIO.TRANSFORM_DIR_FORWARD) + + self.assertEqual(procD, procE) \ No newline at end of file