Jump to content

klepto2

Developers
  • Posts

    853
  • Joined

  • Last visited

Posts posted by klepto2

  1. Hi, i am trying to create some PBR materials based on the great Polyhaven archive. I download the following files: 

    Diffuse, nor_gl, displacement and arm images in PNG format.  A sample zip is attached. 

    When you right-click on the diffuse file in the editor and choose to generate material, a material with all slots filled correctly, but the arm and diffuse textures seems to be stored in BC7 which doesn't seem to work. 

    metal.zip

    • Upvote 1
  2.  

    while developing the PBR Texture generator I have found some flaw with the current hook system. It is somewhat inflexible. Soi tried to add my own Hook Manager:

    enum class HookState
    {
        IDLE,
        PENDING,
        RUNNING,
        FINISHED,
    };
    class HookManager;
    
    class Hook : public Object
    {
        friend class HookManager;
    
    private:
        void* _function;
        shared_ptr<Object> _extra;
        HookState _currentState;
        bool _stopping = false;
        bool _repeating = false;
        shared_ptr<HookManager> _manager;
    public:
        
        Hook(shared_ptr<HookManager> manager, void callback(const UltraEngine::Render::VkRenderer&, shared_ptr<Object>), shared_ptr<Object> extra, bool repeat = false)
        {
            _function = callback;
            _extra = extra;
            _currentState = HookState::IDLE;
            _repeating = repeat;
            _manager = manager;
        }
    
        void Start()
        {
            _currentState = HookState::PENDING;
        }
    
        void Stop()
        {
            _stopping = true;
        }
    
        HookState GetState() { return _currentState; }
    
        void SetExtra(shared_ptr<Object> extra)
        {
            _extra = extra;
        }
    };
    
    class HookManager : public Object
    {
        friend class Hook;
    
    private:
        static void UpdateDispatch(const UltraEngine::Render::VkRenderer& renderer, shared_ptr<Object> extra)
        {
            auto hookManager = extra->As<HookManager>();
            if (hookManager != NULL)
            {
                for (auto hook : hookManager->_hooks)
                {
                    if (hook->_stopping)
                    {
                        hook->_currentState = HookState::IDLE;
                        hook->_stopping = false;
                    }
    
                    if (hook->GetState() != HookState::FINISHED)
                    {
                        if (hook->GetState() == HookState::PENDING)
                        {
                            hook->_currentState = HookState::RUNNING;
                            auto f = (void(*)(const UltraEngine::Render::VkRenderer&, shared_ptr<Object>))hook->_function;
                            f(renderer, hook->_extra);
                        }
                        else if (hook->GetState() == HookState::RUNNING)
                        {
                            if (!hook->_repeating)
                            {
                                hook->_currentState = HookState::FINISHED;
                            }
                            else
                            {
                                hook->_currentState = HookState::PENDING;
                            }
                        }
                    }
                }
            }
        }
    
        inline static map<HookID,map<shared_ptr<World>, shared_ptr<HookManager>>> _mapWorldHookManager;
    
        vector<shared_ptr<Hook>> _hooks;
        shared_ptr<World> _world;
        HookID _hookId;
    
    public:
        static shared_ptr<HookManager> Get(shared_ptr<World> world, HookID hookId)
        {
            if (_mapWorldHookManager.size() == 0)
            {
                _mapWorldHookManager[HOOKID_RENDER] = {};
                _mapWorldHookManager[HOOKID_TRANSFER] = {};
            }
    
            if (HookManager::_mapWorldHookManager[hookId][world] == NULL)
            {
                auto mgr = std::make_shared<HookManager>(world, hookId);
                mgr->StartDispatching();
                HookManager::_mapWorldHookManager[hookId][world] = mgr;
            }
    
            return HookManager::_mapWorldHookManager[hookId][world];
        }
    
        void StartDispatching()
        {
            _world->AddHook(HookID::HOOKID_TRANSFER, HookManager::UpdateDispatch, Self(), true);
        }
    
        void Start(shared_ptr<Hook> hook)
        {
            auto position = std::find(_hooks.begin(), _hooks.end(), hook);
            if (position != _hooks.end())
            {
                hook->Start();
            }
        }
    
        void Stop(shared_ptr<Hook> hook)
        {
            auto position = std::find(_hooks.begin(), _hooks.end(), hook);
            if (position != _hooks.end())
            {
                hook->Stop();
            }
        }
    
        HookManager(shared_ptr<World> world, HookID hookId)
        {
            _world = world;
            _hookId = hookId;
        }
    
        shared_ptr<Hook> AddHook(void callback(const UltraEngine::Render::VkRenderer&, shared_ptr<Object>), shared_ptr<Object> extra , bool repeat = false)
        {
            auto hook = std::make_shared<Hook>(Self()->As<HookManager>(), callback, extra, repeat);
            _hooks.push_back(hook);
            return hook;
        }
    
        void RemoveHook(shared_ptr<Hook> hook)
        {
            auto position = std::find(_hooks.begin(), _hooks.end(), hook);
            if (position != _hooks.end())
                _hooks.erase(position);
        }
    };

     It allows to dispatch, Start/Stop hooks like a thread and also adds the ability check the state of a hook. 

    static void checkCallback(const UltraEngine::Render::VkRenderer&, shared_ptr<Object>)
    {
    }
      
    auto hookManager = HookManager::Get(_world, HOOKID_TRANSFER);
    auto checkHook = _hookManager->AddHook(checkCallback, _world, false);
    hookManager->Start(checkHook);
      
    while(checkHook->GetState() != HookState::Finished)
    {
      world->Update();
      world->Render();
    }  
     
      

    This makes it a bit easier to watch the Hook execution.

    I use it in my PBRTextureGen and in my current state of Computeshader Implementation. It makes it much easier to manage the hooks. It might stll have a few bugs in it as i haven't fully tested every usecase, but for the basics it works :)

     

     

  3. I have done something similar for my PBRGenerator, and will post the class to download data from the gpu soon. Syncing memory is always at a cost this goes for downloading and uploading the data. There are some tricks you can use to increase the speed a lot, but most of the time you just use staging buffers (these are buffers which are bound to eaither gpu or cpu sepending on the direction you want to transfer the data) 

    1. create a buffer object 
    2. map the buffer to the memory
    3. commit the upload or download commands
    4. unmap the buffer and retrieve the data (the buffer can be still mapped)

    There are muliple ways to retrieve data from the gpu. The slowest is to use vkCmdCopyImage, then vkCmdBlitImage (slightly faster and already does format conversion and filtering, is not available for all formats). The fastest way is to use vkCmdCopyBuffer, this copies the pure gpu memory of a bound resource into the host memory. This is mostly used for data readbacks for compute shaders (shader storage buffers), but also useful for image synchronsation. 

    The usage of snycing between gpu and host should be used carefully, it can slow down an application fast, and should only be considered for debugging or useful info, lets say a generated water heightmap, where you want the to calculate eg: the height an object is on. 

    This being said, it would be nice to get access to the index or vertex buffer elements of a specific surface (speaking of particle systems etc.) ;)

     

    • Thanks 1
  4. I don't know exactly if this is the right place for this, but feel free to move it where ever you like,

    After the debugging fix in the latest release, I have started to experiment with the Lua extension integration.

    Some things I noticed:

    • Currently, there is no way to dynamically down cast an entity to its derived class, e.g.: Entity → Model
      • This is not supported by sol out of the box, but might be needed later as well for type checking.
      • Currently, you can access all entities in the world, but not determine if it is something other than an entity.
    • Some things are not yet accessible for extensions:
      • The objects in the scene (I found out how to add items, not very hard) but I can't find a way to access the already added items in the scene. (using the world property of the program is not very useful because of the first problem (cast/type check) I have mentioned)
    • Currently, there are no advanced extensions available, and as I understand that the focus lies on the release on bug fixing I would appreciate some documentation on how to properly script extensions (usage of the Undo/Redo, scene access) 

    It would be nice to see some more advanced scripting options soon.

    I especially like some of the syntaxes which are available through sol3:

    local ent = world:GetEntitiesInArea(min,max)
    for i = 1, #ent do
            local e = ent[i]
            local name = getmetatable(e).__type.name -- unfortunatly only returns "UltraEngine::Entity" and nothing else. No way to cast
            Print(name)
    end

     

  5. As i wanted to investigate some editor scripting possibilities, I tried using VSCode to debug the editor like it is described here:

    Clicking on Debug in VSCode starts the Editor, but the devcat debugger is waiting for the editor to connect.

    image.png.c5b7b3620f86648068eb6ffeb0298a95.png

    Without the ability to debug it is hard to guess, what objects and things are available to script the editor (the available current scripts mainly add some menu item only)

  6. The sliding door component has a small bug in the load method:

      if (t["enabled"].is_boolean()) enabled = t;

    should be:

      if (t["enabled"].is_boolean()) enabled = t["enabled"];

    otherwise after reloading the scene from a saved game the door becomes inactive.

  7. 9 hours ago, reepblue said:

    Must be the component/engine. The system just calls World::Save()/World::Load() functions. 

    I think the same, will investigate it further and file a bug report on this :)

    9 hours ago, reepblue said:

    In due time. This went from being something you dropped in your existing project to a full template. I decided to go this route because adding all the files was tedious, and it helps prevent any conflicts Josh may want to push out. 

    That is fully understandable, I just wanted to point out that, once the system is stable and feature ready enough (and UltraEngine itself as well), you should consider to add some advanced sample as a documentation. But to be fair, this is a free piece of code and is a already a great starting point for anyone who wants to start developing with Ultraengine.

    9 hours ago, reepblue said:

    This is kind of tricky to do since we need to store the previous pause state and I'm not sure where to store it. The softlock is due to the stock Components not being setup to unpause the game. 

    One thing that comes into my mind is: why doing a pause generally. The easiest solution might be to don't pause at all and leave it up to the developer. As a reference the ingame consoles in most games don't pause (i can't really think of any game which pauses the game in this case)

     

    9 hours ago, reepblue said:
    • Terminate() now asks if you really mean to force close the application. Sorta annoying but it feels much cleaner. Now you know if your game closes without asking, it's decently a crash. (To close the game without a confirmation, just emit EVENT_QUIT. Terminate is only used to get you out of a sticky situation.)

    maybe an optional parameter can be used, something like "Terminate(bool force = false, int closeReason = 0);". This could be used in case of errors.

  8. 2 hours ago, reepblue said:

    My guess is that Windows is blocking the direct execution because it's a foreign app.  Manually run the application tell UAC to trust the app. 

    Or build try building the Preprocessor from the GitHub and use your version. Ether way, I didn't factor that into account. 🫢

    thats it :)

    I have tested the template a bit and found a few things:

    • the settings.bat file is not updated with the template name
    • it would be nice to have the console enabled in the sample by default
    • adding the console window and sending an empty command causes a crash
    • closing the console window doesn't resume the scene
    • TODOS: 
      • Terminate behavior (IDEAS)
        • Add a callback function to the terminate method 
        • default show a message box "Do you really want to quit?"
        • This could be extended to allow something like Back to Main menu or Back to Desktop
    • Using the sample scene (don't know if this is a bug in UltraEngine or the template) saving a quick save and reloading disables the door component. The close or open state works, but after the closing or opening the trigger will not work anymore

    Some ideas for the template itself:

    • Try to add some more samples showing the intended workflow (e.g.: Main-Menu, loading and saving, in game options)
    • Provide an in game console which did not need an actual window. (the console is very nice, but it is a bit of the mainstream if another window is opening, especially when the app is running in full screen)
    • Like 1
  9. On 10/29/2023 at 2:51 AM, reepblue said:

    Although cxxopts looks interesting, I wanted to stay exclusive to the engine's API for this. I didn't want to do anything that could/would change the workflow of how things are normally done with the engine.

    I might consider separating the logs by type though, I already have it sorted by colors at this point. 

    Valid point. It is always good to use the provided api. Also, it is not hard to switch this out when needed. 

    Just a small snippet I used in the Texture Gen to get proper redirection in windows apps which have no console:

    if (!AttachConsole(ATTACH_PARENT_PROCESS))
            {
                if (AllocConsole())
                {
                    freopen("conin$", "r", stdin);
                    freopen("conout$", "w", stdout);
                    freopen("conout$", "w", stderr);
                }
            }
            else
            {
                auto consoleHandleOut = GetStdHandle(STD_OUTPUT_HANDLE);
                auto consoleHandleIn = GetStdHandle(STD_INPUT_HANDLE);
                auto consoleHandleErr = GetStdHandle(STD_ERROR_HANDLE);
                if (consoleHandleOut != INVALID_HANDLE_VALUE) {
                    freopen("conout$", "w", stdout);
                    setvbuf(stdout, NULL, _IONBF, 0);
                }
                if (consoleHandleIn != INVALID_HANDLE_VALUE) {
                    freopen("conin$", "r", stdin );
                    setvbuf(stdin, NULL, _IONBF, 0);
                }
                if (consoleHandleErr != INVALID_HANDLE_VALUE) {
                    freopen("conout$", "w", stderr);
                    setvbuf(stderr, NULL, _IONBF, 0);
                }
            }

    This is useful if you compile an UltraEngine app in release, but also want to mix cmdline features with a standard window app. This redirects the output to the console window if possible (e.G: when running from a batch file) otherwise it creates a new console (eg when it is used without a batch, but with a link-file).   

    • Like 1
  10. yeah, this https://github.com/graphitemaster/incbin is the same i want use now. 

    19 hours ago, Josh said:

    The thought has occurred to me, but what is the purpose of loading plugins from memory?

    The idea is to supply a basic set of plugins (or directly needed) directly in the executable itself. The idea is to have the ability to include everything in the executable and provide single file tools. This would make distribution easier. Lets say you have a tools folder with a lot of tools. 

    1. every folder needs the plugins, but have different search pathes for the plugins (eg:plugins or plg)
    2. each tool might be dependend on a specific version of a plugin and is not updated to use the plugins which are there.
    3. the executable will be bigger, but it may be worth it.
  11. I am currently working through this nice gem: https://placeholderart.wordpress.com/2015/07/28/implementation-notes-runtime-environment-map-filtering-for-image-based-lighting/

    and i just made one small adjustment to the ibl shader pipeline (i simply reduce the samples based on the roughness, roughness = 0 --> samples = 32, roughness = 1.0 --> samples = maxSamples(default 1024)) and with this i was able to generate even 4k cubemaps without the lost device error and nearly the same quality. of course this has be altered to be used only, when x mipmaps are available. But maybe i can integrate some other ideas from this blog as well.

  12. 14 hours ago, Josh said:

    Wow, this is so so good. Can it convert a folder of panoramas to environment maps?

    This will convert a whole folder of HDRIs. This saves so much time:

    UltraPBRTextureGen.exe -b -p -d ./hdris

    FYI, with big textures and a 1024 sample count, some graphics cards can time out on that IBL shader and produce the VK_DEVICE_LOST error:
    https://github.com/KhronosGroup/glTF-IBL-Sampler/issues/8

    I remember setting some OS setting or reg key to allow a longer timeout with the Vulkan driver, when I was using the Khronos app.

    yes i noticed this as well, in an early version it was as soon as I created a cubemap > 512px. Unlike the method of the IBL Sampler from Khronos I am using a compute pipeline for this, which means i could reduce the errors by maximizing the Workgroup sizes to the allowed maximum. I have multiple compute shaders with different Local sizes and choose the correct one based on the current MipMapsize. 

    It still happens with textures > 1024 (not everytime but from time to time) as i currently have just added 2 shaders one with a localSize of 1 and one with a local size of 32. But i will try to optimize it. Some suggestions to speed it up are not included in the IBL sampler code (e.g: precalculations of some values).

  13. Wow, this is really amazing and it is very nice to share this gem. I am looking forward for the blog entry.

    Some small ideas after a short look into the code: 

    • The Print outputs in some classes could be managed by some kind of log manager.  With redirecting to error logs etc...
    • While the ParseCommandline and the table class is very powerful, I would use a more abstract and typesafe way for the commandlines

    in the PBR Texture Generator i use this gem: https://github.com/jarro2783/cxxopts/tree/master for command line handling:

    the usage is very simple and straight forward:

    bool showhelp = false;
        bool batch = false;
        bool cubemap = false;
        bool panorama = false;
        string outdir;
        string dir;
        string file;
        int samples;
        float lodbias;
        float exposure;
        int diffuse_res;
        int specular_res;
    
        cxxopts::Options options("UltraPBRTextureGen.exe", "this tool generates the required PBR textures from panorama HDR files or Cubemaps (*.dds or *.tex) files");
        options.allow_unrecognised_options();
        options.add_options()
            ("b, batch", "Batch operations", cxxopts::value<bool>(batch)->default_value("false")) // a bool parameter
            ("c, cubemap", "Convert cubemaps (dds, tex)", cxxopts::value<bool>(cubemap)->default_value("false"))
            ("p, panorama", "Convert panoramas (hdr)", cxxopts::value<bool>(panorama)->default_value("false"))
            ("d, dir", "path to search the files (only used with batch option)", cxxopts::value<std::string>(dir))
            ("o, out", "output folder", cxxopts::value<std::string>(outdir))
            ("f, file", "file to convert (only when not used with batch option)", cxxopts::value<std::string>(file))
            ("e, exposure", "Exposure to be used when importing HDR images", cxxopts::value<float>(exposure)->default_value("4.0"))
            ("s, samples", "Samples to be used", cxxopts::value<int>(samples)->default_value("1024"))
            ("lb, lodbias", "LodBias to be used", cxxopts::value<float>(lodbias)->default_value("0.0"))
            ("dr, diffuse_resolution", "Resolution for the diffuse map", cxxopts::value<int>(diffuse_res)->default_value("128"))
            ("sr, specular_resolution", "Resolution for the specular map", cxxopts::value<int>(specular_res)->default_value("1024"))
            ("h,help", "Show help", cxxopts::value<bool>(showhelp)->default_value("false"));
    
    
        auto result = options.parse(argc, argv);
    
     	if (showhelp)
        {
            Print(options.help());
            return 0;
        }

     

  14. 13 hours ago, Josh said:

    Sorry, I should have mentioned that event.text will contain the printed text, so you can use that to filter which messages get printed and which get skipped. Return true and the message will go into the Print routine.

    Well, no need for mentioning it. It was very obvious ;)

    Filtering is very difficult with the current log messages, maybe the engine logging should be something like this: "[UltraEngine] Timestamp : Texture "xyz" loaded. This way a developer could simply filter out everything which begins with "[UltraEngine]", otherwise this could collide with mesages intended by the developer.

    Another way, which might be useful is some kind of log format setting. A simple function ptr which receives the msg and outputs a custom formatted msg. (Could be added to EngineSettings)

    • Haha 1
  15. UltraPBRTextureGen_v0_8.zip

    New Version with exposure added to the import of hdr images.

    Changes: 

    • HDR files are now loaded as real HDR images and not converted to LDR before processing
    • Exposure parameter added (default 4.0) 
    • Gui:
      • Added Exposure controls and rebuild panorama button
      • When you import a panorama or cubemap, the initial build is triggered.
    • Like 1
  16. Here it is a first release of my PBR Texture generator made with UltraEngine:

    UltraPBRTextureGen.zip

    [New-Version with real hdr and Exposure]

    UltraPBRTextureGen_v0_8.zip

    Features:

    • Standalone UI-App with preview and settings
      • Allows single import of panorama (*.hdr) and cubemap (*dds and *.tex) files
      • Adjustments for samples / lodbias and resolutions
      • Saving is limited (it currently saves only to appdir and the names are fixed to sky_diffuse/sky_specular
    • CLI 
      • Allows batch and single operations on folders or single files
      • Full set of parameters here:
    Usage:
      UltraPBRTextureGen.exe [OPTION...]
    
      -b, --batch        Batch operations
      -c, --cubemap      Convert cubemaps (dds, tex)
      -p, --panorama     Convert panoramas (hdr)
      -d, --dir arg      path to search the files (only used with batch option)
      -o, --out arg      output folder
      -f, --file arg     file to convert (only when not used with batch option)
      -s, --samples arg  Samples to be used (default: 1024)
          --lb arg       LodBias to be used (default: 0.0)
          --dr arg       Resolution for the diffuse map (default: 128)
          --sr arg       Resolution for the specular map (default: 1024)
      -h, --help         Show help
    

    In the folder cli-samples is a subfolder for HDRI images, for the sample commands to work you first need to download one or more HDR images from a source like https://polyhaven.com/hdris

    Screenshot:

    image.thumb.png.5a425f9cf88c2788cb04b266a233ac25.png

    the generated files can directly be used in the UltraEngine Editor under MAP/Environment/(Background/Specular/Diffuse) to provide proper PBR lighting. 

    Planned Features:

    • Fix save behaviour in the GUI-App
    • Add more image formats
    • Add Exposure control for generation like here: https://matheowis.github.io/HDRI-to-CubeMap/
    • provide a small cpp lib to enable on the fly (realtime) generation of these textures.
    • Like 2
    • Thanks 2
    • Upvote 1
  17. 15 hours ago, Josh said:

    Paste in the console:

    function hook() return false end ListenEvent(EVENT_PRINT, nil, hook)

    ok, this prevents any message to be displayed. So, if i want to still use Output i need to write my own print functions, but this is an unforunate step :( maybe the print functions need an optional source parameter. 

    In case someone wants a small sample:

    bool PrintHook(const Event& ev, shared_ptr<Object> extra)
    {
        if (ev.data == 1)
        {
            fwprintf(stdout, ev.text.c_str());
            fflush(stdout);
            return true;
        }
    
        return false;
    }
    
    void PrintLog(const std::string& s) { EmitEvent(EVENT_PRINT, 0, 1, 0, 0, 0, 0, 0, s); }
    void PrintLog(const String& s) { PrintLog(std::string(s)); }
    void PrintLog(const WString& s, const bool linereturn = true) { EmitEvent(EVENT_PRINT, 0, 1, 0, 0, 0, 0, 0, s + (linereturn ? "\n" : "")); };
    void PrintLog(const unsigned int i, const bool linereturn = true) { PrintLog(WString(i), linereturn); }
    void PrintLog(const uint64_t i, const bool linereturn = true) { PrintLog(WString(i), linereturn); }
    void PrintLog(const int i, const bool linereturn = true) { PrintLog(WString(i), linereturn); }
    void PrintLog(const float i, const bool linereturn = true) { PrintLog(WString(i), linereturn); }
    void PrintLog(const double i, const bool linereturn = true) { PrintLog(WString(i), linereturn); }
    void PrintLog(const bool i, const bool linereturn = true) { PrintLog(WString(i), linereturn); }
    void PrintLog(const char* i, const bool linereturn = true) { PrintLog(WString(i), linereturn); }
    void PrintLog(const wchar_t* i, const bool linereturn = true) { PrintLog(WString(i), linereturn); }
    void PrintLog(const table& t) { PrintLog(string(t)); }
      
    int main(int argc, const char* argv[])
    {
        ListenEvent(EVENT_PRINT, NULL, PrintHook);
      
      	PrintLog("My-Message");
      	return 0;
    }
    
      

     

  18. As i am currently working on a cli tool, i found that the constant "spamming" of UltraEngine logs is quite annoying. 

    While these messages are good for debugging or even for release builds,It would by nice to disable them, or redirect them. 

    Espescially the messages with just an infomative character should be optional, like "Texture xxx loaded..." othe messages like errors should go to the stderr and everything else should be managed by the developer.

×
×
  • Create New...