From 1f446f55b4fd43e8ddceea409544603303355099 Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Mon, 13 Jul 2026 01:09:14 +0800 Subject: [PATCH] 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. --- app/render/colorprocessor.cpp | 121 +++++++++++++++++-------------- app/render/worker/workermain.cpp | 44 +++++++---- 2 files changed, 97 insertions(+), 68 deletions(-) diff --git a/app/render/colorprocessor.cpp b/app/render/colorprocessor.cpp index eb6d56063..6f6d8b487 100644 --- a/app/render/colorprocessor.cpp +++ b/app/render/colorprocessor.cpp @@ -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(config, input, transform, - direction); + direction); } ColorProcessorPtr ColorProcessor::Create(OCIO::ConstProcessorRcPtr processor) diff --git a/app/render/worker/workermain.cpp b/app/render/worker/workermain.cpp index 2fcfc2aee..5d7b74bd4 100644 --- a/app/render/worker/workermain.cpp +++ b/app/render/worker/workermain.cpp @@ -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 input_pool_; olive::ShaderCache shader_cache_; + QHash color_processor_cache_; }; } // namespace