mirror of
https://github.com/barkeser2002/flower.git
synced 2026-09-25 14:16:14 +03:00
432 lines
12 KiB
C++
432 lines
12 KiB
C++
#include "editor.h"
|
|
#include "editor_asset.h"
|
|
#include <utf8.h>
|
|
#include <utf8/cpp17.h>
|
|
#include <imgui/imgui_impl_vulkan.h>
|
|
#include <asset/asset.h>
|
|
#include <scene/scene.h>
|
|
#include <asset/asset_common.h>
|
|
#include <nfd.h>
|
|
|
|
#if _WIN32
|
|
#include <Windows.h>
|
|
#endif
|
|
|
|
using namespace engine;
|
|
using namespace engine::ui;
|
|
|
|
Editor* Editor::get()
|
|
{
|
|
static Editor editor;
|
|
return &editor;
|
|
}
|
|
|
|
void Editor::initBuiltinResources()
|
|
{
|
|
auto flushUploadImage = [&](const char* path)
|
|
{
|
|
int32_t texWidth, texHeight, texChannels;
|
|
stbi_uc* pixels = stbi_load(path, &texWidth, &texHeight, &texChannels, 4);
|
|
ASSERT(pixels, "Load builtin folder image fail, check your install path.");
|
|
|
|
auto newImage = std::make_unique<VulkanImage>(
|
|
m_context,
|
|
path,
|
|
buildBasicUploadImageCreateInfo(texWidth, texHeight));
|
|
|
|
auto stageBuffer = std::make_unique<VulkanBuffer>(
|
|
m_context,
|
|
"StageUploadBuffer",
|
|
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
|
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT,
|
|
texWidth * texHeight * 4,
|
|
pixels);
|
|
|
|
m_context->executeImmediatelyMajorGraphics([&](VkCommandBuffer cmd)
|
|
{
|
|
newImage->transitionLayout(cmd, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, buildBasicImageSubresource());
|
|
|
|
VkBufferImageCopy region{};
|
|
region.bufferOffset = 0;
|
|
region.bufferRowLength = 0;
|
|
region.bufferImageHeight = 0;
|
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
region.imageSubresource.mipLevel = 0;
|
|
region.imageSubresource.baseArrayLayer = 0;
|
|
region.imageSubresource.layerCount = 1;
|
|
region.imageOffset = { 0, 0, 0 };
|
|
region.imageExtent = newImage->getExtent();
|
|
vkCmdCopyBufferToImage(cmd, stageBuffer->getVkBuffer(), newImage->getImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
|
|
|
newImage->transitionLayout(cmd, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, buildBasicImageSubresource());
|
|
});
|
|
|
|
stbi_image_free(pixels);
|
|
return std::move(newImage);
|
|
};
|
|
|
|
m_builtinResources.folderImage = flushUploadImage("image/folder.png");
|
|
m_builtinResources.fileImage = flushUploadImage("image/file.png");
|
|
m_builtinResources.materialImage = flushUploadImage("image/material.png");
|
|
m_builtinResources.sceneImage = flushUploadImage("image/scene.png");
|
|
m_builtinResources.meshImage = flushUploadImage("image/mesh.png");
|
|
|
|
m_builtinResources.sunImage = flushUploadImage("image/sun.png");
|
|
m_builtinResources.userImage = flushUploadImage("image/user.png");
|
|
m_builtinResources.effectImage = flushUploadImage("image/effects.png");
|
|
m_builtinResources.postImage = flushUploadImage("image/post.png");
|
|
}
|
|
|
|
void Editor::releaseBuiltinResources()
|
|
{
|
|
// Clear all reference.
|
|
m_builtinResources = {};
|
|
}
|
|
|
|
void Editor::shortcutHandle()
|
|
{
|
|
// Handle undo shortcut. ctrl_z and ctrl_y.
|
|
if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl))
|
|
{
|
|
if (ImGui::IsKeyPressed(ImGuiKey_Z))
|
|
{
|
|
if (m_undo->undo()) {}
|
|
}
|
|
else if (ImGui::IsKeyPressed(ImGuiKey_Y))
|
|
{
|
|
if (m_undo->redo()) {}
|
|
}
|
|
else if (ImGui::IsKeyPressed(ImGuiKey_S))
|
|
{
|
|
if (!m_dirtyAssets.empty())
|
|
{
|
|
saveDirtyAssetActions();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Editor::saveDirtyAssetActions()
|
|
{
|
|
const auto& typeNameMap = EditorAsset::get()->getTypeNameMap();
|
|
const auto& assetMap = EditorAsset::get()->getRegisterMap();
|
|
|
|
std::vector<engine::UUID> saveSets;
|
|
|
|
// Save dirty asset to disk.
|
|
for (auto& assetPair : m_dirtyAssets)
|
|
{
|
|
auto asset = assetPair.second.lock();
|
|
if (!asset)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (asset->savePathUnvalid())
|
|
{
|
|
std::string path;
|
|
|
|
nfdchar_t* outPathChars;
|
|
|
|
std::string suffix = std::string(asset->getSuffix()).erase(0, 1) + "\0";
|
|
nfdchar_t* filterList = suffix.data();
|
|
nfdresult_t result = NFD_SaveDialog(filterList, getProjectAssetPathUtf8().c_str(), &outPathChars);
|
|
|
|
if (result == NFD_OKAY)
|
|
{
|
|
path = outPathChars;
|
|
free(outPathChars);
|
|
}
|
|
|
|
auto u16PathString = utf8::utf8to16(path);
|
|
std::filesystem::path fp(u16PathString);
|
|
if (!path.empty())
|
|
{
|
|
std::filesystem::path assetName = fp.filename();
|
|
std::string assetNameUtf8 = utf8::utf16to8(assetName.u16string());
|
|
|
|
const auto relativePath = buildRelativePathUtf8(getProjectRootPathUtf16(), fp);
|
|
asset->setNameUtf8(assetNameUtf8);
|
|
asset->setRelativePathUtf8(relativePath);
|
|
}
|
|
}
|
|
|
|
if (asset->saveAction())
|
|
{
|
|
saveSets.push_back(asset->getUUID());
|
|
}
|
|
else
|
|
{
|
|
LOG_ERROR("Fail to save asset {}!", asset->getNameUtf8());
|
|
}
|
|
}
|
|
|
|
m_dirtyAssets.clear();
|
|
setupProjectDirectory(m_projectFilePathUtf16);
|
|
}
|
|
|
|
|
|
|
|
int Editor::run(int argc, char** argv)
|
|
{
|
|
// Prepare config.
|
|
Config config;
|
|
config.appName = "flower";
|
|
config.bConsole = false;
|
|
config.bEnableLogFileOut = true;
|
|
config.logFolder = "log";
|
|
config.configFolder = "config";
|
|
config.iconPath = "image/icon.png";
|
|
config.windowInfo.bResizeable = false;
|
|
config.windowInfo.initWidth = 800;
|
|
config.windowInfo.initHeight = 450;
|
|
config.windowInfo.windowShowMode = Config::InitWindowInfo::EWindowMode::Free;
|
|
|
|
// Framework init and register module.
|
|
Framework* app = Framework::get();
|
|
app->initFramework(config);
|
|
{
|
|
app->getEngine().registerRuntimeModule<VulkanContext>();
|
|
app->getEngine().registerRuntimeModule<SceneManager>();
|
|
app->getEngine().registerRuntimeModule<Renderer>();
|
|
app->getEngine().registerRuntimeModule<AssetSystem>();
|
|
}
|
|
|
|
// Try init app.
|
|
if (app->init())
|
|
{
|
|
// Init widgets.
|
|
init();
|
|
|
|
// Run app loop if available.
|
|
app->loop();
|
|
|
|
// Release widgets.
|
|
release();
|
|
|
|
// Release app when loop end.
|
|
app->release();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void Editor::setTitleName() const
|
|
{
|
|
auto activeScene = m_sceneManager->getActiveScene();
|
|
|
|
std::string newTitleName = Framework::get()->getConfig().appName + " - " + m_projectNameUtf8 + " - " + activeScene->getNameUtf8();
|
|
|
|
if (activeScene->isDirty())
|
|
{
|
|
newTitleName += " * ";
|
|
}
|
|
|
|
glfwSetWindowTitle(Framework::get()->getWindow(), newTitleName.c_str());
|
|
}
|
|
|
|
void Editor::setupProjectDirectory(const std::filesystem::path& inProjectPath)
|
|
{
|
|
m_projectRootPathUtf8 = utf8::utf16to8(inProjectPath.parent_path().u16string());
|
|
m_projectFilePathUtf8 = utf8::utf16to8(inProjectPath.u16string());
|
|
m_projectNameUtf8 = utf8::utf16to8(inProjectPath.filename().replace_extension().u16string());
|
|
m_projectAssetPathUtf8 = utf8::utf16to8((inProjectPath.parent_path() / "asset").u16string());
|
|
|
|
m_projectRootPathUtf16 = inProjectPath.parent_path().u16string();
|
|
m_projectFilePathUtf16 = inProjectPath.u16string();
|
|
m_projectNameUtf16 = inProjectPath.filename().replace_extension().u16string();
|
|
m_projectAssetPathUtf16 = (inProjectPath.parent_path() / "asset").u16string();
|
|
|
|
setTitleName();
|
|
|
|
m_assetSystem->setupProject(inProjectPath);
|
|
m_projectContent->setupProject(inProjectPath);
|
|
}
|
|
|
|
void Editor::onAssetDirty(std::shared_ptr<engine::AssetInterface> asset)
|
|
{
|
|
m_dirtyAssets[asset->getUUID()] = asset;
|
|
|
|
if (asset->getType() == EAssetType::Scene)
|
|
{
|
|
auto scene = std::static_pointer_cast<Scene>(asset);
|
|
if (scene == m_sceneManager->getActiveScene())
|
|
{
|
|
setTitleName();
|
|
}
|
|
}
|
|
}
|
|
|
|
VkDescriptorSet Editor::getSet(VulkanImage* image, const VkSamplerCreateInfo& sampler)
|
|
{
|
|
const auto& uuid = image->getRuntimeUUID();
|
|
if (!m_cacheImageSet[uuid])
|
|
{
|
|
m_cacheImageSet[uuid] = ImGui_ImplVulkan_AddTexture(
|
|
m_context->getSamplerCache().createSampler(sampler),
|
|
image->getOrCreateView(buildBasicImageSubresource()),
|
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
|
|
);
|
|
}
|
|
|
|
return m_cacheImageSet.at(uuid);
|
|
}
|
|
|
|
VkDescriptorSet Editor::getClampToTransparentBorderSet(engine::VulkanImage* image)
|
|
{
|
|
static const VkSamplerCreateInfo info = SamplerFactory::pointClampBorder0000();
|
|
|
|
return getSet(image, info);
|
|
}
|
|
|
|
void Editor::init()
|
|
{
|
|
#if _WIN32
|
|
SetConsoleOutputCP(CP_UTF8);
|
|
#endif // _WIN32
|
|
|
|
m_engine = Framework::get()->getEnginePtr();
|
|
|
|
m_context = m_engine->getRuntimeModule<VulkanContext>();
|
|
ASSERT(m_context, "You must register one vulkan context module when require widget ui.");
|
|
|
|
m_renderer = m_engine->getRuntimeModule<Renderer>();
|
|
ASSERT(m_renderer, "You must register one renderer module when require widget ui.");
|
|
|
|
m_sceneManager = m_engine->getRuntimeModule<SceneManager>();
|
|
ASSERT(m_sceneManager, "You must register one scene module when require widget ui.");
|
|
|
|
m_assetSystem = m_engine->getRuntimeModule<AssetSystem>();
|
|
ASSERT(m_assetSystem, "You must register one assetsystem module when require widget ui.");
|
|
|
|
constexpr size_t kMaxUndoItem = 100;
|
|
m_undo = std::make_unique<Undo>(kMaxUndoItem);
|
|
|
|
initBuiltinResources();
|
|
|
|
// Register asset delegates
|
|
{
|
|
m_onAssetDirtyHandle = m_assetSystem->onAssetDirty.addRaw(this, &Editor::onAssetDirty);
|
|
}
|
|
|
|
// Create widgets.
|
|
{
|
|
m_mainDockspace = std::make_unique<MainViewportDockspaceAndMenu>(this);
|
|
m_mainDockspace->init();
|
|
|
|
m_downbar = std::make_unique<DownbarWidget>(this);
|
|
m_downbar->init();
|
|
|
|
// Render Manager before viewport to ensure change value work.
|
|
m_renderManager = std::make_unique<RenderManagerWidget>(this);
|
|
m_renderManager->init();
|
|
|
|
|
|
m_console = std::make_unique<WidgetConsole>(this);
|
|
m_console->init();
|
|
|
|
m_hubWidget = std::make_unique<HubWidget>(this);
|
|
m_hubWidget->init();
|
|
|
|
m_projectContent = std::make_unique<ProjectContentWidget>(this);
|
|
m_projectContent->init();
|
|
|
|
m_outlinerWidget = std::make_unique<SceneOutlinerWidget>(this);
|
|
m_outlinerWidget->init();
|
|
|
|
m_viewport = std::make_unique<ViewportWidget>(this);
|
|
m_viewport->init();
|
|
|
|
m_detail = std::make_unique<WidgetDetail>(this);
|
|
m_detail->init();
|
|
|
|
|
|
m_assetConfigs = std::make_unique<AssetConfigWidgetManager>(this);
|
|
}
|
|
|
|
// Register tick function on renderer delegate.
|
|
m_tickFunctionHandle = m_renderer->tickFunctions.addRaw(this, &Editor::tick);
|
|
m_tickCmdFunctionHandle = m_renderer->tickCmdFunctions.addRaw(this, &Editor::tickWithCmd);
|
|
|
|
|
|
}
|
|
|
|
void Editor::release()
|
|
{
|
|
// unregister tick function.
|
|
CHECK(m_renderer->tickFunctions.remove(m_tickFunctionHandle));
|
|
CHECK(m_renderer->tickCmdFunctions.remove(m_tickCmdFunctionHandle));
|
|
CHECK(m_assetSystem->onAssetDirty.remove(m_onAssetDirtyHandle));
|
|
|
|
m_context->waitDeviceIdle();
|
|
|
|
// Release widgets.
|
|
m_mainDockspace->release();
|
|
m_console->release();
|
|
m_downbar->release();
|
|
m_hubWidget->release();
|
|
m_projectContent->release();
|
|
m_outlinerWidget->release();
|
|
m_viewport->release();
|
|
m_detail->release();
|
|
m_renderManager->release();
|
|
|
|
m_assetConfigs->release();
|
|
|
|
releaseBuiltinResources();
|
|
}
|
|
|
|
void Editor::tick(const RuntimeModuleTickData& tickData, VulkanContext* context)
|
|
{
|
|
if (m_bShouldSetFocus)
|
|
{
|
|
m_bShouldSetFocus = false;
|
|
ImGui::SetWindowFocus(m_focusWindow.c_str());
|
|
}
|
|
|
|
if (m_bHubWidgetActive)
|
|
{
|
|
m_hubWidget->tick(tickData, context);
|
|
}
|
|
else
|
|
{
|
|
m_mainDockspace->tick(tickData, context);
|
|
m_console->tick(tickData, context);
|
|
m_downbar->tick(tickData, context);
|
|
m_projectContent->tick(tickData, context);
|
|
m_outlinerWidget->tick(tickData, context);
|
|
m_viewport->tick(tickData, context);
|
|
m_detail->tick(tickData, context);
|
|
m_renderManager->tick(tickData, context);
|
|
m_assetConfigs->tick(tickData, context);
|
|
}
|
|
|
|
tickFunctions.broadcast(tickData, context);
|
|
|
|
shortcutHandle();
|
|
|
|
|
|
}
|
|
|
|
void Editor::tickWithCmd(const RuntimeModuleTickData& tickData, VkCommandBuffer cmd, VulkanContext* context)
|
|
{
|
|
if (m_bHubWidgetActive)
|
|
{
|
|
m_hubWidget->tickWithCmd(tickData, cmd, context);
|
|
}
|
|
else
|
|
{
|
|
m_mainDockspace->tickWithCmd(tickData, cmd, context);
|
|
m_console->tickWithCmd(tickData, cmd, context);
|
|
m_downbar->tickWithCmd(tickData, cmd, context);
|
|
m_projectContent->tickWithCmd(tickData, cmd, context);
|
|
m_outlinerWidget->tickWithCmd(tickData, cmd, context);
|
|
m_viewport->tickWithCmd(tickData, cmd, context);
|
|
m_detail->tickWithCmd(tickData, cmd, context);
|
|
m_renderManager->tickWithCmd(tickData, cmd, context);
|
|
m_assetConfigs->tickCmd(tickData, cmd, context);
|
|
}
|
|
|
|
tickCmdFunctions.broadcast(tickData, cmd, context);
|
|
}
|