Forráskód Böngészése

added clamping at wallpaper borders parallax, fixed dark screen on particle/bloom hotswap

UwU 1 hónapja
szülő
commit
b8a70925cd

+ 14 - 1
src/WallpaperEngine/Application/ApplicationContext.cpp

@@ -777,6 +777,17 @@ void ApplicationContext::loadSettingsFromArgv () {
 	.flag ()
 	.flag ()
 	.action ([this] (const std::string& value) -> void { this->settings.mouse.disableparallax = true; });
 	.action ([this] (const std::string& value) -> void { this->settings.mouse.disableparallax = true; });
 
 
+    configurationGroup.add_argument ("--allow-parallax-overflow")
+	.help (
+	    "Lets parallax push an image past its own edges, exposing black corners on layers too small for the "
+	    "movement. Disabled by default, which clamps parallax displacement to each image's own size; images "
+	    "whose texture tiles/repeats are always exempt from the clamp, since going past their edges is seamless"
+	)
+	.flag ()
+	.action ([this] (const std::string& value) -> void {
+	    this->settings.mouse.clampParallaxToImageSize = false;
+	});
+
     configurationGroup.add_argument ("-l", "--list-properties")
     configurationGroup.add_argument ("-l", "--list-properties")
 	.help ("List all the available properties and their configuration")
 	.help ("List all the available properties and their configuration")
 	.flag ()
 	.flag ()
@@ -875,7 +886,7 @@ void ApplicationContext::loadSettingsFromArgv () {
 
 
     debuggingGroup.add_argument ("--render-debug")
     debuggingGroup.add_argument ("--render-debug")
 	.help (
 	.help (
-	    "Scene render debug mode: base-only, no-solid-final, pass-log, no-puppet-animation, object=<id>, "
+	    "Scene render debug mode: base-only, no-solid-final, pass-log, brightness-log, no-puppet-animation, object=<id>, "
 	    "skip-object=<id>, or skip-effect=<id>. Can be repeated."
 	    "skip-object=<id>, or skip-effect=<id>. Can be repeated."
 	)
 	)
 	.action ([this] (const std::string& value) -> void {
 	.action ([this] (const std::string& value) -> void {
@@ -896,6 +907,8 @@ void ApplicationContext::loadSettingsFromArgv () {
 		this->settings.render.debug.noSolidFinal = true;
 		this->settings.render.debug.noSolidFinal = true;
 	    } else if (value == "pass-log") {
 	    } else if (value == "pass-log") {
 		this->settings.render.debug.passLog = true;
 		this->settings.render.debug.passLog = true;
+	    } else if (value == "brightness-log") {
+		this->settings.render.debug.brightnessLog = true;
 	    } else if (value == "no-puppet-animation") {
 	    } else if (value == "no-puppet-animation") {
 		this->settings.render.debug.noPuppetAnimation = true;
 		this->settings.render.debug.noPuppetAnimation = true;
 	    } else if (value.rfind ("object=", 0) == 0) {
 	    } else if (value.rfind ("object=", 0) == 0) {

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

@@ -158,6 +158,8 @@ public:
 		bool baseOnly;
 		bool baseOnly;
 		bool noSolidFinal;
 		bool noSolidFinal;
 		bool passLog;
 		bool passLog;
+		/** Logs mean scene brightness every 30 frames */
+		bool brightnessLog;
 		/** Renders puppets in their static bind pose, ignoring animation clips entirely */
 		/** Renders puppets in their static bind pose, ignoring animation clips entirely */
 		bool noPuppetAnimation;
 		bool noPuppetAnimation;
 		std::optional<int> objectFilter;
 		std::optional<int> objectFilter;
@@ -195,6 +197,8 @@ public:
 	struct {
 	struct {
 	    bool enabled;
 	    bool enabled;
 	    bool disableparallax;
 	    bool disableparallax;
+	    /** Clamps parallax displacement so an image never slides past its own edges (no black corners) */
+	    bool clampParallaxToImageSize;
 	} mouse;
 	} mouse;
 
 
 	struct {
 	struct {
@@ -240,6 +244,7 @@ public:
                 .baseOnly = false,
                 .baseOnly = false,
                 .noSolidFinal = false,
                 .noSolidFinal = false,
 	                .passLog = false,
 	                .passLog = false,
+	                .brightnessLog = false,
 	                .noPuppetAnimation = false,
 	                .noPuppetAnimation = false,
 	                .objectFilter = std::nullopt,
 	                .objectFilter = std::nullopt,
 	                .skipObjects = {},
 	                .skipObjects = {},
@@ -267,6 +272,7 @@ public:
         .mouse = {
         .mouse = {
             .enabled = true,
             .enabled = true,
             .disableparallax = false,
             .disableparallax = false,
+            .clampParallaxToImageSize = true,
         },
         },
         .screenshot = {
         .screenshot = {
             .take = false,
             .take = false,

+ 5 - 0
src/WallpaperEngine/Application/WallpaperApplication.cpp

@@ -473,6 +473,8 @@ void WallpaperApplication::advancePlaylist (
 
 
 	this->setupPropertiesForProject (*project);
 	this->setupPropertiesForProject (*project);
 
 
+	// same reason as checkHotswapRequest() - keep the outgoing project alive past setWallpaper()
+	auto outgoingProject = std::move (this->m_backgrounds[screen]);
 	this->m_backgrounds[screen] = std::move (project);
 	this->m_backgrounds[screen] = std::move (project);
 
 
 	const auto scalingIt = this->m_context.settings.general.screenScalings.find (screen);
 	const auto scalingIt = this->m_context.settings.general.screenScalings.find (screen);
@@ -810,6 +812,9 @@ void WallpaperApplication::checkHotswapRequest () {
 	    this->setupAudioSensitivityForProject (*project);
 	    this->setupAudioSensitivityForProject (*project);
 	    this->setupSoundVolumeForProject (*project);
 	    this->setupSoundVolumeForProject (*project);
 
 
+	    // CWallpaper holds a raw reference into its Project (m_wallpaperData), so the outgoing
+	    // project must outlive setWallpaper() below, which destroys the CWallpaper using it
+	    auto outgoingProject = std::move (background);
 	    background = std::move (project);
 	    background = std::move (project);
 
 
 	    const auto scalingIt = this->m_context.settings.general.screenScalings.find (screen);
 	    const auto scalingIt = this->m_context.settings.general.screenScalings.find (screen);

+ 13 - 3
src/WallpaperEngine/Data/Utils/MemoryStream.h

@@ -1,5 +1,6 @@
 #pragma once
 #pragma once
 
 
+#include <algorithm>
 #include <iostream>
 #include <iostream>
 #include <memory>
 #include <memory>
 
 
@@ -10,15 +11,24 @@ struct MemoryStream : std::istream, private std::streambuf {
 	this->setg (this->m_buffer.get (), this->m_buffer.get (), this->m_buffer.get () + size);
 	this->setg (this->m_buffer.get (), this->m_buffer.get (), this->m_buffer.get () + size);
     }
     }
 
 
+    // Callers occasionally derive seek offsets from untrusted file contents (e.g. a corrupt or
+    // unrecognized .mdl section) and never clamp them. Without clamping here, gptr() can end up past
+    // egptr(); the next read then has libstdc++'s xsgetn compute a negative "bytes available" count,
+    // which turns into a huge size_t passed to memmove and segfaults.
     std::streambuf::pos_type
     std::streambuf::pos_type
     seekoff (std::streambuf::off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which) override {
     seekoff (std::streambuf::off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which) override {
+	char* target = gptr ();
 	if (dir == std::ios_base::cur) {
 	if (dir == std::ios_base::cur) {
-	    gbump (off);
+	    target = gptr () + off;
 	} else if (dir == std::ios_base::end) {
 	} else if (dir == std::ios_base::end) {
-	    setg (eback (), egptr () + off, egptr ());
+	    target = egptr () + off;
 	} else if (dir == std::ios_base::beg) {
 	} else if (dir == std::ios_base::beg) {
-	    setg (eback (), eback () + off, egptr ());
+	    target = eback () + off;
 	}
 	}
+
+	target = std::min (std::max (target, eback ()), egptr ());
+	setg (eback (), target, egptr ());
+
 	return gptr () - eback ();
 	return gptr () - eback ();
     }
     }
 
 

+ 18 - 0
src/WallpaperEngine/Render/CWallpaper.cpp

@@ -1,3 +1,5 @@
+#include <vector>
+
 #include "CWallpaper.h"
 #include "CWallpaper.h"
 #include "WallpaperEngine/Logging/Log.h"
 #include "WallpaperEngine/Logging/Log.h"
 #include "WallpaperEngine/Render/Wallpapers/CScene.h"
 #include "WallpaperEngine/Render/Wallpapers/CScene.h"
@@ -273,6 +275,22 @@ void CWallpaper::render (
 
 
     glDrawArrays (GL_TRIANGLES, 0, 6);
     glDrawArrays (GL_TRIANGLES, 0, 6);
 
 
+    if (this->getContext ().getApp ().getContext ().settings.render.debug.brightnessLog) {
+	static uint32_t brightnessFrameCounter = 0;
+	if ((brightnessFrameCounter++ % 30) == 0) {
+	    std::vector<unsigned char> px (viewport.z * viewport.w * 4);
+	    glReadPixels (
+		viewport.x, viewport.y, viewport.z, viewport.w, GL_RGBA, GL_UNSIGNED_BYTE, px.data ()
+	    );
+	    uint64_t sum = 0;
+	    for (size_t i = 0; i < px.size (); i += 4) {
+		sum += px[i] + px[i + 1] + px[i + 2];
+	    }
+	    const double mean = static_cast<double> (sum) / (px.size () / 4 * 3);
+	    sLog.out ("[BRIGHTNESS] frame=", brightnessFrameCounter, " mean=", mean);
+	}
+    }
+
 #if !NDEBUG
 #if !NDEBUG
     glPopDebugGroup ();
     glPopDebugGroup ();
 #endif /* !NDEBUG */
 #endif /* !NDEBUG */

+ 55 - 0
src/WallpaperEngine/Render/Objects/CImage.cpp

@@ -334,6 +334,14 @@ PuppetBoneSet parsePuppetBones (const BinaryReader& reader, size_t mdlsOffset) {
     const uint32_t nextSectionOffset = reader.nextUInt32 ();
     const uint32_t nextSectionOffset = reader.nextUInt32 ();
     const uint32_t boneCount = reader.nextUInt32 ();
     const uint32_t boneCount = reader.nextUInt32 ();
 
 
+    // A bone count this large can only be a garbage read (wrong mdlsOffset or an unrecognized MDLS
+    // layout), not a real rig. Same reasoning as the clip/point-count guards below.
+    constexpr uint32_t maxPlausibleBoneCount = 512;
+    if (boneCount > maxPlausibleBoneCount) {
+	sLog.error ("Puppet bone count (", boneCount, ") looks implausible, skipping puppet mesh skinning");
+	return {};
+    }
+
     PuppetBoneSet result;
     PuppetBoneSet result;
     result.nextSectionOffset = nextSectionOffset;
     result.nextSectionOffset = nextSectionOffset;
     result.bones.reserve (boneCount);
     result.bones.reserve (boneCount);
@@ -357,6 +365,16 @@ PuppetBoneSet parsePuppetBones (const BinaryReader& reader, size_t mdlsOffset) {
 		m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9], m[10], m[11], m[12], m[13], m[14], m[15]
 		m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9], m[10], m[11], m[12], m[13], m[14], m[15]
 	    );
 	    );
 	} else {
 	} else {
+	    // an implausible byte count here means this bone record wasn't decoded correctly; bail out
+	    // rather than seeking by an untrusted amount and reading whatever garbage follows as bones
+	    constexpr uint32_t maxPlausibleMatrixBytes = 4096;
+	    if (matrixBytes > maxPlausibleMatrixBytes) {
+		sLog.error (
+		    "Puppet bone ", i, " has an implausible matrix byte count (", matrixBytes,
+		    "), stopping here (", result.bones.size (), " bone(s) kept)"
+		);
+		break;
+	    }
 	    reader.base ().seekg (static_cast<std::streamoff> (matrixBytes), std::ios::cur);
 	    reader.base ().seekg (static_cast<std::streamoff> (matrixBytes), std::ios::cur);
 	}
 	}
 
 
@@ -1815,6 +1833,10 @@ void CImage::render () {
 	(*cur)->render ();
 	(*cur)->render ();
     }
     }
 
 
+    // restore alpha writes - CParticle::render() never resets glColorMask, so leaving this
+    // disabled here leaks into the next frame's clear if bloom renders last
+    glColorMask (true, true, true, true);
+
 #if !NDEBUG
 #if !NDEBUG
     glPopDebugGroup ();
     glPopDebugGroup ();
 #endif /* DEBUG */
 #endif /* DEBUG */
@@ -1985,6 +2007,24 @@ CImage::ResolvedTransform CImage::updateGeometryBuffers () {
     return transform;
     return transform;
 }
 }
 
 
+namespace {
+// keeps an edge pair (e.g. m_pos.x/.z) from sliding past the viewport once `offset` is added to both,
+// so the image never uncovers ground it doesn't have pixels for; if the image is too small to fully
+// cover the viewport on this axis to begin with, there's no safe offset, so movement is frozen at 0
+float clampParallaxAxis (float offset, float edgeA, float edgeB, float sceneExtent) {
+    const float low = std::min (edgeA, edgeB);
+    const float high = std::max (edgeA, edgeB);
+    const float half = sceneExtent / 2.0f;
+    const float maxOffset = -half - low;
+    const float minOffset = half - high;
+
+    if (minOffset > maxOffset)
+	return 0.0f;
+
+    return std::clamp (offset, minOffset, maxOffset);
+}
+} // namespace
+
 void CImage::updateScreenSpacePosition () {
 void CImage::updateScreenSpacePosition () {
     const ResolvedTransform transform = this->updateGeometryBuffers ();
     const ResolvedTransform transform = this->updateGeometryBuffers ();
 
 
@@ -2009,6 +2049,21 @@ void CImage::updateScreenSpacePosition () {
 	const float referenceSize = static_cast<float> (this->getScene ().getWidth ());
 	const float referenceSize = static_cast<float> (this->getScene ().getWidth ());
 	float x = (depth.x + parallaxAmount) * displacement->x * referenceSize;
 	float x = (depth.x + parallaxAmount) * displacement->x * referenceSize;
 	float y = (depth.y + parallaxAmount) * displacement->y * referenceSize;
 	float y = (depth.y + parallaxAmount) * displacement->y * referenceSize;
+
+	// a texture that isn't UV-clamped tiles/repeats instead of showing black past its edges (GL_REPEAT,
+	// see CTexture.cpp), so sliding it further is harmless and exempt from the clamp; scene.json's own
+	// "clampuvs" overrides the base texture's flag the same way it does for the composite FBOs above
+	const bool textureTiles = !this->getImage ().clampUVs && this->getTexture () != nullptr
+	    && (this->getTexture ()->getFlags () & TextureFlags_ClampUVs) == 0;
+
+	if (this->getScene ().getContext ().getApp ().getContext ().settings.mouse.clampParallaxToImageSize
+	    && !textureTiles) {
+	    const float sceneWidth = static_cast<float> (this->getScene ().getWidth ());
+	    const float sceneHeight = static_cast<float> (this->getScene ().getHeight ());
+	    x = clampParallaxAxis (x, this->m_pos.x, this->m_pos.z, sceneWidth);
+	    y = clampParallaxAxis (y, this->m_pos.y, this->m_pos.w, sceneHeight);
+	}
+
 	mvp = glm::translate (mvp, { x, y, 0.0f });
 	mvp = glm::translate (mvp, { x, y, 0.0f });
     }
     }