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:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user