#include "CImage.h" #include #include "WallpaperEngine/Data/Parsers/MaterialParser.h" #include "WallpaperEngine/Data/Model/Object.h" #include "WallpaperEngine/Data/Model/Material.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; CImage::CImage (Wallpapers::CScene& scene, const Image& image) : Render::CObject (scene, image), Render::CFBOProvider (&scene), m_texture (nullptr), 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) { // get scene width and height to calculate positions auto scene_width = static_cast (scene.getWidth ()); auto scene_height = static_cast (scene.getHeight ()); // TODO: MAKE USE OF THE USER PROPERTIES POINTER HERE TOO! SO EVERYTHING IS UPDATED ACCORDINGLY glm::vec3 origin = this->getImage ().origin->value->getVec3 (); glm::vec2 size = this->getSize (); glm::vec3 scale = this->getImage ().scale->value->getVec3 (); // fullscreen layers should use the whole projection's size // TODO: WHAT SHOULD AUTOSIZE DO? if (this->getImage ().model->fullscreen) { size = {scene_width, scene_height}; origin = {scene_width / 2, scene_height / 2, 0}; // TODO: CHANGE ALIGNMENT TOO? } glm::vec2 scaledSize = size * glm::vec2 (scale); // calculate the center and shift from there this->m_pos.x = origin.x - (scaledSize.x / 2); this->m_pos.w = origin.y + (scaledSize.y / 2); this->m_pos.z = origin.x + (scaledSize.x / 2); this->m_pos.y = origin.y - (scaledSize.y / 2); if (this->getImage ().alignment.find ("top") != std::string::npos) { this->m_pos.y -= scaledSize.y / 2; this->m_pos.w -= scaledSize.y / 2; } else if (this->getImage ().alignment.find ("bottom") != std::string::npos) { this->m_pos.y += scaledSize.y / 2; this->m_pos.w += scaledSize.y / 2; } if (this->getImage ().alignment.find ("left") != std::string::npos) { this->m_pos.x += scaledSize.x / 2; this->m_pos.z += scaledSize.x / 2; } else if (this->getImage ().alignment.find ("right") != std::string::npos) { this->m_pos.x -= scaledSize.x / 2; this->m_pos.z -= scaledSize.x / 2; } // wallpaper engine this->m_pos.x -= scene_width / 2; this->m_pos.y = scene_height / 2 - this->m_pos.y; this->m_pos.z -= scene_width / 2; this->m_pos.w = scene_height / 2 - this->m_pos.w; // detect texture (if any) auto textures = (*this->m_image.model->material->passes.begin ())->textures; if (!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 { // 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 { if (this->m_image.model->solidlayer) { size.x = scene_width; size.y = scene_height; } // if (this->m_image->isSolid ()) // layer receives cursor events: https://docs.wallpaperengine.io/en/scene/scenescript/reference/event/cursor.html // same applies to effects // TODO: create a dummy texture of correct size, fbo constructors should be enough, but this should be properly // handled this->m_texture = std::make_shared ( "", ITexture::TextureFormat::ARGB8888, ITexture::TextureFlags::NoFlags, 1, size.x, size.y, size.x, size.y); } // register both FBOs into the scene std::ostringstream nameA, nameB; // TODO: determine when _rt_imageLayerComposite and _rt_imageLayerAlbedo is used nameA << "_rt_imageLayerComposite_" << this->getImage ().id << "_a"; nameB << "_rt_imageLayerComposite_" << this->getImage ().id << "_b"; this->m_currentMainFBO = this->m_mainFBO = scene.create (nameA.str (), ITexture::TextureFormat::ARGB8888, this->m_texture->getFlags (), 1, {size.x, size.y}, {size.x, size.y}); this->m_currentSubFBO = this->m_subFBO = scene.create (nameB.str (), ITexture::TextureFormat::ARGB8888, this->m_texture->getFlags (), 1, {size.x, size.y}, {size.x, size.y}); // build a list of vertices, these might need some change later (or maybe invert the camera) GLfloat sceneSpacePosition [] = {this->m_pos.x, this->m_pos.y, 0.0f, this->m_pos.x, this->m_pos.w, 0.0f, this->m_pos.z, this->m_pos.y, 0.0f, this->m_pos.z, this->m_pos.y, 0.0f, this->m_pos.x, this->m_pos.w, 0.0f, this->m_pos.z, this->m_pos.w, 0.0f}; float width = 1.0f; float height = 1.0f; if (this->getTexture ()->isAnimated ()) { // animated images use different coordinates as they're essentially a texture atlas width = static_cast (this->getTexture ()->getRealWidth ()) / static_cast (this->getTexture ()->getTextureWidth (0)); height = static_cast (this->getTexture ()->getRealHeight ()) / static_cast (this->getTexture ()->getTextureHeight (0)); } // calculate the correct texCoord limits for the texture based on the texture screen size and real size else if (this->getTexture () != nullptr && (this->getTexture ()->getTextureWidth (0) != this->getTexture ()->getRealWidth () || this->getTexture ()->getTextureHeight (0) != this->getTexture ()->getRealHeight ())) { uint32_t x = 1; uint32_t y = 1; while (x < size.x) x <<= 1; while (y < size.y) y <<= 1; width = scaledSize.x / x; height = scaledSize.y / y; } // TODO: RECALCULATE THESE POSITIONS FOR PASSTHROUGH SO THEY TAKE THE RIGHT PART OF THE TEXTURE float x = 0.0f; float y = 0.0f; if (this->getTexture ()->isAnimated ()) { // animations should be copied completely x = 0.0f; y = 0.0f; width = 1.0f; height = 1.0f; } GLfloat realWidth = size.x; GLfloat realHeight = size.y; GLfloat realX = 0.0; GLfloat realY = 0.0; if (this->getImage ().model->passthrough) { x = -((this->m_pos.x + (scene_width / 2)) / size.x); y = -((this->m_pos.w + (scene_height / 2)) / size.y); height = (this->m_pos.y + (scene_height / 2)) / size.y; width = (this->m_pos.z + (scene_width / 2)) / size.x; if (this->getImage ().model->fullscreen) { realX = -1.0; realY = -1.0; realWidth = 1.0; realHeight = 1.0; } } GLfloat texcoordCopy [] = {x, height, x, y, width, height, width, height, x, y, width, y}; GLfloat copySpacePosition [] = {realX, realHeight, 0.0f, realX, realY, 0.0f, realWidth, realHeight, 0.0f, realWidth, realHeight, 0.0f, realX, realY, 0.0f, realWidth, realY, 0.0f}; GLfloat texcoordPass [] = {0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f}; GLfloat passSpacePosition [] = {-1.0, 1.0, 0.0f, -1.0, -1.0, 0.0f, 1.0, 1.0, 0.0f, 1.0, 1.0, 0.0f, -1.0, -1.0, 0.0f, 1.0, -1.0, 0.0f}; // bind vertex list to the openGL buffers glGenBuffers (1, &this->m_sceneSpacePosition); glBindBuffer (GL_ARRAY_BUFFER, this->m_sceneSpacePosition); glBufferData (GL_ARRAY_BUFFER, sizeof (sceneSpacePosition), sceneSpacePosition, GL_STATIC_DRAW); glGenBuffers (1, &this->m_copySpacePosition); glBindBuffer (GL_ARRAY_BUFFER, this->m_copySpacePosition); glBufferData (GL_ARRAY_BUFFER, sizeof (copySpacePosition), copySpacePosition, GL_STATIC_DRAW); // bind pass' vertex list to the openGL buffers glGenBuffers (1, &this->m_passSpacePosition); glBindBuffer (GL_ARRAY_BUFFER, this->m_passSpacePosition); glBufferData (GL_ARRAY_BUFFER, sizeof (passSpacePosition), passSpacePosition, GL_STATIC_DRAW); glGenBuffers (1, &this->m_texcoordCopy); glBindBuffer (GL_ARRAY_BUFFER, this->m_texcoordCopy); glBufferData (GL_ARRAY_BUFFER, sizeof (texcoordCopy), texcoordCopy, GL_STATIC_DRAW); glGenBuffers (1, &this->m_texcoordPass); glBindBuffer (GL_ARRAY_BUFFER, this->m_texcoordPass); glBufferData (GL_ARRAY_BUFFER, sizeof (texcoordPass), texcoordPass, GL_STATIC_DRAW); this->m_modelViewProjectionScreen = this->getScene ().getCamera ().getProjection () * this->getScene ().getCamera ().getLookAt (); this->m_modelViewProjectionScreenInverse = glm::inverse (this->m_modelViewProjectionScreen); this->m_modelViewProjectionCopy = glm::ortho (0.0, size.x, 0.0, size.y); this->m_modelViewProjectionCopyInverse = glm::inverse (this->m_modelViewProjectionCopy); this->m_modelMatrix = glm::ortho (0.0, size.x, 0.0, size.y); this->m_viewProjectionMatrix = glm::mat4 (1.0); } void CImage::setup () { // do not double-init stuff, that's bad! if (this->m_initialized) { return; } // TODO: CHECK ORDER OF THINGS, 2419444134'S ID 27 DEPENDS ON 104'S COMPOSITE_A WHEN OUR LAST RENDER IS ON COMPOSITE_B // TODO: SUPPORT PASSTHROUGH (IT'S A SHADER) // passthrough images without effects are bad, do not draw them if (this->m_image.model->passthrough && this->m_image.effects.empty ()) { return; } // copy pass to the composite layer for (const auto& cur : this->getImage ().model->material->passes) { this->m_passes.push_back ( new CPass (*this, std::make_shared (this), *cur, std::nullopt, std::nullopt, std::nullopt) ); } // prepare the passes list if (!this->getImage ().effects.empty ()) { // generate the effects used by this material for (const auto& cur : this->m_image.effects) { // do not add non-visible effects, this might need some adjustements tho as some effects might not be visible // but affect the output of the image... if (!cur->visible->value->getBool ()) { continue; } auto fboProvider = std::make_shared (this); // create all the fbos for this effect for (const auto& fbo : cur->effect->fbos) { fboProvider->create (*fbo, this->m_texture->getFlags (), this->getSize ()); } // TODO: MAKE USE OF ZIP OPERATOR IN BOOST? WAY OVERKILL JUST FOR THIS... auto curEffect = cur->effect->passes.begin (); auto endEffect = cur->effect->passes.end (); auto curOverride = cur->passOverrides.begin (); auto endOverride = cur->passOverrides.end (); for (; curEffect != endEffect; curEffect++) { if (!(*curEffect)->material.has_value ()) { if (!(*curEffect)->command.has_value ()) { sLog.error ("Pass without material and command not supported"); continue; } if (!(*curEffect)->source.has_value ()) { sLog.error ("Pass without material and source not supported"); continue; } if (!(*curEffect)->target.has_value ()) { sLog.error ("Pass without material and target not supported"); continue; } if ((*curEffect)->command != Command_Copy) { sLog.error ("Only copy command is supported for pass without material"); continue; } const auto virtualPass = MaterialPass { .blending = "normal", .cullmode = "nocull", .depthtest = "disabled", .depthwrite = "disabled", .shader = "commands/copy", .textures = { {0, *(*curEffect)->source} }, .combos = {} }; const auto& config = this->m_virtualPassess.emplace_back (virtualPass); // build a pass for a copy shader this->m_passes.push_back ( new CPass (*this, fboProvider, config, std::nullopt, std::nullopt, (*curEffect)->target.value ()) ); } else { for (auto& pass : (*curEffect)->material.value ()->passes) { const auto override = curOverride != endOverride ? **curOverride : std::optional> (std::nullopt); const auto target = (*curEffect)->target.has_value () ? *(*curEffect)->target : std::optional> (std::nullopt); this->m_passes.push_back ( new CPass (*this, fboProvider, *pass, override, (*curEffect)->binds, target)); } if (curOverride != endOverride) { curOverride++; } } } } } // extra render pass if there's any blending to be done if (this->m_image.colorBlendMode > 0) { this->m_materials.colorBlending.material = MaterialParser::load (this->getScene ().getScene ().project, "materials/util/effectpassthrough.json"); this->m_materials.colorBlending.override = std::make_unique (ImageEffectPassOverride { .id = -1, .combos = { {"BLENDMODE", this->m_image.colorBlendMode}, }, .constants = {}, .textures = {}, }); this->m_passes.push_back ( new CPass ( *this, std::make_shared (this), **this->m_materials.colorBlending.material->passes.begin (), *this->m_materials.colorBlending.override, std::nullopt, std::nullopt ) ); } // if there's more than one pass the blendmode has to be moved from the beginning to the end if (this->m_passes.size () > 1) { const auto first = this->m_passes.begin (); const auto last = this->m_passes.rbegin (); (*last)->setBlendingMode ((*first)->getBlendingMode ()); (*first)->setBlendingMode ("normal"); } // calculate full animation time (if any) this->m_animationTime = 0.0f; for (const auto& cur : this->getTexture ()->getFrames ()) { this->m_animationTime += cur->frametime; } this->setupPasses (); this->m_initialized = true; } void CImage::setupPasses () { // do a pass on everything and setup proper inputs and values std::shared_ptr drawTo = this->m_currentMainFBO; std::shared_ptr asInput = this->getTexture (); GLuint texcoord = this->getTexCoordCopy (); auto cur = this->m_passes.begin (); auto end = this->m_passes.end (); bool first = true; for (; cur != end; ++cur) { // TODO: PROPERLY CHECK EFFECT'S VISIBILITY AND TAKE IT INTO ACCOUNT // TODO: THIS REQUIRES ON-THE-FLY EVALUATION OF EFFECTS VISIBILITY TO FIGURE OUT // TODO: WHICH ONE IS THE LAST + A FEW OTHER THINGS Effects::CPass* pass = *cur; std::shared_ptr prevDrawTo = drawTo; GLuint spacePosition = (first) ? this->getCopySpacePosition () : this->getPassSpacePosition (); const glm::mat4* projection = (first) ? &this->m_modelViewProjectionCopy : &this->m_modelViewProjectionPass; const glm::mat4* inverseProjection = (first) ? &this->m_modelViewProjectionCopyInverse : &this->m_modelViewProjectionPassInverse; first = false; pass->setModelMatrix (&this->m_modelMatrix); pass->setViewProjectionMatrix (&this->m_viewProjectionMatrix); // set viewport and target texture if needed if (pass->getTarget ().has_value ()) { // setup target texture std::string target = pass->getTarget ().value (); drawTo = pass->getFBOProvider ()->find (target); // spacePosition = this->getPassSpacePosition (); // not a local fbo, try to find a scene fbo with the same name if (drawTo == nullptr) { // this one throws if no fbo was found drawTo = this->getScene ().findFBO (target); } } // determine if it's the last element in the list as this is a screen-copy-like process // TODO: PROPERLY CHECK IF THIS IS ALL THAT'S NEEDED else if (std::next (cur) == end && this->getImage ().visible->value->getBool ()) { // TODO: PROPERLY CHECK EFFECT'S VISIBILITY AND TAKE IT INTO ACCOUNT spacePosition = this->getSceneSpacePosition (); drawTo = this->getScene ().getFBO (); projection = &this->m_modelViewProjectionScreen; inverseProjection = &this->m_modelViewProjectionScreenInverse; } pass->setDestination (drawTo); pass->setInput (asInput); pass->setPosition (spacePosition); pass->setTexCoord (texcoord); pass->setModelViewProjectionMatrix (projection); pass->setModelViewProjectionMatrixInverse (inverseProjection); texcoord = this->getTexCoordPass (); drawTo = prevDrawTo; if (!pass->getTarget ().has_value ()) this->pinpongFramebuffer (&drawTo, &asInput); } } void CImage::pinpongFramebuffer (std::shared_ptr* drawTo, std::shared_ptr* asInput) { // temporarily store FBOs used std::shared_ptr currentMainFBO = this->m_currentMainFBO; std::shared_ptr currentSubFBO = this->m_currentSubFBO; if (drawTo != nullptr) *drawTo = currentSubFBO; if (asInput != nullptr) *asInput = currentMainFBO; // swap the FBOs this->m_currentMainFBO = currentSubFBO; this->m_currentSubFBO = currentMainFBO; } void CImage::render () { // do not try to render something that did not initialize successfully // non-visible materials do need to be rendered if (!this->m_initialized) return; glColorMask (true, true, true, true); // update the position if required // TODO: There's more images that are not affected by parallax, autosize or fullscreen are not affected if (this->getScene ().getScene ().camera.parallax.enabled && !this->getImage ().model->fullscreen) this->updateScreenSpacePosition (); #if !NDEBUG std::string str = "Rendering "; if (this->getScene ().getScene ().camera.bloom.enabled->value->getBool () && this->getId () == -1) str += "bloom"; else { str += this->getImage ().name + " (" + std::to_string (this->getId ()) + ", " + this->getImage ().model->material->filename + ")"; } glPushDebugGroup (GL_DEBUG_SOURCE_APPLICATION, 0, -1, str.c_str ()); #endif /* DEBUG */ auto cur = this->m_passes.begin (); const auto end = this->m_passes.end (); for (; cur != end; ++cur) { // TODO: PROPERLY CHECK EFFECT'S VISIBILITY AND TAKE IT INTO ACCOUNT if (std::next (cur) == end) glColorMask (true, true, true, false); (*cur)->render (); } #if !NDEBUG glPopDebugGroup (); #endif /* DEBUG */ } 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) { return; } const double parallaxAmount = this->getScene ().getScene ().camera.parallax.amount; const glm::vec2 depth = this->getImage ().parallaxDepth; const glm::vec2* displacement = this->getScene ().getParallaxDisplacement (); float x = (depth.x + parallaxAmount) * displacement->x * this->getSize ().x; float y = (depth.y + parallaxAmount) * displacement->y * this->getSize ().x; this->m_modelViewProjectionScreen = glm::translate ( this->getScene ().getCamera ().getProjection () * this->getScene ().getCamera ().getLookAt (), {x, y, 0.0f} ); } std::shared_ptr 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& CImage::getEffects () const { return this->m_effects; } const Effects::CMaterial* CImage::getMaterial () const { return this->m_material; } glm::vec2 CImage::getSize () const { if (this->m_texture == nullptr) return this->getImage ().size; return {this->m_texture->getRealWidth (), this->m_texture->getRealHeight ()}; } GLuint CImage::getSceneSpacePosition () const { return this->m_sceneSpacePosition; } GLuint CImage::getCopySpacePosition () const { return this->m_copySpacePosition; } GLuint CImage::getPassSpacePosition () const { return this->m_passSpacePosition; } GLuint CImage::getTexCoordCopy () const { return this->m_texcoordCopy; } GLuint CImage::getTexCoordPass () const { return this->m_texcoordPass; }