post-title

For a long time I had this idea of writing a toy renderer application that I could use as a sandbox environment for rapid prototyping of things on both SO and shader side of things.

One thing that always fascinated me about tools like ShaderToy was their ability to change a shader on the fly while you are typing, which greatly improves development speed.

On my quest to try and do something similar I started looking into the Win32 API to watch changes in files under a directory. In my case I have a folder called shaders which I’m interested in monitoring so I can recompile any files that were changed while the application was running to immediately see the results on screen.

The simplest of such APIs I could find is the ReadDirectoryChangesW in synchronous mode.

I start with this minimal piece of code to try the API:

#include <iostream>
#include <filesystem>

#include <Windows.h>

namespace fs = std::filesystem;

int main()
{
    HANDLE DirHandle = CreateFile((fs::current_path() / "shaders").wstring().c_str(),
                                  FILE_LIST_DIRECTORY,
                                  FILE_SHARE_READ | FILE_SHARE_WRITE,
                                  NULL,
                                  OPEN_EXISTING,
                                  FILE_FLAG_BACKUP_SEMANTICS, NULL);

    while (true)
    {
        DWORD BytesReturned;
        UCHAR Buffer[64 * 1024];
        const DWORD NotifyFilter = FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE;
        if (ReadDirectoryChangesW(DirHandle, Buffer, sizeof(Buffer), FALSE, NotifyFilter, &BytesReturned, NULL, NULL) == TRUE)
        {
            DWORD Offset = 0;
            FILE_NOTIFY_INFORMATION* Info;
            do
            {
                Info = (FILE_NOTIFY_INFORMATION*) &Buffer[Offset];

                switch (Info->Action)
                {
                    case FILE_ACTION_ADDED:
                        std::cout << "Add: ";
                        break;

                    case FILE_ACTION_MODIFIED:
                        std::cout << "Modified: ";
                        break;

                    case FILE_ACTION_REMOVED:
                        std::cout << "Removed: ";
                        break;

                    case FILE_ACTION_RENAMED_NEW_NAME:
                        std::cout << "Renamed New Name: ";
                        break;

                    case FILE_ACTION_RENAMED_OLD_NAME:
                        std::cout << "Renamed Old Name: ";
                        break;
                }

                std::wstring FileNameW{ Info->FileName, Info->FileNameLength / sizeof(WCHAR) };
                std::filesystem::path FilePath{ FileNameW };

                std::cout << FilePath << std::endl;

                Offset += Info->NextEntryOffset;
            }
            while (Info->NextEntryOffset != 0);
        }
    }

    return 0;
}

Running this program and changing the file triangle.frag using VSCode gives the following output:

Modified: "triangle.frag"
Modified: "triangle.frag"

Double notification for the Modified action but that can be dealt with. Notepad gives a single Modified output.

Now behold, for this is the output of this program when the very same file is modified and saved through Visual Studio (i.e. open the file and press Ctrl+S), in this case Microsoft Visual Studio Professional 2022 (64-bit) - Preview Version 17.7.0 Preview 2.0

Add: "3bymjl5v.l0i~"
Modified: "3bymjl5v.l0i~"
Modified: "3bymjl5v.l0i~"
Add: "triangle.frag~RF728f35.TMP"
Removed: "triangle.frag~RF728f35.TMP"
Renamed Old Name: "triangle.frag"
Renamed New Name: "triangle.frag~RF728f35.TMP"
Renamed Old Name: "3bymjl5v.l0i~"
Renamed New Name: "triangle.frag"
Modified: "triangle.frag"
Removed: "triangle.frag~RF728f35.TMP"

I will leave to you, the reader, to wonder why Visual Studio tends to very slow sometimes. I’m sure there might be a reason behind all these operations but this caught me off guard.