Explorar el Código

More code cleanup:
- Separated program args parsing and validation from initialization code
- Application's main body in it's own class

Signed-off-by: Alexis Maiquez <almamu@almamu.com>

Alexis Maiquez hace 3 años
padre
commit
55760aee4d

+ 5 - 0
CMakeLists.txt

@@ -43,6 +43,11 @@ add_executable(
     src/WallpaperEngine/Logging/CLog.cpp
     src/WallpaperEngine/Logging/CLog.h
 
+    src/WallpaperEngine/Application/CApplicationContext.cpp
+    src/WallpaperEngine/Application/CApplicationContext.h
+    src/WallpaperEngine/Application/CWallpaperApplication.cpp
+    src/WallpaperEngine/Application/CWallpaperApplication.h
+
     src/WallpaperEngine/Assets/CPackageLoadException.cpp
     src/WallpaperEngine/Assets/CPackageLoadException.h
     src/WallpaperEngine/Assets/CAssetLoadException.cpp

+ 15 - 444
main.cpp

@@ -23,227 +23,21 @@
 #include "WallpaperEngine/Assets/CPackageLoadException.h"
 
 #include "Steam/FileSystem/FileSystem.h"
+#include "WallpaperEngine/Application/CApplicationContext.h"
+#include "WallpaperEngine/Application/CWallpaperApplication.h"
 #include "WallpaperEngine/Audio/CAudioContext.h"
 #include "WallpaperEngine/Audio/Drivers/CSDLAudioDriver.h"
 #include "WallpaperEngine/Render/Drivers/COpenGLDriver.h"
 #include "common.h"
 
-#define WORKSHOP_APP_ID 431960
-#define APP_DIRECTORY "wallpaper_engine"
-
-float g_Time;
-float g_TimeLast;
-bool g_KeepRunning = true;
-bool g_AudioEnabled = true;
-int g_AudioVolume = 128;
-
-void print_help (const char* route)
-{
-    sLog.out ("Usage: ", route, " [options] background_path/background_id");
-    sLog.out ("");
-    sLog.out ("where background_path/background_id can be:");
-    sLog.out ("\tthe ID of the background (for autodetection on your steam installation)");
-    sLog.out ("\ta full path to the background's folder");
-    sLog.out ("");
-    sLog.out ("options:");
-    sLog.out ("\t--silent\t\t\t\t\tMutes all the sound the wallpaper might produce");
-    sLog.out ("\t--volume <amount>\t\t\tSets the volume for all the sounds in the background");
-    sLog.out ("\t--screen-root <screen name>\tDisplay as screen's background");
-    sLog.out ("\t--fps <maximum-fps>\t\t\tLimits the FPS to the given number, useful to keep battery consumption low");
-    sLog.out ("\t--assets-dir <path>\t\t\tFolder where the assets are stored");
-    sLog.out ("\t--screenshot\t\t\t\tTakes a screenshot of the background");
-    sLog.out ("\t--list-properties\t\t\tList all the available properties and their possible values");
-    sLog.out ("\t--set-property <name=value>\tOverrides the default value of the given property");
-}
-
-std::string stringPathFixes(const std::string& s)
-{
-    if (s.empty () == true)
-        return s;
-
-    std::string str (s);
-
-    // remove single-quotes from the arguments
-    if (str [0] == '\'' && str [str.size() - 1] == '\'')
-        str
-            .erase (str.size() - 1, 1)
-            .erase (0, 1);
-
-    return std::move (str);
-}
+WallpaperEngine::Application::CWallpaperApplication* appPointer;
 
 void signalhandler(int sig)
 {
-    g_KeepRunning = false;
-}
+    if (appPointer == nullptr)
+        return;
 
-void addPkg (CCombinedContainer* containers, const std::filesystem::path& path, std::string pkgfile)
-{
-    try
-    {
-        auto scene_path = std::filesystem::path (path) / pkgfile;
-
-        // add the package to the list
-        containers->add (new WallpaperEngine::Assets::CPackage (scene_path));
-        sLog.out ("Detected ", pkgfile, " file at ", scene_path, ". Adding to list of searchable paths");
-    }
-    catch (CPackageLoadException& ex)
-    {
-        // ignore this error, the package file was not found
-        sLog.out ("No ", pkgfile, " file found at ", path, ". Defaulting to normal folder storage");
-    }
-    catch (std::runtime_error& ex)
-    {
-        // the package was found but there was an error loading it (wrong header or something)
-        sLog.exception ("Failed to load scene.pkg file: ", ex.what());
-    }
-}
-
-CVirtualContainer* buildVirtualContainer ()
-{
-    CVirtualContainer* container = new WallpaperEngine::Assets::CVirtualContainer ();
-
-    //
-    // Had to get a little creative with the effects to achieve the same bloom effect without any custom code
-    // these virtual files are loaded by an image in the scene that takes current _rt_FullFrameBuffer and
-    // applies the bloom effect to render it out to the screen
-    //
-
-    // add the effect file for screen bloom
-    container->add (
-        "effects/wpenginelinux/bloomeffect.json",
-        "{"
-        "\t\"name\":\"camerabloom_wpengine_linux\","
-        "\t\"group\":\"wpengine_linux_camera\","
-        "\t\"dependencies\":[],"
-        "\t\"passes\":"
-        "\t["
-        "\t\t{"
-        "\t\t\t\"material\": \"materials/util/downsample_quarter_bloom.json\","
-        "\t\t\t\"target\": \"_rt_4FrameBuffer\","
-        "\t\t\t\"bind\":"
-        "\t\t\t["
-        "\t\t\t\t{"
-        "\t\t\t\t\t\"name\": \"_rt_FullFrameBuffer\","
-        "\t\t\t\t\t\"index\": 0"
-        "\t\t\t\t}"
-        "\t\t\t]"
-        "\t\t},"
-        "\t\t{"
-        "\t\t\t\"material\": \"materials/util/downsample_eighth_blur_v.json\","
-        "\t\t\t\"target\": \"_rt_8FrameBuffer\","
-        "\t\t\t\"bind\":"
-        "\t\t\t["
-        "\t\t\t\t{"
-        "\t\t\t\t\t\"name\": \"_rt_4FrameBuffer\","
-        "\t\t\t\t\t\"index\": 0"
-        "\t\t\t\t}"
-        "\t\t\t]"
-        "\t\t},"
-        "\t\t{"
-        "\t\t\t\"material\": \"materials/util/blur_h_bloom.json\","
-        "\t\t\t\"target\": \"_rt_Bloom\","
-        "\t\t\t\"bind\":"
-        "\t\t\t["
-        "\t\t\t\t{"
-        "\t\t\t\t\t\"name\": \"_rt_8FrameBuffer\","
-        "\t\t\t\t\t\"index\": 0"
-        "\t\t\t\t}"
-        "\t\t\t]"
-        "\t\t},"
-        "\t\t{"
-        "\t\t\t\"material\": \"materials/util/combine.json\","
-        "\t\t\t\"target\": \"_rt_FullFrameBuffer\","
-        "\t\t\t\"bind\":"
-        "\t\t\t["
-        "\t\t\t\t{"
-        "\t\t\t\t\t\"name\": \"_rt_imageLayerComposite_-1_a\","
-        "\t\t\t\t\t\"index\": 0"
-        "\t\t\t\t},"
-        "\t\t\t\t{"
-        "\t\t\t\t\t\"name\": \"_rt_Bloom\","
-        "\t\t\t\t\t\"index\": 1"
-        "\t\t\t\t}"
-        "\t\t\t]"
-        "\t\t}"
-        "\t]"
-        "}"
-    );
-
-    // add some model for the image element even if it's going to waste rendering cycles
-    container->add (
-        "models/wpenginelinux.json",
-        "{"
-        "\t\"material\":\"materials/wpenginelinux.json\""
-        "}"
-    );
-
-    // models require materials, so add that too
-    container->add (
-        "materials/wpenginelinux.json",
-        "{"
-        "\t\"passes\":"
-        "\t\t["
-        "\t\t\t{"
-        "\t\t\t\t\"blending\": \"normal\","
-        "\t\t\t\t\"cullmode\": \"nocull\","
-        "\t\t\t\t\"depthtest\": \"disabled\","
-        "\t\t\t\t\"depthwrite\": \"disabled\","
-        "\t\t\t\t\"shader\": \"genericimage2\","
-        "\t\t\t\t\"textures\": [\"_rt_FullFrameBuffer\"]"
-        "\t\t\t}"
-        "\t\t]"
-        "}"
-    );
-
-    return container;
-}
-
-void takeScreenshot (WallpaperEngine::Render::CWallpaper* wp, const std::filesystem::path& filename, FREE_IMAGE_FORMAT format)
-{
-    GLint width, height;
-
-    // bind texture and get the size
-    glBindFramebuffer (GL_FRAMEBUFFER, wp->getWallpaperFramebuffer ());
-    glBindTexture (GL_TEXTURE_2D, wp->getWallpaperTexture ());
-    glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
-    glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);
-
-    // make room for storing the pixel data
-    uint8_t* buffer = new uint8_t [width * height * sizeof (uint8_t) * 3];
-    uint8_t* pixel = buffer;
-
-    // read the image into the buffer
-    glReadPixels (0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, buffer);
-
-    // build the output file with FreeImage
-    FIBITMAP* bitmap = FreeImage_Allocate (width, height, 24);
-    RGBQUAD color;
-
-    // now get access to the pixels
-    for (int y = height; y > 0; y --)
-    {
-        for (int x = 0; x < width; x ++)
-        {
-            color.rgbRed = *pixel ++;
-            color.rgbGreen = *pixel ++;
-            color.rgbBlue = *pixel ++;
-
-            // set the pixel in the destination
-            FreeImage_SetPixelColor (bitmap, x, y, &color);
-        }
-    }
-
-    // finally save the file
-    FreeImage_Save (format, bitmap, filename.c_str (), 0);
-
-    // free all the used memory
-    delete[] buffer;
-
-    FreeImage_Unload (bitmap);
-
-    // unbind the textures
-    glBindTexture (GL_TEXTURE_2D, GL_NONE);
+    appPointer->signal (sig);
 }
 
 void initLogging ()
