Skip to content
27 changes: 19 additions & 8 deletions include/OpenColorIO/OpenColorIO.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();

Expand Down
5 changes: 0 additions & 5 deletions src/OpenColorIO/Caching.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
4 changes: 4 additions & 0 deletions src/OpenColorIO/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5069,6 +5069,10 @@ void Config::setProcessorCacheFlags(ProcessorCacheFlags flags) noexcept
getImpl()->setProcessorCacheFlags(flags);
}

void Config::clearProcessorCache() noexcept
{
getImpl()->m_processorCache.clear();
}

///////////////////////////////////////////////////////////////////////////
// Config::Impl
Expand Down
2 changes: 2 additions & 0 deletions src/bindings/python/PyConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
108 changes: 107 additions & 1 deletion tests/cpu/Caching_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "Caching.cpp"

#include "testutils/UnitTest.h"
#include "UnitTestUtils.h"

namespace OCIO = OCIO_NAMESPACE;

Expand Down Expand Up @@ -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<std::string, DataRcPtr> cache;
OCIO_CHECK_ASSERT(cache.isEnabled());

DataRcPtr entry1 = std::make_shared<Data>();
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;
Expand All @@ -137,5 +157,91 @@ OCIO_ADD_TEST(Caching, processor_cache)
OCIO::GenericCache<std::string, DataRcPtr> 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"
" - !<View> {name: view1, colorspace: cs3}\n"
"\n"
"colorspaces:\n"
" - !<ColorSpace>\n"
" name: cs1\n"
"\n"
" - !<ColorSpace>\n"
" name: cs2\n"
" from_scene_reference: !<MatrixTransform> {offset: [0.11, 0.12, 0.13, 0]}\n"
"\n"
" - !<ColorSpace>\n"
" name: cs3\n"
" from_scene_reference: !<FileTransform> {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);
}
}
}
2 changes: 2 additions & 0 deletions tests/python/OpenColorIOTestSuite.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
import NamedTransformTest
import OCIOZArchiveTest
import OpenColorIOTest
import ProcessorCacheTest
import ProcessorTest
import RangeTransformTest
import TransformsTest
Expand Down Expand Up @@ -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))
Expand Down
72 changes: 72 additions & 0 deletions tests/python/ProcessorCacheTest.py
Original file line number Diff line number Diff line change
@@ -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:
- !<View> {name: view1, colorspace: cs3}

colorspaces:
- !<ColorSpace>
name: cs1

- !<ColorSpace>
name: cs2
from_scene_reference: !<MatrixTransform> {offset: [0.11, 0.12, 0.13, 0]}

- !<ColorSpace>
name: cs3
from_scene_reference: !<FileTransform> {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)