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
+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