@@ -256,244 +50,21 @@ int main (int argc, char* argv[])
 {
     initLogging ();
 
-    std::vector <std::string> screens;
-    std::map <std::string, std::string> propertyOverrides;
-
-    int maximumFPS = 30;
-    bool shouldTakeScreenshot = false;
-    bool shouldListPropertiesAndStop = false;
-    FREE_IMAGE_FORMAT screenshotFormat = FIF_UNKNOWN;
-    std::string path;
-    std::filesystem::path assetsPath;
-    std::filesystem::path screenshotPath;
-
-    static struct option long_options [] = {
-        {"screen-root",     required_argument, 0, 'r'},
-        {"pkg",             required_argument, 0, 'p'},
-        {"dir",             required_argument, 0, 'd'},
-        {"silent",          no_argument,       0, 's'},
-        {"volume",          required_argument, 0, 'v'},
-        {"help",            no_argument,       0, 'h'},
-        {"fps",             required_argument, 0, 'f'},
-        {"assets-dir",      required_argument, 0, 'a'},
-        {"screenshot",      required_argument, 0, 'c'},
-        {"list-properties", no_argument,       0, 'l'},
-        {"set-property",    required_argument, 0, 'o'},
-        {nullptr,                           0, 0,   0}
-    };
-
-    while (true)
-    {
-        int c = getopt_long (argc, argv, "r:p:d:shf:a:", long_options, nullptr);
-
-        if (c == -1)
-            break;
-
-        switch (c)
-        {
-            case 'o':
-                {
-                    std::string value = optarg;
-                    std::string::size_type equals = value.find ('=');
-
-                    // properties without value are treated as booleans for now
-                    if (equals == std::string::npos)
-                        propertyOverrides.insert_or_assign (value, "1");
-                    else
-                        propertyOverrides.insert_or_assign (
-                            value.substr (0, equals),
-                            value.substr (equals + 1)
-                        );
-                }
-                break;
-
-            case 'l':
-                shouldListPropertiesAndStop = true;
-                break;
-
-            case 'r':
-                screens.emplace_back (optarg);
-                break;
-
-            case 'p':
-            case 'd':
-                sLog.error ("--dir/--pkg is deprecated and not used anymore");
-                path = stringPathFixes (optarg);
-                break;
-
-            case 's':
-                g_AudioEnabled = false;
-                break;
-
-            case 'h':
-                print_help (argv [0]);
-                break;
-
-            case 'f':
-                maximumFPS = atoi (optarg);
-                break;
-
-            case 'a': assetsPath = optarg;
-                break;
-
-            case 'v':
-                g_AudioVolume = atoi (optarg);
-                break;
-
-            case 'c':
-                shouldTakeScreenshot = true;
-                screenshotPath = stringPathFixes (optarg);
-                break;
-        }
-    }
-
-    if (path.empty () == true)
-    {
-        if (optind < argc && strlen (argv [optind]) > 0)
-        {
-            path = argv [optind];
-        }
-        else
-        {
-            print_help (argv [0]);
-            return 0;
-        }
-    }
-
-    // validate screenshot file just to make sure
-    if (shouldTakeScreenshot == true)
-    {
-        if (screenshotPath.has_extension () == false)
-            sLog.exception ("Cannot determine screenshot format");
-
-        std::string extension = screenshotPath.extension ();
-
-        if (extension == ".bmp")
-            screenshotFormat = FIF_BMP;
-        else if (extension == ".png")
-            screenshotFormat = FIF_PNG;
-        else if (extension == ".jpg" || extension == ".jpeg")
-            screenshotFormat = FIF_JPEG;
-        else
-            sLog.exception ("Cannot determine screenshot format, unknown extension ", extension);
-    }
-
-    // check if the background might be an ID and try to find the right path in the steam installation folder
-    if (path.find ('/') == std::string::npos)
-        path = Steam::FileSystem::workshopDirectory (WORKSHOP_APP_ID, path);
-
-    WallpaperEngine::Assets::CCombinedContainer containers;
-    // the background's path is required to load project.json regardless of the type of background we're using
-    containers.add (new WallpaperEngine::Assets::CDirectory (path));
-    // add the virtual container for mocked up files
-    containers.add (buildVirtualContainer ());
-    // try to add the common packages
-    addPkg (&containers, path, "scene.pkg");
-    addPkg (&containers, path, "gifscene.pkg");
-
-    if (assetsPath.empty () == true)
-    {
-        try
-        {
-            assetsPath = Steam::FileSystem::appDirectory (APP_DIRECTORY, "assets");
-        }
-        catch (std::runtime_error)
-        {
-            // set current path as assets' folder
-            std::filesystem::path directory = std::filesystem::canonical ("/proc/self/exe")
-                .parent_path () / "assets";
-        }
-    }
-    else
-    {
-        sLog.out ("Found wallpaper engine's assets at ", assetsPath, " based on --assets-dir parameter");
-    }
-
-    if (assetsPath.empty () == true)
-        sLog.exception ("Cannot determine a valid path for the wallpaper engine assets");
-
-    // add containers to the list
-    containers.add (new WallpaperEngine::Assets::CDirectory (assetsPath));
-
-    // parse the project.json file
-    auto project = WallpaperEngine::Core::CProject::fromFile ("project.json", &containers);
-    // go to the right folder so the videos will play
-    if (project->getWallpaper ()->is <WallpaperEngine::Core::CVideo> () == true)
-        chdir (path.c_str ());
-
-    // show properties if required
-    for (auto cur : project->getProperties ())
-    {
-        // update the value of the property
-        auto override = propertyOverrides.find (cur->getName ());
-
-        if (override != propertyOverrides.end ())
-        {
-            sLog.out ("Applying override value for ", cur->getName ());
-
-            cur->update (override->second);
-        }
-
-        if (shouldListPropertiesAndStop)
-            sLog.out (cur->dump ());
-    }
+    WallpaperEngine::Application::CApplicationContext appContext (argc, argv);
+    WallpaperEngine::Application::CWallpaperApplication app (appContext);
 
     // halt if the list-properties option was specified
-    if (shouldListPropertiesAndStop)
+    if (appContext.onlyListProperties)
         return 0;
 
-    // attach signals so if a stop is requested the X11 resources are freed and the program shutsdown gracefully
-    std::signal(SIGINT, signalhandler);
-    std::signal(SIGTERM, signalhandler);
-
-    // initialize sdl audio driver
-    WallpaperEngine::Audio::Drivers::CSDLAudioDriver audioDriver;
-    // initialize audio context
-    WallpaperEngine::Audio::CAudioContext audioContext (&audioDriver);
-    // initialize OpenGL driver
-    WallpaperEngine::Render::Drivers::COpenGLDriver videoDriver ("Wallpaper Engine");
-    // initialize render context
-    WallpaperEngine::Render::CRenderContext context (screens, videoDriver, &containers);
-    // initialize mouse support
-    context.setMouse (new CMouseInput (videoDriver.getWindow ()));
-    // ensure the context knows what wallpaper to render
-    context.setWallpaper (
-        WallpaperEngine::Render::CWallpaper::fromWallpaper (project->getWallpaper (), &context, &audioContext)
-    );
-
-    float startTime, endTime, minimumTime = 1.0f / maximumFPS;
-
-    while (videoDriver.closeRequested () == false && g_KeepRunning == true)
-    {
-        // keep track of the previous frame's time
-        g_TimeLast = g_Time;
-        // calculate the current time value
-        g_Time = videoDriver.getRenderTime ();
-        // get the start time of the frame
-        startTime = g_Time;
-        // render the scene
-        context.render ();
-        // get the end time of the frame
-        endTime = videoDriver.getRenderTime ();
-
-        // ensure the frame time is correct to not overrun FPS
-        if ((endTime - startTime) < minimumTime)
-            usleep ((minimumTime - (endTime - startTime)) * CLOCKS_PER_SEC);
-
-        if (shouldTakeScreenshot == true && videoDriver.getFrameCounter () == 5)
-        {
-            takeScreenshot (context.getWallpaper (), screenshotPath, screenshotFormat);
-            // disable screenshot just in case the counter overflows
-            shouldTakeScreenshot = false;
-        }
-    }
-
-    // ensure this is updated as sometimes it might not come from a signal
-    g_KeepRunning = false;
+    appPointer = &app;
 
-    sLog.out ("Stop requested");
+    // attach signals to gracefully stop
+    std::signal (SIGINT, signalhandler);
+    std::signal (SIGTERM, signalhandler);
 
-    SDL_Quit ();
+    // show the wallpaper application
+    app.show ();
 
     return 0;
 }

