From 8977aa145814497641a9cbf9088aef7576ef2e2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?cedric=20paill=C3=A9?= Date: Fri, 23 May 2025 18:29:40 +0200 Subject: [PATCH 01/10] Add viewer dithering --- Global/Enums.h | 2 + Gui/GuiApplicationManager.cpp | 6 + Gui/GuiResources.qrc | 2 + Gui/Resources/Images/dither_disabled.png | Bin 0 -> 5469 bytes Gui/Resources/Images/dither_enabled.png | Bin 0 -> 9412 bytes Gui/Shaders.cpp | 422 +++++++++++++---------- Gui/Shaders.h | 1 + Gui/ViewerGL.cpp | 8 + Gui/ViewerGL.h | 2 + Gui/ViewerGLPrivate.cpp | 2 + Gui/ViewerGLPrivate.h | 1 + Gui/ViewerTab.cpp | 18 + Gui/ViewerTab.h | 1 + Gui/ViewerTab40.cpp | 9 + Gui/ViewerTabPrivate.cpp | 1 + Gui/ViewerTabPrivate.h | 2 + 16 files changed, 286 insertions(+), 191 deletions(-) create mode 100644 Gui/Resources/Images/dither_disabled.png create mode 100644 Gui/Resources/Images/dither_enabled.png diff --git a/Global/Enums.h b/Global/Enums.h index 37f75d66ce..acd1bab91a 100644 --- a/Global/Enums.h +++ b/Global/Enums.h @@ -299,6 +299,8 @@ enum PixmapEnum NATRON_PIXMAP_VIEWER_GAMMA_DISABLED, NATRON_PIXMAP_VIEWER_GAIN_ENABLED, NATRON_PIXMAP_VIEWER_GAIN_DISABLED, + NATRON_PIXMAP_VIEWER_DITHER_ENABLED, + NATRON_PIXMAP_VIEWER_DITHER_DISABLED, NATRON_PIXMAP_SCRIPT_CLEAR_OUTPUT, NATRON_PIXMAP_SCRIPT_EXEC_SCRIPT, diff --git a/Gui/GuiApplicationManager.cpp b/Gui/GuiApplicationManager.cpp index 25df689ca0..54049f28a2 100644 --- a/Gui/GuiApplicationManager.cpp +++ b/Gui/GuiApplicationManager.cpp @@ -790,6 +790,12 @@ GuiApplicationManager::getIcon(PixmapEnum e, path = NATRON_IMAGES_PATH "interp_curve_z.png"; break; // DON'T add a default: case here + case NATRON_PIXMAP_VIEWER_DITHER_ENABLED: + path = NATRON_IMAGES_PATH "dither_enabled.png"; + break; + case NATRON_PIXMAP_VIEWER_DITHER_DISABLED: + path = NATRON_IMAGES_PATH "dither_disabled.png"; + break; } // switch if ( path.empty() ) { assert(!"Missing image."); diff --git a/Gui/GuiResources.qrc b/Gui/GuiResources.qrc index 3d9ac34815..6218b77fd9 100644 --- a/Gui/GuiResources.qrc +++ b/Gui/GuiResources.qrc @@ -159,6 +159,8 @@ Resources/Images/curve.png Resources/Images/cuspPoints.png Resources/Images/diskcache_icon.png + Resources/Images/dither_enabled.png + Resources/Images/dither_disabled.png Resources/Images/dot_icon.png Resources/Images/ellipse.png Resources/Images/enter_group.png diff --git a/Gui/Resources/Images/dither_disabled.png b/Gui/Resources/Images/dither_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..536fdfbfe94bf5bb2fed9cfbfeddc31b1a6a38fa GIT binary patch literal 5469 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 KMNUMnLSTX`2c=a2 literal 0 HcmV?d00001 diff --git a/Gui/Shaders.cpp b/Gui/Shaders.cpp index 5fe11d0c62..d36e532898 100644 --- a/Gui/Shaders.cpp +++ b/Gui/Shaders.cpp @@ -29,202 +29,242 @@ NATRON_NAMESPACE_ENTER -const char* fragRGB = - "uniform sampler2D Tex;\n" - "uniform float gain;\n" - "uniform float offset;\n" - "uniform int lut;\n" - "uniform float gamma;\n" - "\n" - "float linear_to_srgb(float c) {\n" - " return (c<=0.0031308) ? (12.92*c) : (((1.0+0.055)*pow(c,1.0/2.4))-0.055);\n" - "}\n" - "float linear_to_rec709(float c) {" - " return (c<0.018) ? (4.500*c) : (1.099*pow(c,0.45) - 0.099);\n" - "}\n" - "float linear_to_bt1886(float c) {" - " return pow(c,1.0/2.4);\n" - "}\n" - "void main() {\n" - " vec4 color_tmp = texture2D(Tex,gl_TexCoord[0].st);\n" - " color_tmp.rgb = (color_tmp.rgb * gain) + offset;\n" - " if (lut == 0) { // srgb\n" -// << TO SRGB - " color_tmp.r = linear_to_srgb(color_tmp.r);\n" - " color_tmp.g = linear_to_srgb(color_tmp.g);\n" - " color_tmp.b = linear_to_srgb(color_tmp.b);\n" -// << END TO SRGB - " } else if (lut == 2) { // Rec 709\n" -// << TO REC 709 - " color_tmp.r = linear_to_rec709(color_tmp.r);\n" - " color_tmp.g = linear_to_rec709(color_tmp.g);\n" - " color_tmp.b = linear_to_rec709(color_tmp.b);\n" -// << END TO REC 709 - " } else if (lut == 3) { // BT1886\n" -// << TO BT1886 - " color_tmp.r = linear_to_bt1886(color_tmp.r);\n" - " color_tmp.g = linear_to_bt1886(color_tmp.g);\n" - " color_tmp.b = linear_to_bt1886(color_tmp.b);\n" -// << END TO BT1886 - " }\n" - " if (gamma <= 0.) {\n" - " color_tmp.r = (color_tmp.r >= 1.) ? 1. : 0.;\n" - " color_tmp.g = (color_tmp.g >= 1.) ? 1. : 0.;\n" - " color_tmp.b = (color_tmp.b >= 1.) ? 1. : 0.;\n" - " } else {\n" - " color_tmp.r = pow(color_tmp.r, 1./gamma);\n" - " color_tmp.g = pow(color_tmp.g, 1./gamma);\n" - " color_tmp.b = pow(color_tmp.b, 1./gamma);\n" - " }\n" - " gl_FragColor = color_tmp;\n" - "}\n" -; -const char* vertRGB = - "void main()\n" - "{\n" - " gl_TexCoord[0] = gl_MultiTexCoord0;" - " gl_Position = ftransform();\n" - "}\n" - "\n" -; +const char* fragRGB = R"( + uniform sampler2D Tex; + uniform float gain; + uniform float offset; + uniform int lut; + uniform float gamma; + uniform int dither; + + #if __VERSION__ < 150 + float rnd(vec2 p) + { + return 1.0 - 2.0*fract(sin(dot(p.xy ,vec2(12.9898,78.233))) * 43758.5453); + } + #else + float rnd(vec2 p) + { + int n = int(p.x * 40.0 + p.y * 6400.0); + n = (n << 13) ^ n; + return 1.0 - float( (n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0; + } + #endif + + float linear_to_srgb(float c) { + return (c<=0.0031308) ? (12.92*c) : (((1.0+0.055)*pow(c,1.0/2.4))-0.055); + } + float linear_to_rec709(float c) { + return (c<0.018) ? (4.500*c) : (1.099*pow(c,0.45) - 0.099); + } + float linear_to_bt1886(float c) { + return pow(c,1.0/2.4); + } + void main() { + vec4 color_tmp = texture2D(Tex,gl_TexCoord[0].st); + color_tmp.rgb = (color_tmp.rgb * gain) + offset; + if (lut == 0) { // srgb + // << TO SRGB + color_tmp.r = linear_to_srgb(color_tmp.r); + color_tmp.g = linear_to_srgb(color_tmp.g); + color_tmp.b = linear_to_srgb(color_tmp.b); + // << END TO SRGB + } else if (lut == 2) { // Rec 709 + // << TO REC 709 + color_tmp.r = linear_to_rec709(color_tmp.r); + color_tmp.g = linear_to_rec709(color_tmp.g); + color_tmp.b = linear_to_rec709(color_tmp.b); + // << END TO REC 709 + } else if (lut == 3) { // BT1886 + // << TO BT1886 + color_tmp.r = linear_to_bt1886(color_tmp.r); + color_tmp.g = linear_to_bt1886(color_tmp.g); + color_tmp.b = linear_to_bt1886(color_tmp.b); + // << END TO BT1886 + } + if (gamma <= 0.) { + color_tmp.r = (color_tmp.r >= 1.) ? 1. : 0.; + color_tmp.g = (color_tmp.g >= 1.) ? 1. : 0.; + color_tmp.b = (color_tmp.b >= 1.) ? 1. : 0.; + } else { + color_tmp.r = pow(color_tmp.r, 1./gamma); + color_tmp.g = pow(color_tmp.g, 1./gamma); + color_tmp.b = pow(color_tmp.b, 1./gamma); + } + + if (dither){ + //dithering + ivec2 texsize = textureSize2D(Tex, 0); + vec2 coord = gl_TexCoord[0].st / texsize; + + vec3 c = color_tmp.rgb; + float a = color_tmp.a; + float scale = 255.0; + float seed = 32; + + vec2 pr = (0.9 + 0.1 * seed) * coord.xy * 1000.1; + vec2 pg = (0.9 + 0.1 * seed) * coord.xy * 1000.2; + vec2 pb = (0.9 + 0.1 * seed) * coord.xy * 1000.3; + + + gl_FragColor = vec4(c.rgb + vec3(rnd(pr), rnd(pg), rnd(pb)) * vec3(0.5) / vec3(scale), a); + } else { + gl_FragColor = vec4(color_tmp.rgb, color_tmp.a); + } + } +)"; +const char* vertRGB = R"( + void main() + { + gl_TexCoord[0] = gl_MultiTexCoord0; + gl_Position = ftransform(); + } + )"; /*There's a black texture used for when the user disconnect the viewer It's not just a shader,because we still need coordinates feedback. */ -const char* blackFrag = - "uniform sampler2D Tex;\n" - "void main()\n" - "{\n" - " gl_FragColor = texture2D(Tex,gl_TexCoord[0].st);\n" - "}\n"; -const char *histogramComputation_frag = - "#extension GL_ARB_texture_rectangle : enable\n" - "uniform sampler2DRect Tex;\n" - "uniform int channel;\n" - "void main()\n" - "{\n" - " gl_FragColor = vec4(1.0,0.0,0.0,1.0);\n" - "}\n" -; -const char *histogramComputationVertex_vert = - "#extension GL_ARB_texture_rectangle : enable\n" - "uniform sampler2DRect Tex;\n" - "uniform int channel;\n" - "attribute vec2 TexCoord;\n" - "void main()\n" - "{\n" - "\n" - " vec4 c = texture2DRect(Tex, TexCoord.xy );\n" - "\n" - " float sel = 0.0;\n" - " if(channel == 0){ // luminance\n" - " sel = 0.299*c.r + 0.587*c.g +0.114*c.b;\n" - " }else if(channel == 1){ // red\n" - " sel = c.r;\n" - " }else if(channel == 2){ //green\n" - " sel = c.g;\n" - " }else if(channel == 3){ // blue\n" - " sel = c.b;\n" - " }else if(channel == 4){ // alpha\n" - " sel = c.a;\n" - " }\n" - " clamp(sel, 0.0, 1.0);\n" - "// set new point position to the color intensity in [-1.0,1.0] interval\n" - "// as this is homogeneous coord. clip space\n" - " gl_Position.x =(2.0-4.0/257.0)*sel-1.0+2.0/257.0;\n" - " gl_Position.y = 0.0;\n" - " gl_Position.z = 0.0;\n" - "}\n" -; -const char *histogramRendering_frag = - "#extension GL_ARB_texture_rectangle : enable\n" - "uniform sampler2DRect HistogramTex;\n" - "uniform sampler2DRect MaximumRedTex;\n" - "uniform sampler2DRect MaximumGreenTex;\n" - "uniform sampler2DRect MaximumBlueTex;\n" - "uniform int channel;\n" - "void main()\n" - "{\n" - " if(channel == 0){\n" - " gl_FragColor =vec4(0.8,0.8,0.8,0.8);\n" - " }else if(channel == 1){\n" - " gl_FragColor =vec4(1.0,0.0,0.0,0.8);\n" - " }else if(channel == 2){\n" - " gl_FragColor =vec4(0.0,1.0,0.0,0.8);\n" - " }else if(channel == 3){\n" - " gl_FragColor =vec4(0.0,0.0,1.0,0.8);\n" - " }\n" - " \n" - "}\n" -; -const char *histogramRenderingVertex_vert = - "#extension GL_ARB_texture_rectangle : enable\n" - "uniform sampler2DRect HistogramTex;\n" - "uniform sampler2DRect MaximumRedTex;\n" - "uniform sampler2DRect MaximumGreenTex;\n" - "uniform sampler2DRect MaximumBlueTex;\n" - "uniform int channel;\n" - "attribute vec3 TexCoord;\n" - "void main()\n" - "{\n" - "\n" - " vec4 c = texture2DRect(HistogramTex, TexCoord.xy );\n" - " float bottom = TexCoord.z;\n" - " float maximum = 0.0;\n" - " float maximumRed = texture2DRect(MaximumRedTex,vec2(0.0,0.0)).r;\n" - " float maximumGreen = texture2DRect(MaximumGreenTex,vec2(0.0,0.0)).r;\n" - " float maximumBlue = texture2DRect(MaximumBlueTex,vec2(0.0,0.0)).r;\n" - " maximum = max(max(maximumRed,maximumGreen),maximumBlue);\n" - " if(maximum == 0.0){\n" - " maximum = 1000000.0;\n" - " }\n" - "// set new point position to the color intensity in [-1.0,1.0] interval\n" - "// as this is homogeneous coord. clip space\n" - " gl_Position.x =(2.0-4.0/257.0)*(TexCoord.x/255.0)-1.0+2.0/257.0;\n" - " if(bottom == 1.0){\n" - " gl_Position.y = -1.0;\n" - " }else{\n" - " float y = c.r/maximum;\n" - " gl_Position.y = 2.0*y-1.0;\n" - " }\n" - " gl_Position.z = 0.0;\n" - "}\n" -; -const char* minimal_vert = - "#extension GL_ARB_texture_rectangle : enable\n" - "gl_TexCoord[0]=gl_MultiTexCoord0;" - "gl_Position = ftransform();" +const char* blackFrag = R"( + uniform sampler2D Tex; + void main() + { + gl_FragColor = texture2D(Tex,gl_TexCoord[0].st); + }; +)"; + +const char *histogramComputation_frag = R"( + #extension GL_ARB_texture_rectangle : enable + uniform sampler2DRect Tex; + uniform int channel; + void main() + { + gl_FragColor = vec4(1.0,0.0,0.0,1.0); + } +)"; + +const char *histogramComputationVertex_vert = R"( + #extension GL_ARB_texture_rectangle : enable + uniform sampler2DRect Tex; + uniform int channel; + attribute vec2 TexCoord; + void main() + { + + vec4 c = texture2DRect(Tex, TexCoord.xy ); + + float sel = 0.0; + if(channel == 0){ // luminance + sel = 0.299*c.r + 0.587*c.g +0.114*c.b; + }else if(channel == 1){ // red + sel = c.r; + }else if(channel == 2){ //green + sel = c.g; + }else if(channel == 3){ // blue + sel = c.b; + }else if(channel == 4){ // alpha + sel = c.a; + } + clamp(sel, 0.0, 1.0); + // set new point position to the color intensity in [-1.0,1.0] interval + // as this is homogeneous coord. clip space + gl_Position.x =(2.0-4.0/257.0)*sel-1.0+2.0/257.0; + gl_Position.y = 0.0; + gl_Position.z = 0.0; + } +)"; + +const char *histogramRendering_frag = R"( + #extension GL_ARB_texture_rectangle : enable + uniform sampler2DRect HistogramTex; + uniform sampler2DRect MaximumRedTex; + uniform sampler2DRect MaximumGreenTex; + uniform sampler2DRect MaximumBlueTex; + uniform int channel; + void main() + { + if(channel == 0){ + gl_FragColor =vec4(0.8,0.8,0.8,0.8); + }else if(channel == 1){ + gl_FragColor =vec4(1.0,0.0,0.0,0.8); + }else if(channel == 2){ + gl_FragColor =vec4(0.0,1.0,0.0,0.8); + }else if(channel == 3){ + gl_FragColor =vec4(0.0,0.0,1.0,0.8); + } + + } +)"; + +const char *histogramRenderingVertex_vert = R"( + #extension GL_ARB_texture_rectangle : enable + uniform sampler2DRect HistogramTex; + uniform sampler2DRect MaximumRedTex; + uniform sampler2DRect MaximumGreenTex; + uniform sampler2DRect MaximumBlueTex; + uniform int channel; + attribute vec3 TexCoord; + void main() + { + + vec4 c = texture2DRect(HistogramTex, TexCoord.xy ); + float bottom = TexCoord.z; + float maximum = 0.0; + float maximumRed = texture2DRect(MaximumRedTex,vec2(0.0,0.0)).r; + float maximumGreen = texture2DRect(MaximumGreenTex,vec2(0.0,0.0)).r; + float maximumBlue = texture2DRect(MaximumBlueTex,vec2(0.0,0.0)).r; + maximum = max(max(maximumRed,maximumGreen),maximumBlue); + if(maximum == 0.0){ + maximum = 1000000.0; + } + // set new point position to the color intensity in [-1.0,1.0] interval + // as this is homogeneous coord. clip space + gl_Position.x =(2.0-4.0/257.0)*(TexCoord.x/255.0)-1.0+2.0/257.0; + if(bottom == 1.0){ + gl_Position.y = -1.0; + }else{ + float y = c.r/maximum; + gl_Position.y = 2.0*y-1.0; + } + gl_Position.z = 0.0; + } +)"; + +const char* minimal_vert = R"( + #extension GL_ARB_texture_rectangle : enable + gl_TexCoord[0]=gl_MultiTexCoord0; + gl_Position = ftransform(); ; const char *histogramMaximum_frag = - "#extension GL_ARB_texture_rectangle : enable\n" - "uniform sampler2DRect Tex;\n" - "void main()\n" - "{\n" - " vec4 a,b,c,d;\n" - " vec2 texCoord = gl_TexCoord[0].st;\n" - " texCoord.s = (texCoord.s-0.5) * 4.0 + 0.5;\n" - " a = texture2DRect(Tex,texCoord.st);\n" - " vec2 texCoord1,texCoord2,texCoord3;\n" - " texCoord1 = texCoord.st + vec2(1.0,0.0);\n" - " texCoord2 = texCoord.st + vec2(2.0,0.0);\n" - " texCoord3 = texCoord.st + vec2(3.0,0.0);\n" - " if(texCoord1.s <= 256.0){\n" - " b = texture2DRect(Tex,texCoord1);\n" - " }else{\n" - " b = a;\n" - " }\n" - " if(texCoord2.s <= 256.0){\n" - " c = texture2DRect(Tex,texCoord2);\n" - " }else{\n" - " c = a;\n" - " }\n" - " if(texCoord3.s <= 256.0){\n" - " d = texture2DRect(Tex,texCoord3);\n" - " }else{\n" - " d = a;\n" - " }\n" - " gl_FragColor = vec4(max(max(a.r,b.r),max(c.r,d.r)),0.0,0.0,1.0);\n" - "}\n" -; + #extension GL_ARB_texture_rectangle : enable + uniform sampler2DRect Tex; + void main() + { + vec4 a,b,c,d; + vec2 texCoord = gl_TexCoord[0].st; + texCoord.s = (texCoord.s-0.5) * 4.0 + 0.5; + a = texture2DRect(Tex,texCoord.st); + vec2 texCoord1,texCoord2,texCoord3; + texCoord1 = texCoord.st + vec2(1.0,0.0); + texCoord2 = texCoord.st + vec2(2.0,0.0); + texCoord3 = texCoord.st + vec2(3.0,0.0); + if(texCoord1.s <= 256.0){ + b = texture2DRect(Tex,texCoord1); + }else{ + b = a; + } + if(texCoord2.s <= 256.0){ + c = texture2DRect(Tex,texCoord2); + }else{ + c = a; + } + if(texCoord3.s <= 256.0){ + d = texture2DRect(Tex,texCoord3); + }else{ + d = a; + } + gl_FragColor = vec4(max(max(a.r,b.r),max(c.r,d.r)),0.0,0.0,1.0); + } +)"; + NATRON_NAMESPACE_EXIT diff --git a/Gui/Shaders.h b/Gui/Shaders.h index 7d8835d1e6..a3575b7bad 100644 --- a/Gui/Shaders.h +++ b/Gui/Shaders.h @@ -32,6 +32,7 @@ NATRON_NAMESPACE_ENTER extern const char* fragRGB; extern const char* vertRGB; +extern const char* fragDither; /*There's a black texture used for when the user disconnect the viewer It's not just a shader,because we still need coordinates feedback. diff --git a/Gui/ViewerGL.cpp b/Gui/ViewerGL.cpp index ff31696571..ee933a28e1 100644 --- a/Gui/ViewerGL.cpp +++ b/Gui/ViewerGL.cpp @@ -1720,6 +1720,14 @@ ViewerGL::setLut(int lut) _imp->displayingImageLut = (ViewerColorSpaceEnum)lut; } +void +ViewerGL::setDither(bool dither) +{ + // always running in the main thread + assert( qApp && qApp->thread() == QThread::currentThread() ); + _imp->enableDisplayDither = dither; +} + #define QMouseEventLocalPos(e) ( e->localPos() ) void diff --git a/Gui/ViewerGL.h b/Gui/ViewerGL.h index 9015efa5b4..d98b85db6e 100644 --- a/Gui/ViewerGL.h +++ b/Gui/ViewerGL.h @@ -396,6 +396,8 @@ public Q_SLOTS: void setLut(int lut); + void setDither(bool dither); + bool isWipeHandleVisible() const; void setZoomOrPannedSinceLastFit(bool enabled); diff --git a/Gui/ViewerGLPrivate.cpp b/Gui/ViewerGLPrivate.cpp index 957837e3de..159d501256 100644 --- a/Gui/ViewerGLPrivate.cpp +++ b/Gui/ViewerGLPrivate.cpp @@ -80,6 +80,7 @@ ViewerGL::Implementation::Implementation(ViewerGL* this_, , zoomOrPannedSinceLastFit(false) , oldClick() , displayingImageLut(eViewerColorSpaceSRGB) + , enableDisplayDither(false) , ms(eMouseStateUndefined) , hs(eHoverStateNothing) , textRenderingColor(200, 200, 200, 255) @@ -856,6 +857,7 @@ ViewerGL::Implementation::activateShaderRGB(int texIndex) shaderRGB->setUniformValue("lut", (GLint)displayingImageLut); float gamma = displayTextures[texIndex].gamma; shaderRGB->setUniformValue("gamma", gamma); + shaderRGB->setUniformValue("dither", (GLint)enableDisplayDither); } bool diff --git a/Gui/ViewerGLPrivate.h b/Gui/ViewerGLPrivate.h index c3d66782d8..dafc7deebe 100644 --- a/Gui/ViewerGLPrivate.h +++ b/Gui/ViewerGLPrivate.h @@ -166,6 +166,7 @@ struct ViewerGL::Implementation bool zoomOrPannedSinceLastFit; //< true if the user zoomed or panned the image since the last call to fitToRoD QPoint oldClick; ViewerColorSpaceEnum displayingImageLut; + bool enableDisplayDither; MouseStateEnum ms; /*!< Holds the mouse state*/ HoverStateEnum hs; const QColor textRenderingColor; diff --git a/Gui/ViewerTab.cpp b/Gui/ViewerTab.cpp index 59d026a065..b696b6812f 100644 --- a/Gui/ViewerTab.cpp +++ b/Gui/ViewerTab.cpp @@ -524,6 +524,24 @@ ViewerTab::ViewerTab(const std::list & existingNodesContext, QObject::connect( _imp->checkerboardButton, SIGNAL(clicked(bool)), this, SLOT(onCheckerboardButtonClicked()) ); _imp->secondRowLayout->addWidget(_imp->checkerboardButton); + QPixmap ditheringEnabled, ditheringDisabled; + appPTR->getIcon(NATRON_PIXMAP_VIEWER_DITHER_ENABLED, pixmapIconSize, &ditheringEnabled); + appPTR->getIcon(NATRON_PIXMAP_VIEWER_DITHER_DISABLED, pixmapIconSize, &ditheringDisabled); + QIcon icDthr; + icDthr.addPixmap(ditheringEnabled, QIcon::Normal, QIcon::On); + icDthr.addPixmap(ditheringDisabled, QIcon::Normal, QIcon::Off); + _imp->ditherButton = new Button(icDthr, QString(), _imp->secondSettingsRow); + _imp->ditherButton->setFocusPolicy(Qt::NoFocus); + _imp->ditherButton->setCheckable(true); + _imp->ditherButton->setChecked(false); + _imp->ditherButton->setDown(false); + _imp->ditherButton->setToolTip( NATRON_NAMESPACE::convertFromPlainText(tr("If checked, the viewer draws a checkerboard under input A instead of black (disabled under the wipe area and in stack modes)."), NATRON_NAMESPACE::WhiteSpaceNormal) ); + _imp->ditherButton->setFixedSize(buttonSize); + _imp->ditherButton->setIconSize(buttonIconSize); + QObject::connect( _imp->ditherButton, SIGNAL(clicked(bool)), this, SLOT(onDitherButtonClicked()) ); + _imp->secondRowLayout->addWidget(_imp->ditherButton); + + _imp->viewsComboBox = new ComboBox(_imp->secondSettingsRow); _imp->viewsComboBox->setToolTip( QString::fromUtf8("

") + tr("Active view:") + QString::fromUtf8("

") + tr( "Tells the viewer what view should be displayed.") ); diff --git a/Gui/ViewerTab.h b/Gui/ViewerTab.h index 686e7ad512..291e9c16aa 100644 --- a/Gui/ViewerTab.h +++ b/Gui/ViewerTab.h @@ -397,6 +397,7 @@ public Q_SLOTS: void hideAllToolbars(); void onCheckerboardButtonClicked(); + void onDitherButtonClicked(); void onPickerButtonClicked(bool); diff --git a/Gui/ViewerTab40.cpp b/Gui/ViewerTab40.cpp index 76529f87b8..64dbb14bc1 100644 --- a/Gui/ViewerTab40.cpp +++ b/Gui/ViewerTab40.cpp @@ -675,6 +675,15 @@ ViewerTab::onCheckerboardButtonClicked() _imp->viewer->redraw(); } +void +ViewerTab::onDitherButtonClicked() +{ + _imp->ditherEnabled = !_imp->ditherEnabled; + _imp->ditherButton->setDown(_imp->ditherEnabled); + _imp->viewer->setDither(_imp->ditherEnabled); + _imp->viewer->redraw(); +} + bool ViewerTab::isCheckerboardEnabled() const { diff --git a/Gui/ViewerTabPrivate.cpp b/Gui/ViewerTabPrivate.cpp index a99997976d..caa27eca63 100644 --- a/Gui/ViewerTabPrivate.cpp +++ b/Gui/ViewerTabPrivate.cpp @@ -97,6 +97,7 @@ ViewerTabPrivate::ViewerTabPrivate(ViewerTab* publicInterface, , gammaSlider(NULL) , viewerColorSpace(NULL) , checkerboardButton(NULL) + , ditherButton(NULL) , pickerButton(NULL) , viewsComboBox(NULL) , currentViewIndex(0) diff --git a/Gui/ViewerTabPrivate.h b/Gui/ViewerTabPrivate.h index c2a0df662b..402d31c9fd 100644 --- a/Gui/ViewerTabPrivate.h +++ b/Gui/ViewerTabPrivate.h @@ -115,6 +115,7 @@ struct ViewerTabPrivate ScaleSliderQWidget* gammaSlider; ComboBox* viewerColorSpace; Button* checkerboardButton; + Button* ditherButton; Button* pickerButton; ComboBox* viewsComboBox; ViewIdx currentViewIndex; @@ -192,6 +193,7 @@ struct ViewerTabPrivate bool isFileDialogViewer; mutable QMutex checkerboardMutex; bool checkerboardEnabled; + bool ditherEnabled; mutable QMutex fpsMutex; double fps; From 36ee69b4c5d031478b8e9e3388ab7a347a8626f0 Mon Sep 17 00:00:00 2001 From: Cedric PAILLE Date: Mon, 26 May 2025 10:52:27 +0200 Subject: [PATCH 02/10] Change tooltip message --- Gui/ViewerTab.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gui/ViewerTab.cpp b/Gui/ViewerTab.cpp index b696b6812f..774219a94b 100644 --- a/Gui/ViewerTab.cpp +++ b/Gui/ViewerTab.cpp @@ -535,7 +535,7 @@ ViewerTab::ViewerTab(const std::list & existingNodesContext, _imp->ditherButton->setCheckable(true); _imp->ditherButton->setChecked(false); _imp->ditherButton->setDown(false); - _imp->ditherButton->setToolTip( NATRON_NAMESPACE::convertFromPlainText(tr("If checked, the viewer draws a checkerboard under input A instead of black (disabled under the wipe area and in stack modes)."), NATRON_NAMESPACE::WhiteSpaceNormal) ); + _imp->ditherButton->setToolTip( NATRON_NAMESPACE::convertFromPlainText(tr("If checked, it will activate a dithering filter to avoid banding artifacts (needs 32 bits floating point rendering)"), NATRON_NAMESPACE::WhiteSpaceNormal) ); _imp->ditherButton->setFixedSize(buttonSize); _imp->ditherButton->setIconSize(buttonIconSize); QObject::connect( _imp->ditherButton, SIGNAL(clicked(bool)), this, SLOT(onDitherButtonClicked()) ); From a4aca301d8b011fb892dfce483fb0b11bfd1b067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?cedric=20paill=C3=A9?= Date: Fri, 25 Apr 2025 16:07:49 +0200 Subject: [PATCH 03/10] Build libs install --- App/CMakeLists.txt | 43 +++++++++++++++++++++++++++++++++++--- CMakeLists.txt | 11 ++++++++++ Renderer/CMakeLists.txt | 2 +- libs/gflags/CMakeLists.txt | 2 +- tools/utils/sourceList.py | 2 +- 5 files changed, 54 insertions(+), 6 deletions(-) diff --git a/App/CMakeLists.txt b/App/CMakeLists.txt index 06d530c2ff..c89d01fd29 100644 --- a/App/CMakeLists.txt +++ b/App/CMakeLists.txt @@ -18,10 +18,10 @@ # ***** END LICENSE BLOCK ***** set(Natron_SOURCES NatronApp_main.cpp) -if(WINDOWS) - list(APPEND Natron_SOURCES ../Natron.rc) -endif() add_executable(Natron ${Natron_SOURCES}) +if(WIN32) + target_sources(Natron PRIVATE ../Natron.rc) +endif() if(APPLE) set_target_properties(Natron PROPERTIES MACOSX_BUNDLE TRUE @@ -59,3 +59,40 @@ install(FILES ../Gui/Resources/Images/natronIcon256_linux.png DESTINATION "${CMAKE_INSTALL_DATADIR}/pixmaps") install(FILES ../Gui/Resources/Images/natronProjectIcon_linux.png DESTINATION "${CMAKE_INSTALL_DATADIR}/pixmaps") + +IF(WIN32 AND DEPLOYQT_FOUND) + SET(CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/install_deps_${CMAKE_BUILD_TYPE}.cmake) + + # Find and install needed DLLs + file(GENERATE OUTPUT + "${CONFIG_FILE}" CONTENT + [[ + EXECUTE_PROCESS(COMMAND del ${INSTALL_BINPATH}/*.dll) + SET(TARGET_APP $) + FILE(GET_RUNTIME_DEPENDENCIES + RESOLVED_DEPENDENCIES_VAR deps_resolved + UNRESOLVED_DEPENDENCIES_VAR deps_unresolved + EXECUTABLES ${TARGET_APP} + DIRECTORIES ${CMAKELIBPATH} + PRE_EXCLUDE_REGEXES "api-ms-*" "ext-ms-*" + POST_EXCLUDE_REGEXES ".*system32/.*\\.dll" + ) + MESSAGE(STATUS "Resolving runtime dependencies for ${TARGET_APP}") + FOREACH(dep ${deps_resolved}) + FILE(INSTALL ${dep} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) + MESSAGE(STATUS "Installing ${dep}") + ENDFOREACH() + FOREACH(dep ${deps_unresolved}) + MESSAGE(WARNING "Runtime dependency ${dep} could not be resolved.") + ENDFOREACH() + EXECUTE_PROCESS(COMMAND ${DEPLOYQT_EXE} --no-angle --compiler-runtime ${INSTALL_BINPATH}/Natron.exe) + ]]) + + INSTALL(CODE "SET(INSTALL_BINPATH \"${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}\")") + INSTALL(CODE "SET(DEPLOYQT_EXE \"${DEPLOYQT_EXE}\")") + INSTALL(CODE "SET(CMAKELIBPATH \"${CMAKE_SYSTEM_LIBRARY_PATH};${CMAKE_MINGW_SYSTEM_LIBRARY_PATH};${MINGWPATH}\")") + INSTALL(SCRIPT ${CONFIG_FILE}) + INSTALL(DIRECTORY ${Python3_LIBRARY_DIRS}/python${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR} + DESTINATION lib + PATTERN "*.pyc" EXCLUDE) +ENDIF() \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 48dd73e220..b7f804f31a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,17 @@ if(WIN32) endif() find_package(Python3 COMPONENTS Interpreter Development) +if(WIN32) + # Search qtdeploy executable + find_program(DEPLOYQT_EXE windeployqt.exe) + if(NOT ${DEPLOYQT_EXE} STREQUAL "") + set(DEPLOYQT_FOUND 1) + else() + set(DEPLOYQT_FOUND 0) + message(STATUS "windeployqt.exe not found, will not deploy Qt dependencies") + endif() +endif() + if(IS_DEBUG_BUILD AND WIN32) # Explicitly setting SHIBOKEN_PYTHON_LIBRARIES variable to avoid PYTHON_DEBUG_LIBRARY-NOTFOUND # link errors on Windows debug builds. diff --git a/Renderer/CMakeLists.txt b/Renderer/CMakeLists.txt index b54e1f254c..0b29c2d848 100644 --- a/Renderer/CMakeLists.txt +++ b/Renderer/CMakeLists.txt @@ -18,7 +18,7 @@ # ***** END LICENSE BLOCK ***** set(NatronRenderer_SOURCES NatronRenderer_main.cpp) -if(WINDOWS) +if(WIN32) list(APPEND NatronRenderer_SOURCES ../Natron.rc) endif() add_executable(NatronRenderer ${NatronRenderer_SOURCES}) diff --git a/libs/gflags/CMakeLists.txt b/libs/gflags/CMakeLists.txt index 01397809aa..0bc3f2af5c 100644 --- a/libs/gflags/CMakeLists.txt +++ b/libs/gflags/CMakeLists.txt @@ -30,7 +30,7 @@ set(gflags_SOURCES src/gflags_completions.cc src/gflags_reporting.cc ) -if(WINDOWS) +if(WIN32) set(gflags_HEADERS ${gflags_HEADERS} src/windows_port.h) set(gflags_SOURCES ${gflags_SOURCES} src/windows_port.cc) endif() diff --git a/tools/utils/sourceList.py b/tools/utils/sourceList.py index 13d236f4d9..b1e5871026 100644 --- a/tools/utils/sourceList.py +++ b/tools/utils/sourceList.py @@ -15,7 +15,7 @@ def list_typesystem_cpp_sources(typesystem, out): sources = [f"{package.lower()}_module_wrapper.cpp"] sources.extend([f"{typename.lower()}_wrapper.cpp" for typename in types]) - return [os.path.normpath(os.path.join(out, package, f)) for f in sources] + return [os.path.normpath(os.path.join(out, package, f)).replace("\\", "/") for f in sources] if __name__ == "__main__": From e74b5fae24bff1c1bb6a4ec16c899e814fe373a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?cedric=20paill=C3=A9?= Date: Fri, 25 Apr 2025 16:08:11 +0200 Subject: [PATCH 04/10] Attempt to fix undocking issue --- Engine/AppManager.cpp | 3 +++ Gui/CurveWidget.cpp | 1 + Gui/DopeSheetView.cpp | 2 ++ 3 files changed, 6 insertions(+) diff --git a/Engine/AppManager.cpp b/Engine/AppManager.cpp index 071f46b2d5..f95912b028 100644 --- a/Engine/AppManager.cpp +++ b/Engine/AppManager.cpp @@ -314,6 +314,8 @@ AppManager::loadFromArgs(const CLArgs& cl) std::cout << "argv[" << i << "] = " << StrUtils::utf16_to_utf8( std::wstring(_imp->commandLineArgsWide[i]) ) << std::endl; } #endif + // This should fix GL widgets when undocked + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); // This needs to be done BEFORE creating qApp because // on Linux, X11 will create a context that would corrupt @@ -322,6 +324,7 @@ AppManager::loadFromArgs(const CLArgs& cl) _imp->renderingContextPool.reset( new GPUContextPool() ); initializeOpenGLFunctionsOnce(true); + // QCoreApplication will hold a reference to that appManagerArgc integer until it dies. // Thus ensure that the QCoreApplication is destroyed when returning this function. initializeQApp(_imp->nArgs, &_imp->commandLineArgsUtf8.front()); // calls QCoreApplication::QCoreApplication(), which calls setlocale() diff --git a/Gui/CurveWidget.cpp b/Gui/CurveWidget.cpp index a52108e629..4fc6ed2987 100644 --- a/Gui/CurveWidget.cpp +++ b/Gui/CurveWidget.cpp @@ -164,6 +164,7 @@ CurveWidget::initializeGL() // always running in the main thread assert( qApp && qApp->thread() == QThread::currentThread() ); appPTR->initializeOpenGLFunctionsOnce(); + makeCurrent(); } void diff --git a/Gui/DopeSheetView.cpp b/Gui/DopeSheetView.cpp index b0c7bc0164..7bccaed593 100644 --- a/Gui/DopeSheetView.cpp +++ b/Gui/DopeSheetView.cpp @@ -3171,6 +3171,8 @@ DopeSheetView::initializeGL() return; } + makeCurrent(); + _imp->generateKeyframeTextures(); } From d5ef65cf5f0f033ae2e8eb058e102de417344c31 Mon Sep 17 00:00:00 2001 From: Cedric PAILLE Date: Mon, 2 Jun 2025 09:55:07 +0200 Subject: [PATCH 05/10] Fix GL widgets docking issue --- CMakeLists.txt | 5 ++++- Engine/AppManager.cpp | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b7f804f31a..71693613a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,7 @@ if(WIN32) find_program(DEPLOYQT_EXE windeployqt.exe) if(NOT ${DEPLOYQT_EXE} STREQUAL "") set(DEPLOYQT_FOUND 1) + message(STATUS "windeployqt.exe found : ${DEPLOYQT_EXE}") else() set(DEPLOYQT_FOUND 0) message(STATUS "windeployqt.exe not found, will not deploy Qt dependencies") @@ -95,7 +96,7 @@ if(NATRON_QT6) set(SHIBOKEN_LIB Shiboken6::libshiboken) set(PYSIDE_LIB PySide6::pyside6) else() - find_package(Qt5 5.15 CONFIG REQUIRED COMPONENTS ${QT_COMPONENTS}) + find_package(Qt5 5.15 CONFIG REQUIRED COMPONENTS ${QT_COMPONENTS} NO_SYSTEM_ENVIRONMENT_PATH) find_package(Shiboken2 5.15 CONFIG REQUIRED COMPONENTS libshiboken2 shiboken2) find_package(PySide2 5.15 CONFIG REQUIRED COMPONENTS pyside2) @@ -112,6 +113,8 @@ else() set(PYSIDE_LIB PySide2::pyside2) endif() +message(STATUS ">>> qt dirs ${Qt5Gui_INCLUDE_DIRS}") + if(IS_DEBUG_BUILD AND WIN32) # Remove NDEBUG from Shiboken INTERFACE_COMPILE_DEFINITIONS so it is not inherited in debug builds. get_property(SHIBOKEN_DEFS TARGET ${SHIBOKEN_LIB} PROPERTY INTERFACE_COMPILE_DEFINITIONS) diff --git a/Engine/AppManager.cpp b/Engine/AppManager.cpp index f95912b028..5a2401f970 100644 --- a/Engine/AppManager.cpp +++ b/Engine/AppManager.cpp @@ -316,6 +316,7 @@ AppManager::loadFromArgs(const CLArgs& cl) #endif // This should fix GL widgets when undocked QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); + QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); // This needs to be done BEFORE creating qApp because // on Linux, X11 will create a context that would corrupt @@ -3185,7 +3186,7 @@ void AppManager::registerUNCPath(const QString& path, const QChar& driveLetter) { - assert( QThread::currentThread() == qApp->thread() ); + //assert( QThread::currentThread() == qApp->thread() ); _imp->uncPathMapping[driveLetter] = path; } From 81b5cfe33860957c3235b85c2fa176dc6981d87c Mon Sep 17 00:00:00 2001 From: Cedric PAILLE Date: Fri, 6 Jun 2025 09:33:39 +0200 Subject: [PATCH 06/10] Improve font reading --- Gui/TimeLineGui.cpp | 2 +- Gui/ViewerGL.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Gui/TimeLineGui.cpp b/Gui/TimeLineGui.cpp index a4da578a4f..28b93ee598 100644 --- a/Gui/TimeLineGui.cpp +++ b/Gui/TimeLineGui.cpp @@ -361,7 +361,7 @@ TimeLineGui::paintGL() double screenPixelRatio = _imp->viewerTab->getViewer()->getScreenPixelRatio(); if (screenPixelRatio != _imp->_screenPixelRatio) { _imp->_screenPixelRatio = screenPixelRatio; - _imp->_textFont.reset(new QFont(appFont, appFontSize * screenPixelRatio)); + _imp->_textFont.reset(new QFont(appFont, appFontSize * screenPixelRatio, QFont::Bold)); } } assert(_imp->_textFont); diff --git a/Gui/ViewerGL.cpp b/Gui/ViewerGL.cpp index ee933a28e1..81327072f0 100644 --- a/Gui/ViewerGL.cpp +++ b/Gui/ViewerGL.cpp @@ -260,12 +260,11 @@ ViewerGL::paintGL() } glCheckError(); - { double screenPixelRatio = getScreenPixelRatio(); if (screenPixelRatio != _imp->_screenPixelRatio) { _imp->_screenPixelRatio = screenPixelRatio; - _imp->_textFont.reset(new QFont(appFont, appFontSize * screenPixelRatio)); + _imp->_textFont.reset(new QFont(appFont, appFontSize * screenPixelRatio, QFont::Bold)); } } assert(_imp->_textFont); From 43305a688d3f3c2816f42fcf1e4c87642ad4a4cc Mon Sep 17 00:00:00 2001 From: Cedric PAILLE Date: Fri, 6 Jun 2025 09:34:13 +0200 Subject: [PATCH 07/10] Fix dopesheet crash when an element is clicked in hierarchy view --- Gui/DopeSheetHierarchyView.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gui/DopeSheetHierarchyView.cpp b/Gui/DopeSheetHierarchyView.cpp index a65f46b37b..27f66d6032 100644 --- a/Gui/DopeSheetHierarchyView.cpp +++ b/Gui/DopeSheetHierarchyView.cpp @@ -130,7 +130,7 @@ HierarchyViewSelectionModel::selectChildren(const QModelIndex &index, QItemSelection *selection) const { int row = 0; - QModelIndex childIndex = index.model()->index(row, 0); + QModelIndex childIndex = index.model()->index(row, 0, index); while ( childIndex.isValid() ) { if ( !selection->contains(childIndex) ) { @@ -143,7 +143,7 @@ HierarchyViewSelectionModel::selectChildren(const QModelIndex &index, } ++row; - childIndex = index.model()->index(row, 0); + childIndex = index.model()->index(row, 0, index); } } From a72d9afbd1f5daf2a5da85338ff4cb27d33d9712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?cedric=20paill=C3=A9?= Date: Wed, 18 Jun 2025 11:22:23 +0200 Subject: [PATCH 08/10] Improve project loading/saving time by using binary format --- Engine/AppInstance.h | 8 ++ Engine/EngineFwd.h | 2 + Engine/Project.cpp | 155 +++++++++++++++++++++++++--------- Engine/Settings.cpp | 13 +++ Engine/Settings.h | 3 + Gui/Gui.h | 2 + Gui/Gui20.cpp | 14 +++ Gui/GuiAppInstance.cpp | 14 +++ Gui/GuiAppInstance.h | 2 + Gui/ProjectGui.cpp | 22 ++++- Gui/ProjectGuiSerialization.h | 2 + 11 files changed, 191 insertions(+), 46 deletions(-) diff --git a/Engine/AppInstance.h b/Engine/AppInstance.h index ab3561194c..383f635644 100644 --- a/Engine/AppInstance.h +++ b/Engine/AppInstance.h @@ -218,10 +218,18 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON { } + virtual void loadProjectGui(bool /*isAutosave*/, boost::archive::binary_iarchive & /*archive*/) const + { + } + virtual void saveProjectGui(boost::archive::xml_oarchive & /*archive*/) { } + virtual void saveProjectGui(boost::archive::binary_oarchive & /*archive*/) + { + } + virtual void setupViewersForViews(const std::vector& /*viewNames*/) { } diff --git a/Engine/EngineFwd.h b/Engine/EngineFwd.h index 55f1bf6fbb..28d73dccf7 100644 --- a/Engine/EngineFwd.h +++ b/Engine/EngineFwd.h @@ -39,6 +39,8 @@ namespace boost { namespace archive { class xml_iarchive; class xml_oarchive; +class binary_iarchive; +class binary_oarchive; } namespace serialization { class access; diff --git a/Engine/Project.cpp b/Engine/Project.cpp index cc6c75cf9d..b852190d6c 100644 --- a/Engine/Project.cpp +++ b/Engine/Project.cpp @@ -287,11 +287,6 @@ Project::loadProjectInternal(const QString & path, } bool ret = false; - FStreamsSupport::ifstream ifile; - FStreamsSupport::open( &ifile, filePath.toStdString() ); - if (!ifile) { - throw std::runtime_error( tr("Failed to open %1").arg(filePath).toStdString() ); - } if ( (NATRON_VERSION_MAJOR == 1) && (NATRON_VERSION_MINOR == 0) && (NATRON_VERSION_REVISION == 0) ) { ///Try to determine if the project was made during Natron v1.0.0 - RC2 or RC3 to detect a bug we introduced at that time @@ -315,21 +310,28 @@ Project::loadProjectInternal(const QString & path, } LoadProjectSplashScreen_RAII __raii_splashscreen__(getApp(), name); + bool xml_loaded = false; try { - bool bgProject; - boost::archive::xml_iarchive iArchive(ifile); - { - FlagSetter __raii_loadingProjectInternal__(true, &_imp->isLoadingProjectInternal, &_imp->isLoadingProjectMutex); + // XML loading tests first, then binary loading + FStreamsSupport::ifstream ifile; + FStreamsSupport::open( &ifile, filePath.toStdString()); + if (ifile) { + bool bgProject; + boost::archive::xml_iarchive iArchive(ifile); + { + FlagSetter __raii_loadingProjectInternal__(true, &_imp->isLoadingProjectInternal, &_imp->isLoadingProjectMutex); + + iArchive >> boost::serialization::make_nvp("Background_project", bgProject); + ProjectSerialization projectSerializationObj( getApp() ); + iArchive >> boost::serialization::make_nvp("Project", projectSerializationObj); + ret = load(projectSerializationObj, name, path, mustSave); + } // __raii_loadingProjectInternal__ - iArchive >> boost::serialization::make_nvp("Background_project", bgProject); - ProjectSerialization projectSerializationObj( getApp() ); - iArchive >> boost::serialization::make_nvp("Project", projectSerializationObj); - ret = load(projectSerializationObj, name, path, mustSave); - } // __raii_loadingProjectInternal__ - - if (!bgProject) { - getApp()->loadProjectGui(isAutoSave, iArchive); + if (!bgProject) { + getApp()->loadProjectGui(isAutoSave, iArchive); + } + xml_loaded = true; } } catch (const std::exception &e) { const ProjectBeingLoadedInfo& pInfo = getApp()->getProjectBeingLoadedInfo(); @@ -339,7 +341,6 @@ Project::loadProjectInternal(const QString & path, QString message = tr("This project was saved with a more recent version (%1.%2.%3) of %4. Projects are not forward compatible and may only be opened in a version of %4 equal or more recent than the version that saved it.").arg(pInfo.vMajor).arg(pInfo.vMinor).arg(pInfo.vRev).arg(QString::fromUtf8(NATRON_APPLICATION_NAME)); throw std::runtime_error(message.toStdString()); } - throw std::runtime_error( tr("Unrecognized or damaged project file:").toStdString() + ' ' + e.what()); } catch (...) { const ProjectBeingLoadedInfo& pInfo = getApp()->getProjectBeingLoadedInfo(); if (pInfo.vMajor > NATRON_VERSION_MAJOR || @@ -348,7 +349,48 @@ Project::loadProjectInternal(const QString & path, QString message = tr("This project was saved with a more recent version (%1.%2.%3) of %4. Projects are not forward compatible and may only be opened in a version of %4 equal or more recent than the version that saved it.").arg(pInfo.vMajor).arg(pInfo.vMinor).arg(pInfo.vRev).arg(QString::fromUtf8(NATRON_APPLICATION_NAME)); throw std::runtime_error(message.toStdString()); } - throw std::runtime_error( tr("Unrecognized or damaged project file").toStdString() ); + } + + if (!xml_loaded){ + try { + FStreamsSupport::ifstream ifile; + FStreamsSupport::open( &ifile, filePath.toStdString(), std::ios::in | std::ios::binary ); + if (!ifile) { + throw std::runtime_error( tr("Failed to open %1").arg(filePath).toStdString() ); + } + bool bgProject; + boost::archive::binary_iarchive iArchive(ifile); + { + FlagSetter __raii_loadingProjectInternal__(true, &_imp->isLoadingProjectInternal, &_imp->isLoadingProjectMutex); + + iArchive >> boost::serialization::make_nvp("Background_project", bgProject); + ProjectSerialization projectSerializationObj( getApp() ); + iArchive >> boost::serialization::make_nvp("Project", projectSerializationObj); + ret = load(projectSerializationObj, name, path, mustSave); + } // __raii_loadingProjectInternal__ + + if (!bgProject) { + getApp()->loadProjectGui(isAutoSave, iArchive); + } + } catch (const std::exception &e) { + const ProjectBeingLoadedInfo& pInfo = getApp()->getProjectBeingLoadedInfo(); + if (pInfo.vMajor > NATRON_VERSION_MAJOR || + (pInfo.vMajor == NATRON_VERSION_MAJOR && pInfo.vMinor > NATRON_VERSION_MINOR) || + (pInfo.vMajor == NATRON_VERSION_MAJOR && pInfo.vMinor == NATRON_VERSION_MINOR && pInfo.vRev > NATRON_VERSION_REVISION)) { + QString message = tr("This project was saved with a more recent version (%1.%2.%3) of %4. Projects are not forward compatible and may only be opened in a version of %4 equal or more recent than the version that saved it.").arg(pInfo.vMajor).arg(pInfo.vMinor).arg(pInfo.vRev).arg(QString::fromUtf8(NATRON_APPLICATION_NAME)); + throw std::runtime_error(message.toStdString()); + } + throw std::runtime_error( tr("Unrecognized or damaged project file:").toStdString() + ' ' + e.what()); + } catch (...) { + const ProjectBeingLoadedInfo& pInfo = getApp()->getProjectBeingLoadedInfo(); + if (pInfo.vMajor > NATRON_VERSION_MAJOR || + (pInfo.vMajor == NATRON_VERSION_MAJOR && pInfo.vMinor > NATRON_VERSION_MINOR) || + (pInfo.vMajor == NATRON_VERSION_MAJOR && pInfo.vMinor == NATRON_VERSION_MINOR && pInfo.vRev > NATRON_VERSION_REVISION)) { + QString message = tr("This project was saved with a more recent version (%1.%2.%3) of %4. Projects are not forward compatible and may only be opened in a version of %4 equal or more recent than the version that saved it.").arg(pInfo.vMajor).arg(pInfo.vMinor).arg(pInfo.vRev).arg(QString::fromUtf8(NATRON_APPLICATION_NAME)); + throw std::runtime_error(message.toStdString()); + } + throw std::runtime_error( tr("Unrecognized or damaged project file").toStdString() ); + } } Format f; @@ -599,12 +641,6 @@ Project::saveProjectInternal(const QString & path, tmpFilename.append( QString::number( time.toMSecsSinceEpoch() ) ); { - FStreamsSupport::ofstream ofile; - FStreamsSupport::open( &ofile, tmpFilename.toStdString() ); - if (!ofile) { - throw std::runtime_error( tr("Failed to open file ").toStdString() + tmpFilename.toStdString() ); - } - ///Fix file paths before saving. QString oldProjectPath = QString::fromUtf8( _imp->getProjectPath().c_str() ); @@ -615,27 +651,62 @@ Project::saveProjectInternal(const QString & path, _imp->natronVersion->setValue( generateUserFriendlyNatronVersionName() ); } - try { - boost::archive::xml_oarchive oArchive(ofile); - bool bgProject = getApp()->isBackground(); - oArchive << boost::serialization::make_nvp("Background_project", bgProject); - ProjectSerialization projectSerializationObj( getApp() ); - save(&projectSerializationObj); - oArchive << boost::serialization::make_nvp("Project", projectSerializationObj); - if (!bgProject) { - AppInstancePtr app = getApp(); - if (app) { - app->saveProjectGui(oArchive); + if (!appPTR->getCurrentSettings()->saveAsBinary()){ + try { + FStreamsSupport::ofstream ofile; + FStreamsSupport::open( &ofile, tmpFilename.toStdString(), std::ios::out); + if (!ofile) { + throw std::runtime_error( tr("Failed to open file ").toStdString() + tmpFilename.toStdString() ); + } + boost::archive::xml_oarchive oArchive(ofile); + bool bgProject = getApp()->isBackground(); + oArchive << boost::serialization::make_nvp("Background_project", bgProject); + ProjectSerialization projectSerializationObj( getApp() ); + save(&projectSerializationObj); + oArchive << boost::serialization::make_nvp("Project", projectSerializationObj); + if (!bgProject) { + AppInstancePtr app = getApp(); + if (app) { + app->saveProjectGui(oArchive); + } + } + } catch (...) { + if (!autoSave && updateProjectProperties) { + ///Reset the old project path in case of failure. + _imp->autoSetProjectDirectory(oldProjectPath); } + throw; } - } catch (...) { - if (!autoSave && updateProjectProperties) { - ///Reset the old project path in case of failure. - _imp->autoSetProjectDirectory(oldProjectPath); + } + else + { + try { + FStreamsSupport::ofstream ofile; + FStreamsSupport::open( &ofile, tmpFilename.toStdString(), std::ios::out | std::ios::binary); + if (!ofile) { + throw std::runtime_error( tr("Failed to open file ").toStdString() + tmpFilename.toStdString() ); + } + boost::archive::binary_oarchive oArchive(ofile); + bool bgProject = getApp()->isBackground(); + oArchive << boost::serialization::make_nvp("Background_project", bgProject); + ProjectSerialization projectSerializationObj( getApp() ); + save(&projectSerializationObj); + oArchive << boost::serialization::make_nvp("Project", projectSerializationObj); + if (!bgProject) { + AppInstancePtr app = getApp(); + if (app) { + app->saveProjectGui(oArchive); + } + } + } catch (...) { + if (!autoSave && updateProjectProperties) { + ///Reset the old project path in case of failure. + _imp->autoSetProjectDirectory(oldProjectPath); + } + throw; } - throw; } - } // ofile + } if (!autoSave) { // rotate backups diff --git a/Engine/Settings.cpp b/Engine/Settings.cpp index 64b63b4c7b..31d297c202 100644 --- a/Engine/Settings.cpp +++ b/Engine/Settings.cpp @@ -207,6 +207,12 @@ Settings::initializeKnobsGeneral() "last saved), *.~2~ (third last saved).") ); _generalTab->addKnob(_saveVersions); + _saveAsBinary = AppManager::createKnob( this, tr("Save projects as binary") ); + _saveAsBinary->setName("saveAsBinary"); + _saveAsBinary->setHintToolTip( tr("When checked, %1 will save projects in a binary format which is faster to load and save. " + "When unchecked, projects will be saved in a human-readable text format. " + "Note that the text format is not guaranteed to be stable across versions of %1.").arg( QString::fromUtf8(NATRON_APPLICATION_NAME) ) ); + _hostName = AppManager::createKnob( this, tr("Appear to plug-ins as") ); _hostName->setName("pluginHostName"); _hostName->setHintToolTip( tr("%1 will appear with the name of the selected application to the OpenFX plug-ins. " @@ -1469,6 +1475,7 @@ Settings::setDefaultValues() _autoSaveUnSavedProjects->setDefaultValue(true); _autoSaveDelay->setDefaultValue(5, 0); _saveVersions->setDefaultValue(1); + _saveAsBinary->setDefaultValue(false); _hostName->setDefaultValue(0); _customHostName->setDefaultValue(NATRON_ORGANIZATION_DOMAIN_TOPLEVEL "." NATRON_ORGANIZATION_DOMAIN_SUB "." NATRON_APPLICATION_NAME); @@ -2912,6 +2919,12 @@ Settings::saveVersions() const return _saveVersions->getValue(); } +bool +Settings::saveAsBinary() const +{ + return _saveAsBinary->getValue(); +} + bool Settings::isSnapToNodeEnabled() const diff --git a/Engine/Settings.h b/Engine/Settings.h index 61fa7b582b..43b761d9dc 100644 --- a/Engine/Settings.h +++ b/Engine/Settings.h @@ -181,6 +181,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON int saveVersions() const; + bool saveAsBinary() const; + bool isSnapToNodeEnabled() const; bool isCheckForUpdatesEnabled() const; @@ -434,6 +436,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON KnobBoolPtr _autoSaveUnSavedProjects; KnobIntPtr _autoSaveDelay; KnobIntPtr _saveVersions; + KnobBoolPtr _saveAsBinary; KnobChoicePtr _hostName; KnobStringPtr _customHostName; diff --git a/Gui/Gui.h b/Gui/Gui.h index 8c0a6b52a9..310b559a6f 100644 --- a/Gui/Gui.h +++ b/Gui/Gui.h @@ -305,8 +305,10 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON static QPixmap screenShot(QWidget* w); void loadProjectGui(bool isAutosave, boost::archive::xml_iarchive & obj) const; + void loadProjectGui(bool isAutosave, boost::archive::binary_iarchive & obj) const; void saveProjectGui(boost::archive::xml_oarchive & archive); + void saveProjectGui(boost::archive::binary_oarchive & archive); void setColorPickersColor(double r, double g, double b, double a); diff --git a/Gui/Gui20.cpp b/Gui/Gui20.cpp index 1ccbb77d17..e61470c6b7 100644 --- a/Gui/Gui20.cpp +++ b/Gui/Gui20.cpp @@ -1465,6 +1465,13 @@ Gui::loadProjectGui(bool isAutosave, boost::archive::xml_iarchive & obj) const _imp->_projectGui->load(isAutosave, obj); } +void +Gui::loadProjectGui(bool isAutosave, boost::archive::binary_iarchive & obj) const +{ + assert(_imp->_projectGui); + _imp->_projectGui->load(isAutosave, obj); +} + void Gui::saveProjectGui(boost::archive::xml_oarchive & archive) { @@ -1472,6 +1479,13 @@ Gui::saveProjectGui(boost::archive::xml_oarchive & archive) _imp->_projectGui->save(archive); } +void +Gui::saveProjectGui(boost::archive::binary_oarchive & archive) +{ + assert(_imp->_projectGui); + _imp->_projectGui->save(archive); +} + bool Gui::isAboutToClose() const { diff --git a/Gui/GuiAppInstance.cpp b/Gui/GuiAppInstance.cpp index a4eddc6798..ee378b30d4 100644 --- a/Gui/GuiAppInstance.cpp +++ b/Gui/GuiAppInstance.cpp @@ -788,6 +788,12 @@ GuiAppInstance::loadProjectGui(bool isAutosave, boost::archive::xml_iarchive & a _imp->_gui->loadProjectGui(isAutosave, archive); } +void +GuiAppInstance::loadProjectGui(bool isAutosave, boost::archive::binary_iarchive & archive) const +{ + _imp->_gui->loadProjectGui(isAutosave, archive); +} + void GuiAppInstance::saveProjectGui(boost::archive::xml_oarchive & archive) { @@ -796,6 +802,14 @@ GuiAppInstance::saveProjectGui(boost::archive::xml_oarchive & archive) } } +void +GuiAppInstance::saveProjectGui(boost::archive::binary_oarchive & archive) +{ + if (_imp->_gui) { + _imp->_gui->saveProjectGui(archive); + } +} + void GuiAppInstance::setupViewersForViews(const std::vector& viewNames) { diff --git a/Gui/GuiAppInstance.h b/Gui/GuiAppInstance.h index e542fb6659..d274e81928 100644 --- a/Gui/GuiAppInstance.h +++ b/Gui/GuiAppInstance.h @@ -132,7 +132,9 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON Natron::StandardButtonEnum defaultButton, bool* stopAsking) OVERRIDE FINAL WARN_UNUSED_RETURN; virtual void loadProjectGui(bool isAutosave, boost::archive::xml_iarchive & archive) const OVERRIDE FINAL; + virtual void loadProjectGui(bool isAutosave, boost::archive::binary_iarchive & archive) const OVERRIDE FINAL; virtual void saveProjectGui(boost::archive::xml_oarchive & archive) OVERRIDE FINAL; + virtual void saveProjectGui(boost::archive::binary_oarchive & archive) OVERRIDE FINAL; virtual void notifyRenderStarted(const QString & sequenceName, int firstFrame, int lastFrame, int frameStep, bool canPause, diff --git a/Gui/ProjectGui.cpp b/Gui/ProjectGui.cpp index 0eddd2e6fd..b7e4bae636 100644 --- a/Gui/ProjectGui.cpp +++ b/Gui/ProjectGui.cpp @@ -261,10 +261,18 @@ AddFormatDialog::getFormat() const return Format(0, 0, w, h, name.toStdString(), pa); } +template +void +ProjectGui::save(boost::archive::xml_oarchive & archive) const; + +template +void +ProjectGui::save(boost::archive::binary_oarchive & archive) const; + // Version is handled in ProjectGuiSerialization -template<> +template void -ProjectGui::save(boost::archive::xml_oarchive & archive) const +ProjectGui::save(Archive & archive) const { ProjectGuiSerialization projectGuiSerializationObj; @@ -430,9 +438,15 @@ loadNodeGuiSerialization(Gui* gui, } } // loadNodeGuiSerialization -template<> +template void +ProjectGui::load(bool isAutosave, boost::archive::xml_iarchive & archive); + +template void +ProjectGui::load(bool isAutosave, boost::archive::binary_iarchive & archive); + +template void -ProjectGui::load(bool isAutosave, boost::archive::xml_iarchive & archive) +ProjectGui::load(bool isAutosave, Archive & archive) { ProjectGuiSerialization obj; diff --git a/Gui/ProjectGuiSerialization.h b/Gui/ProjectGuiSerialization.h index 5354c41ce5..ebe645455e 100644 --- a/Gui/ProjectGuiSerialization.h +++ b/Gui/ProjectGuiSerialization.h @@ -38,6 +38,8 @@ GCC_DIAG_OFF(unused-parameter) // /opt/local/include/boost/serialization/smart_cast.hpp:254:25: warning: unused parameter 'u' [-Wunused-parameter] #include #include +#include +#include #include #include // /usr/local/include/boost/serialization/shared_ptr.hpp:112:5: warning: unused typedef 'boost_static_assert_typedef_112' [-Wunused-local-typedef] From cba563c454d78ebe64a31991ea4521fa053f7b9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?cedric=20paill=C3=A9?= Date: Wed, 2 Jul 2025 14:42:40 +0200 Subject: [PATCH 09/10] Fix Python3 callbacks --- Engine/AppManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/AppManager.cpp b/Engine/AppManager.cpp index 5a2401f970..831e1dd562 100644 --- a/Engine/AppManager.cpp +++ b/Engine/AppManager.cpp @@ -4193,7 +4193,7 @@ NATRON_PYTHON_NAMESPACE::getFunctionArguments(const std::string& pyFunc, #endif std::stringstream ss; ss << "import inspect\n"; - ss << "args_spec = inspect.getargspec(" << pyFunc << ")\n"; + ss << "args_spec = inspect.getfullargspec(" << pyFunc << ")\n"; std::string script = ss.str(); std::string output; bool ok = NATRON_PYTHON_NAMESPACE::interpretPythonScript(script, error, &output); From a34f9abc289cff3b7e681c4e7026a4894eb0c91c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?cedric=20paill=C3=A9?= Date: Fri, 13 Feb 2026 17:16:59 +0100 Subject: [PATCH 10/10] Add fast app exit --- Engine/AppManager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Engine/AppManager.cpp b/Engine/AppManager.cpp index 831e1dd562..a8f742fb4a 100644 --- a/Engine/AppManager.cpp +++ b/Engine/AppManager.cpp @@ -480,6 +480,9 @@ AppManager::~AppManager() // After this line, everything is cleaned-up (should be) and the process may resume in the main and could in theory be able to re-create a new AppManager _imp->_qApp.reset(); + + // Fast cleanup + TerminateProcess(GetCurrentProcess(), 0); } class QuitInstanceArgs