Cache color processors in render worker and canonicalize OCIO roles

ColorProcessor creation was done for every exported frame and the input
reference space was passed as a role string (e.g. "scene_linear"). If
getProcessor() rejected the role name the worker would crash, the pool
would retry the frame, and export throughput would drop to near zero with
minimal CPU/GPU usage.

- Canonicalize role names to colorspace names in ColorProcessor so
  "scene_linear" resolves to the config's actual colorspace.
- Wrap processor creation in a try/catch and initialize cpu_processor_ to
  nullptr on failure instead of dereferencing a null processor.
- Cache created ColorProcessor objects in the worker keyed by transform
  so OCIO processor/shader setup is only paid once per export.
This commit is contained in:
2026-07-13 10:19:30 +08:00
parent b3c124fe7a
commit 1f446f55b4
2 changed files with 97 additions and 68 deletions
+67 -54
View File
@@ -29,70 +29,83 @@ namespace olive
{
ColorProcessor::ColorProcessor(ColorManager *config, const QString &input,
const ColorTransform &transform,
Direction direction)
const ColorTransform &transform,
Direction direction)
{
const QString &output = (transform.output().isEmpty()) ?
config->GetDefaultDisplay() :
transform.output();
processor_ = nullptr;
cpu_processor_ = nullptr;
if (transform.is_display()) {
const QString &view = (transform.view().isEmpty()) ?
config->GetDefaultView(output) :
transform.view();
auto display_transform = OCIO::DisplayViewTransform::Create();
display_transform->setSrc(input.toUtf8());
display_transform->setDisplay(output.toUtf8());
display_transform->setView(view.toUtf8());
display_transform->setDirection(direction == kNormal ?
OCIO::TRANSFORM_DIR_FORWARD :
OCIO::TRANSFORM_DIR_INVERSE);
if (transform.look().isEmpty()) {
processor_ = config->GetConfig()->getProcessor(display_transform);
} else {
auto group = OCIO::GroupTransform::Create();
const char *out_cs = OCIO::LookTransform::GetLooksResultColorSpace(
config->GetConfig(), config->GetConfig()->getCurrentContext(),
transform.look().toUtf8());
auto lt = OCIO::LookTransform::Create();
lt->setSrc(input.toUtf8());
lt->setDst(out_cs);
lt->setLooks(transform.look().toUtf8());
lt->setSkipColorSpaceConversion(false);
group->appendTransform(lt);
display_transform->setSrc(out_cs);
group->appendTransform(display_transform);
processor_ = config->GetConfig()->getProcessor(group);
try {
// Resolve role names (e.g. "scene_linear") to canonical colorspace names
// so they can be passed to getProcessor()/DisplayViewTransform.
QString resolved_input = input;
OCIO::ConstConfigRcPtr ocio_config = config->GetConfig();
if (ocio_config && ocio_config->hasRole(input.toUtf8())) {
resolved_input = ocio_config->getCanonicalName(input.toUtf8());
}
} else {
try {
if (direction == kNormal) {
processor_ = config->GetConfig()->getProcessor(input.toUtf8(),
output.toUtf8());
const QString &output = (transform.output().isEmpty()) ?
config->GetDefaultDisplay() :
transform.output();
if (transform.is_display()) {
const QString &view = (transform.view().isEmpty()) ?
config->GetDefaultView(output) :
transform.view();
auto display_transform = OCIO::DisplayViewTransform::Create();
display_transform->setSrc(resolved_input.toUtf8());
display_transform->setDisplay(output.toUtf8());
display_transform->setView(view.toUtf8());
display_transform->setDirection(direction == kNormal ?
OCIO::TRANSFORM_DIR_FORWARD :
OCIO::TRANSFORM_DIR_INVERSE);
if (transform.look().isEmpty()) {
processor_ = ocio_config->getProcessor(display_transform);
} else {
processor_ = config->GetConfig()->getProcessor(output.toUtf8(),
input.toUtf8());
}
} catch (OCIO::Exception &e) {
qWarning() << "ColorProcessor exception:" << e.what();
}
}
auto group = OCIO::GroupTransform::Create();
cpu_processor_ = processor_->getDefaultCPUProcessor();
const char *out_cs = OCIO::LookTransform::GetLooksResultColorSpace(
ocio_config, ocio_config->getCurrentContext(),
transform.look().toUtf8());
auto lt = OCIO::LookTransform::Create();
lt->setSrc(resolved_input.toUtf8());
lt->setDst(out_cs);
lt->setLooks(transform.look().toUtf8());
lt->setSkipColorSpaceConversion(false);
group->appendTransform(lt);
display_transform->setSrc(out_cs);
group->appendTransform(display_transform);
processor_ = ocio_config->getProcessor(group);
}
} else {
if (direction == kNormal) {
processor_ = ocio_config->getProcessor(resolved_input.toUtf8(),
output.toUtf8());
} else {
processor_ = ocio_config->getProcessor(output.toUtf8(),
resolved_input.toUtf8());
}
}
if (processor_) {
cpu_processor_ = processor_->getDefaultCPUProcessor();
}
} catch (OCIO::Exception &e) {
qWarning() << "ColorProcessor exception:" << e.what();
}
}
ColorProcessor::ColorProcessor(OCIO::ConstProcessorRcPtr processor)
{
processor_ = processor;
cpu_processor_ = processor_->getDefaultCPUProcessor();
cpu_processor_ = processor_ ? processor_->getDefaultCPUProcessor() : nullptr;
}
void ColorProcessor::ConvertFrame(Frame *f)
@@ -138,7 +151,7 @@ ColorProcessorPtr ColorProcessor::Create(ColorManager *config,
Direction direction)
{
return std::make_shared<ColorProcessor>(config, input, transform,
direction);
direction);
}
ColorProcessorPtr ColorProcessor::Create(OCIO::ConstProcessorRcPtr processor)
+30 -14
View File
@@ -210,7 +210,7 @@ private:
{
if (hs.protocol_version != kProtocolVersion) {
return Write(ErrorMessage(QStringLiteral("unsupported protocol version %1")
.arg(hs.protocol_version)));
.arg(hs.protocol_version)));
}
if (hs.shm_key.isEmpty() || hs.output_slots <= 0 || hs.slot_data_bytes <= 0) {
@@ -221,7 +221,7 @@ private:
uint32_t(hs.output_slots), size_t(hs.slot_data_bytes));
if (!output_region_.Open(hs.shm_key, bytes, olive::ipc::SharedMemoryRegion::kAttach)) {
return Write(ErrorMessage(QStringLiteral("failed to attach shared memory: %1")
.arg(output_region_.error())));
.arg(output_region_.error())));
}
output_pool_ = olive::ipc::FrameSlotPool::Attach(output_region_.data());
@@ -243,7 +243,7 @@ private:
if (!input_region_.Open(hs.input_shm_key, input_bytes,
olive::ipc::SharedMemoryRegion::kAttach)) {
return Write(ErrorMessage(QStringLiteral("failed to attach input shared memory: %1")
.arg(input_region_.error())));
.arg(input_region_.error())));
}
input_pool_ = olive::ipc::FrameSlotPool::Attach(input_region_.data());
@@ -268,11 +268,12 @@ private:
olive::ProjectSerializer::Load(loaded.get(), path, olive::ProjectSerializer::kProject);
if (result != olive::ProjectSerializer::kSuccess) {
return Write(ErrorMessage(QStringLiteral("failed to load graph %1: %2")
.arg(path, result.GetDetails())));
.arg(path, result.GetDetails())));
}
project_ = std::move(loaded);
node_by_token_.clear();
color_processor_cache_.clear();
const auto &data = result.GetLoadData();
for (auto it = data.node_ptrs.cbegin(); it != data.node_ptrs.cend(); ++it) {
@@ -396,18 +397,32 @@ private:
{
olive::ColorProcessorPtr color_output;
if (message.has_color_transform) {
olive::ColorTransform transform;
if (message.color_is_display) {
transform = olive::ColorTransform(message.color_output,
message.color_view,
message.color_look);
QString cache_key =
QStringLiteral("%1|%2|%3|%4")
.arg(message.color_is_display ? 1 : 0)
.arg(message.color_output,
message.color_view,
message.color_look);
auto it = color_processor_cache_.find(cache_key);
if (it != color_processor_cache_.end()) {
color_output = it.value();
} else {
transform = olive::ColorTransform(message.color_output);
olive::ColorTransform transform;
if (message.color_is_display) {
transform = olive::ColorTransform(message.color_output,
message.color_view,
message.color_look);
} else {
transform = olive::ColorTransform(message.color_output);
}
color_output = olive::ColorProcessor::Create(
project_->color_manager(),
project_->color_manager()->GetReferenceColorSpace(),
transform);
if (color_output) {
color_processor_cache_.insert(cache_key, color_output);
}
}
color_output = olive::ColorProcessor::Create(
project_->color_manager(),
project_->color_manager()->GetReferenceColorSpace(),
transform);
}
ticket->setProperty("coloroutput",
QVariant::fromValue(color_output));
@@ -493,6 +508,7 @@ private:
olive::ipc::SharedMemoryRegion input_region_;
std::optional<olive::ipc::FrameSlotPool> input_pool_;
olive::ShaderCache shader_cache_;
QHash<QString, olive::ColorProcessorPtr> color_processor_cache_;
};
} // namespace