+ 199 - 0
src/WallpaperEngine/Application/CApplicationContext.cpp

@@ -0,0 +1,199 @@
+#include "CApplicationContext.h"
+
+#include "Steam/FileSystem/FileSystem.h"
+#include "WallpaperEngine/Logging/CLog.h"
+
+#include <cstring>
+#include <string>
+#include <getopt.h>
+
+#define WORKSHOP_APP_ID 431960
+#define APP_DIRECTORY "wallpaper_engine"
+
+using namespace WallpaperEngine::Application;
+
+struct option long_options [] = {
+    {"screen-root",     required_argument, 0, 'r'},
+    {"pkg",             required_argument, 0, 'p'},
+    {"dir",             required_argument, 0, 'd'},
+    {"silent",          no_argument,       0, 's'},
+    {"volume",          required_argument, 0, 'v'},
+    {"help",            no_argument,       0, 'h'},
+    {"fps",             required_argument, 0, 'f'},
+    {"assets-dir",      required_argument, 0, 'a'},
+    {"screenshot",      required_argument, 0, 'c'},
+    {"list-properties", no_argument,       0, 'l'},
+    {"set-property",    required_argument, 0, 'o'},
+    {nullptr,                           0, 0,   0}
+};
+
+std::string stringPathFixes(const std::string& s)
+{
+    if (s.empty () == true)
+        return s;
+
+    std::string str (s);
+
+    // remove single-quotes from the arguments
+    if (str [0] == '\'' && str [str.size() - 1] == '\'')
+        str
+            .erase (str.size() - 1, 1)
+            .erase (0, 1);
+
+    return std::move (str);
+}
+
+CApplicationContext::CApplicationContext (int argc, char* argv[]) :
+    takeScreenshot (false),
+    maximumFPS (30),
+    audioVolume (128),
+    audioEnabled (true),
+    onlyListProperties (false)
+{
+    int c;
+
+    while ((c = getopt_long (argc, argv, "r:p:d:shf:a:", long_options, nullptr)) != -1)
+    {
+        switch (c)
+        {
+            case 'o':
+                {
+                    std::string value = optarg;
+                    std::string::size_type equals = value.find ('=');
+
+                    // properties without value are treated as booleans for now
+                    if (equals == std::string::npos)
+                        this->properties.insert_or_assign (value, "1");
+                    else
+                        this->properties.insert_or_assign (
+                            value.substr (0, equals),
+                            value.substr (equals + 1)
+                        );
+                }
+                break;
+
+            case 'l':
+                this->onlyListProperties = true;
+                break;
+
+            case 'r':
+                screens.emplace_back (optarg);
+                break;
+
+            case 'p':
+            case 'd':
+                sLog.error ("--dir/--pkg is deprecated and not used anymore");
+                this->background = stringPathFixes (optarg);
+                break;
+
+            case 's':
+                this->audioEnabled = false;
+                break;
+
+            case 'h':
+                this->printHelp (argv [0]);
+                break;
+
+            case 'f':
+                maximumFPS = atoi (optarg);
+                break;
+
+            case 'a':
+                this->assets = stringPathFixes (optarg);
+                break;
+
+            case 'v':
+                this->audioVolume = std::max (atoi (optarg), 128);
+                break;
+
+            case 'c':
+                this->takeScreenshot = true;
+                this->screenshot = stringPathFixes (optarg);
+                break;
+        }
+    }
+
+    if (this->background.empty () == true)
+    {
+        if (optind < argc && strlen (argv [optind]) > 0)
+        {
+            this->background = argv [optind];
+        }
+        else
+        {
+            this->printHelp (argv [0]);
+        }
+    }
+
+    // perform some extra validation on the inputs
+    this->validatePath ();
+    this->validateAssets ();
+    this->validateScreenshot ();
+}
+
+void CApplicationContext::validatePath ()
+{
+    if (this->background.find ('/') != std::string::npos)
+        return;
+
+    this->background = Steam::FileSystem::workshopDirectory (WORKSHOP_APP_ID, this->background);
+}
+
+void CApplicationContext::validateAssets ()
+{
+    if (this->assets.empty () == false)
+    {
+        sLog.out ("Using wallpaper engine's assets at ", this->assets, " based on --assets-dir parameter");
+        return;
+    }
+
+    try
+    {
+        this->assets = Steam::FileSystem::appDirectory (APP_DIRECTORY, "assets");
+    }
+    catch (std::runtime_error&)
+    {
+        // set current path as assets' folder
+        std::filesystem::path directory = std::filesystem::canonical ("/proc/self/exe")
+                                              .parent_path () / "assets";
+    }
+}
+
+void CApplicationContext::validateScreenshot ()
+{
+    if (this->takeScreenshot == false)
+        return;
+
+    if (this->screenshot.has_extension () == false)
+        sLog.exception ("Cannot determine screenshot format");
+
+    std::string extension = this->screenshot.extension ();
+
+    if (extension == ".bmp")
+        this->screenshotFormat = FIF_BMP;
+    else if (extension == ".png")
+        this->screenshotFormat = FIF_PNG;
+    else if (extension == ".jpg" || extension == ".jpeg")
+        this->screenshotFormat = FIF_JPEG;
+    else
+        sLog.exception ("Cannot determine screenshot format, unknown extension ", extension);
+}
+
+void CApplicationContext::printHelp (const char* route)
+{
+    sLog.out ("Usage: ", route, " [options] background_path/background_id");
+    sLog.out ("");
+    sLog.out ("where background_path/background_id can be:");
+    sLog.out ("\tthe ID of the background (for autodetection on your steam installation)");
+    sLog.out ("\ta full path to the background's folder");
+    sLog.out ("");
+    sLog.out ("options:");
+    sLog.out ("\t--silent\t\t\t\t\tMutes all the sound the wallpaper might produce");
+    sLog.out ("\t--volume <amount>\t\t\tSets the volume for all the sounds in the background");
+    sLog.out ("\t--screen-root <screen name>\tDisplay as screen's background");
+    sLog.out ("\t--fps <maximum-fps>\t\t\tLimits the FPS to the given number, useful to keep battery consumption low");
+    sLog.out ("\t--assets-dir <path>\t\t\tFolder where the assets are stored");
+    sLog.out ("\t--screenshot\t\t\t\tTakes a screenshot of the background");
+    sLog.out ("\t--list-properties\t\t\tList all the available properties and their possible values");
+    sLog.out ("\t--set-property <name=value>\tOverrides the default value of the given property");
+}

