Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shader hot compile and reload #421

Merged
merged 9 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 43 additions & 37 deletions Engine/Source/Editor/EditorApp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
#include "Rendering/TerrainRenderer.h"
#include "Rendering/WorldRenderer.h"
#include "Rendering/ParticleRenderer.h"
#include "Resources/FileWatcher.hpp"
#include "Resources/FileWatcher.h"
#include "Resources/ResourceBuilder.h"
#include "Resources/ShaderBuilder.h"
#include "Scene/SceneDatabase.h"
Expand Down Expand Up @@ -113,39 +113,7 @@ void EditorApp::Init(engine::EngineInitArgs initArgs)
});
resourceThread.detach();

#if 0
// Test code, will be removed in the next PR.
auto FileWatchCallback = [](
WatchID id, WatchAction action,
const char* rootDir, const char* filePath,
const char* oldFilePath, void* user)
{
switch (action)
{
case DMON_ACTION_CREATE:
CD_FATAL("Create");
break;

case DMON_ACTION_DELETE:
CD_FATAL("Delete");
break;

case DMON_ACTION_MODIFY:
CD_FATAL("Modify");
break;

case DMON_ACTION_MOVE:
CD_FATAL("Move");
break;

default:
CD_FATAL("Default");
}
};

m_pFileWatcher = std::make_unique<FileWatcher>();
m_pFileWatcher->Watch(CDENGINE_BUILTIN_SHADER_PATH, FileWatchCallback, DMON_WATCHFLAGS_RECURSIVE, nullptr);
#endif
InitFileWatcher();
}

void EditorApp::Shutdown()
Expand Down Expand Up @@ -377,6 +345,24 @@ void EditorApp::InitDDGIEntity()
}
#endif

void EditorApp::InitFileWatcher()
{
constexpr const char* watchPath = CDENGINE_BUILTIN_SHADER_PATH "shaders/";
roeas marked this conversation as resolved.
Show resolved Hide resolved

m_pFileWatcher = std::make_unique<FileWatcher>();
m_pFileWatcher->SetRenderContext(m_pRenderContext.get());
m_pFileWatcher->SetWindow(GetMainWindow());
m_pFileWatcher->WatchShaders(watchPath);
}

void EditorApp::ShaderHotModifyDetec()
roeas marked this conversation as resolved.
Show resolved Hide resolved
{
if (true == m_crtInputFocus)
{
return;
}
}

void EditorApp::UpdateMaterials()
{
for (engine::Entity entity : m_pSceneWorld->GetMaterialEntities())
Expand All @@ -387,20 +373,37 @@ void EditorApp::UpdateMaterials()
continue;
}

m_pRenderContext->CheckShaderProgram(pMaterialComponent->GetProgramName(), pMaterialComponent->GetFeaturesCombine());
const std::string& programName = pMaterialComponent->GetProgramName();
const std::string& featuresCombine = pMaterialComponent->GetFeaturesCombine();

// New shader feature added, need to compile new variants.
m_pRenderContext->CheckShaderProgram(programName, featuresCombine);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CheckXXX seems like a const function which just returns bool result if shader program modified. How about UpdateShaderProgram?

Copy link
Contributor Author

@roeas roeas Nov 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will add shader compilt task when we don't have a shader program handle and register the new shader feature to ShaderCollections, which did a little bit more than Update?


// Shader source files have been modified, need to re-compile existing variants.
if (true == m_crtInputFocus && false == m_preInputFocus)
roeas marked this conversation as resolved.
Show resolved Hide resolved
{
m_pRenderContext->CheckModifiedShaderProgram(programName, featuresCombine);
}
}

if (true == m_crtInputFocus && false == m_preInputFocus)
roeas marked this conversation as resolved.
Show resolved Hide resolved
{
m_pRenderContext->ClearModifiedProgramNameCrcs();
}
}

