소스 검색

Improve render effect compatibility (#567)

Co-authored-by: Gaiser147 <Gaiser147@users.noreply.github.com>
Graiser147 4 달 전
부모
커밋
095c16c8e0

+ 40 - 0
src/WallpaperEngine/Application/ApplicationContext.cpp

@@ -9,6 +9,8 @@
 #include <cstring>
 #include <fstream>
 #include <iostream>
+#include <optional>
+#include <stdexcept>
 #include <string_view>
 
 #include <argparse/argparse.hpp>
@@ -509,6 +511,44 @@ void ApplicationContext::loadSettingsFromArgv () {
 	.flag ()
 	.store_into (this->settings.general.dumpStructure);
 
+    debuggingGroup.add_argument ("--render-debug")
+	.help (
+		    "Scene render debug mode: base-only, no-solid-final, pass-log, object=<id>, skip-object=<id>, or skip-effect=<id>. Can be repeated."
+	)
+	.action ([this] (const std::string& value) -> void {
+	    const auto parseDebugId = [&value] (const std::string& prefix) -> std::optional<int> {
+		try {
+		    return std::stoi (value.substr (prefix.length ()));
+		} catch (const std::invalid_argument&) {
+		    sLog.exception ("Invalid numeric value for --render-debug ", value);
+		} catch (const std::out_of_range&) {
+		    sLog.exception ("Out-of-range numeric value for --render-debug ", value);
+		}
+		return std::nullopt;
+	    };
+
+	    if (value == "base-only") {
+		this->settings.render.debug.baseOnly = true;
+	    } else if (value == "no-solid-final") {
+		this->settings.render.debug.noSolidFinal = true;
+	    } else if (value == "pass-log") {
+		this->settings.render.debug.passLog = true;
+	    } else if (value.rfind ("object=", 0) == 0) {
+		this->settings.render.debug.objectFilter = parseDebugId ("object=");
+	    } else if (value.rfind ("skip-object=", 0) == 0) {
+		if (const auto id = parseDebugId ("skip-object="); id.has_value ()) {
+		    this->settings.render.debug.skipObjects.emplace_back (*id);
+		}
+	    } else if (value.rfind ("skip-effect=", 0) == 0) {
+		if (const auto id = parseDebugId ("skip-effect="); id.has_value ()) {
+		    this->settings.render.debug.skipEffects.emplace_back (*id);
+		}
+	    } else {
+		sLog.exception ("Invalid render debug mode: ", value);
+	    }
+	})
+	.append ();
+
     program.add_epilog (
 	"Usage examples:\n"
 	"  linux-wallpaperengine --screen-root HDMI-1 --bg 2317494988 --scaling fill --clamp border\n"

+ 17 - 0
src/WallpaperEngine/Application/ApplicationContext.h

@@ -103,6 +103,15 @@ public:
 	     * Example: "firefox" will match "org.mozilla.firefox".
 	     */
 	    std::vector<std::string> fullscreenPauseIgnoreAppIds;
+	    /** Render debugging switches for scene compatibility work */
+	    struct {
+		bool baseOnly;
+		bool noSolidFinal;
+		bool passLog;
+		std::optional<int> objectFilter;
+		std::vector<int> skipObjects;
+		std::vector<int> skipEffects;
+	    } debug;
 
 	    struct {
 		/** The window size used in explicit window */
@@ -166,6 +175,14 @@ public:
             .pauseOnFullscreen = true,
             .pauseOnFullscreenOnlyWhenActive = false,
             .fullscreenPauseIgnoreAppIds = {},
+            .debug = {
+                .baseOnly = false,
+                .noSolidFinal = false,
+	                .passLog = false,
+	                .objectFilter = std::nullopt,
+	                .skipObjects = {},
+	                .skipEffects = {},
+	            },
             .window = {
                 .geometry = {},
                 .clamp = TextureFlags_ClampUVs,

+ 7 - 2
src/WallpaperEngine/Render/CFBO.cpp

@@ -54,8 +54,13 @@ CFBO::CFBO (
 	sLog.exception ("Framebuffers are not properly set");
     }
 
-    // clear the framebuffer
+    // Layer framebuffers must start transparent. The scene clear color is often opaque,
+    // and using it here makes empty layer areas render as solid rectangles.
+    GLfloat previousClearColor[4] = {};
+    glGetFloatv (GL_COLOR_CLEAR_VALUE, previousClearColor);
+    glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
     glClear (GL_COLOR_BUFFER_BIT);
+    glClearColor (previousClearColor[0], previousClearColor[1], previousClearColor[2], previousClearColor[3]);
 
     this->m_resolution = { textureWidth, textureHeight, realWidth, realHeight };
 
@@ -126,4 +131,4 @@ float CFBO::getSpritesheetDuration () const {
 
 void CFBO::incrementUsageCount () const { }
 void CFBO::decrementUsageCount () const { }
-void CFBO::update () const { }
+void CFBO::update () const { }

+ 166 - 62
src/WallpaperEngine/Render/Objects/Effects/CPass.cpp

@@ -32,6 +32,16 @@ extern float g_Daytime;
 const TextureMap DEFAULT_BINDS = {};
 const ImageEffectPassOverride DEFAULT_OVERRIDE = {};
 
+namespace {
+std::string textureSizeLabel (const std::shared_ptr<const TextureProvider>& texture) {
+    if (texture == nullptr) {
+	return "<null>";
+    }
+
+    return std::to_string (texture->getRealWidth ()) + "x" + std::to_string (texture->getRealHeight ());
+}
+}
+
 CPass::CPass (
     CRenderable& renderable, std::shared_ptr<const FBOProvider> fboProvider, const MaterialPass& pass,
     std::optional<std::reference_wrapper<const ImageEffectPassOverride>> override,
@@ -83,7 +93,7 @@ std::shared_ptr<const TextureProvider> CPass::resolveTexture (
 
     // a bind named "previous" is just another way of telling it to use whatever texture there was already
     if (it->second == "previous") {
-	return previous ?: expected;
+	return this->m_previousInput ?: (previous ?: expected);
     }
 
     // the bind actually has a name, search the FBO in the effect and return it
@@ -129,6 +139,7 @@ void CPass::setupRenderFramebuffer () const {
     switch (this->m_pass.depthtest) {
 	case DepthtestMode_Enabled:
 	    glEnable (GL_DEPTH_TEST);
+	    glDepthFunc (GL_LEQUAL);
 	    break;
 	case DepthtestMode_Disabled:
 	default:
@@ -163,65 +174,98 @@ void CPass::setupRenderTexture () {
     // use the shader we have registered
     glUseProgram (this->m_programID);
 
-    // maybe we can do this when setting the texture?
-    auto texture = this->resolveTexture (this->m_input, 0, this->m_input);
+    auto texture0 = this->resolveTexture0 ();
+    const auto animation = this->resolveTextureAnimationState (texture0);
 
-    uint32_t currentTexture = 0;
-    glm::vec2 translation = { 0.0f, 0.0f };
-    glm::vec4 rotation = { 0.0f, 0.0f, 0.0f, 0.0f };
+    this->bindTextureUnit (0, texture0, animation.currentTexture);
+    this->bindTextureOverrides (animation.currentTexture, texture0);
 
-    if (texture->isAnimated ()) {
-	// calculate current texture and frame
-	double currentRenderTime = fmod (
-	    static_cast<double> (this->getContext ().getDriver ().getRenderTime ()),
-	    this->m_renderable.getAnimationTime ()
+    if (texture0 != nullptr) {
+	this->m_texture0Resolution = *texture0->getResolution ();
+    }
+
+    // used in animations when one of the frames is vertical instead of horizontal
+    // rotation with translation = origin and end of the image to display
+    if (this->g_Texture0Rotation != -1) {
+	glUniform4f (
+	    this->g_Texture0Rotation, animation.rotation.x, animation.rotation.y, animation.rotation.z,
+	    animation.rotation.w
 	);
+    }
+    // this actually picks the origin point of the image from the atlast
+    if (this->g_Texture0Translation != -1) {
+	glUniform2f (this->g_Texture0Translation, animation.translation.x, animation.translation.y);
+    }
+}
 
-	for (const auto& frameCur : texture->getFrames ()) {
-	    currentRenderTime -= frameCur->frametime;
+std::shared_ptr<const TextureProvider> CPass::resolveTexture0 () {
+    auto texture0 = this->resolveTexture (this->m_input, 0, this->m_input);
+    if (const auto texture0Override = this->m_textures.find (0); texture0Override != this->m_textures.end ()) {
+	texture0 = texture0Override->second == nullptr ? (this->m_previousInput ?: this->m_input) : texture0Override->second;
+    }
 
-	    if (currentRenderTime <= 0.0f) {
-		// frame found, store coordinates and done
-		currentTexture = frameCur->frameNumber;
+    return texture0;
+}
 
-		translation.x = frameCur->x / texture->getTextureWidth (currentTexture);
-		translation.y = frameCur->y / texture->getTextureHeight (currentTexture);
+CPass::TextureAnimationState CPass::resolveTextureAnimationState (
+    const std::shared_ptr<const TextureProvider>& texture
+) const {
+    TextureAnimationState state;
 
-		rotation.x = frameCur->width1 / static_cast<float> (texture->getTextureWidth (currentTexture));
-		rotation.y = frameCur->width2 / static_cast<float> (texture->getTextureWidth (currentTexture));
-		rotation.z = frameCur->height2 / static_cast<float> (texture->getTextureHeight (currentTexture));
-		rotation.w = frameCur->height1 / static_cast<float> (texture->getTextureHeight (currentTexture));
-		break;
-	    }
-	}
+    if (texture == nullptr || !texture->isAnimated ()) {
+	return state;
     }
 
-    // first texture is a bit special as we have to take what comes from the chain first
-    glActiveTexture (GL_TEXTURE0);
-    glBindTexture (GL_TEXTURE_2D, texture->getTextureID (currentTexture));
+    double currentRenderTime = fmod (
+	static_cast<double> (this->getContext ().getDriver ().getRenderTime ()),
+	this->m_renderable.getAnimationTime ()
+    );
 
-    // continue on the map from the second texture
-    if (!this->m_textures.empty ()) {
-	for (const auto& [index, expectedTexture] : this->m_textures) {
-	    if (expectedTexture == nullptr) {
-		texture = this->m_input;
-	    } else {
-		texture = expectedTexture;
-	    }
+    for (const auto& frameCur : texture->getFrames ()) {
+	currentRenderTime -= frameCur->frametime;
 
-	    glActiveTexture (GL_TEXTURE0 + index);
-	    glBindTexture (GL_TEXTURE_2D, texture->getTextureID (0));
+	if (currentRenderTime > 0.0f) {
+	    continue;
 	}
+
+	state.currentTexture = frameCur->frameNumber;
+	state.translation.x = frameCur->x / texture->getTextureWidth (state.currentTexture);
+	state.translation.y = frameCur->y / texture->getTextureHeight (state.currentTexture);
+
+	state.rotation.x = frameCur->width1 / static_cast<float> (texture->getTextureWidth (state.currentTexture));
+	state.rotation.y = frameCur->width2 / static_cast<float> (texture->getTextureWidth (state.currentTexture));
+	state.rotation.z = frameCur->height2 / static_cast<float> (texture->getTextureHeight (state.currentTexture));
+	state.rotation.w = frameCur->height1 / static_cast<float> (texture->getTextureHeight (state.currentTexture));
+	break;
     }
 
-    // used in animations when one of the frames is vertical instead of horizontal
-    // rotation with translation = origin and end of the image to display
-    if (this->g_Texture0Rotation != -1) {
-	glUniform4f (this->g_Texture0Rotation, rotation.x, rotation.y, rotation.z, rotation.w);
+    return state;
+}
+
+void CPass::bindTextureUnit (
+    int index, const std::shared_ptr<const TextureProvider>& texture, uint32_t frame
+) const {
+    if (texture == nullptr) {
+	return;
     }
-    // this actually picks the origin point of the image from the atlast
-    if (this->g_Texture0Translation != -1) {
-	glUniform2f (this->g_Texture0Translation, translation.x, translation.y);
+
+    glActiveTexture (GL_TEXTURE0 + index);
+    glBindTexture (GL_TEXTURE_2D, texture->getTextureID (frame));
+}
+
+void CPass::bindTextureOverrides (
+    uint32_t currentTexture, std::shared_ptr<const TextureProvider>& texture0
+) const {
+    for (const auto& [index, expectedTexture] : this->m_textures) {
+	auto texture = expectedTexture == nullptr ? (this->m_previousInput ?: this->m_input) : expectedTexture;
+	if (texture == nullptr) {
+	    continue;
+	}
+
+	this->bindTextureUnit (index, texture, index == 0 ? currentTexture : 0);
+	if (index == 0) {
+	    texture0 = texture;
+	}
     }
 }
 
@@ -352,6 +396,47 @@ void CPass::cleanupRenderSetup () {
 }
 
 void CPass::render () {
+    const auto& debug = this->getContext ().getApp ().getContext ().settings.render.debug;
+    if (debug.passLog) {
+	sLog.out (
+	    "Render pass object=", this->m_renderable.getId (), " shader=", this->m_pass.shader, " target=",
+	    this->m_target.has_value () ? this->m_target.value ().get () : std::string ("<screen/local>"),
+	    " drawTo=", this->m_drawTo ? this->m_drawTo->getName () : std::string ("<null>"),
+	    " drawSize=", textureSizeLabel (this->m_drawTo), " inputSize=", textureSizeLabel (this->m_input)
+	);
+	for (const auto* uniformName : { "g_TintColor", "g_CompositeColor", "g_BlendAlpha", "g_CompositeAlpha" }) {
+	    const auto uniform = this->m_uniforms.find (uniformName);
+	    if (uniform == this->m_uniforms.end ()) {
+		continue;
+	    }
+
+	    switch (uniform->second->type) {
+		case Vector3: {
+		    const auto* v = static_cast<const glm::vec3*> (uniform->second->value);
+		    sLog.out ("  uniform ", uniformName, "=", v->x, " ", v->y, " ", v->z);
+		    break;
+		}
+		case Float: {
+		    const auto* v = static_cast<const float*> (uniform->second->value);
+		    sLog.out ("  uniform ", uniformName, "=", *v);
+		    break;
+		}
+		default:
+		    break;
+	    }
+	}
+    }
+
+    if (this->m_drawTo == nullptr) {
+	sLog.error ("Skipping render pass for object ", this->m_renderable.getId (), ": no destination FBO set");
+	return;
+    }
+
+    if (this->m_input == nullptr) {
+	sLog.error ("Skipping render pass for object ", this->m_renderable.getId (), ": no input texture set");
+	return;
+    }
+
     this->setupRenderFramebuffer ();
     this->setupRenderTexture ();
     this->setupRenderUniforms ();
@@ -369,6 +454,10 @@ void CPass::setDestination (std::shared_ptr<const CFBO> drawTo) { this->m_drawTo
 
 void CPass::setInput (std::shared_ptr<const TextureProvider> input) { this->m_input = std::move (input); }
 
+void CPass::setPreviousInput (std::shared_ptr<const TextureProvider> input) {
+    this->m_previousInput = std::move (input);
+}
+
 void CPass::setModelViewProjectionMatrix (const glm::mat4* projection) {
     this->m_modelViewProjectionMatrix = projection;
 }
@@ -466,9 +555,14 @@ void CPass::setupShaders () {
     const std::string& shaderName
 	= this->m_override.shaderOverride.has_value () ? this->m_override.shaderOverride.value () : this->m_pass.shader;
 
+    TextureMap passTextures = this->m_pass.textures;
+    for (const auto& [index, texture] : this->m_pass.usertextures) {
+	passTextures.insert_or_assign (index, texture);
+    }
+
     this->m_shader = new Render::Shaders::Shader (
 	this->m_renderable.getAssetLocator (), shaderName, this->m_combos, this->m_override.combos,
-	this->m_pass.textures, this->m_override.textures, this->m_override.constants
+	passTextures, this->m_override.textures, this->m_override.constants
     );
 
     const auto [vertex, fragment]
@@ -569,11 +663,18 @@ void CPass::setupTextureUniforms () {
     }
 
     for (const auto& [index, textureName] : this->m_pass.textures) {
-	// ignore first texture as that'll be the input of the previous pass (or the image if it's the first pass)
-	if (index == 0) {
-	    continue;
+	try {
+	    if (textureName.find ("_rt_") == 0 || textureName.find ("_alias_") == 0) {
+		this->m_textures[index] = this->resolveFBO (textureName);
+	    } else if (!textureName.empty ()) {
+		this->m_textures[index] = this->getContext ().resolveTexture (textureName);
+	    }
+	} catch (std::runtime_error& ex) {
+	    sLog.error ("Cannot resolve texture ", textureName, " for pass ", ex.what ());
 	}
+    }
 
+    for (const auto& [index, textureName] : this->m_pass.usertextures) {
 	try {
 	    if (textureName.find ("_rt_") == 0 || textureName.find ("_alias_") == 0) {
 		this->m_textures[index] = this->resolveFBO (textureName);
@@ -581,16 +682,12 @@ void CPass::setupTextureUniforms () {
 		this->m_textures[index] = this->getContext ().resolveTexture (textureName);
 	    }
 	} catch (std::runtime_error& ex) {
-	    sLog.error ("Cannot resolve texture ", textureName, " for pass ", ex.what ());
+	    sLog.error ("Cannot resolve user texture ", textureName, " for pass ", ex.what ());
 	}
     }
 
     // override any texture
     for (const auto& [index, textureName] : this->m_override.textures) {
-	if (index == 0) {
-	    continue;
-	}
-
 	try {
 	    if (textureName.find ("_rt_") == 0 || textureName.find ("_alias_") == 0) {
 		this->m_textures[index] = this->resolveFBO (textureName);
@@ -624,7 +721,9 @@ void CPass::setupTextureUniforms () {
     this->addUniform ("g_Texture5", 5);
     this->addUniform ("g_Texture6", 6);
     this->addUniform ("g_Texture7", 7);
-    this->addUniform ("g_Texture0Resolution", texture->getResolution ());
+    this->addUniform ("g_TextureReductionScale", 1.0f);
+    this->m_texture0Resolution = *texture->getResolution ();
+    this->addUniform ("g_Texture0Resolution", &this->m_texture0Resolution);
 
     for (const auto& [textureIndex, expectedTexture] : this->m_textures) {
 	std::ostringstream namestream;
@@ -634,6 +733,8 @@ void CPass::setupTextureUniforms () {
 	texture = this->resolveTexture (expectedTexture, textureIndex, texture);
 	this->addUniform (namestream.str (), texture->getResolution ());
     }
+
+    this->addUniform ("g_Texture0Resolution", &this->m_texture0Resolution);
 }
 
 void CPass::setupUniforms () {
@@ -653,15 +754,18 @@ void CPass::setupUniforms () {
     this->addUniform ("g_Alpha", renderable.getAlpha ());
     this->addUniform ("g_Color", renderable.getColor ());
     this->addUniform ("g_Color4", renderable.getColor4 ());
-    // TODO: VALIDATE THAT G_COMPOSITECOLOR REALLY COMES FROM THIS ONE
-    this->addUniform ("g_CompositeColor", renderable.getCompositeColor ());
+    if (!this->m_uniforms.contains ("g_CompositeColor")) {
+	this->addUniform ("g_CompositeColor", renderable.getCompositeColor ());
+    }
     // add some external variables
     this->addUniform ("g_Time", &g_Time);
     this->addUniform ("g_Daytime", &g_Daytime);
     // add model-view-projection matrix
     this->addUniform ("g_ModelViewProjectionMatrixInverse", &this->m_modelViewProjectionMatrixInverse);
     this->addUniform ("g_ModelViewProjectionMatrix", &this->m_modelViewProjectionMatrix);
+    this->addUniform ("g_EffectModelViewProjectionMatrix", &this->m_modelViewProjectionMatrix);
     this->addUniform ("g_ModelMatrix", &this->m_modelMatrix);
+    this->addUniform ("g_EffectModelMatrix", &this->m_modelMatrix);
     this->addUniform ("g_NormalModelMatrix", glm::identity<glm::mat3> ());
     this->addUniform ("g_ViewProjectionMatrix", &this->m_viewProjectionMatrix);
     this->addUniform ("g_PointerPosition", scene.getMousePosition ());
@@ -797,15 +901,15 @@ void CPass::addUniform (ShaderVariable* value) {
 
 void CPass::addUniform (const ShaderVariable* value, const DynamicValue* setting) {
     if (value->is<ShaderVariableFloat> ()) {
-	this->addUniform (value->getName (), setting->getFloat ());
+	this->addUniform (value->getName (), &setting->getFloat ());
     } else if (value->is<ShaderVariableInteger> ()) {
-	this->addUniform (value->getName (), setting->getInt ());
+	this->addUniform (value->getName (), &setting->getInt ());
     } else if (value->is<ShaderVariableVector2> ()) {
-	this->addUniform (value->getName (), setting->getVec2 ());
+	this->addUniform (value->getName (), &setting->getVec2 ());
     } else if (value->is<ShaderVariableVector3> ()) {
-	this->addUniform (value->getName (), setting->getVec3 ());
+	this->addUniform (value->getName (), &setting->getVec3 ());
     } else if (value->is<ShaderVariableVector4> ()) {
-	this->addUniform (value->getName (), setting->getVec4 ());
+	this->addUniform (value->getName (), &setting->getVec4 ());
     } else {
 	sLog.error ("Cannot convert setting dynamic value  to ", value->getName (), ". Using default value");
     }

+ 19 - 5
src/WallpaperEngine/Render/Objects/Effects/CPass.h

@@ -31,10 +31,11 @@ public:
     );
     ~CPass ();
 
-    void render ();
+	    void render ();
 
-    void setDestination (std::shared_ptr<const CFBO> drawTo);
-    void setInput (std::shared_ptr<const TextureProvider> input);
+	    void setDestination (std::shared_ptr<const CFBO> drawTo);
+	    void setInput (std::shared_ptr<const TextureProvider> input);
+	    void setPreviousInput (std::shared_ptr<const TextureProvider> input);
     void setTexCoord (GLuint texcoord);
     void setPosition (GLuint position);
     void setModelViewProjectionMatrix (const glm::mat4* projection);
@@ -110,6 +111,12 @@ private:
 	const GLuint* value;
     };
 
+    struct TextureAnimationState {
+	uint32_t currentTexture = 0;
+	glm::vec2 translation = { 0.0f, 0.0f };
+	glm::vec4 rotation = { 0.0f, 0.0f, 0.0f, 0.0f };
+    };
+
     static GLuint compileShader (const char* shader, GLuint type);
     void setupShaders ();
     void setupShaderVariables ();
@@ -145,6 +152,11 @@ private:
 
     void setupRenderFramebuffer () const;
     void setupRenderTexture ();
+    [[nodiscard]] std::shared_ptr<const TextureProvider> resolveTexture0 ();
+    [[nodiscard]] TextureAnimationState
+    resolveTextureAnimationState (const std::shared_ptr<const TextureProvider>& texture) const;
+    void bindTextureUnit (int index, const std::shared_ptr<const TextureProvider>& texture, uint32_t frame) const;
+    void bindTextureOverrides (uint32_t currentTexture, std::shared_ptr<const TextureProvider>& texture0) const;
     void setupRenderUniforms ();
     void setupRenderReferenceUniforms ();
     void setupRenderAttributes () const;
@@ -180,8 +192,10 @@ private:
 
     Render::Shaders::Shader* m_shader = nullptr;
 
-    std::shared_ptr<const CFBO> m_drawTo = nullptr;
-    std::shared_ptr<const TextureProvider> m_input = nullptr;
+	    std::shared_ptr<const CFBO> m_drawTo = nullptr;
+	    std::shared_ptr<const TextureProvider> m_input = nullptr;
+	    std::shared_ptr<const TextureProvider> m_previousInput = nullptr;
+	    glm::vec4 m_texture0Resolution = {};
 
     GLuint m_programID;
 

+ 74 - 22
src/WallpaperEngine/Render/Shaders/ShaderUnit.cpp

@@ -34,6 +34,12 @@
 	  "#define CAST3(x) (vec3(x))\n"                                                                               \
 	  "#define CAST4(x) (vec4(x))\n"                                                                               \
 	  "#define CAST3X3(x) (mat3(x))\n"                                                                             \
+	  "#define float2 vec2\n"                                                                                     \
+	  "#define float3 vec3\n"                                                                                     \
+	  "#define float4 vec4\n"                                                                                     \
+	  "#define int2 ivec2\n"                                                                                      \
+	  "#define int3 ivec3\n"                                                                                      \
+	  "#define int4 ivec4\n"                                                                                      \
 	  "#define saturate(x) (clamp(x, 0.0, 1.0))\n"                                                                 \
 	  "#define texSample2D texture\n"                                                                              \
 	  "#define texSample2DLod textureLod\n"                                                                        \
@@ -71,9 +77,9 @@ void ShaderUnit::preprocess () {
     this->m_preprocessed = this->m_content;
     this->m_includes = "";
 
-    this->preprocessVariables ();
     this->preprocessIncludes ();
     this->preprocessRequires ();
+    this->preprocessVariables ();
 
     // replace gl_FragColor with the equivalent
     const std::string from = "gl_FragColor";
@@ -87,9 +93,6 @@ void ShaderUnit::preprocess () {
 }
 
 void ShaderUnit::preprocessVariables () {
-    this->m_preprocessed = this->m_content;
-    this->m_includes = "";
-
     size_t start = 0, end = 0;
     while ((end = this->m_preprocessed.find ('\n', start)) != std::string::npos) {
 	// Extract a line from the string
@@ -371,16 +374,71 @@ std::string ShaderUnit::generateLightingV1 () const {
 	   "// end of generated module LightingV1\n";
 }
 
+std::string ShaderUnit::applyLinkedVaryingCompatibility (std::string source) const {
+    if (this->m_type != GLSLContext::UnitType_Vertex || this->m_link == nullptr) {
+	return source;
+    }
+
+    std::regex fragmentVec4Varying (R"(\bvarying\s+vec4\s+([A-Za-z_][A-Za-z0-9_]*)\s*;)");
+    std::smatch varyingMatch;
+    std::string linked = this->m_link->m_preprocessed;
+    size_t linkedOffset = 0;
+
+    while (std::regex_search (linked.cbegin () + linkedOffset, linked.cend (), varyingMatch, fragmentVec4Varying)) {
+	const std::string name = varyingMatch[1].str ();
+	linkedOffset += varyingMatch.position () + varyingMatch.length ();
+
+	const std::regex vertexVec2Decl ("\\bvarying\\s+vec2\\s+" + name + "\\s*;");
+	if (!std::regex_search (source, vertexVec2Decl)) {
+	    continue;
+	}
+
+	source = std::regex_replace (source, vertexVec2Decl, "varying vec4 " + name + ";");
+
+	const std::regex assignment ("(^|\\n)([ \\t]*)" + name + "\\s*=\\s*([^;\\n]+);");
+	std::smatch assignmentMatch;
+	size_t offset = 0;
+	while (std::regex_search (source.cbegin () + offset, source.cend (), assignmentMatch, assignment)) {
+	    const std::string prefix = assignmentMatch[1].str ();
+	    const std::string indent = assignmentMatch[2].str ();
+	    const std::string expression = assignmentMatch[3].str ();
+	    const std::string replacement = prefix + indent + name + " = vec4(" + expression + ", 0.0, 1.0);";
+	    const size_t position = offset + assignmentMatch.position ();
+	    source.replace (position, assignmentMatch.length (), replacement);
+	    offset = position + replacement.length ();
+	}
+    }
+
+    return source;
+}
+
+std::string ShaderUnit::applyFragmentTexCoordCompatibility (std::string source) const {
+    if (this->m_type != GLSLContext::UnitType_Fragment) {
+	return source;
+    }
+
+    const std::regex wideTexCoordDecl (R"(\bvarying\s+vec[34]\s+v_TexCoord\s*;)");
+    if (!std::regex_search (source, wideTexCoordDecl) || source.find ("v_TexCoord.xy") == std::string::npos) {
+	return source;
+    }
+
+    const std::string original = source;
+    const std::regex texCoordBeforeCast2 (R"(\bv_TexCoord\b(\s*[-+*/]\s*CAST2\s*\())");
+    source = std::regex_replace (source, texCoordBeforeCast2, "v_TexCoord.xy$1");
+
+    const std::regex cast2BeforeTexCoord (R"((CAST2\s*\([^)]+\)\s*[-+*/]\s*)\bv_TexCoord\b)");
+    source = std::regex_replace (source, cast2BeforeTexCoord, "$1v_TexCoord.xy");
+
+    if (source != original) {
+	sLog.out ("Applied fragment TexCoord vec2 compatibility in ", this->m_file);
+    }
+
+    return source;
+}
+
 void ShaderUnit::parseComboConfiguration (const std::string& content, const int defaultValue) {
     // TODO: SUPPORT REQUIRES SO WE PROPERLY FOLLOW THE REQUIRED CHAIN
-    JSON data;
-    try {
-	data = JSON::parse (content);
-    } catch (const std::exception& e) {
-	sLog.error ("Failed to parse combo configuration in shader ", this->m_file, ": ", e.what ());
-	sLog.error ("Content: ", content);
-	return;
-    }
+    const auto data = JSON::parse (content);
     const auto combo = data.require<std::string> ("combo", "cannot parse combo information");
     // ignore type as it seems to be used only on the editor
     // const auto type = data.find ("type");
@@ -415,14 +473,7 @@ void ShaderUnit::parseComboConfiguration (const std::string& content, const int
 void ShaderUnit::parseParameterConfiguration (
     const std::string& type, const std::string& name, const std::string& content
 ) {
-    JSON data;
-    try {
-	data = JSON::parse (content);
-    } catch (const std::exception& e) {
-	sLog.error ("Failed to parse parameter configuration in shader ", this->m_file, ": ", e.what ());
-	sLog.error ("Parameter: ", type, " ", name, ", content: ", content);
-	return;
-    }
+    const auto data = JSON::parse (content);
     const auto material = data.optional ("material");
     const auto defvalue = data.optional ("default");
     // auto range = data.find ("range");
@@ -649,11 +700,12 @@ const std::string& ShaderUnit::compile () {
     }
 
     // this should be the rest of the shader
-    this->m_final += this->m_preprocessed;
+    this->m_final
+	+= this->applyFragmentTexCoordCompatibility (this->applyLinkedVaryingCompatibility (this->m_preprocessed));
 
     // the pass itself handles shader compilation, the unit doesn't have enough information for this step
     return this->m_final;
 }
 
 const std::vector<Variables::ShaderVariable*>& ShaderUnit::getParameters () const { return this->m_parameters; }
-const TextureMap& ShaderUnit::getTextures () const { return this->m_defaultTextures; }
+const TextureMap& ShaderUnit::getTextures () const { return this->m_defaultTextures; }

+ 8 - 0
src/WallpaperEngine/Render/Shaders/ShaderUnit.h

@@ -96,6 +96,14 @@ private:
      * @return GLSL code defining PerformLighting_V1
      */
     [[nodiscard]] std::string generateLightingV1 () const;
+    /**
+     * Adjusts vertex varyings when a workshop shader declares a narrower vertex type than its fragment peer.
+     */
+    [[nodiscard]] std::string applyLinkedVaryingCompatibility (std::string source) const;
+    /**
+     * Adjusts fragment shaders that use wide texture coordinates as vec2 values in Wallpaper Engine effects.
+     */
+    [[nodiscard]] std::string applyFragmentTexCoordCompatibility (std::string source) const;
 
     /**
      * Parses a COMBO value to add the proper define to the code