+ 34 - 0
src/WallpaperEngine/Application/CApplicationContext.h

@@ -0,0 +1,34 @@
+#pragma once
+
+#include <FreeImage.h>
+#include <filesystem>
+#include <map>
+#include <string>
+#include <vector>
+
+namespace WallpaperEngine::Application
+{
+    class CApplicationContext
+    {
+    public:
+        CApplicationContext (int argc, char* argv[]);
+
+        std::vector <std::string> screens;
+        std::map <std::string, std::string> properties;
+        std::string background;
+        std::filesystem::path assets;
+        std::filesystem::path screenshot;
+        bool takeScreenshot;
+        int maximumFPS;
+        int audioVolume;
+        bool audioEnabled;
+        bool onlyListProperties;
+        FREE_IMAGE_FORMAT screenshotFormat;
+
+    private:
+        void validatePath ();
+        void validateAssets ();
+        void validateScreenshot ();
+        void printHelp (const char* route);
+    };
+}

+ 265 - 0
src/WallpaperEngine/Application/CWallpaperApplication.cpp

@@ -0,0 +1,265 @@
+#include "CWallpaperApplication.h"
+#include "WallpaperEngine/Assets/CDirectory.h"
+#include "WallpaperEngine/Assets/CVirtualContainer.h"
+#include "WallpaperEngine/Audio/Drivers/CSDLAudioDriver.h"
+#include "WallpaperEngine/Core/CVideo.h"
+#include "WallpaperEngine/Logging/CLog.h"
+#include "WallpaperEngine/Render/CRenderContext.h"
+#include "WallpaperEngine/Render/Drivers/COpenGLDriver.h"
+
+#include <unistd.h>
+
+float g_Time;
+float g_TimeLast;
+bool g_KeepRunning = true;
+bool g_AudioEnabled = true;
+int g_AudioVolume = 128;
+
+using namespace WallpaperEngine::Application;
+
+CWallpaperApplication::CWallpaperApplication (CApplicationContext& context) :
+    m_context (context)
+{
+    this->setupContainer ();
+    this->loadProject ();
+    this->setupProperties ();
+}
+
+void CWallpaperApplication::setupContainer ()
+{
+    this->m_vfs.add (new CDirectory (this->m_context.background));
+    this->m_vfs.addPkg (std::filesystem::path (this->m_context.background) / "scene.pkg");
+    this->m_vfs.addPkg (std::filesystem::path (this->m_context.background) / "gifscene.pkg");
+    this->m_vfs.add (new CDirectory (this->m_context.assets));
+
+    // TODO: move this somewhere else?
+    CVirtualContainer* container = new CVirtualContainer ();
+
+    //
+    // Had to get a little creative with the effects to achieve the same bloom effect without any custom code
+    // these virtual files are loaded by an image in the scene that takes current _rt_FullFrameBuffer and
+    // applies the bloom effect to render it out to the screen
+    //
+
+    // add the effect file for screen bloom
+    container->add (
+        "effects/wpenginelinux/bloomeffect.json",
+        "{"
+        "\t\"name\":\"camerabloom_wpengine_linux\","
+        "\t\"group\":\"wpengine_linux_camera\","
+        "\t\"dependencies\":[],"
+        "\t\"passes\":"
+        "\t["
+        "\t\t{"
+        "\t\t\t\"material\": \"materials/util/downsample_quarter_bloom.json\","
+        "\t\t\t\"target\": \"_rt_4FrameBuffer\","
+        "\t\t\t\"bind\":"
+        "\t\t\t["
+        "\t\t\t\t{"
+        "\t\t\t\t\t\"name\": \"_rt_FullFrameBuffer\","
+        "\t\t\t\t\t\"index\": 0"
+        "\t\t\t\t}"
+        "\t\t\t]"
+        "\t\t},"
+        "\t\t{"
+        "\t\t\t\"material\": \"materials/util/downsample_eighth_blur_v.json\","
+        "\t\t\t\"target\": \"_rt_8FrameBuffer\","
+        "\t\t\t\"bind\":"
+        "\t\t\t["
+        "\t\t\t\t{"
+        "\t\t\t\t\t\"name\": \"_rt_4FrameBuffer\","
+        "\t\t\t\t\t\"index\": 0"
+        "\t\t\t\t}"
+        "\t\t\t]"
+        "\t\t},"
+        "\t\t{"
+        "\t\t\t\"material\": \"materials/util/blur_h_bloom.json\","
+        "\t\t\t\"target\": \"_rt_Bloom\","
+        "\t\t\t\"bind\":"
+        "\t\t\t["
+        "\t\t\t\t{"
+        "\t\t\t\t\t\"name\": \"_rt_8FrameBuffer\","
+        "\t\t\t\t\t\"index\": 0"
+        "\t\t\t\t}"
+        "\t\t\t]"
+        "\t\t},"
+        "\t\t{"
+        "\t\t\t\"material\": \"materials/util/combine.json\","
+        "\t\t\t\"target\": \"_rt_FullFrameBuffer\","
+        "\t\t\t\"bind\":"
+        "\t\t\t["
+        "\t\t\t\t{"
+        "\t\t\t\t\t\"name\": \"_rt_imageLayerComposite_-1_a\","
+        "\t\t\t\t\t\"index\": 0"
+        "\t\t\t\t},"
+        "\t\t\t\t{"
+        "\t\t\t\t\t\"name\": \"_rt_Bloom\","
+        "\t\t\t\t\t\"index\": 1"
+        "\t\t\t\t}"
+        "\t\t\t]"
+        "\t\t}"
+        "\t]"
+        "}"
+    );
+
+    // add some model for the image element even if it's going to waste rendering cycles
+    container->add (
+        "models/wpenginelinux.json",
+        "{"
+        "\t\"material\":\"materials/wpenginelinux.json\""
+        "}"
+    );
+
+    // models require materials, so add that too
+    container->add (
+        "materials/wpenginelinux.json",
+        "{"
+        "\t\"passes\":"
+        "\t\t["
+        "\t\t\t{"
+        "\t\t\t\t\"blending\": \"normal\","
+        "\t\t\t\t\"cullmode\": \"nocull\","
+        "\t\t\t\t\"depthtest\": \"disabled\","
+        "\t\t\t\t\"depthwrite\": \"disabled\","
+        "\t\t\t\t\"shader\": \"genericimage2\","
+        "\t\t\t\t\"textures\": [\"_rt_FullFrameBuffer\"]"
+        "\t\t\t}"
+        "\t\t]"
+        "}"
+    );
+
+    this->m_vfs.add (container);
+}
+
+void CWallpaperApplication::loadProject ()
+{
+    this->m_project = CProject::fromFile ("project.json", &this->m_vfs);
+    // go to the right folder so the videos will play
+    // TODO: stop doing chdir and use full path
+    if (this->m_project->getWallpaper ()->is <WallpaperEngine::Core::CVideo> () == true)
+        chdir (this->m_context.background.c_str ());
+}
+
+void CWallpaperApplication::setupProperties ()
+{
+    // show properties if required
+    for (auto cur : this->m_project->getProperties ())
+    {
+        // update the value of the property
+        auto override = this->m_context.properties.find (cur->getName ());
+
+        if (override != this->m_context.properties.end ())
+        {
+            sLog.out ("Applying override value for ", cur->getName ());
+
+            cur->update (override->second);
+        }
+
+        if (this->m_context.onlyListProperties)
+            sLog.out (cur->dump ());
+    }
+}
+
+void CWallpaperApplication::takeScreenshot (WallpaperEngine::Render::CWallpaper* wp, const std::filesystem::path& filename, FREE_IMAGE_FORMAT format)
+{
+    GLint width, height;
+
+    // bind texture and get the size
+    glBindFramebuffer (GL_FRAMEBUFFER, wp->getWallpaperFramebuffer ());
+    glBindTexture (GL_TEXTURE_2D, wp->getWallpaperTexture ());
+    glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
+    glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);
+
+    // make room for storing the pixel data
+    uint8_t* buffer = new uint8_t [width * height * sizeof (uint8_t) * 3];
+    uint8_t* pixel = buffer;
+
+    // read the image into the buffer
+    glReadPixels (0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, buffer);
+
+    // build the output file with FreeImage
+    FIBITMAP* bitmap = FreeImage_Allocate (width, height, 24);
+    RGBQUAD color;
+
+    // now get access to the pixels
+    for (int y = height; y > 0; y --)
+    {
+        for (int x = 0; x < width; x ++)
+        {
+            color.rgbRed = *pixel ++;
+            color.rgbGreen = *pixel ++;
+            color.rgbBlue = *pixel ++;
+
+            // set the pixel in the destination
+            FreeImage_SetPixelColor (bitmap, x, y, &color);
+        }
+    }
+
+    // finally save the file
+    FreeImage_Save (format, bitmap, filename.c_str (), 0);
+
+    // free all the used memory
+    delete[] buffer;
+
+    FreeImage_Unload (bitmap);
+
+    // unbind the textures
+    glBindTexture (GL_TEXTURE_2D, GL_NONE);
+}
+
+void CWallpaperApplication::show ()
+{
+    // initialize sdl audio driver
+    WallpaperEngine::Audio::Drivers::CSDLAudioDriver audioDriver;
+    // initialize audio context
+    WallpaperEngine::Audio::CAudioContext audioContext (&audioDriver);
+    // initialize OpenGL driver
+    WallpaperEngine::Render::Drivers::COpenGLDriver videoDriver (this->m_project->getTitle ().c_str ());
+    // initialize render context
+    WallpaperEngine::Render::CRenderContext context (this->m_context.screens, videoDriver, &this->m_vfs);
+    // initialize mouse support
+    context.setMouse (new CMouseInput (videoDriver.getWindow ()));
+    // ensure the context knows what wallpaper to render
+    context.setWallpaper (
+        WallpaperEngine::Render::CWallpaper::fromWallpaper (this->m_project->getWallpaper (), &context, &audioContext)
+    );
+
+    float startTime, endTime, minimumTime = 1.0f / this->m_context.maximumFPS;
+
+    while (videoDriver.closeRequested () == false && g_KeepRunning == true)
+    {
+        // keep track of the previous frame's time
+        g_TimeLast = g_Time;
+        // calculate the current time value
+        g_Time = videoDriver.getRenderTime ();
+        // get the start time of the frame
+        startTime = g_Time;
+        // render the scene
+        context.render ();
+        // get the end time of the frame
+        endTime = videoDriver.getRenderTime ();
+
+        // ensure the frame time is correct to not overrun FPS
+        if ((endTime - startTime) < minimumTime)
+            usleep ((minimumTime - (endTime - startTime)) * CLOCKS_PER_SEC);
+
+        if (this->m_context.takeScreenshot == true && videoDriver.getFrameCounter () == 5)
+        {
+            this->takeScreenshot (context.getWallpaper (), this->m_context.screenshot, this->m_context.screenshotFormat);
+            // disable screenshot just in case the counter overflows
+            this->m_context.takeScreenshot = false;
+        }
+    }
+
+    // ensure this is updated as sometimes it might not come from a signal
+    g_KeepRunning = false;
+
+    sLog.out ("Stop requested");
+
+    SDL_Quit ();
+}
+
+void CWallpaperApplication::signal (int signal)
+{
+    g_KeepRunning = false;
+}