void EditorApp::LazyCompileAndLoadShaders()
void EditorApp::RuntimeCompileAndLoadShaders()
roeas marked this conversation as resolved.
Show resolved Hide resolved
{
bool doCompile = false;

// 1. Compile
for (const auto& task : m_pRenderContext->GetShaderCompileTasks())
{
ShaderBuilder::BuildShader(m_pRenderContext.get(), task);
doCompile = true;
}

// 2. Load
if (doCompile)
{
ResourceBuilder::Get().Update(true);
Expand Down Expand Up @@ -629,6 +632,7 @@ bool EditorApp::Update(float deltaTime)
}

GetMainWindow()->Update();
m_crtInputFocus = GetMainWindow()->GetInputFocus();
m_pEditorImGuiContext->Update(deltaTime);
m_pSceneWorld->Update();

Expand Down Expand Up @@ -675,7 +679,7 @@ bool EditorApp::Update(float deltaTime)
m_pEngineImGuiContext->Update(deltaTime);

UpdateMaterials();
LazyCompileAndLoadShaders();
RuntimeCompileAndLoadShaders();

for (std::unique_ptr<engine::Renderer>& pRenderer : m_pEngineRenderers)
{
Expand All @@ -693,6 +697,8 @@ bool EditorApp::Update(float deltaTime)

engine::Input::Get().FlushInputs();

m_preInputFocus = m_crtInputFocus;

return !GetMainWindow()->ShouldClose();
}

Expand Down
7 changes: 6 additions & 1 deletion Engine/Source/Editor/EditorApp.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,13 @@ class EditorApp final : public engine::IApplication
void InitDDGIEntity();
#endif

void InitFileWatcher();
void ShaderHotModifyDetec();
void UpdateMaterials();
void LazyCompileAndLoadShaders();
void RuntimeCompileAndLoadShaders();

bool m_crtInputFocus = true;
bool m_preInputFocus = true;
Comment on lines +91 to +92
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like you want to check if current window is active. This will be improved after my WindowManager refactor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feature is most interested in detecting the moment when the editor window regains focus.


bool m_bInitEditor = false;
engine::EngineInitArgs m_initArgs;
Expand Down
135 changes: 135 additions & 0 deletions Engine/Source/Editor/Resources/FileWatcher.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#include "FileWatcher.h"

#include "Log/Log.h"
#include "Path/Path.h"
#include "Rendering/RenderContext.h"
#include "Window/Window.h"

#define DMON_LOG_DEBUG(s) do { CD_INFO(s); } while(0)
#define DMON_LOG_ERROR(s) do { CD_ERROR(s); assert(false); } while(0)

#define DMON_IMPL
#include "dmon.h"

namespace editor
{

using WatchID = dmon_watch_id;
using WatchAction = dmon_action;

class ShaderHotModifyCallbackWrapper final
{
public:
ShaderHotModifyCallbackWrapper() = default;
ShaderHotModifyCallbackWrapper(const ShaderHotModifyCallbackWrapper&) = delete;
ShaderHotModifyCallbackWrapper& operator=(const ShaderHotModifyCallbackWrapper&) = delete;
ShaderHotModifyCallbackWrapper(ShaderHotModifyCallbackWrapper&&) = delete;
ShaderHotModifyCallbackWrapper& operator=(ShaderHotModifyCallbackWrapper&&) = delete;
~ShaderHotModifyCallbackWrapper() = default;

static void Callback(
WatchID id, WatchAction action,
const char* rootDir, const char* filePath,
const char* oldFilePath, void* user)
{
switch (action)
{
case DMON_ACTION_CREATE:
CD_TRACE("New file created");
CD_TRACE(" Path : {0}", rootDir);
CD_TRACE(" Name : {0}", filePath);
break;

case DMON_ACTION_DELETE:
CD_TRACE("File deleted");
CD_TRACE(" Path : {0}", rootDir);
CD_TRACE(" Name : {0}", filePath);
break;

case DMON_ACTION_MODIFY:
CD_TRACE("File Modified");
CD_TRACE(" Path : {0}", rootDir);
CD_TRACE(" Name : {0}", filePath);

if (m_pWindow->GetInputFocus() || engine::Path::GetExtension(filePath) != ".sc")
{
// Return when window get focus.
// Return when a non-shader file is detected.
return;
}

m_pRenderContext->CheckModifiedProgram(engine::Path::GetFileNameWithoutExtension(filePath));

break;

case DMON_ACTION_MOVE:
CD_TRACE("File moved");
CD_TRACE(" Path : {0}", rootDir);
CD_TRACE(" Old Name : {0}", oldFilePath);
CD_TRACE(" New Name : {0}", filePath);
break;

default:
CD_WARN("Unknown WatchAction!");
}
}

static engine::RenderContext* m_pRenderContext;
static engine::Window* m_pWindow;
};

engine::RenderContext* ShaderHotModifyCallbackWrapper::m_pRenderContext = nullptr;
engine::Window* ShaderHotModifyCallbackWrapper::m_pWindow = nullptr;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One way to avoid static is to add delegates for EditorApp methods to bind. Here to invoke.
Can be improved after my multiple window PR merged. Or may cause many conflicts.


FileWatcher::FileWatcher()
{
Init();
}

FileWatcher::~FileWatcher()
{
Deinit();
}

void FileWatcher::Init() const
{
dmon_init();
}

void FileWatcher::Deinit() const
{
dmon_deinit();
roeas marked this conversation as resolved.
Show resolved Hide resolved
}

uint32_t FileWatcher::WatchShaders(const char* rootDir)
{
ShaderHotModifyCallbackWrapper::m_pRenderContext = m_pRenderContext;
ShaderHotModifyCallbackWrapper::m_pWindow = m_pWindow;

WatchID watchID = dmon_watch(rootDir, ShaderHotModifyCallbackWrapper::Callback, 0, nullptr);

CD_INFO("Start watching {0}", rootDir);
CD_INFO(" Watch ID {0}", watchID.id);

m_witchInfos[watchID.id] = rootDir;
roeas marked this conversation as resolved.
Show resolved Hide resolved

return watchID.id;
}

void FileWatcher::UnWatch(uint32_t watchID)
{
dmon_unwatch(WatchID{ watchID });
m_witchInfos.erase(watchID);
}

void FileWatcher::SetWitchInfos(std::map<uint32_t, const char*> witchInfos)
roeas marked this conversation as resolved.
Show resolved Hide resolved
{
m_witchInfos = cd::MoveTemp(witchInfos);
}

const char* FileWatcher::GetWatchingPath(uint32_t id) const
{
return m_witchInfos.at(id);
}

}
48 changes: 48 additions & 0 deletions Engine/Source/Editor/Resources/FileWatcher.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#pragma once

#include <cstdint>
#include <map>

namespace engine
{

class RenderContext;
class Window;

}

namespace editor
{

class FileWatcher final
{

public:
FileWatcher(const FileWatcher&) = delete;
FileWatcher& operator=(const FileWatcher&) = delete;
FileWatcher(FileWatcher&&) = delete;
FileWatcher& operator=(FileWatcher&&) = delete;

FileWatcher();
~FileWatcher();

void Init() const;
void SetRenderContext(engine::RenderContext* pRenderContext) { m_pRenderContext = pRenderContext; }
void SetWindow(engine::Window* pWindow) { m_pWindow = pWindow; }
void Deinit() const;

uint32_t WatchShaders(const char* rootDir);
void UnWatch(uint32_t watchID);

void SetWitchInfos(std::map<uint32_t, const char*>);
std::map<uint32_t, const char*>& GetWitchInfos() { return m_witchInfos; }
const std::map<uint32_t, const char*>& GetWitchInfos() const { return m_witchInfos; }
const char* GetWatchingPath(uint32_t id) const;

private:
std::map<uint32_t, const char*> m_witchInfos;
engine::RenderContext* m_pRenderContext;
engine::Window* m_pWindow;
};

}
Loading