Переглянути джерело

Render particles using WP shaders via CPass (#473)

* chore: base render behavior for all renderable objects

* chore: make CParticle inherit from CRenderable

* chore: extend CPass with geometry callbacks and public uniform API

* chore: use WP particle shaders via CPass instead of custom inline GLSL

* feat: add support for genericropeparticle shader

* fix: prevent near/far clipping of particles

* fix: use parsed spritesheet metadata instead of recomputing from resolution

* fix: use parsed spritesheet grid and add rope subdivision with UV features

* chore: fix the rendering of some image objects caused by the CRenderable introduction

* chore: disable formatting as upstream is having issues https://github.com/jidicula/clang-format-action/issues/267

---------

Co-authored-by: Almamu <wpengine@alma.mu>
Aziz 7 місяців тому
батько
коміт
27202ec45a

+ 12 - 12
.github/workflows/cmake.yml

@@ -20,20 +20,20 @@ on:
   workflow_dispatch:
 
 jobs:
-  formatting:
-    name: Formatting check
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v4
-      - name: Run clang-format style check for C/C++ programs.
-        uses: jidicula/clang-format-action@v4.16.0
-        with:
-          check-path: "src"
-          exclude-regex: "(src/External)"
-      - run: echo "Formatted with clang-format version ${{ steps.clang-format.outputs.clang-format-version }}"
+#  formatting:
+#    name: Formatting check
+#    runs-on: ubuntu-latest
+#    steps:
+#      - uses: actions/checkout@v4
+#      - name: Run clang-format style check for C/C++ programs.
+#        uses: jidicula/clang-format-action@v4.16.0
+#        with:
+#          check-path: "src"
+#          exclude-regex: "(src/External)"
+#      - run: echo "Formatted with clang-format version ${{ steps.clang-format.outputs.clang-format-version }}"
 
   tests:
-    needs: formatting
+    #needs: formatting
     env:
       BUILD_TYPE: Release
 

+ 2 - 0
CMakeLists.txt

@@ -387,6 +387,8 @@ set(COMMON_SOURCES
     src/WallpaperEngine/Render/CTexture.h
     src/WallpaperEngine/Render/TextureProvider.h
 
+    src/WallpaperEngine/Render/Objects/CRenderable.cpp
+    src/WallpaperEngine/Render/Objects/CRenderable.h
     src/WallpaperEngine/Render/Objects/CImage.h
     src/WallpaperEngine/Render/Objects/CImage.cpp
     src/WallpaperEngine/Render/Objects/CSound.h

+ 8 - 0
src/WallpaperEngine/Data/Model/Object.h

@@ -1,5 +1,6 @@
 #pragma once
 
+#include <optional>
 #include <string>
 #include <utility>
 #include <vector>
@@ -49,6 +50,7 @@ struct ImageEffectPassOverride {
     ComboMap combos;
     ShaderConstantMap constants;
     TextureMap textures;
+    std::optional<std::string> shaderOverride; // Overrides MaterialPass::shader when set
 };
 
 /**
@@ -495,6 +497,12 @@ struct ParticleRenderer {
     float maxLength;
     float minLength;
     float subdivision;
+    float segments;     // ropetrail: number of history segments per particle
+    float uvScale;
+    bool uvScrolling;
+    bool uvSmoothing;   // rope only: reduces flickering when lifetimes are identical
+    bool fadeAlpha;     // ropetrail: fade alpha along trail
+    bool fadeSize;      // ropetrail: fade size along trail
 };
 
 /**

+ 18 - 2
src/WallpaperEngine/Data/Parsers/ObjectParser.cpp

@@ -386,6 +386,12 @@ ParticleUniquePtr ObjectParser::parseParticle (const JSON& it, const Project& pr
 		    .maxLength = 10.0f,
 		    .minLength = 0.0f,
 		    .subdivision = 1.0f,
+		    .segments = 4.0f,
+		    .uvScale = 1.0f,
+		    .uvScrolling = false,
+		    .uvSmoothing = true,
+		    .fadeAlpha = false,
+		    .fadeSize = false,
 		}
 	    );
 	}
@@ -772,12 +778,22 @@ ParticleRenderer ObjectParser::parseParticleRenderer (const JSON& it) {
 	name = nameIt->get<std::string> ();
     }
 
+    // Renderer-type-specific defaults
+    float subdivisionDefault = (name == "rope") ? 4.0f : 1.0f;
+    float lengthDefault = (name == "ropetrail") ? 1.0f : 0.05f;
+
     return ParticleRenderer {
 	.name = name,
-	.length = it.optional ("length", 0.05f),
+	.length = it.optional ("length", lengthDefault),
 	.maxLength = it.optional ("maxlength", 10.0f),
 	.minLength = it.optional ("minlength", 0.0f),
-	.subdivision = it.optional ("subdivision", 3.0f),
+	.subdivision = it.optional ("subdivision", subdivisionDefault),
+	.segments = it.optional ("segments", 4.0f),
+	.uvScale = it.optional ("uvscale", 1.0f),
+	.uvScrolling = it.optional ("uvscrolling", false),
+	.uvSmoothing = it.optional ("uvsmoothing", true),
+	.fadeAlpha = it.optional ("fadealpha", false),
+	.fadeSize = it.optional ("fadesize", false),
     };
 }
 

+ 34 - 23
src/WallpaperEngine/Render/Objects/CImage.cpp

@@ -1,4 +1,7 @@
 #include "CImage.h"
+
+#include "CRenderable.h"
+
 #include <sstream>
 
 #include "WallpaperEngine/Data/Model/Material.h"
@@ -12,12 +15,12 @@ using namespace WallpaperEngine::Data::Parsers;
 using namespace WallpaperEngine::Data::Builders;
 
 CImage::CImage (Wallpapers::CScene& scene, const Image& image) :
-    Render::CObject (scene, image), Render::FBOProvider (&scene), m_texture (nullptr), m_sceneSpacePosition (GL_NONE),
+    CRenderable (scene, image, *image.model->material), m_sceneSpacePosition (GL_NONE),
     m_copySpacePosition (GL_NONE), m_passSpacePosition (GL_NONE), m_texcoordCopy (GL_NONE), m_texcoordPass (GL_NONE),
     m_modelViewProjectionScreen (), m_modelViewProjectionPass (glm::mat4 (1.0)), m_modelViewProjectionCopy (),
     m_modelViewProjectionScreenInverse (), m_modelViewProjectionPassInverse (glm::inverse (m_modelViewProjectionPass)),
     m_modelViewProjectionCopyInverse (), m_modelMatrix (), m_viewProjectionMatrix (), m_image (image),
-    m_material (nullptr), m_colorBlendMaterial (nullptr), m_pos (), m_animationTime (0.0), m_initialized (false) {
+    m_material (nullptr), m_colorBlendMaterial (nullptr), m_pos (), m_initialized (false) {
     // get scene width and height to calculate positions
     auto scene_width = static_cast<float> (scene.getWidth ());
     auto scene_height = static_cast<float> (scene.getHeight ());
@@ -27,18 +30,10 @@ CImage::CImage (Wallpapers::CScene& scene, const Image& image) :
     glm::vec2 size = this->getSize ();
     glm::vec3 scale = this->getImage ().scale->value->getVec3 ();
 
-    // detect texture (if any)
-
-    if (auto textures = (*this->m_image.model->material->passes.begin ())->textures; !textures.empty ()) {
-	std::string textureName = textures.begin ()->second;
+    this->detectTexture ();
 
-	if (textureName.find ("_rt_") == 0 || textureName.find ("_alias_") == 0) {
-	    this->m_texture = this->getScene ().findFBO (textureName);
-	} else {
-	    // get the first texture on the first pass (this one represents the image assigned to this object)
-	    this->m_texture = this->getContext ().resolveTexture (textureName);
-	}
-    } else {
+    // detect texture (if any)
+    if (this->m_texture == nullptr) {
 	if (this->m_image.model->solidlayer && size.x == 0.0f && size.y == 0.0f) {
 	    size.x = scene_width;
 	    size.y = scene_height;
@@ -348,12 +343,7 @@ void CImage::setup () {
 	(*first)->setBlendingMode (BlendingMode_Normal);
     }
 
-    // calculate full animation time (if any)
-    this->m_animationTime = 0.0f;
-
-    for (const auto& cur : this->getTexture ()->getFrames ()) {
-	this->m_animationTime += cur->frametime;
-    }
+    CRenderable::setup ();
 
     this->setupPasses ();
     this->m_initialized = true;
@@ -483,6 +473,31 @@ void CImage::render () {
 #endif /* DEBUG */
 }
 
+const float& CImage::getBrightness () const {
+    return this->m_image.brightness;
+}
+
+const float& CImage::getUserAlpha () const {
+    return this->m_image.alpha->value->getFloat ();
+}
+
+const float& CImage::getAlpha () const {
+    return this->m_image.alpha->value->getFloat ();
+}
+
+const glm::vec3& CImage::getColor () const {
+    return this->m_image.color->value->getVec3 ();
+}
+
+const glm::vec4& CImage::getColor4() const {
+    return this->m_image.color->value->getVec4 ();
+}
+
+const glm::vec3& CImage::getCompositeColor () const {
+    return this->m_image.color->value->getVec3 ();
+}
+
+
 void CImage::updateScreenSpacePosition () {
     // do not perform any changes to the image based on the parallax if it was explicitly disabled
     if (this->getScene ().getContext ().getApp ().getContext ().settings.mouse.disableparallax) {
@@ -501,10 +516,6 @@ void CImage::updateScreenSpacePosition () {
     );
 }
 
-std::shared_ptr<const TextureProvider> CImage::getTexture () const { return this->m_texture; }
-
-double CImage::getAnimationTime () const { return this->m_animationTime; }
-
 const Image& CImage::getImage () const { return this->m_image; }
 
 const std::vector<CEffect*>& CImage::getEffects () const { return this->m_effects; }

+ 10 - 7
src/WallpaperEngine/Render/Objects/CImage.h

@@ -1,5 +1,6 @@
 #pragma once
 
+#include "CRenderable.h"
 #include "WallpaperEngine/Render/CObject.h"
 #include "WallpaperEngine/Render/Objects/Effects/CPass.h"
 #include "WallpaperEngine/Render/Wallpapers/CScene.h"
@@ -21,13 +22,13 @@ class CPass;
 namespace WallpaperEngine::Render::Objects {
 class CEffect;
 
-class CImage final : public CObject, public FBOProvider {
+class CImage final : public CRenderable {
     friend CObject;
 
 public:
     CImage (Wallpapers::CScene& scene, const Image& image);
 
-    void setup ();
+    void setup () override;
     void render () override;
 
     [[nodiscard]] const Image& getImage () const;
@@ -40,8 +41,13 @@ public:
     [[nodiscard]] GLuint getPassSpacePosition () const;
     [[nodiscard]] GLuint getTexCoordCopy () const;
     [[nodiscard]] GLuint getTexCoordPass () const;
-    [[nodiscard]] std::shared_ptr<const TextureProvider> getTexture () const;
-    [[nodiscard]] double getAnimationTime () const;
+
+    [[nodiscard]] const float& getBrightness () const override;
+    [[nodiscard]] const float& getUserAlpha () const override;
+    [[nodiscard]] const float& getAlpha () const override;
+    [[nodiscard]] const glm::vec3& getColor () const override;
+    [[nodiscard]] const glm::vec4& getColor4 () const override;
+    [[nodiscard]] const glm::vec3& getCompositeColor () const override;
 
     /**
      * Performs a ping-pong on the available framebuffers to be able to continue rendering things to them
@@ -57,7 +63,6 @@ protected:
     void updateScreenSpacePosition ();
 
 private:
-    std::shared_ptr<const TextureProvider> m_texture = nullptr;
     GLuint m_sceneSpacePosition;
     GLuint m_copySpacePosition;
     GLuint m_passSpacePosition;
@@ -89,8 +94,6 @@ private:
 
     glm::vec4 m_pos = {};
 
-    double m_animationTime = 0.0;
-
     bool m_initialized = false;
 
     struct {

Різницю між файлами не показано, бо вона завелика
+ 408 - 403
src/WallpaperEngine/Render/Objects/CParticle.cpp


+ 60 - 32
src/WallpaperEngine/Render/Objects/CParticle.h

@@ -1,12 +1,15 @@
 #pragma once
 
+#include "CRenderable.h"
 #include "WallpaperEngine/Data/Model/Object.h"
-#include "WallpaperEngine/Render/CObject.h"
+#include "WallpaperEngine/Render/Objects/Effects/CPass.h"
 #include "WallpaperEngine/Render/Wallpapers/CScene.h"
 
 #include <functional>
+#include <glm/mat4x4.hpp>
 #include <glm/vec3.hpp>
 #include <glm/vec4.hpp>
+#include <memory>
 #include <random>
 #include <vector>
 
@@ -101,19 +104,26 @@ using InitializerFunc = std::function<void (ParticleInstance&)>;
 using OperatorFunc = std::function<
     void (std::vector<ParticleInstance>&, uint32_t, const std::vector<ControlPointData>&, float, float)>;
 
-class CParticle final : public CObject {
+class CParticle final : public CRenderable {
     friend CObject;
 
 public:
     CParticle (Wallpapers::CScene& scene, const Particle& particle);
     ~CParticle ();
 
-    void setup ();
+    void setup () override;
     void render () override;
     void update (float dt);
 
     [[nodiscard]] const Particle& getParticle () const;
 
+    [[nodiscard]] const float& getBrightness () const override;
+    [[nodiscard]] const float& getUserAlpha () const override;
+    [[nodiscard]] const float& getAlpha () const override;
+    [[nodiscard]] const glm::vec3& getColor () const override;
+    [[nodiscard]] const glm::vec4& getColor4 () const override;
+    [[nodiscard]] const glm::vec3& getCompositeColor () const override;
+
 protected:
     void setupEmitters ();
     void setupInitializers ();
@@ -151,7 +161,11 @@ protected:
 
     // Rendering
     void renderSprites ();
-    void setupBuffers ();
+    void renderRope ();
+    void setupPass ();
+    void setupGeometryCallbacks ();
+    void setupParticleUniforms ();
+    void updateMatrices ();
 
 private:
     const Particle& m_particle;
@@ -171,31 +185,37 @@ private:
 
     double m_time { 0.0 };
 
+    // CPass-based rendering
+    Effects::CPass* m_pass { nullptr };
+    std::unique_ptr<ImageEffectPassOverride> m_passOverride;
+    std::shared_ptr<FBOProvider> m_passFBOProvider;
+    TextureMap m_passBinds;
+    GLsizei m_activeIndexCount { 0 };
+
+    // REFRACT support: copy of scene FBO to avoid read-write conflict
+    bool m_hasRefract { false };
+    std::shared_ptr<CFBO> m_refractFBO;
+
     // OpenGL buffers
     GLuint m_vao { 0 };
     GLuint m_vbo { 0 };
-    GLuint m_ebo { 0 }; // Element Buffer Object for indexed rendering
-    GLuint m_shaderProgram { 0 };
-
-    // Cached uniform locations
-    GLint m_uniformTexture { -1 };
-    GLint m_uniformHasTexture { -1 };
-    GLint m_uniformTextureFormat { -1 };
-    GLint m_uniformSpritesheetSize { -1 };
-    GLint m_uniformOverbright { -1 };
-    GLint m_uniformUseTrailRenderer { -1 };
-    GLint m_uniformPerspective { -1 };
-    GLint m_uniformTrailLength { -1 };
-    GLint m_uniformTrailMaxLength { -1 };
-    GLint m_uniformTrailMinLength { -1 };
-    GLint m_uniformTextureRatio { -1 };
-    GLint m_uniformCameraPos { -1 };
-    GLint m_uniformVelocityRotation { -1 };
-
-    // Particle material texture
-    std::shared_ptr<const TextureProvider> m_texture { nullptr };
-    Data::Model::BlendingMode m_blendingMode { Data::Model::BlendingMode_Translucent };
-    Data::Assets::TextureFormat m_textureFormat { Data::Assets::TextureFormat_ARGB8888 };
+    GLuint m_ebo { 0 };
+    GLint m_prevVAO { 0 };
+
+    // Particle-specific uniform data (stored here, pointed to by CPass)
+    glm::mat4 m_modelMatrix { 1.0f };
+    glm::mat4 m_modelMatrixInverse { 1.0f };
+    glm::mat4 m_mvpMatrix { 1.0f };
+    glm::mat4 m_mvpMatrixInverse { 1.0f };
+    glm::mat4 m_viewProjectionMatrix { 1.0f };
+    glm::vec3 m_orientationUp { 0.0f, 1.0f, 0.0f };
+    glm::vec3 m_orientationRight { 1.0f, 0.0f, 0.0f };
+    glm::vec3 m_orientationForward { 0.0f, 0.0f, 1.0f };
+    glm::vec3 m_viewUp { 0.0f, 1.0f, 0.0f };
+    glm::vec3 m_viewRight { 1.0f, 0.0f, 0.0f };
+    glm::vec3 m_eyePosition { 0.0f, 0.0f, 1000.0f };
+    glm::vec4 m_renderVar0 { 0.0f };
+    glm::vec4 m_renderVar1 { 0.0f };
 
     // Spritesheet animation data
     int m_spritesheetCols { 0 };
@@ -204,14 +224,26 @@ private:
     float m_spritesheetDuration { 1.0f };
 
     // Material shader constants
-    float m_overbright { 1.0f }; // Brightness multiplier for additive particles
+    float m_overbright { 1.0f };
+    float m_refractAmount { 0.05f }; // Default from shader annotation
 
     // Renderer configuration
     bool m_useTrailRenderer { false };
     float m_trailLength { 0.05f };
     float m_trailMaxLength { 10.0f };
     float m_trailMinLength { 0.0f };
-    int m_trailSubdivision { 3 }; // Number of segments per trail
+    // Rope renderer (rope + ropetrail both use genericropeparticle shader)
+    bool m_useRopeRenderer { false };
+    int m_ropeSubdivision { 4 };     // Catmull-Rom subdivisions between points (smoothing)
+    int m_ropeSegments { 4 };        // ropetrail: historical position snapshots per particle
+    float m_ropeUVScale { 1.0f };
+    bool m_ropeUVScrolling { false };
+    bool m_ropeUVSmoothing { true };  // rope only
+    bool m_uniformLifetimes { false }; // true when lifetime min==max (enables UV smoothing)
+
+    // Per-vertex float counts for different renderer types
+    static constexpr int SPRITE_FLOATS_PER_VERTEX = 17;
+    static constexpr int ROPE_FLOATS_PER_VERTEX = 26;
 
     // Transformed origin (screen space to centered space conversion)
     glm::vec3 m_transformedOrigin { 0.0f };
@@ -224,9 +256,5 @@ private:
     std::mt19937 m_rng;
 
     bool m_initialized { false };
-
-    // Helper methods
-    GLuint compileShader (GLenum type, const char* source);
-    GLuint createShaderProgram ();
 };
 } // namespace WallpaperEngine::Render::Objects

+ 43 - 0
src/WallpaperEngine/Render/Objects/CRenderable.cpp

@@ -0,0 +1,43 @@
+#include "CRenderable.h"
+
+#include "WallpaperEngine/Data/Model/Material.h"
+#include "WallpaperEngine/Data/Model/Object.h"
+#include "WallpaperEngine/Data/Parsers/MaterialParser.h"
+
+using namespace WallpaperEngine;
+using namespace WallpaperEngine::Render::Objects;
+using namespace WallpaperEngine::Render::Objects::Effects;
+using namespace WallpaperEngine::Data::Parsers;
+using namespace WallpaperEngine::Data::Builders;
+
+CRenderable::CRenderable (Wallpapers::CScene& scene, const Object& object, const Material& material) :
+    CObject (scene, object),
+    Render::FBOProvider (&scene),
+    m_material (material) {
+}
+
+void CRenderable::detectTexture () {
+    if (TextureMap* textures = &(*this->m_material.passes.begin ())->textures; !textures->empty ()) {
+        std::string textureName = textures->begin ()->second;
+
+        if (textureName.find ("_rt_") == 0 || textureName.find ("_alias_") == 0) {
+            this->m_texture = this->getScene ().findFBO (textureName);
+        } else {
+            this->m_texture = this->getContext ().resolveTexture (textureName);
+        }
+    }
+}
+
+void CRenderable::setup () {
+    // calculate full animation time (if any)
+    this->m_animationTime = 0.0f;
+
+    for (const auto& cur : this->getTexture ()->getFrames ()) {
+        this->m_animationTime += cur->frametime;
+    }
+}
+
+std::shared_ptr<const TextureProvider> CRenderable::getTexture () const { return this->m_texture; }
+
+
+double CRenderable::getAnimationTime () const { return this->m_animationTime; }

+ 40 - 0
src/WallpaperEngine/Render/Objects/CRenderable.h

@@ -0,0 +1,40 @@
+#pragma once
+#include "WallpaperEngine/Render/CObject.h"
+#include "WallpaperEngine/Render/FBOProvider.h"
+#include "WallpaperEngine/Render/Objects/Effects/CPass.h"
+#include "WallpaperEngine/Render/Wallpapers/CScene.h"
+
+#include "WallpaperEngine/Render/Shaders/Shader.h"
+
+using namespace WallpaperEngine;
+using namespace WallpaperEngine::Render;
+
+namespace WallpaperEngine::Render::Objects {
+class CRenderable : public CObject, public FBOProvider {
+    friend CObject;
+
+public:
+    CRenderable (Wallpapers::CScene& scene, const Object& object, const Material& material);
+
+    [[nodiscard]]std::shared_ptr<const TextureProvider> getTexture () const;
+
+    [[nodiscard]] double getAnimationTime () const;
+
+    virtual void setup ();
+
+    [[nodiscard]] virtual const float& getBrightness() const = 0;
+    [[nodiscard]] virtual const float& getUserAlpha() const = 0;
+    [[nodiscard]] virtual const float& getAlpha() const = 0;
+    [[nodiscard]] virtual const glm::vec3& getColor() const = 0;
+    [[nodiscard]] virtual const glm::vec4& getColor4() const = 0;
+    [[nodiscard]] virtual const glm::vec3& getCompositeColor() const = 0;
+
+protected:
+    void detectTexture();
+
+    double m_animationTime = 0.0;
+
+    std::shared_ptr<const TextureProvider> m_texture = nullptr;
+    const Material& m_material;
+};
+}

+ 65 - 30
src/WallpaperEngine/Render/Objects/Effects/CPass.cpp

@@ -33,12 +33,12 @@ const TextureMap DEFAULT_BINDS = {};
 const ImageEffectPassOverride DEFAULT_OVERRIDE = {};
 
 CPass::CPass (
-    CImage& image, std::shared_ptr<const FBOProvider> fboProvider, const MaterialPass& pass,
+    CRenderable& renderable, std::shared_ptr<const FBOProvider> fboProvider, const MaterialPass& pass,
     std::optional<std::reference_wrapper<const ImageEffectPassOverride>> override,
     std::optional<std::reference_wrapper<const TextureMap>> binds,
     std::optional<std::reference_wrapper<std::string>> target
 ) :
-    Helpers::ContextAware (image), m_image (image), m_fboProvider (std::move (fboProvider)), m_pass (pass),
+    Helpers::ContextAware (renderable), m_renderable (renderable), m_fboProvider (std::move (fboProvider)), m_pass (pass),
     m_binds (binds.has_value () ? binds.value ().get () : DEFAULT_BINDS),
     m_override (override.has_value () ? override.value ().get () : DEFAULT_OVERRIDE), m_target (target),
     m_blendingmode (pass.blending) {
@@ -153,7 +153,7 @@ void CPass::setupRenderTexture () {
     if (texture->isAnimated ()) {
 	// calculate current texture and frame
 	double currentRenderTime = fmod (
-	    static_cast<double> (this->getContext ().getDriver ().getRenderTime ()), this->m_image.getAnimationTime ()
+	    static_cast<double> (this->getContext ().getDriver ().getRenderTime ()), this->m_renderable.getAnimationTime ()
 	);
 
 	for (const auto& frameCur : texture->getFrames ()) {
@@ -278,6 +278,11 @@ void CPass::setupRenderUniforms () {
 }
 
 void CPass::setupRenderAttributes () const {
+    if (this->m_setupAttribsCallback) {
+	this->m_setupAttribsCallback ();
+	return;
+    }
+
     for (const auto& cur : this->m_attribs) {
 	glEnableVertexAttribArray (cur->id);
 	glBindBuffer (GL_ARRAY_BUFFER, *cur->value);
@@ -286,7 +291,7 @@ void CPass::setupRenderAttributes () const {
 #if !NDEBUG
 	glObjectLabel (
 	    GL_BUFFER, *cur->value, -1,
-	    ("Image " + std::to_string (this->m_image.getId ()) + " Pass " + this->m_pass.shader + " " + cur->name)
+	    ("Image " + std::to_string (this->m_renderable.getId ()) + " Pass " + this->m_pass.shader + " " + cur->name)
 		.c_str ()
 	);
 #endif /* DEBUG */
@@ -294,15 +299,24 @@ void CPass::setupRenderAttributes () const {
 }
 
 void CPass::renderGeometry () const {
+    if (this->m_drawGeometryCallback) {
+	this->m_drawGeometryCallback ();
+	return;
+    }
+
     // start actual rendering now
     glBindBuffer (GL_ARRAY_BUFFER, this->a_Position);
     glDrawArrays (GL_TRIANGLES, 0, 6);
 }
 
 void CPass::cleanupRenderSetup () {
-    // disable vertex attribs array and textures
-    for (const auto& cur : this->m_attribs) {
-	glDisableVertexAttribArray (cur->id);
+    if (this->m_cleanupAttribsCallback) {
+	this->m_cleanupAttribsCallback ();
+    } else {
+	// disable vertex attribs array
+	for (const auto& cur : this->m_attribs) {
+	    glDisableVertexAttribArray (cur->id);
+	}
     }
 
     // unbind all the used textures
@@ -328,7 +342,7 @@ void CPass::render () {
 
 std::shared_ptr<const FBOProvider> CPass::getFBOProvider () const { return this->m_fboProvider; }
 
-const CImage& CPass::getImage () const { return this->m_image; }
+const CRenderable& CPass::getRenderable () const { return this->m_renderable; }
 
 void CPass::setDestination (std::shared_ptr<const CFBO> drawTo) { this->m_drawTo = std::move (drawTo); }
 
@@ -360,6 +374,16 @@ std::optional<std::reference_wrapper<std::string>> CPass::getTarget () const { r
 
 Render::Shaders::Shader* CPass::getShader () const { return this->m_shader; }
 
+GLuint CPass::getProgramID () const { return this->m_programID; }
+
+void CPass::setGeometryCallback (
+    GeometryCallback setupAttribs, GeometryCallback drawGeometry, GeometryCallback cleanupAttribs
+) {
+    this->m_setupAttribsCallback = std::move (setupAttribs);
+    this->m_drawGeometryCallback = std::move (drawGeometry);
+    this->m_cleanupAttribsCallback = std::move (cleanupAttribs);
+}
+
 GLuint CPass::compileShader (const char* shader, GLuint type) {
     // reserve shaders in OpenGL
     const GLuint shaderID = glCreateShader (type);
@@ -400,7 +424,7 @@ GLuint CPass::compileShader (const char* shader, GLuint type) {
 
 void CPass::setupShaders () {
     // ensure the constants are defined
-    const auto texture0 = this->m_image.getTexture ();
+    const auto texture0 = this->m_renderable.getTexture ();
 
     // copy the combos from the pass
     this->m_combos.insert (this->m_pass.combos.begin (), this->m_pass.combos.end ());
@@ -418,8 +442,12 @@ void CPass::setupShaders () {
     // TODO: REVIEW THE SHADER TEXTURES HERE, THE ONES PASSED ON TO THE SHADER SHOULD NOT BE IN THE LIST
     // TODO: USED TO BUILD THE TEXTURES LATER
     // use the combos copied from the pass so it includes the texture format
+    const std::string& shaderName = this->m_override.shaderOverride.has_value ()
+	? this->m_override.shaderOverride.value ()
+	: this->m_pass.shader;
+
     this->m_shader = new Render::Shaders::Shader (
-	this->m_image.getAssetLocator (), this->m_pass.shader, this->m_combos, this->m_override.combos,
+	this->m_renderable.getAssetLocator (), shaderName, this->m_combos, this->m_override.combos,
 	this->m_pass.textures, this->m_override.textures, this->m_override.constants
     );
 
@@ -462,9 +490,9 @@ void CPass::setupShaders () {
     }
 
 #if !NDEBUG
-    glObjectLabel (GL_PROGRAM, this->m_programID, -1, this->m_pass.shader.c_str ());
-    glObjectLabel (GL_SHADER, vertexShaderID, -1, (this->m_pass.shader + ".vert").c_str ());
-    glObjectLabel (GL_SHADER, fragmentShaderID, -1, (this->m_pass.shader + ".frag").c_str ());
+    glObjectLabel (GL_PROGRAM, this->m_programID, -1, shaderName.c_str ());
+    glObjectLabel (GL_SHADER, vertexShaderID, -1, (shaderName + ".vert").c_str ());
+    glObjectLabel (GL_SHADER, fragmentShaderID, -1, (shaderName + ".frag").c_str ());
 #endif /* DEBUG */
 
     // after being liked shaders can be dettached and deleted
@@ -566,7 +594,7 @@ void CPass::setupTextureUniforms () {
     }
 
     // resolve the main texture
-    std::shared_ptr<const TextureProvider> texture = this->resolveTexture (this->m_image.getTexture (), 0);
+    std::shared_ptr<const TextureProvider> texture = this->resolveTexture (this->m_renderable.getTexture (), 0);
     // register all the texture uniforms with correct values
     this->addUniform ("g_Texture0", 0);
     this->addUniform ("g_Texture1", 1);
@@ -591,22 +619,22 @@ void CPass::setupTextureUniforms () {
 void CPass::setupUniforms () {
     this->setupTextureUniforms ();
 
-    const auto& image = this->m_image.getImage ();
-    const auto& scene = this->m_image.getScene ();
-    const auto& sceneData = this->m_image.getScene ().getScene ();
-    const auto& recorder = this->m_image.getScene ().getAudioContext ().getRecorder ();
+    const auto& renderable = this->m_renderable;
+    const auto& scene = this->m_renderable.getScene ();
+    const auto& sceneData = this->m_renderable.getScene ().getScene ();
+    const auto& recorder = this->m_renderable.getScene ().getAudioContext ().getRecorder ();
 
     // lighting variables
     this->addUniform ("g_LightAmbientColor", sceneData.colors.ambient);
     this->addUniform ("g_LightSkylightColor", sceneData.colors.skylight);
     // register variables like brightness and alpha with some default value
-    this->addUniform ("g_Brightness", image.brightness);
-    this->addUniform ("g_UserAlpha", image.alpha->value->getFloat ());
-    this->addUniform ("g_Alpha", image.alpha->value->getFloat ());
-    this->addUniform ("g_Color", image.color->value->getVec3 ());
-    this->addUniform ("g_Color4", image.color->value->getVec4 ());
+    this->addUniform ("g_Brightness", renderable.getBrightness ());
+    this->addUniform ("g_UserAlpha", renderable.getUserAlpha ());
+    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", image.color->value->getVec3 ());
+    this->addUniform ("g_CompositeColor", renderable.getCompositeColor ());
     // add some external variables
     this->addUniform ("g_Time", &g_Time);
     this->addUniform ("g_Daytime", &g_Daytime);
@@ -715,20 +743,27 @@ void CPass::setupShaderVariables () {
 	}
     }
 
-    // find variables in the shaders and set the value with the constants if possible
-    for (const auto& [name, value] : this->m_override.constants) {
+    // apply material pass constants (e.g. constantshadervalues from the material JSON)
+    for (const auto& [name, value] : this->m_pass.constants) {
 	const auto [vertex, fragment] = this->m_shader->findParameter (name);
 
-	// variable not found, can be ignored
 	if (vertex == nullptr && fragment == nullptr) {
 	    continue;
 	}
 
-	// get one instance of it
 	ShaderVariable* var = vertex == nullptr ? fragment : vertex;
+	this->addUniform (var, value->value.get ());
+    }
+
+    // apply override constants (highest priority, overrides both defaults and pass constants)
+    for (const auto& [name, value] : this->m_override.constants) {
+	const auto [vertex, fragment] = this->m_shader->findParameter (name);
+
+	if (vertex == nullptr && fragment == nullptr) {
+	    continue;
+	}
 
-	// this takes care of all possible casts, even invalid ones, which will use whatever default behaviour
-	// of the underlying CDynamicValue used for the value
+	ShaderVariable* var = vertex == nullptr ? fragment : vertex;
 	this->addUniform (var, value->value.get ());
     }
 }

+ 23 - 8
src/WallpaperEngine/Render/Objects/Effects/CPass.h

@@ -1,5 +1,6 @@
 #pragma once
 
+#include <functional>
 #include <glm/gtc/type_ptr.hpp>
 #include <utility>
 
@@ -12,7 +13,7 @@
 #include "WallpaperEngine/Render/Shaders/Variables/ShaderVariable.h"
 
 namespace WallpaperEngine::Render::Objects {
-class CImage;
+class CRenderable;
 }
 
 namespace WallpaperEngine::Render::Objects::Effects {
@@ -23,7 +24,7 @@ using namespace WallpaperEngine::Data::Model;
 class CPass final : public Helpers::ContextAware {
 public:
     CPass (
-	CImage& image, std::shared_ptr<const FBOProvider> fboProvider, const MaterialPass& pass,
+	CRenderable& renderable, std::shared_ptr<const FBOProvider> fboProvider, const MaterialPass& pass,
 	std::optional<std::reference_wrapper<const ImageEffectPassOverride>> override,
 	std::optional<std::reference_wrapper<const TextureMap>> binds,
 	std::optional<std::reference_wrapper<std::string>> target
@@ -44,10 +45,23 @@ public:
     [[nodiscard]] std::shared_ptr<const CFBO> resolveFBO (const std::string& name) const;
 
     [[nodiscard]] std::shared_ptr<const FBOProvider> getFBOProvider () const;
-    [[nodiscard]] const CImage& getImage () const;
+    [[nodiscard]] const CRenderable& getRenderable () const;
     [[nodiscard]] const MaterialPass& getPass () const;
     [[nodiscard]] std::optional<std::reference_wrapper<std::string>> getTarget () const;
     [[nodiscard]] Render::Shaders::Shader* getShader () const;
+    [[nodiscard]] GLuint getProgramID () const;
+
+    // Custom geometry rendering support (for particles, etc.)
+    using GeometryCallback = std::function<void ()>;
+    void setGeometryCallback (
+	GeometryCallback setupAttribs, GeometryCallback drawGeometry, GeometryCallback cleanupAttribs
+    );
+
+    // Public uniform setters for external callers (pointer-based, updated per-frame)
+    void addUniform (const std::string& name, const float* value, int count = 1);
+    void addUniform (const std::string& name, const glm::vec3* value);
+    void addUniform (const std::string& name, const glm::vec4* value);
+    void addUniform (const std::string& name, const glm::mat4* value);
 
 private:
     enum UniformType {
@@ -115,12 +129,8 @@ private:
     void addUniform (const std::string& name, glm::mat4 value);
     void addUniform (const std::string& name, const int* value, int count = 1);
     void addUniform (const std::string& name, const double* value, int count = 1);
-    void addUniform (const std::string& name, const float* value, int count = 1);
     void addUniform (const std::string& name, const glm::vec2* value);
-    void addUniform (const std::string& name, const glm::vec3* value);
-    void addUniform (const std::string& name, const glm::vec4* value);
     void addUniform (const std::string& name, const glm::mat3* value);
-    void addUniform (const std::string& name, const glm::mat4* value);
     void addUniform (const std::string& name, const int** value);
     void addUniform (const std::string& name, const double** value);
     void addUniform (const std::string& name, const float** value);
@@ -146,7 +156,7 @@ private:
 	std::shared_ptr<const TextureProvider> previous = nullptr
     );
 
-    CImage& m_image;
+    CRenderable& m_renderable;
     std::shared_ptr<const FBOProvider> m_fboProvider;
     const MaterialPass& m_pass;
     const TextureMap& m_binds;
@@ -180,5 +190,10 @@ private:
     GLint g_Texture0Translation;
     GLuint a_TexCoord;
     GLuint a_Position;
+
+    // Custom geometry callbacks (for particles, etc.)
+    GeometryCallback m_setupAttribsCallback;
+    GeometryCallback m_drawGeometryCallback;
+    GeometryCallback m_cleanupAttribsCallback;
 };
 } // namespace WallpaperEngine::Render::Objects::Effects

+ 5 - 1
src/WallpaperEngine/Render/Shaders/ShaderUnit.cpp

@@ -282,7 +282,11 @@ void ShaderUnit::preprocessIncludes () {
 	    ifdefStack.pop ();
 
 	    if (latest > stackStart && latest <= current) {
-		latest = this->m_preprocessed.find ('\n', current);
+		// The insertion point is inside a conditional block.
+		// Move to BEFORE the #if so includes are available to all branches
+		// (e.g. genericropeparticle.vert has #if GS_ENABLED wrapping two main() functions).
+		size_t beforeIfdef = this->m_preprocessed.rfind ('\n', stackStart);
+		latest = (beforeIfdef != std::string::npos) ? beforeIfdef : 0;
 	    }
 	}
 

Деякі файли не було показано, через те що забагато файлів було змінено