+ 31 - 0
src/WallpaperEngine/Application/CWallpaperApplication.h

@@ -0,0 +1,31 @@
+#pragma once
+
+#include "CApplicationContext.h"
+#include "WallpaperEngine/Assets/CCombinedContainer.h"
+#include "WallpaperEngine/Core/CProject.h"
+#include "WallpaperEngine/Render/CWallpaper.h"
+
+namespace WallpaperEngine::Application
+{
+    using namespace WallpaperEngine::Core;
+    using namespace WallpaperEngine::Assets;
+
+    class CWallpaperApplication
+    {
+    public:
+        CWallpaperApplication (CApplicationContext& context);
+
+        void show ();
+        void signal (int signal);
+
+    private:
+        void setupContainer ();
+        void loadProject ();
+        void setupProperties ();
+        void takeScreenshot (WallpaperEngine::Render::CWallpaper* wp, const std::filesystem::path& filename, FREE_IMAGE_FORMAT format);
+
+        CProject* m_project;
+        CApplicationContext& m_context;
+        CCombinedContainer m_vfs;
+    };
+}

+ 23 - 0
src/WallpaperEngine/Assets/CCombinedContainer.cpp

@@ -1,5 +1,8 @@
 #include "CCombinedContainer.h"
 #include "CAssetLoadException.h"
