diff --git a/app/common/ffmpegutils.cpp b/app/common/ffmpegutils.cpp index cd6427896..f6d9a5391 100644 --- a/app/common/ffmpegutils.cpp +++ b/app/common/ffmpegutils.cpp @@ -167,6 +167,8 @@ AVPixelFormat FFmpegUtils::GetFFmpegPixelFormat(const PixelFormat &pix_fmt, switch (pix_fmt) { case PixelFormat::U8: return AV_PIX_FMT_RGB24; + case PixelFormat::U10: + return AV_PIX_FMT_NONE; case PixelFormat::U16: return AV_PIX_FMT_RGB48; case PixelFormat::F16: @@ -185,6 +187,8 @@ AVPixelFormat FFmpegUtils::GetFFmpegPixelFormat(const PixelFormat &pix_fmt, switch (pix_fmt) { case PixelFormat::U8: return AV_PIX_FMT_RGBA; + case PixelFormat::U10: + return AV_PIX_FMT_NONE; case PixelFormat::U16: return AV_PIX_FMT_RGBA64; case PixelFormat::F16: @@ -209,6 +213,8 @@ PixelFormat FFmpegUtils::GetCompatiblePixelFormat(const PixelFormat &pix_fmt) switch (pix_fmt) { case PixelFormat::U8: return PixelFormat::U8; + case PixelFormat::U10: + return PixelFormat::U8; case PixelFormat::U16: case PixelFormat::F16: case PixelFormat::F32: diff --git a/app/common/ocioutils.cpp b/app/common/ocioutils.cpp index 61b538528..ae420b44e 100644 --- a/app/common/ocioutils.cpp +++ b/app/common/ocioutils.cpp @@ -29,6 +29,8 @@ OCIO::BitDepth OCIOUtils::GetOCIOBitDepthFromPixelFormat(PixelFormat format) switch (format) { case PixelFormat::U8: return OCIO::BIT_DEPTH_UINT8; + case PixelFormat::U10: + return OCIO::BIT_DEPTH_UINT10; case PixelFormat::U16: return OCIO::BIT_DEPTH_UINT16; break; diff --git a/app/common/oiioutils.h b/app/common/oiioutils.h index b4ef2c010..d4fd18ff3 100644 --- a/app/common/oiioutils.h +++ b/app/common/oiioutils.h @@ -39,6 +39,8 @@ public: switch (format) { case PixelFormat::U8: return OIIO::TypeDesc::UINT8; + case PixelFormat::U10: + return OIIO::TypeDesc::UNKNOWN; case PixelFormat::U16: return OIIO::TypeDesc::UINT16; case PixelFormat::F16: diff --git a/app/node/generator/solid/solid.cpp b/app/node/generator/solid/solid.cpp index 3c857d015..11e7e3137 100644 --- a/app/node/generator/solid/solid.cpp +++ b/app/node/generator/solid/solid.cpp @@ -66,10 +66,6 @@ void SolidGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - Color c = value[kColorInput].toColor(); - fprintf(stderr, - "SolidGenerator::Value color=%f %f %f %f\n", - c.red(), c.green(), c.blue(), c.alpha()); table->Push(NodeValue::kTexture, Texture::Job(globals.vparams(), ShaderJob(value)), this); } diff --git a/app/pluginSupport/OliveClip.cpp b/app/pluginSupport/OliveClip.cpp index 86d099b88..cb9f71e76 100644 --- a/app/pluginSupport/OliveClip.cpp +++ b/app/pluginSupport/OliveClip.cpp @@ -305,6 +305,8 @@ const std::string &olive::plugin::OliveClipInstance::getUnmappedBitDepth() const return kBitDepthNoneStr; case PixelFormat::U8: return kBitDepthByteStr; + case PixelFormat::U10: + return kBitDepthNoneStr; case PixelFormat::U16: return kBitDepthShortStr; case PixelFormat::F16: diff --git a/app/render/framehashcache.cpp b/app/render/framehashcache.cpp index 7f192d7d6..e124e619e 100644 --- a/app/render/framehashcache.cpp +++ b/app/render/framehashcache.cpp @@ -433,6 +433,8 @@ bool FrameHashCache::SaveCacheFrame(const QString &filename, fmt = QImage::Format_RGB888; } break; + case PixelFormat::U10: + break; case PixelFormat::U16: if (frame->channel_count() == VideoParams::kRGBAChannelCount) { fmt = QImage::Format_RGBA64_Premultiplied; diff --git a/app/render/opengl/openglrenderer.cpp b/app/render/opengl/openglrenderer.cpp index b900cdb04..01f875f4d 100644 --- a/app/render/opengl/openglrenderer.cpp +++ b/app/render/opengl/openglrenderer.cpp @@ -835,6 +835,11 @@ GLint OpenGLRenderer::GetInternalFormat(PixelFormat format, int channel_layout) return GL_RGBA8; } break; + case PixelFormat::U10: + if (channel_layout == 4) { + return GL_RGB10_A2; + } + break; case PixelFormat::U16: switch (channel_layout) { case 1: @@ -884,6 +889,8 @@ GLenum OpenGLRenderer::GetPixelType(PixelFormat format) switch (format) { case PixelFormat::U8: return GL_UNSIGNED_BYTE; + case PixelFormat::U10: + return GL_UNSIGNED_INT_2_10_10_10_REV; case PixelFormat::U16: return GL_UNSIGNED_SHORT; case PixelFormat::F16: diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 21544cb46..f13df8bbd 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -688,9 +688,11 @@ RenderTicketWatcher *PreviewAutoCacher::RenderFrame(Node *node, rvp.AddCache(frame_cache); } else { // Preview/display frames are rendered at reduced precision to cut the - // GPU->CPU readback and IPC transfer bandwidth in half. The internal - // render pipeline stays F32/ACEScg; only the final preview copy is F16. - rvp.force_format = PixelFormat::F16; + // GPU->CPU readback and IPC transfer bandwidth. The internal render + // pipeline stays F32/ACEScg; the final preview copy is packed 10-bit + // RGBA (4 bytes/pixel) to preserve 10-bit panel precision while halving + // bandwidth compared to F16. + rvp.force_format = PixelFormat::U10; rvp.force_channel_count = VideoParams::kRGBAChannelCount; } diff --git a/app/render/videoparams.cpp b/app/render/videoparams.cpp index f5252d407..7ad44107b 100644 --- a/app/render/videoparams.cpp +++ b/app/render/videoparams.cpp @@ -209,6 +209,8 @@ int VideoParams::GetBytesPerChannel(PixelFormat format) break; case PixelFormat::U8: return 1; + case PixelFormat::U10: + return 0; // packed format, use GetBytesPerPixel instead case PixelFormat::U16: case PixelFormat::F16: return 2; @@ -221,6 +223,10 @@ int VideoParams::GetBytesPerChannel(PixelFormat format) int VideoParams::GetBytesPerPixel(PixelFormat format, int channels) { + if (format == PixelFormat::U10) { + // Packed 10-bit RGBA10A2: 4 bytes per RGBA pixel regardless of channel count + return channels == VideoParams::kRGBAChannelCount ? 4 : 0; + } return GetBytesPerChannel(format) * channels; } @@ -238,6 +244,8 @@ QString VideoParams::GetFormatName(PixelFormat format) switch (format) { case PixelFormat::U8: return QCoreApplication::translate("VideoParams", "8-bit"); + case PixelFormat::U10: + return QCoreApplication::translate("VideoParams", "10-bit Packed"); case PixelFormat::U16: return QCoreApplication::translate("VideoParams", "16-bit Integer"); case PixelFormat::F16: diff --git a/app/render/vulkan/vulkanrenderer.cpp b/app/render/vulkan/vulkanrenderer.cpp index a11210be1..76594b050 100644 --- a/app/render/vulkan/vulkanrenderer.cpp +++ b/app/render/vulkan/vulkanrenderer.cpp @@ -1127,6 +1127,11 @@ VkFormat VulkanRenderer::PixelFormatToVkFormat(PixelFormat format, case 4: return VK_FORMAT_R8G8B8A8_UNORM; } break; + case PixelFormat::U10: + if (channel_count == 4) { + return VK_FORMAT_A2B10G10R10_UNORM_PACK32; + } + break; case PixelFormat::U16: switch (channel_count) { case 1: return VK_FORMAT_R16_UNORM; @@ -1203,6 +1208,8 @@ int VulkanRenderer::GetVkFormatBytesPerPixel(VkFormat format) const return 3; case VK_FORMAT_R8G8B8A8_UNORM: return 4; + case VK_FORMAT_A2B10G10R10_UNORM_PACK32: + return 4; case VK_FORMAT_R16_UNORM: case VK_FORMAT_R16_SFLOAT: return 2; diff --git a/ext/core b/ext/core index faf4ba9d3..c4f8f7bde 160000 --- a/ext/core +++ b/ext/core @@ -1 +1 @@ -Subproject commit faf4ba9d3ff50cfdb93937691ce406214cf41812 +Subproject commit c4f8f7bde16a9e3807382de0c7f60c52d0e7799d diff --git a/tests/gtest/color_lut_test.cpp b/tests/gtest/color_lut_test.cpp index 2f08cc762..6dbf151d7 100644 --- a/tests/gtest/color_lut_test.cpp +++ b/tests/gtest/color_lut_test.cpp @@ -11,6 +11,9 @@ #include "node/color/ociolut/ociolut.h" #include "node/color/threewaycolor/threewaycolor.h" #include "node/factory.h" +#include "node/generator/solid/solid.h" +#include "node/project.h" +#include "node/traverser.h" #include "render/colorprocessor.h" namespace OCIO = OCIO_NAMESPACE; @@ -46,6 +49,117 @@ QString WriteTestCube(QTemporaryDir *dir) return path; } +// CPU-only traverser that resolves SolidGenerator and ColorTransformJob jobs +// into real frames so we can compare pixels without a GPU/worker process. +class PixelColorTransformTraverser : public olive::NodeTraverser { +public: + void Resolve(olive::NodeValue &value) + { + ResolveJobs(value); + } + + olive::FramePtr source_frame; + olive::FramePtr output_frame; + +protected: + virtual void ProcessShader(olive::TexturePtr destination, + const olive::Node *node, + const olive::ShaderJob *job) override + { + Q_UNUSED(destination) + Q_UNUSED(node) + + const olive::Color c = + job->GetValues().value(olive::SolidGenerator::kColorInput).toColor(); + + olive::VideoParams p = destination->params(); + p.set_format(olive::core::PixelFormat::F32); + p.set_channel_count(olive::VideoParams::kRGBAChannelCount); + + source_frame = olive::Frame::Create(); + source_frame->set_video_params(p); + source_frame->allocate(); + + for (int y = 0; y < p.effective_height(); ++y) { + for (int x = 0; x < p.effective_width(); ++x) { + source_frame->set_pixel(x, y, c); + } + } + } + + virtual void ProcessColorTransform(olive::TexturePtr destination, + const olive::Node *node, + const olive::ColorTransformJob *job) override + { + Q_UNUSED(destination) + Q_UNUSED(node) + + olive::ColorProcessorPtr processor = job->GetColorProcessor(); + if (!processor || !source_frame) { + return; + } + + output_frame = olive::Frame::Create(); + output_frame->set_video_params(source_frame->video_params()); + output_frame->allocate(); + + std::memcpy(output_frame->data(), source_frame->const_data(), + source_frame->allocated_size()); + + processor->ConvertFrame(output_frame); + } +}; + +QString WriteTestCubeLut(QTemporaryDir *dir, const char *title, + float low, float high) +{ + const QString path = QDir(dir->path()).filePath( + QStringLiteral("%1.cube").arg(QString::fromUtf8(title))); + QFile file(path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + return QString(); + } + + QTextStream stream(&file); + stream << "TITLE \"" << title << "\"\n"; + stream << "LUT_1D_SIZE 2\n"; + stream << "DOMAIN_MIN 0.0 0.0 0.0\n"; + stream << "DOMAIN_MAX 1.0 1.0 1.0\n"; + stream << high << " " << high << " " << high << "\n"; + stream << low << " " << low << " " << low << "\n"; + file.close(); + return path; +} + +// Non-symmetric 1D LUT so forward and inverse produce different, predictable +// results. Table: +// 0.0 -> 0.00 +// 0.5 -> 0.75 +// 1.0 -> 1.00 +// Forward: (0.25, 0.50, 0.75) -> (0.375, 0.750, 0.875) +// Inverse: (0.25, 0.50, 0.75) -> (0.167, 0.333, 0.500) +QString WriteAsymmetricCube(QTemporaryDir *dir) +{ + const QString path = + QDir(dir->path()).filePath(QStringLiteral("asymmetric.cube")); + QFile file(path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + return QString(); + } + + const QByteArray data = + "TITLE \"Oak test asymmetric\"\n" + "LUT_1D_SIZE 3\n" + "DOMAIN_MIN 0.0 0.0 0.0\n" + "DOMAIN_MAX 1.0 1.0 1.0\n" + "0.00 0.00 0.00\n" + "0.75 0.75 0.75\n" + "1.00 1.00 1.00\n"; + file.write(data); + file.close(); + return path; +} + } // namespace TEST(ColorLut, OcioSupportsCubeAnd3dlExtensions) @@ -111,3 +225,263 @@ TEST(ColorV04, FactoryCreatesColorNodes) EXPECT_FLOAT_EQ(neutral.blue(), 0.5f); EXPECT_FLOAT_EQ(neutral.alpha(), 1.0f); } + +// ----------------------------------------------------------------------------- +// E2E-style regression tests for OCIOLutNode. +// +// These tests build a tiny node graph (SolidGenerator -> OCIOLutNode), drive it +// through NodeTraverser, and compare the resulting pixels. They specifically +// exercise the previously broken paths: +// * OCIOLutNode::Value() must ensure the processor exists before rendering. +// * Switching the LUT direction must update the processor and change pixels. +// * Switching the LUT file must update the processor and change pixels. +// ----------------------------------------------------------------------------- + +TEST(ColorLutNode, ForwardDirectionInvertsPixels) +{ + olive::ColorManager::SetUpDefaultConfig(); + + olive::Project project; + project.Initialize(); + + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + const QString path = WriteTestCubeLut(&dir, "invert", 0.0f, 1.0f); + ASSERT_FALSE(path.isEmpty()); + + auto *solid = new olive::SolidGenerator(); + solid->setParent(&project); + solid->SetStandardValue( + olive::SolidGenerator::kColorInput, + QVariant::fromValue(olive::Color(0.25f, 0.50f, 0.75f, 1.0f))); + + auto *lut = new olive::OCIOLutNode(); + lut->setParent(&project); + lut->SetStandardValue(olive::OCIOLutNode::kFileInput, path); + lut->SetStandardValue(olive::OCIOLutNode::kDirectionInput, 0); // Forward + + olive::Node::ConnectEdge( + solid, olive::NodeInput(lut, olive::OCIOLutNode::kTextureInput)); + + const olive::VideoParams params( + 16, 16, olive::core::PixelFormat::F32, + olive::VideoParams::kRGBAChannelCount); + + PixelColorTransformTraverser traverser; + traverser.SetCacheVideoParams(params); + + olive::NodeValueTable table = traverser.GenerateTable( + lut, olive::TimeRange(olive::core::rational(0), + olive::core::rational(1, 30))); + olive::NodeValue tex_val = table.Get(olive::NodeValue::kTexture); + traverser.Resolve(tex_val); + + ASSERT_TRUE(traverser.output_frame); + const olive::Color out = traverser.output_frame->get_pixel(0, 0); + + EXPECT_NEAR(out.red(), 0.75f, 0.02f); + EXPECT_NEAR(out.green(), 0.50f, 0.02f); + EXPECT_NEAR(out.blue(), 0.25f, 0.02f); + EXPECT_NEAR(out.alpha(), 1.0f, 0.001f); +} + +TEST(ColorLutNode, InverseDirectionReversesForwardTransform) +{ + olive::ColorManager::SetUpDefaultConfig(); + + olive::Project project; + project.Initialize(); + + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + const QString path = WriteTestCubeLut(&dir, "invert", 0.0f, 1.0f); + ASSERT_FALSE(path.isEmpty()); + + auto *solid = new olive::SolidGenerator(); + solid->setParent(&project); + solid->SetStandardValue( + olive::SolidGenerator::kColorInput, + QVariant::fromValue(olive::Color(0.75f, 0.50f, 0.25f, 1.0f))); + + auto *lut = new olive::OCIOLutNode(); + lut->setParent(&project); + lut->SetStandardValue(olive::OCIOLutNode::kFileInput, path); + lut->SetStandardValue(olive::OCIOLutNode::kDirectionInput, 1); // Inverse + + olive::Node::ConnectEdge( + solid, olive::NodeInput(lut, olive::OCIOLutNode::kTextureInput)); + + const olive::VideoParams params( + 16, 16, olive::core::PixelFormat::F32, + olive::VideoParams::kRGBAChannelCount); + + PixelColorTransformTraverser traverser; + traverser.SetCacheVideoParams(params); + + olive::NodeValueTable table = traverser.GenerateTable( + lut, olive::TimeRange(olive::core::rational(0), + olive::core::rational(1, 30))); + olive::NodeValue tex_val = table.Get(olive::NodeValue::kTexture); + traverser.Resolve(tex_val); + + ASSERT_TRUE(traverser.output_frame); + const olive::Color out = traverser.output_frame->get_pixel(0, 0); + + EXPECT_NEAR(out.red(), 0.25f, 0.02f); + EXPECT_NEAR(out.green(), 0.50f, 0.02f); + EXPECT_NEAR(out.blue(), 0.75f, 0.02f); + EXPECT_NEAR(out.alpha(), 1.0f, 0.001f); +} + +TEST(ColorLutNode, SwitchingDirectionUpdatesProcessorAndPixels) +{ + olive::ColorManager::SetUpDefaultConfig(); + + olive::Project project; + project.Initialize(); + + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + const QString path = WriteAsymmetricCube(&dir); + ASSERT_FALSE(path.isEmpty()); + + auto *solid = new olive::SolidGenerator(); + solid->setParent(&project); + solid->SetStandardValue( + olive::SolidGenerator::kColorInput, + QVariant::fromValue(olive::Color(0.25f, 0.50f, 0.75f, 1.0f))); + + auto *lut = new olive::OCIOLutNode(); + lut->setParent(&project); + lut->SetStandardValue(olive::OCIOLutNode::kFileInput, path); + lut->SetStandardValue(olive::OCIOLutNode::kDirectionInput, 0); // Forward + + olive::Node::ConnectEdge( + solid, olive::NodeInput(lut, olive::OCIOLutNode::kTextureInput)); + + const olive::VideoParams params( + 16, 16, olive::core::PixelFormat::F32, + olive::VideoParams::kRGBAChannelCount); + + // First render: forward direction. + PixelColorTransformTraverser forward_traverser; + forward_traverser.SetCacheVideoParams(params); + olive::NodeValueTable forward_table = forward_traverser.GenerateTable( + lut, olive::TimeRange(olive::core::rational(0), + olive::core::rational(1, 30))); + olive::NodeValue forward_tex = + forward_table.Get(olive::NodeValue::kTexture); + forward_traverser.Resolve(forward_tex); + + ASSERT_TRUE(forward_traverser.output_frame); + const olive::Color forward_out = + forward_traverser.output_frame->get_pixel(0, 0); + EXPECT_NEAR(forward_out.red(), 0.375f, 0.02f); + EXPECT_NEAR(forward_out.green(), 0.750f, 0.02f); + EXPECT_NEAR(forward_out.blue(), 0.875f, 0.02f); + + // Switch direction. Before the Value()/EnsureProcessor() fix, the node + // would keep using the old forward processor. + lut->SetStandardValue(olive::OCIOLutNode::kDirectionInput, 1); // Inverse + + PixelColorTransformTraverser inverse_traverser; + inverse_traverser.SetCacheVideoParams(params); + olive::NodeValueTable inverse_table = inverse_traverser.GenerateTable( + lut, olive::TimeRange(olive::core::rational(0), + olive::core::rational(1, 30))); + olive::NodeValue inverse_tex = + inverse_table.Get(olive::NodeValue::kTexture); + inverse_traverser.Resolve(inverse_tex); + + ASSERT_TRUE(inverse_traverser.output_frame); + const olive::Color inverse_out = + inverse_traverser.output_frame->get_pixel(0, 0); + + // Inverse of the asymmetric LUT moves the same color to a darker result. + EXPECT_NEAR(inverse_out.red(), 0.167f, 0.02f); + EXPECT_NEAR(inverse_out.green(), 0.333f, 0.02f); + EXPECT_NEAR(inverse_out.blue(), 0.500f, 0.02f); + + // The two outputs must differ from each other. + EXPECT_GT(std::abs(forward_out.red() - inverse_out.red()), 0.1f); + EXPECT_GT(std::abs(forward_out.green() - inverse_out.green()), 0.1f); + EXPECT_GT(std::abs(forward_out.blue() - inverse_out.blue()), 0.1f); +} + +TEST(ColorLutNode, SwitchingFileUpdatesProcessorAndPixels) +{ + olive::ColorManager::SetUpDefaultConfig(); + + olive::Project project; + project.Initialize(); + + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + + const QString invert_path = WriteTestCubeLut( + &dir, "invert", 0.0f, 1.0f); + const QString boost_path = WriteTestCubeLut( + &dir, "boost", 0.5f, 1.0f); + ASSERT_FALSE(invert_path.isEmpty()); + ASSERT_FALSE(boost_path.isEmpty()); + + auto *solid = new olive::SolidGenerator(); + solid->setParent(&project); + solid->SetStandardValue( + olive::SolidGenerator::kColorInput, + QVariant::fromValue(olive::Color(0.25f, 0.50f, 0.75f, 1.0f))); + + auto *lut = new olive::OCIOLutNode(); + lut->setParent(&project); + lut->SetStandardValue(olive::OCIOLutNode::kFileInput, invert_path); + lut->SetStandardValue(olive::OCIOLutNode::kDirectionInput, 0); // Forward + + olive::Node::ConnectEdge( + solid, olive::NodeInput(lut, olive::OCIOLutNode::kTextureInput)); + + const olive::VideoParams params( + 16, 16, olive::core::PixelFormat::F32, + olive::VideoParams::kRGBAChannelCount); + + // Render with invert LUT. + PixelColorTransformTraverser invert_traverser; + invert_traverser.SetCacheVideoParams(params); + olive::NodeValueTable invert_table = invert_traverser.GenerateTable( + lut, olive::TimeRange(olive::core::rational(0), + olive::core::rational(1, 30))); + olive::NodeValue invert_tex = invert_table.Get(olive::NodeValue::kTexture); + invert_traverser.Resolve(invert_tex); + + ASSERT_TRUE(invert_traverser.output_frame); + const olive::Color invert_out = + invert_traverser.output_frame->get_pixel(0, 0); + EXPECT_NEAR(invert_out.red(), 0.75f, 0.02f); + + // Switch to a different LUT file. Before the fix, the node could keep the + // old invert processor cached and the output would not change. + lut->SetStandardValue(olive::OCIOLutNode::kFileInput, boost_path); + + PixelColorTransformTraverser boost_traverser; + boost_traverser.SetCacheVideoParams(params); + olive::NodeValueTable boost_table = boost_traverser.GenerateTable( + lut, olive::TimeRange(olive::core::rational(0), + olive::core::rational(1, 30))); + olive::NodeValue boost_tex = boost_table.Get(olive::NodeValue::kTexture); + boost_traverser.Resolve(boost_tex); + + ASSERT_TRUE(boost_traverser.output_frame); + const olive::Color boost_out = + boost_traverser.output_frame->get_pixel(0, 0); + + // boost LUT maps: + // 0.0 -> 1.0, 1.0 -> 0.5 (linear: f(x) = 1 - 0.5*x) + // so (0.25, 0.50, 0.75) -> (0.875, 0.750, 0.625). + EXPECT_NEAR(boost_out.red(), 0.875f, 0.02f); + EXPECT_NEAR(boost_out.green(), 0.750f, 0.02f); + EXPECT_NEAR(boost_out.blue(), 0.625f, 0.02f); + + // The two outputs must differ (boost raises all channels vs invert). + EXPECT_GT(std::abs(boost_out.red() - invert_out.red()), 0.1f); + EXPECT_GT(std::abs(boost_out.green() - invert_out.green()), 0.1f); + EXPECT_GT(std::abs(boost_out.blue() - invert_out.blue()), 0.1f); +} diff --git a/tests/gtest/render_worker_footage_test.cpp b/tests/gtest/render_worker_footage_test.cpp index 4a6edb203..2a962aa3f 100644 --- a/tests/gtest/render_worker_footage_test.cpp +++ b/tests/gtest/render_worker_footage_test.cpp @@ -232,7 +232,7 @@ protected: } // ---- spawn worker ---- - worker_.setProcessChannelMode(QProcess::ForwardedErrorChannel); + worker_.setProcessChannelMode(QProcess::SeparateChannels); worker_.start(worker_path_, QStringList{QStringLiteral("--backend"), backend}); if (!worker_.waitForStarted(kTimeoutMs)) { return false; @@ -362,6 +362,13 @@ protected: if (worker_.state() == QProcess::NotRunning) { std::cerr << "WaitForMessage: worker exited with code " << worker_.exitCode() << std::endl; + std::cerr << "Worker stdout buffer: " + << read_buffer_.toStdString() << std::endl; + QByteArray err = worker_.readAllStandardError(); + if (!err.isEmpty()) { + std::cerr << "Worker stderr:\n" + << err.toStdString() << std::endl; + } return false; } }