Преглед изворни кода

chore: cleanup resources on wallpaper destruction (addresses #468)

Almamu пре 7 месеци
родитељ
комит
a9b994561e

+ 2 - 1
src/WallpaperEngine/Audio/AudioContext.cpp

@@ -4,7 +4,8 @@
 namespace WallpaperEngine::Audio {
 AudioContext::AudioContext (Drivers::AudioDriver& driver) : m_driver (driver) { }
 
-void AudioContext::addStream (AudioStream* stream) const { this->m_driver.addStream (stream); }
+int AudioContext::addStream (AudioStream* stream) const { return this->m_driver.addStream (stream); }
+void AudioContext::removeStream (int streamId) const { this->m_driver.removeStream (streamId); }
 
 AVSampleFormat AudioContext::getFormat () const { return this->m_driver.getFormat (); }
 

+ 6 - 1
src/WallpaperEngine/Audio/AudioContext.h

@@ -31,7 +31,12 @@ namespace Audio {
 	 *
 	 * @param stream
 	 */
-	void addStream (AudioStream* stream) const;
+	int addStream (AudioStream* stream) const;
+
+	/**
+	 * @param streamId The stream to stop playing
+	 */
+	void removeStream (int streamId) const;
 
 	/**
 	 * TODO: MAYBE THIS SHOULD BE OUR OWN DEFINITIONS INSTEAD OF LIBRARY SPECIFIC ONES?

+ 20 - 14
src/WallpaperEngine/Audio/AudioStream.cpp

@@ -18,7 +18,8 @@ int audio_read_thread (void* arg) {
 	sLog.exception ("Cannot create mutex for audio playback waiting");
     }
 
-    while (ret >= 0 && stream->getAudioContext ().getApplicationContext ().state.general.keepRunning) {
+    while (ret >= 0 && stream->getAudioContext ().getApplicationContext ().state.general.keepRunning
+	   && stream->isInitialized ()) {
 	// give the cpu some time to play the queued frames if there's enough info there
 	if (stream->getQueueSize () >= MAX_QUEUE_SIZE
 	    || (stream->getQueuePacketCount () > MIN_FRAMES
@@ -50,14 +51,9 @@ int audio_read_thread (void* arg) {
 	} else {
 	    av_packet_unref (packet);
 	}
-
-	if (!stream->isInitialized ()) {
-	    break;
-	}
     }
 
     // stop the audio too just in case
-    stream->stop ();
     SDL_DestroyMutex (waitMutex);
 
     return 0;
@@ -144,6 +140,21 @@ AudioStream::AudioStream (AudioContext& audioContext, AVCodecContext* context) :
 }
 
 AudioStream::~AudioStream () {
+    // stop the audio
+    this->stop ();
+
+    if (this->m_audioThread != nullptr) {
+	// wait for the thread to finish
+	SDL_WaitThread (this->m_audioThread, nullptr);
+    }
+
+    this->m_audioThread = nullptr;
+
+    if (this->m_queue != nullptr) {
+	// wait for the audio buffers to be done
+	SDL_CondWait (this->m_queue->wait, this->m_queue->mutex);
+    }
+
     if (this->m_swrctx != nullptr && swr_is_initialized (this->m_swrctx) == true) {
 	swr_close (this->m_swrctx);
     }
@@ -168,17 +179,12 @@ AudioStream::~AudioStream () {
     delete this->m_queue;
 
     if (this->m_formatContext != nullptr) {
-	avformat_close_input (&this->m_formatContext);
+	avformat_free_context (this->m_formatContext);
     }
+
     if (this->m_context != nullptr) {
 	avcodec_free_context (&this->m_context);
     }
-    if (this->m_audioStream != NO_AUDIO_STREAM) {
-	av_freep (&this->m_formatContext->streams[this->m_audioStream]);
-    }
-    if (this->m_formatContext != nullptr) {
-	avformat_free_context (this->m_formatContext);
-    }
 }
 
 void AudioStream::loadCustomContent (const char* filename) {
@@ -227,7 +233,7 @@ void AudioStream::loadCustomContent (const char* filename) {
     this->initialize ();
 
     // initialize an SDL thread to read the file
-    SDL_CreateThread (audio_read_thread, filename, this);
+    this->m_audioThread = SDL_CreateThread (audio_read_thread, filename, this);
 }
 
 void AudioStream::initialize () {

+ 3 - 1
src/WallpaperEngine/Audio/AudioStream.h

@@ -84,7 +84,7 @@ public:
      */
     [[nodiscard]] bool isRepeat () const;
     /**
-     * Stops decoding and playbak of the stream
+     * Stops decoding and playback of the stream
      */
     void stop ();
     /**
@@ -201,5 +201,7 @@ private:
 	SDL_cond* wait = nullptr;
 	SDL_cond* cond = nullptr;
     }* m_queue {};
+
+    SDL_Thread* m_audioThread = nullptr;
 };
 } // namespace WallpaperEngine::Audio

+ 7 - 1
src/WallpaperEngine/Audio/Drivers/AudioDriver.h

@@ -40,7 +40,13 @@ namespace Audio {
 	     *
 	     * @param stream
 	     */
-	    virtual void addStream (AudioStream* stream) = 0;
+	    virtual int addStream (AudioStream* stream) = 0;
+
+	    /**
+	     *
+	     * @param streamId The stream to stop playing
+	     */
+	    virtual void removeStream (int streamId) = 0;
 
 	    /**
 	     * Updates status of the different audio settings

+ 25 - 4
src/WallpaperEngine/Audio/Drivers/SDLAudioDriver.cpp

@@ -17,7 +17,9 @@ void audio_callback (void* userdata, uint8_t* streamData, int length) {
 	return;
     }
 
-    for (const auto& buffer : driver->getStreams ()) {
+    SDL_LockMutex (driver->getStreamMutex ());
+
+    for (const auto& buffer : driver->getStreams () | std::views::values) {
 	uint8_t* streamDataPointer = streamData;
 	int streamLength = length;
 
@@ -66,12 +68,17 @@ void audio_callback (void* userdata, uint8_t* streamData, int length) {
 	    buffer->audio_buf_index += len1;
 	}
     }
+
+    // TODO: DO WE NEED TO ALSO LOCK WHILE THE AUDIO IS PLAYING? OR SOMEHOW WAIT UNTIL THE STREAM IS NOT IN USE ANYMORE?
+    SDL_UnlockMutex (driver->getStreamMutex ());
 }
 
 SDLAudioDriver::SDLAudioDriver (
     Application::ApplicationContext& applicationContext, Detectors::AudioPlayingDetector& detector,
     Recorders::PlaybackRecorder& recorder
 ) : AudioDriver (applicationContext, detector, recorder), m_audioSpec () {
+    this->m_streamListMutex = SDL_CreateMutex ();
+
     if (SDL_InitSubSystem (SDL_INIT_AUDIO) < 0) {
 	sLog.error ("Cannot initialize SDL audio system, SDL_GetError: ", SDL_GetError ());
 	sLog.error ("Continuing without audio support");
@@ -111,9 +118,21 @@ SDLAudioDriver::~SDLAudioDriver () {
     SDL_QuitSubSystem (SDL_INIT_AUDIO);
 }
 
-void SDLAudioDriver::addStream (AudioStream* stream) { this->m_streams.push_back (new SDLAudioBuffer { stream }); }
+int SDLAudioDriver::addStream (AudioStream* stream) {
+    const int newStreamId = this->m_lastStreamID;
+    this->m_lastStreamID++;
+
+    SDL_LockMutex (this->m_streamListMutex);
+
+    this->m_streams.insert_or_assign (newStreamId, new SDLAudioBuffer { stream });
 
-const std::vector<SDLAudioBuffer*>& SDLAudioDriver::getStreams () { return this->m_streams; }
+    SDL_UnlockMutex (this->m_streamListMutex);
+
+    return newStreamId;
+}
+void SDLAudioDriver::removeStream (int streamId) { this->m_streams.erase (streamId); }
+
+const std::map<int, SDLAudioBuffer*>& SDLAudioDriver::getStreams () { return this->m_streams; }
 
 AVSampleFormat SDLAudioDriver::getFormat () const {
     switch (this->m_audioSpec.format) {
@@ -140,4 +159,6 @@ int SDLAudioDriver::getSampleRate () const { return this->m_audioSpec.freq; }
 
 int SDLAudioDriver::getChannels () const { return this->m_audioSpec.channels; }
 
-const SDL_AudioSpec& SDLAudioDriver::getSpec () const { return this->m_audioSpec; }
+const SDL_AudioSpec& SDLAudioDriver::getSpec () const { return this->m_audioSpec; }
+
+SDL_mutex* SDLAudioDriver::getStreamMutex () const { return this->m_streamListMutex; }

+ 11 - 3
src/WallpaperEngine/Audio/Drivers/SDLAudioDriver.h

@@ -33,11 +33,13 @@ public:
     ~SDLAudioDriver () override;
 
     /** @inheritdoc */
-    void addStream (AudioStream* stream) override;
+    int addStream (AudioStream* stream) override;
+    /** @inheritdoc */
+    void removeStream (int streamId) override;
     /**
      * @return All the registered audio streams
      */
-    const std::vector<SDLAudioBuffer*>& getStreams ();
+    const std::map<int, SDLAudioBuffer*>& getStreams ();
 
     /** @inheritdoc */
     [[nodiscard]] AVSampleFormat getFormat () const override;
@@ -50,7 +52,13 @@ public:
      */
     [[nodiscard]] const SDL_AudioSpec& getSpec () const;
 
+    [[nodiscard]] SDL_mutex* getStreamMutex () const;
+
 private:
+    /** The mutex lock used to access the stream list mutex */
+    SDL_mutex* m_streamListMutex;
+    /** The last streamID used */
+    int m_lastStreamID = 0;
     /** The device's ID */
     SDL_AudioDeviceID m_deviceID;
     /** If the driver is initialized or not */
@@ -58,6 +66,6 @@ private:
     /** The sound output configuration */
     SDL_AudioSpec m_audioSpec {};
     /** All the playable steams */
-    std::vector<SDLAudioBuffer*> m_streams {};
+    std::map<int, SDLAudioBuffer*> m_streams {};
 };
 } // namespace WallpaperEngine::Audio::Drivers

+ 4 - 4
src/WallpaperEngine/Data/Model/Object.h

@@ -497,12 +497,12 @@ struct ParticleRenderer {
     float maxLength;
     float minLength;
     float subdivision;
-    float segments;     // ropetrail: number of history segments per particle
+    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
+    bool uvSmoothing; // rope only: reduces flickering when lifetimes are identical
+    bool fadeAlpha; // ropetrail: fade alpha along trail
+    bool fadeSize; // ropetrail: fade size along trail
 };
 
 /**

+ 2 - 1
src/WallpaperEngine/Render/CObject.h

@@ -37,9 +37,10 @@ public:
     [[nodiscard]] const AssetLocator& getAssetLocator () const;
     [[nodiscard]] int getId () const;
 
+    virtual ~CObject () override = default;
+
 protected:
     CObject (Wallpapers::CScene& scene, const Object& object);
-    virtual ~CObject () override = default;
 
 private:
     Wallpapers::CScene& m_scene;

+ 19 - 1
src/WallpaperEngine/Render/CWallpaper.cpp

@@ -39,7 +39,25 @@ CWallpaper::CWallpaper (
     glBufferData (GL_ARRAY_BUFFER, sizeof (position), position, GL_STATIC_DRAW);
 }
 
-CWallpaper::~CWallpaper () = default;
+CWallpaper::~CWallpaper () {
+    // destroy shader programs
+    GLuint attachedShaders[2];
+    GLsizei attachedCount = 0;
+
+    // destroy shaders (we only attach 2 to each program)
+    glGetAttachedShaders (this->m_shader, 2, &attachedCount, attachedShaders);
+
+    for (auto i = 0; i < attachedCount; i++) {
+	glDeleteShader (attachedShaders[i]);
+    }
+
+    glDeleteProgram (this->m_shader);
+
+    // destroy used buffers
+    glDeleteBuffers (1, &this->m_texCoordBuffer);
+    glDeleteBuffers (1, &this->m_positionBuffer);
+    glDeleteVertexArrays (1, &this->m_vaoBuffer);
+}
 
 const AssetLocator& CWallpaper::getAssetLocator () const { return *this->m_wallpaperData.project.assetLocator; }
 

+ 28 - 25
src/WallpaperEngine/Render/Objects/CImage.cpp

@@ -15,12 +15,12 @@ using namespace WallpaperEngine::Data::Parsers;
 using namespace WallpaperEngine::Data::Builders;
 
 CImage::CImage (Wallpapers::CScene& scene, const Image& image) :
-    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_initialized (false) {
+    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_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 ());
@@ -210,6 +210,22 @@ CImage::CImage (Wallpapers::CScene& scene, const Image& image) :
     this->m_viewProjectionMatrix = glm::mat4 (1.0);
 }
 
+CImage::~CImage () {
+    // delete passes first as they depend on the image's data
+    for (auto* pass : this->m_passes) {
+	delete pass;
+    }
+
+    this->m_passes.clear ();
+
+    // free any gl resources
+    glDeleteBuffers (1, &this->m_sceneSpacePosition);
+    glDeleteBuffers (1, &this->m_copySpacePosition);
+    glDeleteBuffers (1, &this->m_passSpacePosition);
+    glDeleteBuffers (1, &this->m_texcoordCopy);
+    glDeleteBuffers (1, &this->m_texcoordPass);
+}
+
 void CImage::setup () {
     // do not double-init stuff, that's bad!
     if (this->m_initialized) {
@@ -473,30 +489,17 @@ 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::getBrightness () const { return this->m_image.brightness; }
 
-const float& CImage::getAlpha () const {
-    return this->m_image.alpha->value->getFloat ();
-}
+const float& CImage::getUserAlpha () const { return this->m_image.alpha->value->getFloat (); }
 
-const glm::vec3& CImage::getColor () const {
-    return this->m_image.color->value->getVec3 ();
-}
+const float& CImage::getAlpha () const { return this->m_image.alpha->value->getFloat (); }
 
-const glm::vec4& CImage::getColor4() const {
-    return this->m_image.color->value->getVec4 ();
-}
+const glm::vec3& CImage::getColor () const { return this->m_image.color->value->getVec3 (); }
 
-const glm::vec3& CImage::getCompositeColor () 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

+ 1 - 0
src/WallpaperEngine/Render/Objects/CImage.h

@@ -27,6 +27,7 @@ class CImage final : public CRenderable {
 
 public:
     CImage (Wallpapers::CScene& scene, const Image& image);
+    ~CImage () override;
 
     void setup () override;
     void render () override;

+ 72 - 42
src/WallpaperEngine/Render/Objects/CParticle.cpp

@@ -204,10 +204,11 @@ void CParticle::render () {
 	m_time = g_Time;
 	// Skip update on first frame to avoid weird initial burst
 	// This ensures all particles start from a clean state
-	if (m_useRopeRenderer)
+	if (m_useRopeRenderer) {
 	    renderRope ();
-	else
+	} else {
 	    renderSprites ();
+	}
 	return;
     }
 
@@ -224,10 +225,11 @@ void CParticle::render () {
 
     // Render particles
     if (m_particleCount > 0 && m_particle.material) {
-	if (m_useRopeRenderer)
+	if (m_useRopeRenderer) {
 	    renderRope ();
-	else
+	} else {
 	    renderSprites ();
+	}
     }
 }
 
@@ -413,7 +415,8 @@ EmitterFunc CParticle::createBoxEmitter (const ParticleEmitter& emitter) {
     return [this, emitter, transformedEmitterOrigin, controlPointIndex, rate, flippedDirections, limitOnePerFrame,
 	    randomPeriodicEmission, emissionTimer = 0.0f, elapsedTime = 0.0f, delayTimer = emitter.delay,
 	    durationTimer = 0.0f, periodicTimer = 0.0f, periodicDuration = 0.0f, periodicDelay = 0.0f, emitting = false,
-	    instantaneousEmitted = false] (std::vector<ParticleInstance>& particles, uint32_t& count, float dt) mutable {
+	    instantaneousEmitted
+	    = false] (std::vector<ParticleInstance>& particles, uint32_t& count, float dt) mutable {
 	if (count >= particles.size ()) {
 	    return;
 	}
@@ -561,9 +564,9 @@ EmitterFunc CParticle::createSphereEmitter (const ParticleEmitter& emitter) {
     bool isRope = m_useRopeRenderer;
     std::string particleName = m_particle.name;
 
-    return [this, emitter, transformedEmitterOrigin, controlPointIndex, rate, lifetime, limitOnePerFrame,
-	    isRope, particleName, emissionTimer = 0.0f, remaining = emitter.instantaneous,
-	    lastSpawnTime = -1.0f, noEmitFrames = 0u] (std::vector<ParticleInstance>& particles, uint32_t& count, float dt) mutable {
+    return [this, emitter, transformedEmitterOrigin, controlPointIndex, rate, lifetime, limitOnePerFrame, isRope,
+	    particleName, emissionTimer = 0.0f, remaining = emitter.instantaneous, lastSpawnTime = -1.0f,
+	    noEmitFrames = 0u] (std::vector<ParticleInstance>& particles, uint32_t& count, float dt) mutable {
 	if (count >= particles.size ()) {
 	    return;
 	}
@@ -841,8 +844,8 @@ InitializerFunc CParticle::createTurbulentVelocityRandomInitializer (const Turbu
     DynamicValue* rightVal = init.right->value.get ();
     DynamicValue* speedOverride = m_particle.instanceOverride.speed->value.get ();
 
-    return [this, speedMin, speedMax, offsetVal, scaleVal, forwardVal, timeScaleVal, phaseMinVal, phaseMaxVal,
-	    rightVal, speedOverride] (ParticleInstance& p) {
+    return [this, speedMin, speedMax, offsetVal, scaleVal, forwardVal, timeScaleVal, phaseMinVal, phaseMaxVal, rightVal,
+	    speedOverride] (ParticleInstance& p) {
 	// Get direction parameters
 	glm::vec3 forward = forwardVal->getVec3 ();
 	glm::vec3 right = rightVal->getVec3 ();
@@ -1672,7 +1675,7 @@ void CParticle::setupPass () {
 
     // Force texture 0 to use the input (particle texture) rather than the shader's
     // default "util/white" annotation, which would override it in setupRenderTexture()
-    m_passBinds = {{0, "previous"}};
+    m_passBinds = { { 0, "previous" } };
 
     // Check if material uses REFRACT combo
     auto refractIt = firstPass.combos.find ("REFRACT");
@@ -1696,9 +1699,7 @@ void CParticle::setupPass () {
     }
 
     // Create CPass with the WP particle shader
-    m_pass = new Effects::CPass (
-	*this, m_passFBOProvider, firstPass, *m_passOverride, m_passBinds, std::nullopt
-    );
+    m_pass = new Effects::CPass (*this, m_passFBOProvider, firstPass, *m_passOverride, m_passBinds, std::nullopt);
 
     // Set destination to scene FBO and input to particle texture
     m_pass->setDestination (getScene ().getFBO ());
@@ -1738,13 +1739,34 @@ void CParticle::setupPass () {
 	const GLint loc5 = glGetAttribLocation (program, "a_TexCoordC4");
 	const GLint loc6 = glGetAttribLocation (program, "a_Color");
 
-	if (loc0 >= 0) { glEnableVertexAttribArray (loc0); glVertexAttribPointer (loc0, 4, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof (float) * 0)); }
-	if (loc1 >= 0) { glEnableVertexAttribArray (loc1); glVertexAttribPointer (loc1, 4, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof (float) * 4)); }
-	if (loc2 >= 0) { glEnableVertexAttribArray (loc2); glVertexAttribPointer (loc2, 4, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof (float) * 8)); }
-	if (loc3 >= 0) { glEnableVertexAttribArray (loc3); glVertexAttribPointer (loc3, 4, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof (float) * 12)); }
-	if (loc4 >= 0) { glEnableVertexAttribArray (loc4); glVertexAttribPointer (loc4, 4, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof (float) * 16)); }
-	if (loc5 >= 0) { glEnableVertexAttribArray (loc5); glVertexAttribPointer (loc5, 2, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof (float) * 20)); }
-	if (loc6 >= 0) { glEnableVertexAttribArray (loc6); glVertexAttribPointer (loc6, 4, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof (float) * 22)); }
+	if (loc0 >= 0) {
+	    glEnableVertexAttribArray (loc0);
+	    glVertexAttribPointer (loc0, 4, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof (float) * 0));
+	}
+	if (loc1 >= 0) {
+	    glEnableVertexAttribArray (loc1);
+	    glVertexAttribPointer (loc1, 4, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof (float) * 4));
+	}
+	if (loc2 >= 0) {
+	    glEnableVertexAttribArray (loc2);
+	    glVertexAttribPointer (loc2, 4, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof (float) * 8));
+	}
+	if (loc3 >= 0) {
+	    glEnableVertexAttribArray (loc3);
+	    glVertexAttribPointer (loc3, 4, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof (float) * 12));
+	}
+	if (loc4 >= 0) {
+	    glEnableVertexAttribArray (loc4);
+	    glVertexAttribPointer (loc4, 4, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof (float) * 16));
+	}
+	if (loc5 >= 0) {
+	    glEnableVertexAttribArray (loc5);
+	    glVertexAttribPointer (loc5, 2, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof (float) * 20));
+	}
+	if (loc6 >= 0) {
+	    glEnableVertexAttribArray (loc6);
+	    glVertexAttribPointer (loc6, 4, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof (float) * 22));
+	}
     } else {
 	// Sprite vertex layout: 5 attributes, 17 floats/vertex, stride=68 bytes
 	// a_Position(3) + a_TexCoordVec4(4) + a_Color(4) + a_TexCoordVec4C1(4) + a_TexCoordC2(2) = 17
@@ -1756,11 +1778,26 @@ void CParticle::setupPass () {
 	const GLint loc3 = glGetAttribLocation (program, "a_TexCoordVec4C1");
 	const GLint loc4 = glGetAttribLocation (program, "a_TexCoordC2");
 
-	if (loc0 >= 0) { glEnableVertexAttribArray (loc0); glVertexAttribPointer (loc0, 3, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof (float) * 0)); }
-	if (loc1 >= 0) { glEnableVertexAttribArray (loc1); glVertexAttribPointer (loc1, 4, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof (float) * 3)); }
-	if (loc2 >= 0) { glEnableVertexAttribArray (loc2); glVertexAttribPointer (loc2, 4, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof (float) * 7)); }
-	if (loc3 >= 0) { glEnableVertexAttribArray (loc3); glVertexAttribPointer (loc3, 4, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof (float) * 11)); }
-	if (loc4 >= 0) { glEnableVertexAttribArray (loc4); glVertexAttribPointer (loc4, 2, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof (float) * 15)); }
+	if (loc0 >= 0) {
+	    glEnableVertexAttribArray (loc0);
+	    glVertexAttribPointer (loc0, 3, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof (float) * 0));
+	}
+	if (loc1 >= 0) {
+	    glEnableVertexAttribArray (loc1);
+	    glVertexAttribPointer (loc1, 4, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof (float) * 3));
+	}
+	if (loc2 >= 0) {
+	    glEnableVertexAttribArray (loc2);
+	    glVertexAttribPointer (loc2, 4, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof (float) * 7));
+	}
+	if (loc3 >= 0) {
+	    glEnableVertexAttribArray (loc3);
+	    glVertexAttribPointer (loc3, 4, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof (float) * 11));
+	}
+	if (loc4 >= 0) {
+	    glEnableVertexAttribArray (loc4);
+	    glVertexAttribPointer (loc4, 2, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof (float) * 15));
+	}
     }
 
     glBindVertexArray (prevVAO);
@@ -1827,9 +1864,8 @@ void CParticle::updateMatrices () {
 	float farz = getScene ().getCamera ().getFarZ ();
 
 	glm::mat4 perspectiveProj = glm::perspective (fov, aspect, nearz, farz);
-	glm::mat4 perspectiveView = glm::lookAt (
-	    glm::vec3 (0.0f, 0.0f, 1000.0f), glm::vec3 (0.0f, 0.0f, 0.0f), glm::vec3 (0.0f, 1.0f, 0.0f)
-	);
+	glm::mat4 perspectiveView
+	    = glm::lookAt (glm::vec3 (0.0f, 0.0f, 1000.0f), glm::vec3 (0.0f, 0.0f, 0.0f), glm::vec3 (0.0f, 1.0f, 0.0f));
 
 	m_viewProjectionMatrix = perspectiveProj * perspectiveView;
 	m_eyePosition = glm::vec3 (0.0f, 0.0f, 1000.0f);
@@ -1875,9 +1911,7 @@ void CParticle::updateMatrices () {
 		textureRatio = (h * frameHeight) / (w * frameWidth);
 	    }
 	}
-	m_renderVar1 = glm::vec4 (
-	    frameWidth, frameHeight, static_cast<float> (m_spritesheetFrames), textureRatio
-	);
+	m_renderVar1 = glm::vec4 (frameWidth, frameHeight, static_cast<float> (m_spritesheetFrames), textureRatio);
     } else {
 	// No spritesheet - texture ratio is height/width
 	float textureRatio = 1.0f;
@@ -2072,13 +2106,12 @@ void CParticle::renderRope () {
     const int subdivision = std::max (1, m_ropeSubdivision);
 
     // Catmull-Rom spline evaluation
-    auto catmullRom = [] (const glm::vec3& p0, const glm::vec3& p1,
-			  const glm::vec3& p2, const glm::vec3& p3, float t) -> glm::vec3 {
+    auto catmullRom = [] (const glm::vec3& p0, const glm::vec3& p1, const glm::vec3& p2, const glm::vec3& p3,
+			  float t) -> glm::vec3 {
 	float t2 = t * t, t3 = t2 * t;
-	return 0.5f * ((2.0f * p1) +
-		       (-p0 + p2) * t +
-		       (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * t2 +
-		       (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3);
+	return 0.5f
+	    * ((2.0f * p1) + (-p0 + p2) * t + (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * t2
+	       + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3);
     };
 
     // First pass: evaluate spline to get all interpolated points
@@ -2100,10 +2133,7 @@ void CParticle::renderRope () {
 
 	    splinePositions[idx] = catmullRom (p0.position, p1.position, p2.position, p3.position, t);
 	    splineSizes[idx] = glm::mix (p1.size, p2.size, t);
-	    splineColors[idx] = glm::mix (
-		glm::vec4 (p1.color, p1.alpha),
-		glm::vec4 (p2.color, p2.alpha), t
-	    );
+	    splineColors[idx] = glm::mix (glm::vec4 (p1.color, p1.alpha), glm::vec4 (p2.color, p2.alpha), t);
 	}
     }
     // Last point is the final particle

+ 3 - 3
src/WallpaperEngine/Render/Objects/CParticle.h

@@ -234,11 +234,11 @@ private:
     float m_trailMinLength { 0.0f };
     // 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
+    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_ropeUVSmoothing { true }; // rope only
     bool m_uniformLifetimes { false }; // true when lifetime min==max (enables UV smoothing)
 
     // Per-vertex float counts for different renderer types

+ 8 - 12
src/WallpaperEngine/Render/Objects/CRenderable.cpp

@@ -11,20 +11,17 @@ 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) {
-}
+    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;
+	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);
-        }
+	if (textureName.find ("_rt_") == 0 || textureName.find ("_alias_") == 0) {
+	    this->m_texture = this->getScene ().findFBO (textureName);
+	} else {
+	    this->m_texture = this->getContext ().resolveTexture (textureName);
+	}
     }
 }
 
@@ -33,11 +30,10 @@ void CRenderable::setup () {
     this->m_animationTime = 0.0f;
 
     for (const auto& cur : this->getTexture ()->getFrames ()) {
-        this->m_animationTime += cur->frametime;
+	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; }

+ 8 - 8
src/WallpaperEngine/Render/Objects/CRenderable.h

@@ -16,21 +16,21 @@ class CRenderable : public CObject, public FBOProvider {
 public:
     CRenderable (Wallpapers::CScene& scene, const Object& object, const Material& material);
 
-    [[nodiscard]]std::shared_ptr<const TextureProvider> getTexture () const;
+    [[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;
+    [[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();
+    void detectTexture ();
 
     double m_animationTime = 0.0;
 

+ 5 - 4
src/WallpaperEngine/Render/Objects/CSound.cpp

@@ -15,8 +15,11 @@ CSound::CSound (Wallpapers::CScene& scene, const Sound& sound) : CObject (scene,
 CSound::~CSound () {
     // free all the sound buffers and streams
     for (const auto& stream : this->m_audioStreams) {
-	delete stream;
+	this->getScene ().getAudioContext ().removeStream (stream.first);
+	delete stream.second;
     }
+
+    this->m_audioStreams.clear ();
 }
 
 void CSound::load () {
@@ -26,10 +29,8 @@ void CSound::load () {
 
 	stream->setRepeat (this->m_sound.playbackmode.has_value () && this->m_sound.playbackmode == "loop");
 
-	this->m_audioStreams.push_back (stream);
-
 	// add the stream to the context so it can be played
-	this->getScene ().getAudioContext ().addStream (stream);
+	this->m_audioStreams.insert_or_assign (this->getScene ().getAudioContext ().addStream (stream), stream);
     }
 }
 

+ 1 - 1
src/WallpaperEngine/Render/Objects/CSound.h

@@ -23,7 +23,7 @@ protected:
     void load ();
 
 private:
-    std::vector<Audio::AudioStream*> m_audioStreams = {};
+    std::map<int, Audio::AudioStream*> m_audioStreams = {};
 
     const Sound& m_sound;
 };

+ 21 - 6
src/WallpaperEngine/Render/Objects/Effects/CPass.cpp

@@ -38,13 +38,28 @@ CPass::CPass (
     std::optional<std::reference_wrapper<const TextureMap>> binds,
     std::optional<std::reference_wrapper<std::string>> target
 ) :
-    Helpers::ContextAware (renderable), m_renderable (renderable), m_fboProvider (std::move (fboProvider)), m_pass (pass),
-    m_binds (binds.has_value () ? binds.value ().get () : DEFAULT_BINDS),
+    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) {
     this->setupShaders ();
 }
 
+CPass::~CPass () {
+    // destroy shader programs
+    GLuint attachedShaders[2];
+    GLsizei attachedCount;
+
+    // destroy shaders (we only attach 2 to each program)
+    glGetAttachedShaders (this->m_programID, 2, &attachedCount, attachedShaders);
+
+    for (auto i = 0; i < attachedCount; i++) {
+	glDeleteShader (attachedShaders[i]);
+    }
+
+    glDeleteProgram (this->m_programID);
+}
+
 std::shared_ptr<const TextureProvider> CPass::resolveTexture (
     std::shared_ptr<const TextureProvider> expected, int index, std::shared_ptr<const TextureProvider> previous
 ) {
@@ -153,7 +168,8 @@ void CPass::setupRenderTexture () {
     if (texture->isAnimated ()) {
 	// calculate current texture and frame
 	double currentRenderTime = fmod (
-	    static_cast<double> (this->getContext ().getDriver ().getRenderTime ()), this->m_renderable.getAnimationTime ()
+	    static_cast<double> (this->getContext ().getDriver ().getRenderTime ()),
+	    this->m_renderable.getAnimationTime ()
 	);
 
 	for (const auto& frameCur : texture->getFrames ()) {
@@ -442,9 +458,8 @@ 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;
+    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_renderable.getAssetLocator (), shaderName, this->m_combos, this->m_override.combos,

+ 3 - 3
src/WallpaperEngine/Render/Objects/Effects/CPass.h

@@ -29,6 +29,7 @@ public:
 	std::optional<std::reference_wrapper<const TextureMap>> binds,
 	std::optional<std::reference_wrapper<std::string>> target
     );
+    ~CPass ();
 
     void render ();
 
@@ -53,9 +54,8 @@ public:
 
     // Custom geometry rendering support (for particles, etc.)
     using GeometryCallback = std::function<void ()>;
-    void setGeometryCallback (
-	GeometryCallback setupAttribs, GeometryCallback drawGeometry, GeometryCallback cleanupAttribs
-    );
+    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);

+ 12 - 0
src/WallpaperEngine/Render/Wallpapers/CScene.cpp

@@ -136,6 +136,18 @@ CScene::CScene (
     }
 }
 
+CScene::~CScene () {
+    // bloom object is in the objects list, so no need to explicitly delete it
+    this->m_bloomObject = nullptr;
+
+    for (const auto& val : this->m_objects | std::views::values) {
+	delete val;
+    }
+
+    this->m_objectsByRenderOrder.clear ();
+    this->m_objects.clear ();
+}
+
 Render::CObject* CScene::createObject (const Object& object) {
     Render::CObject* renderObject = nullptr;
 

+ 2 - 0
src/WallpaperEngine/Render/Wallpapers/CScene.h

@@ -19,6 +19,8 @@ public:
 	const WallpaperState::TextureUVsScaling& scalingMode, const uint32_t& clampMode
     );
 
+    ~CScene () override;
+
     [[nodiscard]] Camera& getCamera () const;
 
     [[nodiscard]] const Scene& getScene () const;

+ 13 - 0
src/WallpaperEngine/Render/Wallpapers/CVideo.cpp

@@ -82,6 +82,19 @@ CVideo::CVideo (
     this->setupFramebuffers ();
 }
 
+CVideo::~CVideo () {
+    // stop mpv and cleanup resources
+    if (this->m_mpvGl) {
+	mpv_render_context_free (this->m_mpvGl);
+	this->m_mpvGl = nullptr;
+    }
+
+    if (this->m_mpv) {
+	mpv_terminate_destroy (this->m_mpv);
+	this->m_mpv = nullptr;
+    }
+}
+
 void CVideo::setSize (const int width, const int height) {
     this->m_width = width > 0 ? width : this->m_width;
     this->m_height = height > 0 ? height : this->m_height;

+ 2 - 0
src/WallpaperEngine/Render/Wallpapers/CVideo.h

@@ -13,6 +13,8 @@ public:
 	const WallpaperState::TextureUVsScaling& scalingMode, const uint32_t& clampMode
     );
 
+    ~CVideo () override;
+
     const Video& getVideo () const;
 
     [[nodiscard]] int getWidth () const override;