+#include "CPackage.h"
+#include "CPackageLoadException.h"
+#include "WallpaperEngine/Logging/CLog.h"
 
 using namespace WallpaperEngine::Assets;
 
@@ -8,6 +11,26 @@ void CCombinedContainer::add (CContainer* container)
     this->m_containers.emplace_back (container);
 }
 
+void CCombinedContainer::addPkg (const std::filesystem::path& path)
+{
+    try
+    {
+        // add the package to the list
+        this->add (new CPackage (path));
+        sLog.out ("Detected ", path.filename (), " file at ", path, ". Adding to list of searchable paths");
+    }
+    catch (CPackageLoadException& ex)
+    {
+        // ignore this error, the package file was not found
+        sLog.out ("No ", path.filename (), " file found at ", path, ". Defaulting to normal folder storage");
+    }
+    catch (std::runtime_error& ex)
+    {
+        // the package was found but there was an error loading it (wrong header or something)
+        sLog.exception ("Failed to load scene.pkg file: ", ex.what());
+    }
+}
+
 const void* CCombinedContainer::readFile (std::string filename, uint32_t* length) const
 {
     for (auto cur : this->m_containers)

+ 2 - 4
src/WallpaperEngine/Assets/CCombinedContainer.h

@@ -1,11 +1,8 @@
-//
-// Created by almamu on 8/8/21.
-//
-
 #pragma once
 
 #include <vector>
 #include <stdexcept>
+#include <filesystem>
 
 #include "CContainer.h"
 
@@ -20,6 +17,7 @@ namespace WallpaperEngine::Assets
          * @param container
          */
         void add (CContainer* container);
+        void addPkg (const std::filesystem::path& path);
 
         const void* readFile (std::string filename, uint32_t* length) const override;
 

+ 46 - 51
src/WallpaperEngine/Render/Objects/Effects/CPass.cpp

@@ -716,68 +716,53 @@ void CPass::setupShaderVariables ()
         if (vertexVar == nullptr && pixelVar == nullptr)
             continue;
 
-        // if both can be found, ensure they're the correct type
-        /*if (vertexVar != nullptr && pixelVar != nullptr)
-        {
-            if (vertexVar->getType () != pixelVar->getType ())
-                throw std::runtime_error ("Pixel and vertex shader variable types do not match");
-        }*/
-
         // get one instance of it
         CShaderVariable* var = vertexVar == nullptr ? pixelVar : vertexVar;
 
         // ensure the shader's and the constant are of the same type
-        if (cur.second->getType () != var->getType ())
+        if (cur.second->getType () == var->getType ())
         {
-            // there's situations where this type mismatch is actually expected
-            // integers and floats are equivalent, this could be detected at load time
-            // but that'd mean to compile the shader in the load, and not on the render stage
-            // so take into account these conversions here
+            this->addUniform (var->getName (), cur.second);
+            continue;
+        }
 
-            if (cur.second->is <CShaderConstantFloat> () == true && var->is <CShaderVariableInteger> () == true)
-            {
-                // create an integer value from a float
-                this->addUniform (var->getName (), static_cast <int> (*cur.second->as <CShaderConstantFloat> ()->getValue ()));
-            }
-            else if (cur.second->is <CShaderConstantInteger> () == true && var->is <CShaderVariableFloat> () == true)
-            {
-                // create a float value from an integer
-                this->addUniform (var->getName (), static_cast <float> (*cur.second->as <CShaderConstantInteger> ()->getValue ()));
-            }
-            else if (cur.second->is <CShaderConstantVector4> () == true && var->is <CShaderVariableVector2> () == true)
-            {
-                CShaderConstantVector4* val = cur.second->as <CShaderConstantVector4> ();
+        // there's situations where this type mismatch is actually expected
+        // integers and floats are equivalent, this could be detected at load time
+        // but that'd mean to compile the shader in the load, and not on the render stage
+        // so take into account these conversions here
+        if (cur.second->is <CShaderConstantFloat> () == true && var->is <CShaderVariableInteger> () == true)
+        {
+            // create an integer value from a float
+            this->addUniform (var->getName (), static_cast <int> (*cur.second->as <CShaderConstantFloat> ()->getValue ()));
+        }
+        else if (cur.second->is <CShaderConstantInteger> () == true && var->is <CShaderVariableFloat> () == true)
+        {
+            // create a float value from an integer
+            this->addUniform (var->getName (), static_cast <float> (*cur.second->as <CShaderConstantInteger> ()->getValue ()));
+        }
+        else if (cur.second->is <CShaderConstantVector4> () == true && var->is <CShaderVariableVector2> () == true)
+        {
+            CShaderConstantVector4* val = cur.second->as <CShaderConstantVector4> ();
 
-                // create a new vector2 with the first two values
-                this->addUniform (var->getName (), {val->getValue ()->x, val->getValue ()->y});
-            }
-            else if (cur.second->is <CShaderConstantVector4> () == true && var->is <CShaderVariableVector3> () == true)
-            {
-                CShaderConstantVector4* val = cur.second->as <CShaderConstantVector4> ();
+            // create a new vector2 with the first two values
+            this->addUniform (var->getName (), {val->getValue ()->x, val->getValue ()->y});
+        }
+        else if (cur.second->is <CShaderConstantVector4> () == true && var->is <CShaderVariableVector3> () == true)
+        {
+            CShaderConstantVector4* val = cur.second->as <CShaderConstantVector4> ();
 
-                this->addUniform (var->getName (), {val->getValue ()->x, val->getValue ()->y, val->getValue ()->z});
-            }
-            else
-            {
-                sLog.exception (
-                    "Constant ",
-                    cur.first,
-                    " type does not match pixel/vertex shader variable and cannot be converted (",
-                    cur.second->getType (),
-                    " to ",
-                    var->getType ()
-                );
-            }
+            this->addUniform (var->getName (), {val->getValue ()->x, val->getValue ()->y, val->getValue ()->z});
         }
         else
         {
-            // now determine the constant's type and register the correct uniform for it
-            if (cur.second->is <CShaderConstantFloat> ())
-                this->addUniform (var->getName (), cur.second->as <CShaderConstantFloat> ()->getValue ());
-            else if (cur.second->is <CShaderConstantInteger> ())
-                this->addUniform (var->getName (), cur.second->as <CShaderConstantInteger> ()->getValue ());
-            else if (cur.second->is <CShaderConstantVector4> ())
-                this->addUniform (var->getName (), cur.second->as <CShaderConstantVector4> ()->getValue ());
+            sLog.exception (
+                "Constant ",
+                cur.first,
+                " type does not match pixel/vertex shader variable and cannot be converted (",
+                cur.second->getType (),
+                " to ",
+                var->getType ()
+            );
         }
     }
 
@@ -804,6 +789,16 @@ void CPass::addUniform (CShaderVariable* value)
     else if (value->is <CShaderVariableVector4> ())
         this->addUniform (value->getName (), const_cast <glm::vec4*> (reinterpret_cast <const glm::vec4*> (value->as <CShaderVariableVector4> ()->getValue ())));
 }
+void CPass::addUniform (const std::string& name, CShaderConstant* value)
+{
+    // now determine the constant's type and register the correct uniform for it
+    if (value->is <CShaderConstantFloat> ())
+        this->addUniform (name, value->as <CShaderConstantFloat> ()->getValue ());
+    else if (value->is <CShaderConstantInteger> ())
+        this->addUniform (name, value->as <CShaderConstantInteger> ()->getValue ());
+    else if (value->is <CShaderConstantVector4> ())
+        this->addUniform (name, value->as <CShaderConstantVector4> ()->getValue ());
+}
 
 void CPass::addUniform (const std::string& name, int value)
 {

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

@@ -4,6 +4,7 @@
 #include <utility>
 
 #include "WallpaperEngine/Render/Shaders/Variables/CShaderVariable.h"
+#include "WallpaperEngine/Core/Objects/Effects/Constants/CShaderConstant.h"
 #include "WallpaperEngine/Render/Objects/Effects/CMaterial.h"
 #include "WallpaperEngine/Render/Shaders/Compiler.h"
 #include "WallpaperEngine/Assets/ITexture.h"
@@ -13,6 +14,7 @@ namespace WallpaperEngine::Render::Objects::Effects
 {
     using namespace WallpaperEngine::Assets;
     using namespace WallpaperEngine::Render::Shaders::Variables;
+    using namespace WallpaperEngine::Core::Objects::Effects::Constants;
 
     class CMaterial;
 
@@ -90,6 +92,7 @@ namespace WallpaperEngine::Render::Objects::Effects
         void setupAttributes ();
         void addAttribute (const std::string& name, GLint type, GLint elements, const GLuint* value);
         void addUniform (CShaderVariable* value);
+        void addUniform (const std::string& name, CShaderConstant* value);
         void addUniform (const std::string& name, int value);
         void addUniform (const std::string& name, double value);
         void addUniform (const std::string& name, float value);