diff --git a/.gitignore b/.gitignore index 8a228113f..30a1b6063 100644 --- a/.gitignore +++ b/.gitignore @@ -20,8 +20,9 @@ ThirdParty/ConanRecipes/**/CMakeUserPresets.json !ThirdParty/ConanRecipes/build_recipes.py aqtinstall.log -# Claude +# Agents CLAUDE.md +AGENTS.md # Test Project (generated from the editor's Default project template by CI / tooling) /TestProject diff --git a/CMake/Common.cmake b/CMake/Common.cmake index 9be785636..f8ec3df74 100644 --- a/CMake/Common.cmake +++ b/CMake/Common.cmake @@ -33,9 +33,3 @@ if (MSVC) NOMINMAX=1 ) endif () - -# This project and its downstream consumers reference Qt only through the versioned Qt6:: targets. Suppressing the -# versionless Qt:: aliases stops the single-config Qt host tools from triggering "IMPORTED_LOCATION not set ... -# configuration " errors in the IDE file-API codemodel on non-Release builds, where they only carry a Release -# import while Debug/RelWithDebInfo/MinSizeRel are mapped onto it. -set(QT_NO_CREATE_VERSIONLESS_TARGETS TRUE) diff --git a/CMake/Target.cmake b/CMake/Target.cmake index fc6cfe5f4..7f5e2737d 100644 --- a/CMake/Target.cmake +++ b/CMake/Target.cmake @@ -86,41 +86,79 @@ function(exp_gather_target_runtime_dependencies_recurse) set(${arg_OUT_DEP_TARGET} ${result_dep_target} PARENT_SCOPE) endfunction() -# Every runtime file lands in the single shared per-sub-project Binaries directory. To keep the copies generator-agnostic -# and race-free, each destination is owned by exactly one copy step rather than being re-copied by every consumer: -# - files referenced through a target ($) get a per-file owner created in the consumer's scope, so the -# generator expression resolves where the target is visible (imported third-party targets such as Qt are -# directory-scoped and would be invisible in a global aggregate target). A first-party owner additionally waits on its -# producing target; merging these into one target is impossible because a build tool such as MirrorTool consumes a dll -# while another dll's producer transitively depends on the tool, which would close a build cycle; -# - prebuilt third-party files given as plain paths, plus resources, carry no target and are batched (deduplicated) into -# one per-sub-project assets target whose copies run sequentially (see exp_finalize_dist_assets). -function(exp_add_runtime_dep_copy) +# All runtime files share one per-sub-project Binaries directory, so each must have a single copy owner or parallel builds +# race on the same destination: a first-party shared library copies itself via a POST_BUILD step on its producer (see +# exp_add_library), while imported targets, plain-path third-party files and resources go through one batched assets +# target (see exp_finalize_dist_assets). An imported target's $ is not resolvable in that source-scope +# batch target, so its file is resolved to a concrete path here first. + +# Resolve an imported target's on-disk file for a config, trying the per-config location, its config mapping, the +# config-less IMPORTED_LOCATION, then any available imported configuration. +function(exp_get_imported_location) set(options "") - set(singleValueArgs KEY SRC PRODUCER OUTPUT_TARGET) + set(singleValueArgs TARGET CONFIG OUTPUT) set(multiValueArgs "") cmake_parse_arguments(arg "${options}" "${singleValueArgs}" "${multiValueArgs}" ${ARGN}) - string(MAKE_C_IDENTIFIER "${arg_KEY}" key_id) - set(registry_property EXP_RUNTIME_DEP_COPY_${SUB_PROJECT_NAME}_${key_id}) - - get_property(copy_target GLOBAL PROPERTY ${registry_property}) - if (NOT copy_target) - exp_get_runtime_output_dir(OUTPUT out_dir) - set(copy_target ${SUB_PROJECT_NAME}.CopyDll.${key_id}) - add_custom_target( - ${copy_target} - COMMAND ${CMAKE_COMMAND} -E make_directory ${out_dir} - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${arg_SRC} ${out_dir} - ) - set_target_properties(${copy_target} PROPERTIES FOLDER ${AUX_TARGETS_FOLDER}) - if (arg_PRODUCER) - add_dependencies(${copy_target} ${arg_PRODUCER}) + set(loc "loc-NOTFOUND") + if (NOT "${arg_CONFIG}" STREQUAL "") + string(TOUPPER "${arg_CONFIG}" config_upper) + get_target_property(loc ${arg_TARGET} IMPORTED_LOCATION_${config_upper}) + if (NOT loc) + get_target_property(mapped_configs ${arg_TARGET} MAP_IMPORTED_CONFIG_${config_upper}) + if (mapped_configs) + foreach (mapped ${mapped_configs}) + string(TOUPPER "${mapped}" mapped_upper) + get_target_property(loc ${arg_TARGET} IMPORTED_LOCATION_${mapped_upper}) + if (loc) + break() + endif () + endforeach () + endif () endif () - set_property(GLOBAL PROPERTY ${registry_property} ${copy_target}) + endif () + if (NOT loc) + get_target_property(loc ${arg_TARGET} IMPORTED_LOCATION) + endif () + if (NOT loc) + get_target_property(imported_configs ${arg_TARGET} IMPORTED_CONFIGURATIONS) + if (imported_configs) + list(GET imported_configs 0 first_config) + string(TOUPPER "${first_config}" first_upper) + get_target_property(loc ${arg_TARGET} IMPORTED_LOCATION_${first_upper}) + endif () + endif () + if (NOT loc) + set(loc "") endif () - set(${arg_OUTPUT_TARGET} ${copy_target} PARENT_SCOPE) + set(${arg_OUTPUT} ${loc} PARENT_SCOPE) +endfunction() + +# Resolve an imported target's runtime file to a concrete path the source-scope assets target can copy: a plain path for +# single-config generators, or a per-config $<$:path> of literals (no target reference) for multi-config ones. +function(exp_resolve_imported_runtime_file) + set(options "") + set(singleValueArgs TARGET OUTPUT_SRC) + set(multiValueArgs "") + cmake_parse_arguments(arg "${options}" "${singleValueArgs}" "${multiValueArgs}" ${ARGN}) + + set(src_expr "") + if (with_multi_config_generator) + foreach (config ${CMAKE_CONFIGURATION_TYPES}) + exp_get_imported_location(TARGET ${arg_TARGET} CONFIG ${config} OUTPUT loc) + if (loc) + string(APPEND src_expr "$<$:${loc}>") + endif () + endforeach () + else () + exp_get_imported_location(TARGET ${arg_TARGET} CONFIG "${CMAKE_BUILD_TYPE}" OUTPUT loc) + if (loc) + set(src_expr ${loc}) + endif () + endif () + + set(${arg_OUTPUT_SRC} ${src_expr} PARENT_SCOPE) endfunction() function(exp_schedule_dist_assets_finalize) @@ -207,19 +245,14 @@ function(exp_process_runtime_dependencies) endif () if (referenced AND TARGET ${referenced}) - set(producer "") get_target_property(referenced_imported ${referenced} IMPORTED) - if (NOT referenced_imported) - set(producer ${referenced}) + if (referenced_imported) + exp_resolve_imported_runtime_file(TARGET ${referenced} OUTPUT_SRC src) + if (src) + set_property(GLOBAL APPEND PROPERTY EXP_DIST_ASSET_FILES_${SUB_PROJECT_NAME} ${src}) + endif () endif () - - exp_add_runtime_dep_copy( - KEY ${r} - SRC ${r} - PRODUCER ${producer} - OUTPUT_TARGET copy_target - ) - add_dependencies(${arg_NAME} ${copy_target}) + # first-party shared libs copy themselves via a POST_BUILD step in exp_add_library; nothing to wire up here else () set_property(GLOBAL APPEND PROPERTY EXP_DIST_ASSET_FILES_${SUB_PROJECT_NAME} ${r}) endif () @@ -561,6 +594,16 @@ function(exp_add_library) ARCHIVE_OUTPUT_DIRECTORY ${archive_output_directory} ) + if ("${arg_TYPE}" STREQUAL "SHARED") + exp_get_runtime_output_dir(OUTPUT dist_dir) + add_custom_command( + TARGET ${arg_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory ${dist_dir} + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${dist_dir} + VERBATIM + ) + endif () + foreach (inc ${arg_PUBLIC_INC}) get_filename_component(absolute_inc ${inc} ABSOLUTE) list(APPEND public_build_inc $) diff --git a/Editor/CMakeLists.txt b/Editor/CMakeLists.txt index aa7f874cc..10b57687f 100644 --- a/Editor/CMakeLists.txt +++ b/Editor/CMakeLists.txt @@ -1,82 +1,17 @@ -find_package( - Qt6 - COMPONENTS Core Gui Widgets WebEngineWidgets - REQUIRED -) -set(QT_ROOT ${QT6_INSTALL_PREFIX}) - -qt_standard_project_setup() - -if (APPLE) - set(platform_executable_hint MACOSX_BUNDLE) - set(bundle_install_dest BUNDLE DESTINATION ${SUB_PROJECT_NAME}/Binaries) - set(platform_fwk_dir ${QT_ROOT}/lib) -endif () +set(imgui_stdlib_path ${imgui_PACKAGE_FOLDER_RELEASE}/res/misc/cpp) -set(editor_qt_libs Qt6::Core Qt6::Gui Qt6::Widgets Qt6::WebEngineWidgets) -set(editor_libs Core RHI Runtime httplib::httplib ${editor_qt_libs}) - -exp_add_mirror_info_source_generation_target( +file(GLOB_RECURSE editor_sources Src/*.cpp) +exp_add_executable( NAME Editor - OUTPUT_SRC EDITOR_MIRROR_GENERATED_SRC - OUTPUT_TARGET_NAME EDITOR_MIRROR_GENERATED_TARGET - SEARCH_DIR Include - PRIVATE_INC Include + SRC ${editor_sources} ${imgui_stdlib_path}/imgui_stdlib.cpp + LIB Core RHI Runtime glfw imgui::imgui + INC Include ${imgui_stdlib_path} LIB ${editor_libs} - FRAMEWORK_DIR ${platform_fwk_dir} -) - -file(GLOB_RECURSE SOURCES Src/*.cpp) -qt_add_executable(Editor ${platform_executable_hint} ${SOURCES} ${EDITOR_MIRROR_GENERATED_SRC}) -set_target_properties(Editor PROPERTIES FOLDER ${BASE_TARGETS_FOLDER}) - -exp_get_runtime_output_dir(OUTPUT runtime_output_dir) -set_target_properties( - Editor PROPERTIES - RUNTIME_OUTPUT_DIRECTORY ${runtime_output_dir} -) - -target_include_directories(Editor PRIVATE Include) -target_link_libraries(Editor PRIVATE ${editor_libs}) - -add_dependencies(Editor ${EDITOR_MIRROR_GENERATED_TARGET} ${platform_rhi_targets}) - -exp_process_runtime_dependencies( - NAME Editor DEP_TARGET ${platform_rhi_targets} + REFLECT Include ) -install( - TARGETS Editor - RUNTIME DESTINATION ${SUB_PROJECT_NAME}/Binaries - ${bundle_install_dest} -) -export( - TARGETS Editor - NAMESPACE ${SUB_PROJECT_NAME}:: - APPEND FILE ${CMAKE_BINARY_DIR}/${SUB_PROJECT_NAME}Targets.cmake -) - -if (WIN32) - set(qt_win_deploy_executable ${QT_ROOT}/bin/windeployqt.exe) - add_custom_command( - TARGET Editor POST_BUILD - COMMAND ${qt_win_deploy_executable} $ - ) - install( - CODE "execute_process(COMMAND ${qt_win_deploy_executable} ${CMAKE_INSTALL_PREFIX}/${SUB_PROJECT_NAME}/Binaries/$)" - ) -elseif (APPLE) - set(qt_mac_deploy_executable ${QT_ROOT}/bin/macdeployqt) - add_custom_command( - TARGET Editor POST_BUILD - COMMAND ${qt_mac_deploy_executable} $/Editor.app -no-strip - ) - install( - CODE "execute_process(COMMAND ${qt_mac_deploy_executable} ${CMAKE_INSTALL_PREFIX}/${SUB_PROJECT_NAME}/Binaries/Editor.app -no-strip)" - ) -endif () -# ---- begin shaders --------------------------------------------------------------------------------- +# ---- begin shaders/resources ----------------------------------------------------------------------- get_engine_shader_resources(OUTPUT editor_resources) file(GLOB_RECURSE shaders Shader/*.es*) @@ -97,33 +32,7 @@ exp_add_resources_copy_command( NAME Editor RES ${editor_resources} ) -# ---- end shaders ----------------------------------------------------------------------------------- - -# ---- begin web project ----------------------------------------------------------------------------- -if (WIN32) - find_program(npm_executable NAMES npm.cmd npm REQUIRED NO_CACHE) -else () - find_program(npm_executable NAMES npm REQUIRED NO_CACHE) -endif () - -add_custom_target( - Editor.Web - COMMAND ${CMAKE_COMMAND} -E env ${npm_executable} install --no-fund --registry=https://registry.npmmirror.com - COMMAND ${CMAKE_COMMAND} -E env ${npm_executable} run build - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Web - VERBATIM -) -set_target_properties(Editor.Web PROPERTIES FOLDER ${AUX_TARGETS_FOLDER}) -add_dependencies(Editor Editor.Web) - -add_custom_command( - TARGET Editor.Web POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory_if_different ${CMAKE_CURRENT_SOURCE_DIR}/Web/dist $/Web -) -if (NOT APPLE) - install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory_if_different ${CMAKE_CURRENT_SOURCE_DIR}/Web/dist ${CMAKE_INSTALL_PREFIX}/${SUB_PROJECT_NAME}/Binaries/Web)") -endif () -# ---- end web project ------------------------------------------------------------------------------- +# ---- end shaders/resources ------------------------------------------------------------------------- # ---- begin test project ---------------------------------------------------------------------------- # Render the Default project template into TestProject/ at configure time, so local and CI builds can build it diff --git a/Editor/Include/Editor/EditorApplication.h b/Editor/Include/Editor/EditorApplication.h new file mode 100644 index 000000000..2fa71e7e5 --- /dev/null +++ b/Editor/Include/Editor/EditorApplication.h @@ -0,0 +1,63 @@ +// +// Created by johnk on 2026/7/7. +// + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Editor { + enum class EditorApplicationMode : uint8_t { + projectHub, + editor + }; + + struct EditorApplicationDesc { + EditorApplicationMode mode; + std::string rhiType; + std::string projectRoot; + }; + + class EditorApplication final { + public: + explicit EditorApplication(EditorApplicationDesc inDesc); + ~EditorApplication(); + + NonCopyable(EditorApplication) + NonMovable(EditorApplication) + + int Run(); + + private: + void InitializeImGui(); + void ShutdownImGui(); + void InstallCallbacks(); + void BeginImGuiFrame(float inDeltaSeconds) const; + void DrawDockSpace() const; + void RenderEditorFrame(float inDeltaSeconds); + void RenderProjectHubFrame(); + void HandleKey(int inKey, int inScancode, int inAction, int inMods); + void HandleChar(uint32_t inCodepoint) const; + void HandleMouseButton(int inButton, int inAction, int inMods); + void HandleCursor(double inX, double inY); + void HandleScroll(double inXOffset, double inYOffset) const; + float NextDeltaSeconds(); + + EditorApplicationDesc desc; + Common::UniquePtr window; + Common::UniquePtr context; + Common::UniquePtr sceneRenderCanvas; + Common::UniquePtr projectHubFrame; + EditorFrame editorFrame; + double lastFrameSeconds; + double lastCursorX; + double lastCursorY; + bool hasLastCursor; + bool requestQuit; + }; +} diff --git a/Editor/Include/Editor/EditorContext.h b/Editor/Include/Editor/EditorContext.h new file mode 100644 index 000000000..274183554 --- /dev/null +++ b/Editor/Include/Editor/EditorContext.h @@ -0,0 +1,35 @@ +// +// Created by johnk on 2026/7/4. +// + +#pragma once + +#include +#include + +namespace Editor { + class EditorContext final { + public: + EditorContext(); + ~EditorContext(); + + SceneClient& GetSceneClient() const; + Runtime::Entity GetSelectedEntity() const; + uint64_t GetSelectionVersion() const; + uint64_t GetWorldStructureVersion() const; + uint64_t GetComponentsVersion() const; + + void SetSelectedEntity(Runtime::Entity inEntity); + Runtime::Entity CreateEntity(const std::string& inName); + void DestroyEntity(Runtime::Entity inEntity); + void RenameEntity(Runtime::Entity inEntity, const std::string& inName); + void NotifyComponentsChanged(Runtime::Entity inEntity); + + private: + Common::UniquePtr sceneClient; + Runtime::Entity selectedEntity; + uint64_t selectionVersion; + uint64_t worldStructureVersion; + uint64_t componentsVersion; + }; +} diff --git a/Editor/Include/Editor/EditorLog.h b/Editor/Include/Editor/EditorLog.h new file mode 100644 index 000000000..7a7daba95 --- /dev/null +++ b/Editor/Include/Editor/EditorLog.h @@ -0,0 +1,44 @@ +// +// Created by johnk on 2026/7/5. +// + +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace Editor { + // log stream attached to the engine logger that keeps a bounded history and forwards structured entries to + // listeners (the log panel backend), log calls arrive from arbitrary threads so everything is mutex-guarded and + // listeners must marshal to their own thread themselves + class EditorLogStream final : public Core::LogStream { + public: + using EntryListener = std::function; + using ListenerHandle = size_t; + + // attaches the singleton stream to the engine logger on first use + static EditorLogStream& Get(); + + void Write(const Core::LogEntry& inEntry) override; + void Flush() override; + + std::vector Snapshot() const; + ListenerHandle AddListener(EntryListener inListener); + void RemoveListener(ListenerHandle inHandle); + + private: + static constexpr size_t maxHistoryEntries = 2000; + + EditorLogStream(); + + mutable std::mutex mutex; + std::deque history; + std::unordered_map listeners; + ListenerHandle nextListenerHandle; + }; +} diff --git a/Editor/Include/Editor/EditorWindow.h b/Editor/Include/Editor/EditorWindow.h new file mode 100644 index 000000000..aec4c0142 --- /dev/null +++ b/Editor/Include/Editor/EditorWindow.h @@ -0,0 +1,97 @@ +// +// Created by johnk on 2026/7/7. +// + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +struct GLFWwindow; +struct ImDrawData; + +namespace Editor { + struct EditorWindowDesc { + std::string title; + uint32_t width; + uint32_t height; + }; + + class EditorWindow final : public Runtime::Window { + public: + explicit EditorWindow(const EditorWindowDesc& inDesc); + ~EditorWindow() override; + + NonCopyable(EditorWindow) + NonMovable(EditorWindow) + + GLFWwindow* GetNativeWindow() const; + uint32_t GetWidth() const; + uint32_t GetHeight() const; + uint32_t GetFramebufferWidth() const; + uint32_t GetFramebufferHeight() const; + bool ShouldClose() const; + void RequestClose() const; + void PollEvents() const; + void SetTitle(const std::string& inTitle) const; + void SetDrawData(ImDrawData* inDrawData); + void RecreateSwapChainIfNeeded(); + void WaitRenderingIdle() const; + + void RenderPendingUi(); + void RenderUiOnly(ImDrawData& inDrawData); + + private: + struct ImGuiPassParams { + Common::FVec2 displayPos; + Common::FVec2 displaySize; + }; + + void* GetPlatformWindow() const; + void RenderUiPass(ImDrawData* inDrawData, RHI::LoadOp inLoadOp, const Common::LinearColor& inClearValue); + void RenderImGuiDrawData(ImDrawData& inDrawData, RHI::RasterPassCommandRecorder& inRecorder); + void CreateImGuiDeviceObjects(); + void CreateImGuiFontTexture(); + void CreateImGuiDynamicBuffers(int inVertexCount, int inIndexCount); + void UpdateImGuiBuffers(ImDrawData& inDrawData); + void UpdateImGuiPassParams(const ImDrawData& inDrawData); + RHI::BindGroup* GetOrCreateImGuiTextureBindGroup(RHI::TextureView& inTextureView); + ImDrawData* TakeDrawData(); + static void DestroyDrawData(ImDrawData* inDrawData); + + GLFWwindow* window; + Common::UniquePtr uiFrameFence; + Common::UniquePtr uiCommandBuffer; + Render::ShaderCompileOutput imguiVertexShaderCompileOutput; + Render::ShaderCompileOutput imguiPixelShaderCompileOutput; + Common::UniquePtr imguiVertexShader; + Common::UniquePtr imguiPixelShader; + Common::UniquePtr imguiBindGroupLayout; + Common::UniquePtr imguiPipelineLayout; + Common::UniquePtr imguiPipeline; + Common::UniquePtr imguiFontTexture; + Common::UniquePtr imguiFontTextureView; + Common::UniquePtr imguiImageSampler; + Common::UniquePtr imguiPassParamsBuffer; + Common::UniquePtr imguiPassParamsBufferView; + std::unordered_map> imguiFrameTextureBindGroups; + Common::UniquePtr imguiVertexBuffer; + Common::UniquePtr imguiVertexBufferView; + Common::UniquePtr imguiIndexBuffer; + Common::UniquePtr imguiIndexBufferView; + int imguiVertexBufferCapacity; + int imguiIndexBufferCapacity; + uint32_t framebufferWidth; + uint32_t framebufferHeight; + mutable std::mutex drawDataMutex; + ImDrawData* pendingDrawData; + }; +} diff --git a/Editor/Include/Editor/Frame/EditorFrame.h b/Editor/Include/Editor/Frame/EditorFrame.h new file mode 100644 index 000000000..9a167700d --- /dev/null +++ b/Editor/Include/Editor/Frame/EditorFrame.h @@ -0,0 +1,30 @@ +// +// Created by johnk on 2026/7/7. +// + +#pragma once + +#include + +#include +#include + +namespace Editor { + class EditorFrame final { + public: + EditorFrame(); + ~EditorFrame(); + + void Render(EditorContext& inContext, Runtime::Canvas& inSceneRenderCanvas, bool& outRequestQuit); + + private: + void RenderMenuBar(EditorContext& inContext, bool& outRequestQuit); + void RenderSceneTab(EditorContext& inContext, Runtime::Canvas& inSceneRenderCanvas); + void RenderOutlinerTab(EditorContext& inContext); + void RenderInspectorTab(EditorContext& inContext); + void RenderLogTab(); + + std::string createEntityName; + int selectedAddComponentIndex; + }; +} diff --git a/Editor/Include/Editor/Frame/ProjectHubFrame.h b/Editor/Include/Editor/Frame/ProjectHubFrame.h new file mode 100644 index 000000000..1e0758ba2 --- /dev/null +++ b/Editor/Include/Editor/Frame/ProjectHubFrame.h @@ -0,0 +1,61 @@ +// +// Created by johnk on 2026/7/8. +// + +#pragma once + +#include +#include + +#include +#include + +namespace Editor { + struct EClass() RecentProjectInfo { + EClassBody(RecentProjectInfo) + + EProperty() std::string name; + EProperty() std::string path; + }; + + struct EClass() ProjectTemplateInfo { + EClassBody(ProjectTemplateInfo) + + EProperty() std::string name; + EProperty() std::string path; + }; + + struct EClass() CreateProjectResult { + EClassBody(CreateProjectResult) + + EProperty() bool success; + EProperty() std::string error; + EProperty() std::string projectPath; + }; + + class EditorWindow; + + class ProjectHubFrame final { + public: + ProjectHubFrame(); + ~ProjectHubFrame(); + + void Render(EditorWindow& inWindow, const std::string& inRhiType); + + private: + CreateProjectResult CreateProject(); + void OpenProject(EditorWindow& inWindow, const std::string& inProjectPath, const std::string& inRhiType); + void SaveRecentProjects() const; + void TouchRecentProject(const std::string& inProjectPath); + + Common::Path recentProjectsFile; + std::string engineVersion; + std::vector projectTemplates; + std::vector recentProjects; + std::string projectName; + std::string directory; + int selectedTemplateIndex; + std::string statusMessage; + std::string lastCreatedProjectPath; + }; +} diff --git a/Editor/Include/Editor/Qt/EngineSerialization.h b/Editor/Include/Editor/Qt/EngineSerialization.h deleted file mode 100644 index 3165c8d37..000000000 --- a/Editor/Include/Editor/Qt/EngineSerialization.h +++ /dev/null @@ -1,257 +0,0 @@ -// Created by Kindem on 2025/8/17. -// - -#pragma once - -#include -#include -#include -#include - -#include -#include - -namespace Common { - template <> - struct Serializer { - static constexpr size_t typeId = HashUtils::StrCrc32("QString"); - - static size_t Serialize(BinarySerializeStream& outStream, const QString& inString) - { - return Serializer::Serialize(outStream, inString.toStdString()); - } - - static size_t Deserialize(BinaryDeserializeStream& inStream, QString& outString) - { - std::string stdString; - const size_t deserialized = Serializer::Deserialize(inStream, stdString); - outString = QString::fromStdString(stdString); - return deserialized; - } - }; - - template - struct Serializer> { - static constexpr size_t typeId - = HashUtils::StrCrc32("QList") + - + Serializer::typeId; - - static size_t Serialize(BinarySerializeStream& outStream, const QList& inList) - { - size_t serialized = Serializer::Serialize(outStream, inList.size()); - for (const auto& element : inList) { - serialized += Serializer::Serialize(outStream, element); - } - return serialized; - } - - static size_t Deserialize(BinaryDeserializeStream& inStream, QList& outList) - { - size_t deserialized = 0; - - outList.clear(); - uint64_t size; - deserialized += Serializer::Deserialize(inStream, size); - - outList.reserve(size); - for (auto i = 0; i < size; i++) { - T element; - deserialized += Serializer::Deserialize(inStream, element); - outList.emplaceBack(std::move(element)); - } - return deserialized; - } - }; - - template - struct Serializer> { - static constexpr size_t typeId - = HashUtils::StrCrc32("QSet") - + Serializer::typeId; - - static size_t Serialize(BinarySerializeStream& outStream, const QSet& inSet) - { - size_t serialized = Serializer::Serialize(outStream, inSet.size()); - for (const auto& element : inSet) { - serialized += Serializer::Serialize(outStream, element); - } - return serialized; - } - - static size_t Deserialize(BinaryDeserializeStream& inStream, QSet& outSet) - { - size_t deserialized = 0; - - outSet.clear(); - uint64_t size; - deserialized += Serializer::Deserialize(inStream, size); - - outSet.reserve(size); - for (auto i = 0; i < size; i++) { - T temp; - deserialized += Serializer::Deserialize(inStream, temp); - outSet.insert(std::move(temp)); - } - return deserialized; - } - }; - - template - struct Serializer> { - static constexpr size_t typeId - = HashUtils::StrCrc32("QMap") - + Serializer::typeId - + Serializer::typeId; - - static size_t Serialize(BinarySerializeStream& outStream, const QMap& inMap) - { - size_t serialized = Serializer::Serialize(outStream, inMap.size()); - for (const auto& [key, value] : inMap) { - serialized += Serializer::Serialize(outStream, key); - serialized += Serializer::Serialize(outStream, value); - } - return serialized; - } - - static size_t Deserialize(BinaryDeserializeStream& inStream, QMap& outMap) - { - size_t deserialized = 0; - - outMap.clear(); - uint64_t size; - deserialized += Serializer::Deserialize(inStream, size); - - for (auto i = 0; i < size; i++) { - K key; - V value; - deserialized += Serializer::Deserialize(inStream, key); - deserialized += Serializer::Deserialize(inStream, value); - outMap.insert(std::move(key), std::move(value)); - } - return deserialized; - } - }; - - template <> - struct JsonSerializer { - static void JsonSerialize(rapidjson::Value& outJsonValue, rapidjson::Document::AllocatorType& inAllocator, const QString& inValue) - { - const std::string stdString = inValue.toStdString(); - JsonSerializer::JsonSerialize(outJsonValue, inAllocator, stdString); - } - - static void JsonDeserialize(const rapidjson::Value& inJsonValue, QString& outValue) - { - std::string stdString; - JsonSerializer::JsonDeserialize(inJsonValue, stdString); - outValue = QString::fromStdString(stdString); - } - }; - - template - struct JsonSerializer> { - static void JsonSerialize(rapidjson::Value& outJsonValue, rapidjson::Document::AllocatorType& inAllocator, const QList& inList) - { - outJsonValue.SetArray(); - outJsonValue.Reserve(inList.size(), inAllocator); - for (const auto& element : inList) { - rapidjson::Value jsonElement; - JsonSerializer::JsonSerialize(jsonElement, inAllocator, element); - outJsonValue.PushBack(jsonElement, inAllocator); - } - } - - static void JsonDeserialize(const rapidjson::Value& inJsonValue, QList& outList) - { - outList.clear(); - - if (!inJsonValue.IsArray()) { - return; - } - outList.reserve(inJsonValue.Size()); - for (auto i = 0; i < inJsonValue.Size(); i++) { - T element; - JsonSerializer::JsonDeserialize(inJsonValue[i], element); - outList.emplaceBack(std::move(element)); - } - } - }; - - template - struct JsonSerializer> { - static void JsonSerialize(rapidjson::Value& outJsonValue, rapidjson::Document::AllocatorType& inAllocator, const QSet& inSet) - { - outJsonValue.SetArray(); - outJsonValue.Reserve(inSet.size(), inAllocator); - for (const auto& element : inSet) { - rapidjson::Value jsonElement; - JsonSerializer::JsonSerialize(jsonElement, inAllocator, element); - outJsonValue.PushBack(jsonElement, inAllocator); - } - } - - static void JsonDeserialize(const rapidjson::Value& inJsonValue, QSet& outSet) - { - outSet.clear(); - - if (!inJsonValue.IsArray()) { - return; - } - outSet.reserve(inJsonValue.Size()); - for (auto i = 0; i < inJsonValue.Size(); i++) { - T element; - JsonSerializer::JsonDeserialize(inJsonValue[i], element); - outSet.insert(std::move(element)); - } - } - }; - - template - struct JsonSerializer> { - static void JsonSerialize(rapidjson::Value& outJsonValue, rapidjson::Document::AllocatorType& inAllocator, const QMap& inMap) - { - outJsonValue.SetArray(); - outJsonValue.Reserve(inMap.size(), inAllocator); - for (const auto& [key, value] : inMap) { - rapidjson::Value jsonElement; - jsonElement.SetObject(); - - rapidjson::Value jsonKey; - JsonSerializer::JsonSerialize(jsonElement, inAllocator, key); - - rapidjson::Value jsonValue; - JsonSerializer::JsonSerialize(jsonValue, inAllocator, value); - - outJsonValue.AddMember("key", jsonKey, inAllocator); - outJsonValue.AddMember("value", jsonValue, inAllocator); - outJsonValue.PushBack(jsonElement, inAllocator); - } - } - - static void JsonDeserialize(const rapidjson::Value& inJsonValue, QMap& outMap) - { - outMap.clear(); - - if (!inJsonValue.IsArray()) { - return; - } - for (auto i = 0; i < inJsonValue.Size(); i++) { - K key; - V value; - - const auto& jsonElement = inJsonValue[i]; - if (!jsonElement.IsObject()) { - continue; - } - if (jsonElement.HasMember("key")) { - JsonSerializer::JsonDeserialize(jsonElement["key"], key); - } - if (jsonElement.HasMember("value")) { - JsonSerializer::JsonDeserialize(jsonElement["value"], value); - } - outMap.insert(std::move(key), std::move(value)); - } - } - }; -} - diff --git a/Editor/Include/Editor/Qt/JsonSerialization.h b/Editor/Include/Editor/Qt/JsonSerialization.h deleted file mode 100644 index 9434dea47..000000000 --- a/Editor/Include/Editor/Qt/JsonSerialization.h +++ /dev/null @@ -1,1529 +0,0 @@ -// -// Created by Kindem on 2025/8/18. -// - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace Editor { - template struct QtJsonSerializer {}; - template concept QtJsonSerializable = requires( - const T& inValue, T& outValue, - const QJsonValue& inJsonValue, QJsonValue& outJsonValue) - { - QtJsonSerializer::QtJsonSerialize(outJsonValue, inValue); - QtJsonSerializer::QtJsonDeserialize(inJsonValue, outValue); - }; - - template void QtJsonSerialize(QJsonValue& outJsonValue, const T& inValue); - template void QtJsonDeserialize(const QJsonValue& inJsonValue, T& outValue); -} - -namespace Editor { - template - void QtJsonSerialize(QJsonValue& outJsonValue, const T& inValue) - { - if constexpr (QtJsonSerializable) { - QtJsonSerializer::QtJsonSerialize(outJsonValue, inValue); - } else { - QuickFailWithReason("your type is not support json serialization"); - } - } - - template - void QtJsonDeserialize(const QJsonValue& inJsonValue, T& outValue) - { - if constexpr (QtJsonSerializable) { - QtJsonSerializer::QtJsonDeserialize(inJsonValue, outValue); - } else { - QuickFailWithReason("your type is not support json serialization"); - } - } - - template <> - struct QtJsonSerializer { - static void QtJsonSerialize(QJsonValue& outJsonValue, bool inValue) - { - outJsonValue = inValue; - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, bool& outValue) - { - if (!inJsonValue.isBool()) { - return; - } - outValue = inJsonValue.toBool(); - } - }; - - template <> - struct QtJsonSerializer { - static void QtJsonSerialize(QJsonValue& outJsonValue, int8_t inValue) - { - outJsonValue = inValue; - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, int8_t& outValue) - { - if (!inJsonValue.isDouble()) { - return; - } - outValue = static_cast(inJsonValue.toInt()); - } - }; - - template <> - struct QtJsonSerializer { - static void QtJsonSerialize(QJsonValue& outJsonValue, uint8_t inValue) - { - outJsonValue = inValue; - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, uint8_t& outValue) - { - if (!inJsonValue.isDouble()) { - return; - } - outValue = static_cast(inJsonValue.toInt()); - } - }; - - template <> - struct QtJsonSerializer { - static void QtJsonSerialize(QJsonValue& outJsonValue, int16_t inValue) - { - outJsonValue = inValue; - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, int16_t& outValue) - { - if (!inJsonValue.isDouble()) { - return; - } - outValue = static_cast(inJsonValue.toInt()); - } - }; - - template <> - struct QtJsonSerializer { - static void QtJsonSerialize(QJsonValue& outJsonValue, uint16_t inValue) - { - outJsonValue = inValue; - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, uint16_t& outValue) - { - if (!inJsonValue.isDouble()) { - return; - } - outValue = static_cast(inJsonValue.toInt()); - } - }; - - template <> - struct QtJsonSerializer { - static void QtJsonSerialize(QJsonValue& outJsonValue, int32_t inValue) - { - outJsonValue = inValue; - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, int32_t& outValue) - { - if (!inJsonValue.isDouble()) { - return; - } - outValue = inJsonValue.toInt(); - } - }; - - template <> - struct QtJsonSerializer { - static void QtJsonSerialize(QJsonValue& outJsonValue, uint32_t inValue) - { - outJsonValue = static_cast(inValue); - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, uint32_t& outValue) - { - if (!inJsonValue.isDouble()) { - return; - } - outValue = static_cast(inJsonValue.toInteger()); - } - }; - - template <> - struct QtJsonSerializer { - static void QtJsonSerialize(QJsonValue& outJsonValue, int64_t inValue) - { - outJsonValue = static_cast(inValue); - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, int64_t& outValue) - { - if (!inJsonValue.isDouble()) { - return; - } - outValue = inJsonValue.toInteger(); - } - }; - - template <> - struct QtJsonSerializer { - static void QtJsonSerialize(QJsonValue& outJsonValue, uint64_t inValue) - { - outJsonValue = static_cast(inValue); - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, uint64_t& outValue) - { - if (!inJsonValue.isDouble()) { - return; - } - outValue = static_cast(inJsonValue.toInteger()); - } - }; - - template <> - struct QtJsonSerializer { - static void QtJsonSerialize(QJsonValue& outJsonValue, float inValue) - { - outJsonValue = inValue; - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, float& outValue) - { - if (!inJsonValue.isDouble()) { - return; - } - outValue = static_cast(inJsonValue.toDouble()); - } - }; - - template <> - struct QtJsonSerializer { - static void QtJsonSerialize(QJsonValue& outJsonValue, double inValue) - { - outJsonValue = inValue; - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, double& outValue) - { - if (!inJsonValue.isDouble()) { - return; - } - outValue = inJsonValue.toDouble(); - } - }; - - template <> - struct QtJsonSerializer { - static void QtJsonSerialize(QJsonValue& outJsonValue, const std::string& inValue) - { - outJsonValue = QString::fromStdString(inValue); - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, std::string& outValue) - { - if (!inJsonValue.isString()) { - return; - } - outValue = inJsonValue.toString().toStdString(); - } - }; - - template <> - struct QtJsonSerializer { - static void QtJsonSerialize(QJsonValue& outJsonValue, const std::wstring& inValue) - { - outJsonValue = QString::fromStdWString(inValue); - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, std::wstring& outValue) - { - if (!inJsonValue.isString()) { - return; - } - outValue = inJsonValue.toString().toStdWString(); - } - }; - - template - struct QtJsonSerializer> { - static void QtJsonSerialize(QJsonValue& outJsonValue, const std::optional& inValue) - { - if (inValue.has_value()) { - QtJsonSerializer::QtJsonSerialize(outJsonValue, inValue.value()); - } else { - outJsonValue = QJsonValue::Null; - } - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, std::optional& outValue) - { - outValue.reset(); - if (inJsonValue.isNull()) { - return; - } - T& outValueRef = outValue.emplace(); - QtJsonSerializer::QtJsonDeserialize(inJsonValue, outValueRef); - } - }; - - template - struct QtJsonSerializer> { - static void QtJsonSerialize(QJsonValue& outJsonValue, const std::pair& inValue) - { - QJsonValue jsonKey; - QtJsonSerializer::QtJsonSerialize(jsonKey, inValue.first); - - QJsonValue jsonValue; - QtJsonSerializer::QtJsonSerialize(jsonValue, inValue.second); - - QJsonObject jsonObject; - jsonObject["key"] = jsonKey; - jsonObject["value"] = jsonValue; - outJsonValue = std::move(jsonObject); - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, std::pair& outValue) - { - if (!inJsonValue.isObject()) { - return; - } - const QJsonObject jsonObject = inJsonValue.toObject(); - if (jsonObject.contains("key")) { - const QJsonValue jsonKey = jsonObject["key"]; - QtJsonSerializer::QtJsonDeserialize(jsonKey, outValue.first); - } - if (jsonObject.contains("value")) { - const QJsonValue jsonValue = jsonObject["value"]; - QtJsonSerializer::QtJsonDeserialize(jsonValue, outValue.second); - } - } - }; - - template - struct QtJsonSerializer> { - static void QtJsonSerialize(QJsonValue& outJsonValue, const std::array& inValue) - { - QJsonArray jsonArray; - for (const auto& element : inValue) { - QJsonValue jsonElement; - QtJsonSerializer::QtJsonSerialize(jsonElement, element); - jsonArray.append(jsonElement); - } - outJsonValue = std::move(jsonArray); - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, std::array& outValue) - { - if (!inJsonValue.isArray()) { - return; - } - const QJsonArray jsonArray = inJsonValue.toArray(); - if (jsonArray.size() != N) { - return; - } - for (auto i = 0; i < N; i++) { - QtJsonSerializer::QtJsonDeserialize(jsonArray[i], outValue[i]); - } - } - }; - - template - struct QtJsonSerializer> { - static void QtJsonSerialize(QJsonValue& outJsonValue, const std::vector& inValue) - { - QJsonArray jsonArray; - for (const auto& element : inValue) { - QJsonValue jsonElement; - QtJsonSerializer::QtJsonSerialize(jsonElement, element); - jsonArray.append(jsonElement); - } - outJsonValue = std::move(jsonArray); - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, std::vector& outValue) - { - if (!inJsonValue.isArray()) { - return; - } - - const QJsonArray jsonArray = inJsonValue.toArray(); - outValue.clear(); - outValue.reserve(jsonArray.size()); - for (const auto& jsonElement : jsonArray) { - T element; - QtJsonSerializer::QtJsonDeserialize(jsonElement, element); - outValue.emplace_back(std::move(element)); - } - } - }; - - template - struct QtJsonSerializer> { - static void QtJsonSerialize(QJsonValue& outJsonValue, const std::list& inValue) - { - QJsonArray jsonArray; - for (const auto& element : inValue) { - QJsonValue jsonElement; - QtJsonSerializer::QtJsonSerialize(jsonElement, element); - jsonArray.append(jsonElement); - } - outJsonValue = std::move(jsonArray); - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, std::list& outValue) - { - if (!inJsonValue.isArray()) { - return; - } - - const QJsonArray jsonArray = inJsonValue.toArray(); - outValue.clear(); - for (const auto& jsonElement : jsonArray) { - T element; - QtJsonSerializer::QtJsonDeserialize(jsonElement, element); - outValue.emplace_back(std::move(element)); - } - } - }; - - template - struct QtJsonSerializer> { - static void QtJsonSerialize(QJsonValue& outJsonValue, const std::unordered_set& inValue) - { - QJsonArray jsonArray; - for (const auto& element : inValue) { - QJsonValue jsonElement; - QtJsonSerializer::QtJsonSerialize(jsonElement, element); - jsonArray.append(jsonElement); - } - outJsonValue = std::move(jsonArray); - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, std::unordered_set& outValue) - { - if (!inJsonValue.isArray()) { - return; - } - - const QJsonArray jsonArray = inJsonValue.toArray(); - outValue.clear(); - outValue.reserve(jsonArray.size()); - for (const auto& jsonElement : jsonArray) { - T element; - QtJsonSerializer::QtJsonDeserialize(jsonElement, element); - outValue.emplace(std::move(element)); - } - } - }; - - template - struct QtJsonSerializer> { - static void QtJsonSerialize(QJsonValue& outJsonValue, const std::set& inValue) - { - QJsonArray jsonArray; - for (const auto& element : inValue) { - QJsonValue jsonElement; - QtJsonSerializer::QtJsonSerialize(jsonElement, element); - jsonArray.append(jsonElement); - } - outJsonValue = std::move(jsonArray); - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, std::set& outValue) - { - if (!inJsonValue.isArray()) { - return; - } - - const QJsonArray jsonArray = inJsonValue.toArray(); - outValue.clear(); - for (const auto& jsonElement : jsonArray) { - T element; - QtJsonSerializer::QtJsonDeserialize(jsonElement, element); - outValue.emplace(std::move(element)); - } - } - }; - - template - struct QtJsonSerializer> { - static void QtJsonSerialize(QJsonValue& outJsonValue, const std::unordered_map& inValue) - { - QJsonArray jsonArray; - for (const auto& pair : inValue) { - QJsonValue jsonPair; - QtJsonSerializer>::QtJsonSerialize(jsonPair, pair); - jsonArray.append(jsonPair); - } - outJsonValue = std::move(jsonArray); - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, std::unordered_map& outValue) - { - if (!inJsonValue.isArray()) { - return; - } - - const QJsonArray jsonArray = inJsonValue.toArray(); - outValue.clear(); - outValue.reserve(jsonArray.size()); - for (const auto& jsonPair : jsonArray) { - std::pair pair; - QtJsonSerializer>::QtJsonDeserialize(jsonPair, pair); - outValue.emplace(std::move(pair)); - } - } - }; - - template - struct QtJsonSerializer> { - static void QtJsonSerialize(QJsonValue& outJsonValue, const std::map& inValue) - { - QJsonArray jsonArray; - for (const auto& pair : inValue) { - QJsonValue jsonPair; - QtJsonSerializer>::QtJsonSerialize(jsonPair, pair); - jsonArray.append(jsonPair); - } - outJsonValue = std::move(jsonArray); - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, std::map& outValue) - { - if (!inJsonValue.isArray()) { - return; - } - - const QJsonArray jsonArray = inJsonValue.toArray(); - outValue.clear(); - for (const auto& jsonPair : jsonArray) { - std::pair pair; - QtJsonSerializer>::QtJsonDeserialize(jsonPair, pair); - outValue.emplace(std::move(pair)); - } - } - }; - - template - struct QtJsonSerializer> { - template - static void QtJsonSerializeInternal(QJsonObject& outJsonObject, const std::tuple& inValue, std::index_sequence) - { - (void) std::initializer_list { ([&]() -> void { - const auto key = std::to_string(I); - - QJsonValue jsonValue; - QtJsonSerializer::QtJsonSerialize(jsonValue, std::get(inValue)); - - outJsonObject[QString::fromStdString(key)] = jsonValue; - }(), 0)... }; - } - - template - static void QtJsonDeserializeInternal(const QJsonObject& inJsonObject, std::tuple& outValue, std::index_sequence) - { - (void) std::initializer_list { ([&]() -> void { - const auto key = std::to_string(I); - const QJsonValue jsonValue = inJsonObject[QString::fromStdString(key)]; - if (jsonValue.isNull()) { - return; - } - QtJsonSerializer::QtJsonDeserialize(jsonValue, std::get(outValue)); - }(), 0)... }; - } - - static void QtJsonSerialize(QJsonValue& outJsonValue, const std::tuple& inValue) - { - QJsonObject jsonObject; - QtJsonSerializeInternal(jsonObject, inValue, std::make_index_sequence {}); - outJsonValue = std::move(jsonObject); - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, std::tuple& outValue) - { - outValue = {}; - if (!inJsonValue.isObject()) { - return; - } - QtJsonDeserializeInternal(inJsonValue.toObject(), outValue, std::make_index_sequence {}); - } - }; - - template - struct QtJsonSerializer> { - static void QtJsonSerialize(QJsonValue& outJsonValue, const std::variant& inValue) - { - QJsonValue jsonType; - QtJsonSerializer::QtJsonSerialize(jsonType, inValue.index()); - - QJsonValue jsonContent; - std::visit([&](T0&& v) -> void { - QtJsonSerializer>::QtJsonSerialize(jsonContent, v); - }, inValue); - - QJsonObject jsonObject; - jsonObject["type"] = jsonType; - jsonObject["content"] = jsonContent; - outJsonValue = std::move(jsonObject); - } - - template - static void QtJsonDeserializeInternal(const QJsonValue& inContentJsonValue, std::variant& outValue, size_t inAspectIndex, std::index_sequence) - { - (void) std::initializer_list { ([&]() -> void { - if (I != inAspectIndex) { - return; - } - - T temp; - QtJsonSerializer::QtJsonDeserialize(inContentJsonValue, temp); - outValue = std::move(temp); - }(), 0)... }; - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, std::variant& outValue) - { - if (!inJsonValue.isObject()) { - return; - } - - const QJsonObject jsonObject = inJsonValue.toObject(); - if (!jsonObject.contains("type") || !jsonObject.contains("content")) { - return; - } - const QJsonValue jsonType = jsonObject["type"]; - const QJsonValue jsonContent = jsonObject["content"]; - - uint64_t aspectIndex = 0; - QtJsonSerializer::QtJsonDeserialize(jsonType, aspectIndex); - QtJsonDeserializeInternal(jsonContent, outValue, aspectIndex, std::make_index_sequence {}); - } - }; - - template - struct QtJsonSerializer> { - static void QtJsonSerialize(QJsonValue& outJsonValue, const Common::Vec& inValue) - { - QJsonArray jsonArray; - for (auto i = 0; i < L; i++) { - QJsonValue jsonElement; - QtJsonSerializer::QtJsonSerialize(jsonElement, inValue[i]); - jsonArray.append(jsonElement); - } - outJsonValue = std::move(jsonArray); - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, Common::Vec& outValue) - { - if (!inJsonValue.isArray()) { - return; - } - const QJsonArray jsonArray = inJsonValue.toArray(); - if (jsonArray.size() != L) { - return; - } - for (auto i = 0; i < L; i++) { - QtJsonSerializer::QtJsonDeserialize(jsonArray[i], outValue[i]); - } - } - }; - - template - struct QtJsonSerializer> { - static void QtJsonSerialize(QJsonValue& jsonValue, const Common::Mat& inValue) - { - QJsonArray jsonArray; - for (auto i = 0; i < R; i++) { - for (auto j = 0; j < C; j++) { - QJsonValue jsonElement; - QtJsonSerializer::QtJsonSerialize(jsonElement, inValue.At(i, j)); - jsonArray.append(jsonElement); - } - } - jsonValue = std::move(jsonArray); - } - - static void QtJsonDeserialize(const QJsonValue& jsonValue, Common::Mat& outValue) - { - if (!jsonValue.isArray()) { - return; - } - const QJsonArray jsonArray = jsonValue.toArray(); - if (jsonArray.size() != R * C) { - return; - } - for (auto i = 0; i < jsonArray.size(); i++) { - auto row = i / C; - auto col = i % C; - QtJsonSerializer::QtJsonDeserialize(jsonArray[i], outValue.At(row, col)); - } - } - }; - - template - struct QtJsonSerializer> { - static void QtJsonSerialize(QJsonValue& outJsonValue, const Common::Quaternion& inValue) - { - QJsonValue jsonW; - QtJsonSerializer::QtJsonSerialize(jsonW, inValue.w); - - QJsonValue jsonX; - QtJsonSerializer::QtJsonSerialize(jsonX, inValue.x); - - QJsonValue jsonY; - QtJsonSerializer::QtJsonSerialize(jsonY, inValue.y); - - QJsonValue jsonZ; - QtJsonSerializer::QtJsonSerialize(jsonZ, inValue.z); - - QJsonArray jsonArray; - jsonArray.append(jsonX); - jsonArray.append(jsonY); - jsonArray.append(jsonZ); - jsonArray.append(jsonW); - outJsonValue = std::move(jsonArray); - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, Common::Quaternion& outValue) - { - if (!inJsonValue.isArray()) { - return; - } - const QJsonArray jsonArray = inJsonValue.toArray(); - if (jsonArray.size() != 4) { - return; - } - - const QJsonValue jsonX = jsonArray[0]; - QtJsonSerializer::QtJsonDeserialize(jsonX, outValue.x); - - const QJsonValue jsonY = jsonArray[1]; - QtJsonSerializer::QtJsonDeserialize(jsonY, outValue.y); - - const QJsonValue jsonZ = jsonArray[2]; - QtJsonSerializer::QtJsonDeserialize(jsonZ, outValue.z); - - const QJsonValue jsonW = jsonArray[3]; - QtJsonSerializer::QtJsonDeserialize(jsonW, outValue.w); - } - }; - - template <> - struct QtJsonSerializer { - static void QtJsonSerialize(QJsonValue& outJsonValue, const Common::Color& inValue) - { - QJsonValue jsonR; - QtJsonSerializer::QtJsonSerialize(jsonR, inValue.r); - - QJsonValue jsonG; - QtJsonSerializer::QtJsonSerialize(jsonG, inValue.g); - - QJsonValue jsonB; - QtJsonSerializer::QtJsonSerialize(jsonB, inValue.b); - - QJsonValue jsonA; - QtJsonSerializer::QtJsonSerialize(jsonA, inValue.a); - - QJsonArray jsonArray; - jsonArray.append(jsonR); - jsonArray.append(jsonG); - jsonArray.append(jsonB); - jsonArray.append(jsonA); - outJsonValue = std::move(jsonArray); - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, Common::Color& outValue) - { - if (!inJsonValue.isArray()) { - return; - } - const QJsonArray jsonArray = inJsonValue.toArray(); - if (jsonArray.size() != 4) { - return; - } - - const QJsonValue jsonR = jsonArray[0]; - QtJsonSerializer::QtJsonDeserialize(jsonR, outValue.r); - - const QJsonValue jsonG = jsonArray[1]; - QtJsonSerializer::QtJsonDeserialize(jsonG, outValue.g); - - const QJsonValue jsonB = jsonArray[2]; - QtJsonSerializer::QtJsonDeserialize(jsonB, outValue.b); - - const QJsonValue jsonA = jsonArray[3]; - QtJsonSerializer::QtJsonDeserialize(jsonA, outValue.a); - } - }; - - template <> - struct QtJsonSerializer { - static void QtJsonSerialize(QJsonValue& outJsonValue, const Common::LinearColor& inValue) - { - QJsonValue jsonR; - QtJsonSerializer::QtJsonSerialize(jsonR, inValue.r); - - QJsonValue jsonG; - QtJsonSerializer::QtJsonSerialize(jsonG, inValue.g); - - QJsonValue jsonB; - QtJsonSerializer::QtJsonSerialize(jsonB, inValue.b); - - QJsonValue jsonA; - QtJsonSerializer::QtJsonSerialize(jsonA, inValue.a); - - QJsonArray jsonArray; - jsonArray.append(jsonR); - jsonArray.append(jsonG); - jsonArray.append(jsonB); - jsonArray.append(jsonA); - outJsonValue = std::move(jsonArray); - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, Common::LinearColor& outValue) - { - if (!inJsonValue.isArray()) { - return; - } - const QJsonArray jsonArray = inJsonValue.toArray(); - if (jsonArray.size() != 4) { - return; - } - - const QJsonValue jsonR = jsonArray[0]; - QtJsonSerializer::QtJsonDeserialize(jsonR, outValue.r); - - const QJsonValue jsonG = jsonArray[1]; - QtJsonSerializer::QtJsonDeserialize(jsonG, outValue.g); - - const QJsonValue jsonB = jsonArray[2]; - QtJsonSerializer::QtJsonDeserialize(jsonB, outValue.b); - - const QJsonValue jsonA = jsonArray[3]; - QtJsonSerializer::QtJsonDeserialize(jsonA, outValue.a); - } - }; - - template <> - struct QtJsonSerializer { - static void QtJsonSerialize(QJsonValue& outJsonValue, const QString& inValue) - { - outJsonValue = inValue; - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, QString& outValue) - { - if (!inJsonValue.isString()) { - return; - } - outValue = inJsonValue.toString(); - } - }; - - template - struct QtJsonSerializer> { - static void QtJsonSerialize(QJsonValue& outJsonValue, const QList& inValue) - { - QJsonArray jsonArray; - for (const auto& element : inValue) { - QJsonValue jsonElement; - QtJsonSerializer::QtJsonSerialize(jsonElement, element); - jsonArray.push_back(jsonElement); - } - outJsonValue = std::move(jsonArray); - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, QList& outValue) - { - if (!inJsonValue.isArray()) { - return; - } - const QJsonArray jsonArray = inJsonValue.toArray(); - outValue.clear(); - outValue.reserve(jsonArray.size()); - for (const auto& jsonElement : jsonArray) { - T element; - QtJsonSerializer::QtJsonDeserialize(jsonElement, element); - outValue.emplaceBack(std::move(element)); - } - } - }; - - template - struct QtJsonSerializer> { - static void QtJsonSerialize(QJsonValue& outJsonValue, const QSet& inValue) - { - QJsonArray jsonArray; - for (const auto& element : inValue) { - QJsonValue jsonElement; - QtJsonSerializer::QtJsonSerialize(jsonElement, element); - jsonArray.push_back(jsonElement); - } - outJsonValue = std::move(jsonArray); - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, QSet& outValue) - { - if (!inJsonValue.isArray()) { - return; - } - const QJsonArray jsonArray = inJsonValue.toArray(); - outValue.clear(); - outValue.reserve(jsonArray.size()); - for (const auto& jsonElement : jsonArray) { - T element; - QtJsonSerializer::QtJsonDeserialize(jsonElement, element); - outValue.insert(std::move(element)); - } - } - }; - - template - struct QtJsonSerializer> { - static void QtJsonSerialize(QJsonValue& outJsonValue, const QMap& inValue) - { - QJsonArray jsonArray; - for (const auto& [key, value] : inValue) { - QJsonValue jsonKey; - QtJsonSerializer::QtJsonSerialize(jsonKey, key); - - QJsonValue jsonValue; - QtJsonSerializer::QtJsonSerialize(jsonValue, value); - - QJsonObject jsonObject; - jsonObject["key"] = jsonKey; - jsonObject["value"] = jsonValue; - jsonArray.append(std::move(jsonObject)); - } - outJsonValue = std::move(jsonArray); - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, QMap& outValue) - { - if (!inJsonValue.isArray()) { - return; - } - const QJsonArray jsonArray = inJsonValue.toArray(); - outValue.clear(); - for (const auto& jsonElement : jsonArray) { - if (!jsonElement.isObject()) { - continue; - } - const QJsonObject jsonObject = jsonElement.toObject(); - - K key; - if (jsonObject.contains("key")) { - const QJsonValue jsonKey = jsonObject["key"]; - QtJsonSerializer::QtJsonDeserialize(jsonKey, key); - } - - V value; - if (jsonObject.contains("value")) { - const QJsonValue jsonValue = jsonObject["value"]; - QtJsonSerializer::QtJsonDeserialize(jsonValue, value); - } - - outValue.emplace(std::move(key), std::move(value)); - } - } - }; - - template - struct QtJsonSerializer { - static void QtJsonSerializeDyn(QJsonObject& outJsonObject, const Mirror::Class& inClass, const Mirror::Argument& inValue) - { - const auto* baseClass = inClass.GetBaseClass(); - const auto& memberVariables = inClass.GetMemberVariables(); - const auto defaultObject = inClass.GetDefaultObject(); - - if (baseClass != nullptr) { - QJsonObject baseContentJson; - QtJsonSerializeDyn(baseContentJson, *baseClass, inValue); - outJsonObject["_base"] = std::move(baseContentJson); - } - - for (const auto& memberVariable : memberVariables | std::views::values) { - if (memberVariable.IsTransient()) { - continue; - } - - const bool sameAsDefault = defaultObject.Empty() || !memberVariable.GetTypeInfo()->equalComparable - ? false - : memberVariable.GetDyn(inValue) == memberVariable.GetDyn(defaultObject); - - if (sameAsDefault) { - continue; - } - QJsonValue memberContentJson; - Mirror::Any memberRef = memberVariable.GetDyn(inValue); - SerializeValueDyn(memberContentJson, memberRef); - outJsonObject[QString::fromStdString(memberVariable.GetName())] = memberContentJson; - } - } - - static void QtJsonDeserializeDyn(const QJsonObject& inJsonObject, const Mirror::Class& inClass, const Mirror::Argument& outValue) - { - const auto* baseClass = inClass.GetBaseClass(); - const auto defaultObject = inClass.GetDefaultObject(); - - if (inJsonObject.contains("_base")) { - if (const QJsonValue baseContentJson = inJsonObject["_base"]; - baseClass != nullptr && baseContentJson.isObject()) { - QtJsonDeserializeDyn(baseContentJson.toObject(), *baseClass, outValue); - } - } - - for (const auto& memberVariable : inClass.GetMemberVariables() | std::views::values) { - const auto& memberName = memberVariable.GetName(); - if (const QJsonValue memberContentJson = inJsonObject[QString::fromStdString(memberName)]; - !memberContentJson.isNull()) { - DeserializeValueDyn(memberContentJson, memberVariable.GetDyn(outValue)); - } else { - if (!defaultObject.Empty()) { - memberVariable.SetDyn(outValue, memberVariable.GetDyn(defaultObject)); - } - } - } - } - - static void QtJsonSerialize(QJsonValue& outJsonValue, const T& inValue) - { - QJsonObject jsonObject; - QtJsonSerializeDyn(jsonObject, Mirror::Class::Get(), Mirror::ForwardAsArg(inValue)); - outJsonValue = std::move(jsonObject); - } - - static void QtJsonDeserialize(const QJsonValue& inJsonValue, T& outValue) - { - if (inJsonValue.isNull()) { - return; - } - QtJsonDeserializeDyn(inJsonValue.toObject(), Mirror::Class::Get(), Mirror::ForwardAsArg(outValue)); - } - - private: - using SupportedSimpleTypes = std::tuple< - bool, int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t, float, double, std::string, std::wstring, - Common::FVec2, Common::FVec3, Common::FVec4, Common::FMat4x4, Common::FQuat, Common::Color, Common::LinearColor, - QString - >; - - using SupportedTemplateViews = std::tuple< - Common::Wrap, Common::Wrap, Common::Wrap, Common::Wrap, - Common::Wrap, Common::Wrap, Common::Wrap, Common::Wrap, - Common::Wrap, Common::Wrap, Common::Wrap, Common::Wrap, Common::Wrap - >; - - static auto BuildSimpleTypeDynSerializers() - { - std::unordered_map> result; - Common::TupleTypeTraverser::Each([&](T0&&) -> void { - using RawType = std::decay_t; - result.emplace(Mirror::GetTypeId(), [](QJsonValue& outJsonValue, const Mirror::Any& inRef) -> void { - QtJsonSerializer::QtJsonSerialize(outJsonValue, inRef.As()); - }); - }); - return result; - } - - static auto BuildSimpleTypeDynDeserializers() - { - std::unordered_map> result; - Common::TupleTypeTraverser::Each([&](T0&&) -> void { - using RawType = std::decay_t; - result.emplace(Mirror::GetTypeId(), [](const QJsonValue& inJsonValue, const Mirror::Any& outRef) -> void { - QtJsonSerializer::QtJsonDeserialize(inJsonValue, outRef.As()); - }); - }); - return result; - } - - static auto BuildTemplateViewDynSerializers() - { - std::unordered_map> result; - Common::TupleTypeTraverser::Each([&](T0&&) -> void { - using RawType = typename std::decay_t::RawType; - result.emplace(RawType::id, [](QJsonValue& outJsonValue, const Mirror::Any& inRef) -> void { - const RawType view(inRef); - SerializeDynWithView(outJsonValue, view); - }); - }); - return result; - } - - static auto BuildTemplateViewDynDeserializers() - { - std::unordered_map> result; - Common::TupleTypeTraverser::Each([&](T0&&) -> void { - using RawType = typename std::decay_t::RawType; - result.emplace(RawType::id, [](const QJsonValue& inJsonValue, const Mirror::Any& inRef) -> void { - const RawType view(inRef); - DeserializeDynWithView(inJsonValue, view); - }); - }); - return result; - } - - static void SerializeDynWithView(QJsonValue& outJsonValue, const Mirror::StdOptionalView& inView) - { - if (inView.HasValue()) { - SerializeValueDyn(outJsonValue, inView.ConstValue()); - } else { - outJsonValue = QJsonValue::Null; - } - } - - static void DeserializeDynWithView(const QJsonValue& inJsonValue, const Mirror::StdOptionalView& inView) - { - inView.Reset(); - if (inJsonValue.isNull()) { - return; - } - const Mirror::Any newValueRef = inView.EmplaceDefault(); - DeserializeValueDyn(inJsonValue, newValueRef); - } - - static void SerializeDynWithView(QJsonValue& outJsonValue, const Mirror::StdPairView& inView) - { - QJsonValue jsonKey; - SerializeValueDyn(jsonKey, inView.ConstKey()); - - QJsonValue jsonValue; - SerializeValueDyn(jsonValue, inView.ConstValue()); - - QJsonObject jsonObject; - jsonObject["key"] = jsonKey; - jsonObject["value"] = jsonValue; - outJsonValue = std::move(jsonObject); - } - - static void DeserializeDynWithView(const QJsonValue& inJsonValue, const Mirror::StdPairView& inView) - { - if (!inJsonValue.isObject()) { - return; - } - inView.Reset(); - const QJsonObject jsonObject = inJsonValue.toObject(); - if (jsonObject.contains("key")) { - const QJsonValue jsonKey = jsonObject["key"]; - DeserializeValueDyn(jsonKey, inView.Key()); - } - if (jsonObject.contains("value")) { - const QJsonValue jsonValue = jsonObject["value"]; - DeserializeValueDyn(jsonValue, inView.Value()); - } - } - - static void SerializeDynWithView(QJsonValue& outJsonValue, const Mirror::StdArrayView& inView) - { - QJsonArray jsonArray; - for (auto i = 0; i < inView.Size(); i++) { - QJsonValue jsonElement; - SerializeValueDyn(jsonElement, inView.ConstAt(i)); - jsonArray.append(jsonElement); - } - outJsonValue = std::move(jsonArray); - } - - static void DeserializeDynWithView(const QJsonValue& inJsonValue, const Mirror::StdArrayView& inView) - { - if (!inJsonValue.isArray()) { - return; - } - const QJsonArray jsonArray = inJsonValue.toArray(); - if (jsonArray.size() != inView.Size()) { - return; - } - for (auto i = 0; i < jsonArray.size(); i++) { - const QJsonValue jsonElement = jsonArray[i]; - DeserializeValueDyn(jsonElement, inView.At(i)); - } - } - - static void SerializeDynWithView(QJsonValue& outJsonValue, const Mirror::StdVectorView& inView) - { - QJsonArray jsonArray; - for (auto i = 0; i < inView.Size(); i++) { - QJsonValue jsonElement; - SerializeValueDyn(jsonElement, inView.ConstAt(i)); - jsonArray.append(jsonElement); - } - outJsonValue = std::move(jsonArray); - } - - static void DeserializeDynWithView(const QJsonValue& inJsonValue, const Mirror::StdVectorView& inView) - { - if (!inJsonValue.isArray()) { - return; - } - const QJsonArray jsonArray = inJsonValue.toArray(); - inView.Clear(); - inView.Reserve(jsonArray.size()); - for (const auto& jsonElement : jsonArray) { - DeserializeValueDyn(jsonElement, inView.EmplaceDefaultBack()); - } - } - - static void SerializeDynWithView(QJsonValue& outJsonValue, const Mirror::StdListView& inView) - { - QJsonArray jsonArray; - inView.ConstTraverse([&](const Mirror::Any& elementRef) -> void { - QJsonValue jsonElement; - SerializeValueDyn(jsonElement, elementRef); - jsonArray.append(jsonElement); - }); - outJsonValue = std::move(jsonArray); - } - - static void DeserializeDynWithView(const QJsonValue& inJsonValue, const Mirror::StdListView& inView) - { - if (!inJsonValue.isArray()) { - return; - } - const QJsonArray jsonArray = inJsonValue.toArray(); - inView.Clear(); - for (const auto& jsonElement : jsonArray) { - DeserializeValueDyn(jsonElement, inView.EmplaceDefaultBack()); - } - } - - static void SerializeDynWithView(QJsonValue& outJsonValue, const Mirror::StdUnorderedSetView& inView) - { - QJsonArray jsonArray; - inView.ConstTraverse([&](const Mirror::Any& elementRef) -> void { - QJsonValue jsonElement; - SerializeValueDyn(jsonElement, elementRef); - jsonArray.append(jsonElement); - }); - outJsonValue = std::move(jsonArray); - } - - static void DeserializeDynWithView(const QJsonValue& inJsonValue, const Mirror::StdUnorderedSetView& inView) - { - if (!inJsonValue.isArray()) { - return; - } - const QJsonArray jsonArray = inJsonValue.toArray(); - inView.Clear(); - inView.Reserve(jsonArray.size()); - for (const auto& jsonElement : jsonArray) { - Mirror::Any element = inView.CreateElement(); - DeserializeValueDyn(jsonElement, element); - inView.Emplace(element); - } - } - - static void SerializeDynWithView(QJsonValue& outJsonValue, const Mirror::StdSetView& inView) - { - QJsonArray jsonArray; - inView.ConstTraverse([&](const Mirror::Any& elementRef) -> void { - QJsonValue jsonElement; - SerializeValueDyn(jsonElement, elementRef); - jsonArray.append(jsonElement); - }); - outJsonValue = std::move(jsonArray); - } - - static void DeserializeDynWithView(const QJsonValue& inJsonValue, const Mirror::StdSetView& inView) - { - if (!inJsonValue.isArray()) { - return; - } - const QJsonArray jsonArray = inJsonValue.toArray(); - inView.Clear(); - for (const auto& jsonElement : jsonArray) { - Mirror::Any element = inView.CreateElement(); - DeserializeValueDyn(jsonElement, element); - inView.Emplace(element); - } - } - - static void SerializeDynWithView(QJsonValue& outJsonValue, const Mirror::StdUnorderedMapView& inView) - { - QJsonArray jsonArray; - inView.ConstTraverse([&](const Mirror::Any& inKey, const Mirror::Any& inValue) -> void { - QJsonValue jsonKey; - SerializeValueDyn(jsonKey, inKey); - - QJsonValue jsonValue; - SerializeValueDyn(jsonValue, inValue); - - QJsonObject jsonObject; - jsonObject["key"] = jsonKey; - jsonObject["value"] = jsonValue; - jsonArray.append(jsonObject); - }); - outJsonValue = std::move(jsonArray); - } - - static void DeserializeDynWithView(const QJsonValue& inJsonValue, const Mirror::StdUnorderedMapView& inView) - { - if (!inJsonValue.isArray()) { - return; - } - const QJsonArray jsonArray = inJsonValue.toArray(); - inView.Clear(); - inView.Reserve(jsonArray.size()); - for (const auto& jsonElement : jsonArray) { - if (!jsonElement.isObject()) { - continue; - } - const QJsonObject jsonObject = jsonElement.toObject(); - Mirror::Any key = inView.CreateKey(); - if (jsonObject.contains("key")) { - QJsonValue jsonKey = jsonObject["key"]; - DeserializeValueDyn(jsonKey, key); - } - Mirror::Any value = inView.CreateValue(); - if (jsonObject.contains("value")) { - QJsonValue jsonValue = jsonObject["value"]; - DeserializeValueDyn(jsonValue, value); - } - inView.Emplace(key, value); - } - } - - static void SerializeDynWithView(QJsonValue& outJsonValue, const Mirror::StdMapView& inView) - { - QJsonArray jsonArray; - inView.ConstTraverse([&](const Mirror::Any& inKey, const Mirror::Any& inValue) -> void { - QJsonValue jsonKey; - SerializeValueDyn(jsonKey, inKey); - - QJsonValue jsonValue; - SerializeValueDyn(jsonValue, inValue); - - QJsonObject jsonObject; - jsonObject["key"] = jsonKey; - jsonObject["value"] = jsonValue; - jsonArray.append(jsonObject); - }); - outJsonValue = std::move(jsonArray); - } - - static void DeserializeDynWithView(const QJsonValue& inJsonValue, const Mirror::StdMapView& inView) - { - if (!inJsonValue.isArray()) { - return; - } - const QJsonArray jsonArray = inJsonValue.toArray(); - inView.Clear(); - for (const auto& jsonElement : jsonArray) { - if (!jsonElement.isObject()) { - continue; - } - const QJsonObject jsonObject = jsonElement.toObject(); - Mirror::Any key = inView.CreateKey(); - if (jsonObject.contains("key")) { - QJsonValue jsonKey = jsonObject["key"]; - DeserializeValueDyn(jsonKey, key); - } - Mirror::Any value = inView.CreateValue(); - if (jsonObject.contains("value")) { - QJsonValue jsonValue = jsonObject["value"]; - DeserializeValueDyn(jsonValue, value); - } - inView.Emplace(key, value); - } - } - - static void SerializeDynWithView(QJsonValue& outJsonValue, const Mirror::StdTupleView& inView) - { - QJsonObject jsonObject; - inView.ConstTraverse([&](const Mirror::Any& inElementRef, size_t inIndex) -> void { - QJsonValue jsonElement; - SerializeValueDyn(jsonElement, inElementRef); - jsonObject[QString::number(inIndex)] = jsonElement; - }); - outJsonValue = std::move(jsonObject); - } - - static void DeserializeDynWithView(const QJsonValue& inJsonValue, const Mirror::StdTupleView& inView) - { - if (!inJsonValue.isObject()) { - return; - } - const QJsonObject jsonObject = inJsonValue.toObject(); - for (const auto& key : jsonObject.keys()) { - const auto index = key.toUInt(); - const QJsonValue jsonElement = jsonObject[key]; - const Mirror::Any elementRef = inView.Get(index); - DeserializeValueDyn(jsonElement, elementRef); - } - } - - static void SerializeDynWithView(QJsonValue& outJsonValue, const Mirror::StdVariantView& inView) - { - QJsonValue typeJson; - QtJsonSerializer::QtJsonSerialize(typeJson, inView.Index()); - - QJsonValue contentJson; - inView.Visit([&](const Mirror::Any& inValueRef) -> void { - SerializeValueDyn(contentJson, inValueRef); - }); - - QJsonObject jsonObject; - jsonObject["type"] = typeJson; - jsonObject["content"] = contentJson; - outJsonValue = std::move(jsonObject); - } - - static void DeserializeDynWithView(const QJsonValue& inJsonValue, const Mirror::StdVariantView& inView) - { - if (!inJsonValue.isObject()) { - return; - } - const QJsonObject jsonObject = inJsonValue.toObject(); - if (!jsonObject.contains("type") || !jsonObject.contains("content")) { - return; - } - - const QJsonValue typeJson = jsonObject["type"]; - const QJsonValue contentJson = jsonObject["content"]; - - uint64_t type = 0; - QtJsonSerializer::QtJsonDeserialize(typeJson, type); - - Mirror::Any tempObj = inView.CreateElement(type); - DeserializeValueDyn(contentJson, tempObj); - - (void) inView.Emplace(type, tempObj); - } - - static void SerializeDynWithView(QJsonValue& outJsonValue, const QListMetaView& inView) - { - QJsonArray jsonArray; - for (auto i = 0; i < inView.Size(); i++) { - QJsonValue jsonElement; - SerializeValueDyn(jsonElement, inView.ConstAt(i)); - jsonArray.append(jsonElement); - } - outJsonValue = std::move(jsonArray); - } - - static void DeserializeDynWithView(const QJsonValue& inJsonValue, const QListMetaView& inView) - { - if (!inJsonValue.isArray()) { - return; - } - const QJsonArray jsonArray = inJsonValue.toArray(); - inView.Clear(); - inView.Reserve(jsonArray.size()); - for (const auto& jsonElement : jsonArray) { - DeserializeValueDyn(jsonElement, inView.EmplaceDefaultBack()); - } - } - - static void SerializeDynWithView(QJsonValue& outJsonValue, const QMapMetaView& inView) - { - QJsonArray jsonArray; - inView.ConstTraverse([&](const Mirror::Any& inKey, const Mirror::Any& inValue) -> void { - QJsonValue jsonKey; - SerializeValueDyn(jsonKey, inKey); - - QJsonValue jsonValue; - SerializeValueDyn(jsonValue, inValue); - - QJsonObject jsonObject; - jsonObject["key"] = jsonKey; - jsonObject["value"] = jsonValue; - jsonArray.append(jsonObject); - }); - outJsonValue = std::move(jsonArray); - } - - static void DeserializeDynWithView(const QJsonValue& inJsonValue, const QMapMetaView& inView) - { - if (!inJsonValue.isArray()) { - return; - } - const QJsonArray jsonArray = inJsonValue.toArray(); - inView.Clear(); - inView.Reserve(jsonArray.size()); - for (const auto& jsonElement : jsonArray) { - if (!jsonElement.isObject()) { - continue; - } - const QJsonObject jsonObject = jsonElement.toObject(); - Mirror::Any key = inView.CreateKey(); - if (jsonObject.contains("key")) { - QJsonValue jsonKey = jsonObject["key"]; - DeserializeValueDyn(jsonKey, key); - } - Mirror::Any value = inView.CreateValue(); - if (jsonObject.contains("value")) { - QJsonValue jsonValue = jsonObject["value"]; - DeserializeValueDyn(jsonValue, value); - } - inView.Emplace(key, value); - } - } - - static void SerializeValueDyn(QJsonValue& outJsonValue, const Mirror::Any& inValueRef) - { - static auto simpleTypeDynSerializers = BuildSimpleTypeDynSerializers(); - static auto templateViewDynSerializers = BuildTemplateViewDynSerializers(); - - if (inValueRef.HasTemplateView()) { - const Mirror::TemplateViewId templateViewId = inValueRef.GetTemplateViewId(); - AssertWithReason(templateViewDynSerializers.contains(templateViewId), std::format("QtJsonSerializeValueDyn not support type {}", inValueRef.Type()->name)); - templateViewDynSerializers.at(templateViewId)(outJsonValue, inValueRef); - } else if (const Mirror::Class* clazz = inValueRef.GetDynamicClass(); clazz != nullptr) { - AssertWithReason(!inValueRef.Type()->isPointer, "QtJsonSerializeValueDyn not support meta object pointer"); - QJsonObject jsonObject; - QtJsonSerializeDyn(jsonObject, *clazz, inValueRef); - outJsonValue = std::move(jsonObject); - } else { - const Mirror::TypeId typeId = inValueRef.TypeId(); - AssertWithReason(simpleTypeDynSerializers.contains(typeId), std::format("QtJsonSerializeValueDyn not support type {}", inValueRef.Type()->name)); - simpleTypeDynSerializers.at(typeId)(outJsonValue, inValueRef); - } - } - - static void DeserializeValueDyn(const QJsonValue& inJsonValue, const Mirror::Any& outValueRef) - { - static auto simpleTypeDynDeserializers = BuildSimpleTypeDynDeserializers(); - static auto templateViewDynDeserializers = BuildTemplateViewDynDeserializers(); - - if (outValueRef.HasTemplateView()) { - const Mirror::TemplateViewId templateViewId = outValueRef.GetTemplateViewId(); - AssertWithReason(templateViewDynDeserializers.contains(templateViewId), std::format("QtJsonSerializeValueDyn not support type {}", outValueRef.Type()->name)); - templateViewDynDeserializers.at(templateViewId)(inJsonValue, outValueRef); - } else if (const Mirror::Class* clazz = outValueRef.GetDynamicClass(); clazz != nullptr) { - AssertWithReason(!outValueRef.Type()->isPointer, "QtJsonSerializeValueDyn not support meta object pointer"); - if (!inJsonValue.isObject()) { - return; - } - const QJsonObject jsonObject = inJsonValue.toObject(); - QtJsonDeserializeDyn(jsonObject, *clazz, outValueRef); - } else { - const Mirror::TypeId typeId = outValueRef.TypeId(); - AssertWithReason(simpleTypeDynDeserializers.contains(typeId), std::format("QtJsonSerializeValueDyn not support type {}", outValueRef.Type()->name)); - simpleTypeDynDeserializers.at(typeId)(inJsonValue, outValueRef); - } - } - }; -} diff --git a/Editor/Include/Editor/Qt/MirrorTemplateView.h b/Editor/Include/Editor/Qt/MirrorTemplateView.h deleted file mode 100644 index 79170e8cf..000000000 --- a/Editor/Include/Editor/Qt/MirrorTemplateView.h +++ /dev/null @@ -1,375 +0,0 @@ -// -// Created by Kindem on 2025/8/26. -// - -#pragma once - -#include -#include - -#include - -namespace Editor { - struct QListMetaViewRtti { - static constexpr Mirror::TemplateViewId id = Common::HashUtils::StrCrc32("Editor::QListMetaView"); - - using GetElementTypeFunc = const Mirror::TypeInfo*(); - using GetSizeFunc = size_t(const Mirror::Any&); - using ReserveFunc = void(const Mirror::Any&, size_t); - using ResizeFunc = void(const Mirror::Any&, size_t); - using ClearFunc = void(const Mirror::Any&); - using GetElementFunc = Mirror::Any(const Mirror::Any&, size_t); - using GetConstElementFunc = Mirror::Any(const Mirror::Any&, size_t); - using EmplaceBackFunc = Mirror::Any(const Mirror::Any&, const Mirror::Argument&); - using EmplaceDefaultBackFunc = Mirror::Any(const Mirror::Any&); - using RemoveFunc = void(const Mirror::Any&, size_t); - - template static const Mirror::TypeInfo* GetElementType(); - template static size_t GetSize(const Mirror::Any& inRef); - template static void Reserve(const Mirror::Any& inRef, size_t inSize); - template static void Resize(const Mirror::Any& inRef, size_t inSize); - template static void Clear(const Mirror::Any& inRef); - template static Mirror::Any GetElement(const Mirror::Any& inRef, size_t inIndex); - template static Mirror::Any GetConstElement(const Mirror::Any& inRef, size_t inIndex); - template static Mirror::Any EmplaceBack(const Mirror::Any& inRef, const Mirror::Argument& inTempObj); - template static Mirror::Any EmplaceDefaultBack(const Mirror::Any& inRef); - template static void Remove(const Mirror::Any& inRef, size_t inIndex); - - GetElementTypeFunc* getElementType; - GetSizeFunc* getSize; - ReserveFunc* reserve; - ResizeFunc* resize; - ClearFunc* clear; - GetElementFunc* getElement; - GetConstElementFunc* getConstElement; - EmplaceBackFunc* emplaceBack; - EmplaceDefaultBackFunc* emplaceDefaultBack; - RemoveFunc* remove; - }; - - template - static constexpr QListMetaViewRtti qListMetaViewRttiImpl = { - &QListMetaViewRtti::GetElementType, - &QListMetaViewRtti::GetSize, - &QListMetaViewRtti::Reserve, - &QListMetaViewRtti::Resize, - &QListMetaViewRtti::Clear, - &QListMetaViewRtti::GetElement, - &QListMetaViewRtti::GetConstElement, - &QListMetaViewRtti::EmplaceBack, - &QListMetaViewRtti::EmplaceDefaultBack, - &QListMetaViewRtti::Remove - }; - - class QListMetaView { - public: - static constexpr Mirror::TemplateViewId id = QListMetaViewRtti::id; - - explicit QListMetaView(const Mirror::Any& inRef); - NonCopyable(QListMetaView) - NonMovable(QListMetaView) - - const Mirror::TypeInfo* ElementType() const; - size_t Size() const; - void Reserve(size_t inSize) const; - void Resize(size_t inSize) const; - void Clear() const; - Mirror::Any At(size_t inIndex) const; - Mirror::Any ConstAt(size_t inIndex) const; - Mirror::Any EmplaceBack(const Mirror::Argument& inTempObj) const; - Mirror::Any EmplaceDefaultBack() const; - void Remove(size_t inIndex) const; - - private: - Mirror::Any ref; - const QListMetaViewRtti* rtti; - }; - - struct QMapMetaViewRtti { - static constexpr Mirror::TemplateViewId id = Common::HashUtils::StrCrc32("Editor::QMapMetaView"); - - using GetKeyTypeFunc = const Mirror::TypeInfo*(); - using GetValueTypeFunc = const Mirror::TypeInfo*(); - using CreateKeyFunc = Mirror::Any(); - using CreateValueFunc = Mirror::Any(); - using GetSizeFunc = size_t(const Mirror::Any&); - using ReserveFunc = void(const Mirror::Any&, size_t); - using ClearFunc = void(const Mirror::Any&); - using GetOrAddFunc = Mirror::Any(const Mirror::Any&, const Mirror::Argument&); - using ConstAtFunc = Mirror::Any(const Mirror::Any&, const Mirror::Argument&); - using TraverseFunc = void(const Mirror::Any&, const std::function&); - using ConstTraverseFunc = void(const Mirror::Any&, const std::function&); - using ContainsFunc = bool(const Mirror::Any&, const Mirror::Argument&); - using EmplaceFunc = void(const Mirror::Any&, const Mirror::Argument&, const Mirror::Argument&); - using EraseFunc = void(const Mirror::Any&, const Mirror::Argument&); - - template static const Mirror::TypeInfo* GetKeyType(); - template static const Mirror::TypeInfo* GetValueType(); - template static Mirror::Any CreateKey(); - template static Mirror::Any CreateValue(); - template static size_t GetSize(const Mirror::Any& inRef); - template static void Reserve(const Mirror::Any& inRef, size_t inSize); - template static void Clear(const Mirror::Any& inRef); - template static Mirror::Any GetOrAdd(const Mirror::Any& inRef, const Mirror::Argument& inKey); - template static Mirror::Any ConstAt(const Mirror::Any& inRef, const Mirror::Argument& inKey); - template static void Traverse(const Mirror::Any& inRef, const std::function& inVisitor); - template static void ConstTraverse(const Mirror::Any& inRef, const std::function& inVisitor); - template static bool Contains(const Mirror::Any& inRef, const Mirror::Argument& inKey); - template static void Emplace(const Mirror::Any& inRef, const Mirror::Argument& inKey, const Mirror::Argument& inValue); - template static void Erase(const Mirror::Any& inRef, const Mirror::Argument& inKey); - - GetKeyTypeFunc* getKeyType; - GetValueTypeFunc* getValueType; - CreateKeyFunc* createKey; - CreateValueFunc* createValue; - GetSizeFunc* getSize; - ReserveFunc* reserve; - ClearFunc* clear; - GetOrAddFunc* getOrAdd; - ConstAtFunc* constAt; - TraverseFunc* traverse; - ConstTraverseFunc* constTraverse; - ContainsFunc* contains; - EmplaceFunc* emplace; - EraseFunc* erase; - }; - - template - static constexpr QMapMetaViewRtti qMapMetaViewRttiImpl = { - &QMapMetaViewRtti::GetKeyType, - &QMapMetaViewRtti::GetValueType, - &QMapMetaViewRtti::CreateKey, - &QMapMetaViewRtti::CreateValue, - &QMapMetaViewRtti::GetSize, - &QMapMetaViewRtti::Reserve, - &QMapMetaViewRtti::Clear, - &QMapMetaViewRtti::GetOrAdd, - &QMapMetaViewRtti::ConstAt, - &QMapMetaViewRtti::Traverse, - &QMapMetaViewRtti::ConstTraverse, - &QMapMetaViewRtti::Contains, - &QMapMetaViewRtti::Emplace, - &QMapMetaViewRtti::Erase - }; - - class QMapMetaView { - public: - using PairTraverser = std::function; - static constexpr Mirror::TemplateViewId id = QMapMetaViewRtti::id; - - explicit QMapMetaView(const Mirror::Any& inRef); - NonCopyable(QMapMetaView) - NonMovable(QMapMetaView) - - const Mirror::TypeInfo* GetKeyType() const; - const Mirror::TypeInfo* GetValueType() const; - Mirror::Any CreateKey() const; - Mirror::Any CreateValue() const; - size_t GetSize() const; - void Reserve(size_t inSize) const; - void Clear() const; - Mirror::Any GetOrAdd(const Mirror::Argument& inKey) const; - Mirror::Any ConstAt(const Mirror::Argument& inKey) const; - void Traverse(const PairTraverser& inTraverser) const; - void ConstTraverse(const PairTraverser& inTraverser) const; - bool Contains(const Mirror::Argument& inKey) const; - void Emplace(const Mirror::Argument& inTempKey, const Mirror::Argument& inTempValue) const; - void Erase(const Mirror::Argument& inKey) const; - - private: - Mirror::Any ref; - const QMapMetaViewRtti* rtti; - }; -} // namespace Editor - -namespace Mirror { - template - struct TemplateViewRttiGetter> { - static constexpr TemplateViewId Id(); - static const void* Get(); - }; - - template - struct TemplateViewRttiGetter> { - static constexpr TemplateViewId Id(); - static const void* Get(); - }; -} - -namespace Editor { - template - const Mirror::TypeInfo* QListMetaViewRtti::GetElementType() - { - return Mirror::GetTypeInfo(); - } - - template - size_t QListMetaViewRtti::GetSize(const Mirror::Any& inRef) - { - return inRef.As&>().size(); - } - - template - void QListMetaViewRtti::Reserve(const Mirror::Any& inRef, size_t inSize) - { - inRef.As&>().reserve(inSize); - } - - template - void QListMetaViewRtti::Resize(const Mirror::Any& inRef, size_t inSize) - { - inRef.As&>().resize(inSize); - } - - template - void QListMetaViewRtti::Clear(const Mirror::Any& inRef) - { - inRef.As&>().clear(); - } - - template - Mirror::Any QListMetaViewRtti::GetElement(const Mirror::Any& inRef, size_t inIndex) - { - return std::ref(inRef.As&>()[inIndex]); - } - - template - Mirror::Any QListMetaViewRtti::GetConstElement(const Mirror::Any& inRef, size_t inIndex) - { - return std::ref(inRef.As&>()[inIndex]); - } - - template - Mirror::Any QListMetaViewRtti::EmplaceBack(const Mirror::Any& inRef, const Mirror::Argument& inTempObj) - { - return std::ref(inRef.As&>().emplaceBack(std::move(inTempObj.As()))); - } - - template - Mirror::Any QListMetaViewRtti::EmplaceDefaultBack(const Mirror::Any& inRef) - { - return std::ref(inRef.As&>().emplaceBack()); - } - - template - void QListMetaViewRtti::Remove(const Mirror::Any& inRef, size_t inIndex) - { - inRef.As&>().remove(inIndex); - } - - template - const Mirror::TypeInfo* QMapMetaViewRtti::GetKeyType() - { - return Mirror::GetTypeInfo(); - } - - template - const Mirror::TypeInfo* QMapMetaViewRtti::GetValueType() - { - return Mirror::GetTypeInfo(); - } - - template - Mirror::Any QMapMetaViewRtti::CreateKey() - { - return K(); - } - - template - Mirror::Any QMapMetaViewRtti::CreateValue() - { - return V(); - } - - template - size_t QMapMetaViewRtti::GetSize(const Mirror::Any& inRef) - { - return inRef.As&>().size(); - } - - template - void QMapMetaViewRtti::Reserve(const Mirror::Any& inRef, size_t inSize) - { - inRef.As&>().reserve(inSize); - } - - template - void QMapMetaViewRtti::Clear(const Mirror::Any& inRef) - { - inRef.As&>().clear(); - } - - template - Mirror::Any QMapMetaViewRtti::ConstAt(const Mirror::Any& inRef, const Mirror::Argument& inKey) - { - return inRef.As&>()[inKey]; - } - - template - Mirror::Any QMapMetaViewRtti::GetOrAdd(const Mirror::Any& inRef, const Mirror::Argument& inKey) - { - return inRef.As&>()[inKey]; - } - - template - void QMapMetaViewRtti::Traverse(const Mirror::Any& inRef, const std::function& inVisitor) - { - for (QMap& map = inRef.As&>(); - const auto& [key, value] : map) { - inVisitor(std::ref(key), std::ref(value)); - } - } - - template - void QMapMetaViewRtti::ConstTraverse(const Mirror::Any& inRef, const std::function& inVisitor) - { - for (const QMap& map = inRef.As&>(); - const auto& [key, value] : map) { - inVisitor(std::ref(key), std::ref(value)); - } - } - - template - bool QMapMetaViewRtti::Contains(const Mirror::Any& inRef, const Mirror::Argument& inKey) - { - return inRef.As&>().contains(inKey); - } - - template - void QMapMetaViewRtti::Emplace(const Mirror::Any& inRef, const Mirror::Argument& inKey, const Mirror::Argument& inValue) - { - inRef.As&>().emplace(inKey, inValue); - } - - template - void QMapMetaViewRtti::Erase(const Mirror::Any& inRef, const Mirror::Argument& inKey) - { - inRef.As&>().remove(inKey); - } -} // namespace Editor - -namespace Mirror { - template - constexpr TemplateViewId TemplateViewRttiGetter>::Id() - { - return Editor::QListMetaViewRtti::id; - } - - template - const void* TemplateViewRttiGetter>::Get() - { - return &Editor::qListMetaViewRttiImpl; - } - - template - constexpr TemplateViewId TemplateViewRttiGetter>::Id() - { - return Editor::QMapMetaViewRtti::id; - } - - template - const void* TemplateViewRttiGetter>::Get() - { - return &Editor::qMapMetaViewRttiImpl; - } -} // namespace Mirror diff --git a/Editor/Include/Editor/SceneClient.h b/Editor/Include/Editor/SceneClient.h new file mode 100644 index 000000000..d30fbe216 --- /dev/null +++ b/Editor/Include/Editor/SceneClient.h @@ -0,0 +1,66 @@ +// +// Created by johnk on 2026/7/4. +// + +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace Editor { + class EditorWindow; + + class SceneClient final : public Runtime::Client { + public: + SceneClient(); + ~SceneClient() override; + + NonCopyable(SceneClient) + NonMovable(SceneClient) + + Runtime::World& GetWorld() override; + Runtime::RenderSurface* GetRenderSurface() override; + void SetEditorWindow(EditorWindow& inWindow); + void SetRenderSurface(Runtime::RenderSurface* inRenderSurface); + void ResizeRenderSurface(uint32_t inWidth, uint32_t inHeight); + // loads the project's main level when present, otherwise authors the default level content (player start, + // ground and a cube using engine-default unlit assets materialized into the project) and saves it + void OpenProjectLevel(); + void SaveLevel(); + // the transient camera entity the editor scene renders through and moves, entityNull before the level is opened + Runtime::Entity GetEditorCamera() const; + void TickEditorCamera(float inDeltaSeconds); + void SetSceneHovered(bool inHovered); + void SetSceneFocused(bool inFocused); + bool IsSceneHovered() const; + bool IsCameraLooking() const; + void OnKey(int inKey, bool inPressed); + void BeginCameraLook(); + void EndCameraLook(); + void AddCameraLookDelta(float inDeltaX, float inDeltaY); + + private: + void CreateEditorCamera(); + Common::FVec3 CameraMoveInput() const; + + Core::Uri levelUri; + Runtime::World world; + EditorWindow* window; + Runtime::RenderSurface* renderSurface; + Runtime::Entity editorCamera; + std::set pressedKeys; + bool sceneHovered; + bool sceneFocused; + bool cameraLooking; + bool cameraAnglesInitialized; + float cameraYaw; + float cameraPitch; + float pendingLookDeltaX; + float pendingLookDeltaY; + }; +} diff --git a/Editor/Include/Editor/Utils/ImGuiCompatibility.h b/Editor/Include/Editor/Utils/ImGuiCompatibility.h new file mode 100644 index 000000000..115df8483 --- /dev/null +++ b/Editor/Include/Editor/Utils/ImGuiCompatibility.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include + +namespace Editor { + class ImGuiCompatibility final { + public: + static ImGuiKey ToImGuiKey(int inGlfwKey); + static std::optional ToImGuiMouseButton(int inGlfwButton); + static void UpdateKeyModifiers(int inGlfwMods); + }; +} diff --git a/Editor/Include/Editor/WebUIServer.h b/Editor/Include/Editor/WebUIServer.h deleted file mode 100644 index 6b16f66da..000000000 --- a/Editor/Include/Editor/WebUIServer.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// Created by johnk on 2025/8/8. -// - -#pragma once - -#include - -#include -#include - -namespace Editor { - class WebUIServer { - public: - static WebUIServer& Get(); - - void Start(); - void Stop(); - const std::string& BaseUrl() const; - - private: - WebUIServer(); - - std::string baseUrl; - Common::UniquePtr productServerThread; - Common::UniquePtr productServer; - }; -} diff --git a/Editor/Include/Editor/Widget/Editor.h b/Editor/Include/Editor/Widget/Editor.h deleted file mode 100644 index a11b63537..000000000 --- a/Editor/Include/Editor/Widget/Editor.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// Created by Kindem on 2025/3/22. -// - -#pragma once - -#include - -namespace Editor { - class ExplosionEditor final : public QWidget { - Q_OBJECT - - public: - ExplosionEditor(); - }; -} diff --git a/Editor/Include/Editor/Widget/GraphicsWidget.h b/Editor/Include/Editor/Widget/GraphicsWidget.h deleted file mode 100644 index 88e707816..000000000 --- a/Editor/Include/Editor/Widget/GraphicsWidget.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// Created by Kindem on 2025/3/16. -// - -#pragma once - -#include -#include - -#include -#include - -namespace Editor { - class GraphicsWidget : public QWidget { - Q_OBJECT - - public: - explicit GraphicsWidget(QWidget* inParent = nullptr); - ~GraphicsWidget() override; - - RHI::Device& GetDevice() const; - RHI::Surface& GetSurface() const; - - protected: - QPaintEngine* paintEngine() const override; - - void WaitDeviceIdle() const; - - RHI::Device* device; - Common::UniquePtr surface; - }; -} diff --git a/Editor/Include/Editor/Widget/InputWidgets.h b/Editor/Include/Editor/Widget/InputWidgets.h new file mode 100644 index 000000000..0953e690c --- /dev/null +++ b/Editor/Include/Editor/Widget/InputWidgets.h @@ -0,0 +1,138 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace Editor { + template + class InputWidget; + + template <> + class InputWidget final { + public: + static bool Render(const std::string& inLabel, bool& inValue); + }; + + template <> + class InputWidget final { + public: + static bool Render(const std::string& inLabel, int8_t& inValue); + }; + + template <> + class InputWidget final { + public: + static bool Render(const std::string& inLabel, uint8_t& inValue); + }; + + template <> + class InputWidget final { + public: + static bool Render(const std::string& inLabel, int16_t& inValue); + }; + + template <> + class InputWidget final { + public: + static bool Render(const std::string& inLabel, uint16_t& inValue); + }; + + template <> + class InputWidget final { + public: + static bool Render(const std::string& inLabel, int32_t& inValue); + }; + + template <> + class InputWidget final { + public: + static bool Render(const std::string& inLabel, uint32_t& inValue); + }; + + template <> + class InputWidget final { + public: + static bool Render(const std::string& inLabel, int64_t& inValue); + }; + + template <> + class InputWidget final { + public: + static bool Render(const std::string& inLabel, uint64_t& inValue); + }; + + template <> + class InputWidget final { + public: + static bool Render(const std::string& inLabel, float& inValue); + }; + + template <> + class InputWidget final { + public: + static bool Render(const std::string& inLabel, double& inValue); + }; + + template <> + class InputWidget final { + public: + static bool Render(const std::string& inLabel, std::string& inValue); + }; + + template <> + class InputWidget final { + public: + static bool Render(const std::string& inLabel, Core::Uri& inValue); + }; + + template <> + class InputWidget final { + public: + static bool Render(const std::string& inLabel, Common::FVec2& inValue); + }; + + template <> + class InputWidget final { + public: + static bool Render(const std::string& inLabel, Common::FVec3& inValue); + }; + + template <> + class InputWidget final { + public: + static bool Render(const std::string& inLabel, Common::FVec4& inValue); + }; + + template <> + class InputWidget final { + public: + static bool Render(const std::string& inLabel, Common::FQuat& inValue); + }; + + template <> + class InputWidget final { + public: + static bool Render(const std::string& inLabel, Common::FTransform& inValue); + }; + + template <> + class InputWidget final { + public: + static bool Render(const std::string& inLabel, Common::LinearColor& inValue); + }; + + template <> + class InputWidget final { + public: + static bool Render(const std::string& inLabel, Common::Color& inValue); + }; + + bool RenderInputWidget(const std::string& inLabel, Mirror::Any inValue); +} diff --git a/Editor/Include/Editor/Widget/ProjectHub.h b/Editor/Include/Editor/Widget/ProjectHub.h deleted file mode 100644 index de1370326..000000000 --- a/Editor/Include/Editor/Widget/ProjectHub.h +++ /dev/null @@ -1,75 +0,0 @@ -// -// Created by johnk on 2025/8/3. -// - -#pragma once - -#include -#include -#include -#include - -namespace Editor { - class ProjectHub; - - struct EClass() RecentProjectInfo { - EClassBody(RecentProjectInfo) - - EProperty() std::string name; - EProperty() std::string path; - }; - - struct EClass() ProjectTemplateInfo { - EClassBody(ProjectTemplateInfo) - - EProperty() std::string name; - EProperty() std::string path; - }; - - struct EClass() CreateProjectResult { - EClassBody(CreateProjectResult) - - EProperty() bool success; - EProperty() std::string error; - EProperty() std::string projectPath; - }; - - class ProjectHubBackend final : public QObject { - Q_OBJECT - Q_PROPERTY(QString engineVersion READ GetEngineVersion CONSTANT) - Q_PROPERTY(QJsonValue projectTemplates READ GetProjectTemplates CONSTANT) - Q_PROPERTY(QJsonValue recentProjects READ GetRecentProjects NOTIFY RecentProjectsChanged) - - public: - explicit ProjectHubBackend(ProjectHub* parent = nullptr); - ~ProjectHubBackend() override; - - public Q_SLOTS: - QJsonValue CreateProject(const QString& inName, const QString& inDirectory, const QString& inTemplatePath); - void OpenProject(const QString& inProjectPath); - QString BrowseDirectory() const; - - Q_SIGNALS: - void RecentProjectsChanged(); - - private: - QString GetEngineVersion() const; - QJsonValue GetProjectTemplates() const; - QJsonValue GetRecentProjects() const; - - Common::Path recentProjectsFile; - std::string engineVersion; - std::vector projectTemplates; - std::vector recentProjects; - }; - - class ProjectHub final : public WebWidget { - Q_OBJECT - - public: - explicit ProjectHub(QWidget* inParent = nullptr); - - private: - ProjectHubBackend* backend; - }; -} diff --git a/Editor/Include/Editor/Widget/Prototype.h b/Editor/Include/Editor/Widget/Prototype.h deleted file mode 100644 index d848e0050..000000000 --- a/Editor/Include/Editor/Widget/Prototype.h +++ /dev/null @@ -1,103 +0,0 @@ -// -// Created by johnk on 2026/6/21. -// - -#pragma once - -#include -#include - -#include - -#include -#include -#include - -namespace Editor { - class PrototypeTriangleWidget final : public GraphicsWidget { - Q_OBJECT - - public: - explicit PrototypeTriangleWidget(QWidget* inParent = nullptr); - ~PrototypeTriangleWidget() override; - - void SetRotationSpeed(float inRadiansPerSecond); - float GetRotationSpeed() const; - - protected: - void resizeEvent(QResizeEvent* event) override; - - private: - static constexpr uint8_t swapChainTextureNum = 2; - - void RecreateSwapChain(uint32_t inWidth, uint32_t inHeight); - void DispatchFrame(); - void DrawFrame(); - - std::atomic rotationSpeed; - float rotation; - double lastFrameSeconds; - - Common::UniquePtr imageReadySemaphore; - Common::UniquePtr renderFinishedSemaphore; - Common::UniquePtr frameFence; - Common::UniquePtr swapChain; - std::array swapChainTextures; - std::array, swapChainTextureNum> swapChainTextureViews; - Render::ShaderCompileOutput vsCompileOutput; - Render::ShaderCompileOutput psCompileOutput; - Common::UniquePtr vsModule; - Common::UniquePtr psModule; - Common::UniquePtr bindGroupLayout; - Common::UniquePtr pipelineLayout; - Common::UniquePtr pipeline; - Common::UniquePtr vertexBuffer; - Common::UniquePtr vertexBufferView; - Common::UniquePtr uniformBuffer; - Common::UniquePtr uniformBufferView; - Common::UniquePtr bindGroup; - Common::UniquePtr commandBuffer; - Common::UniquePtr drawThread; - std::atomic_bool running; - }; - - class PrototypeBackend final : public QObject { - Q_OBJECT - Q_PROPERTY(double rotationSpeed READ GetRotationSpeed CONSTANT) - - public: - explicit PrototypeBackend(PrototypeTriangleWidget* inTriangle, QObject* inParent = nullptr); - ~PrototypeBackend() override; - - public Q_SLOTS: - void SetRotationSpeed(double inDegreesPerSecond); - - private: - double GetRotationSpeed() const; - - PrototypeTriangleWidget* triangle; - }; - - class PrototypeWebWidget final : public WebWidget { - Q_OBJECT - - public: - explicit PrototypeWebWidget(PrototypeTriangleWidget* inTriangle, QWidget* inParent = nullptr); - ~PrototypeWebWidget() override; - - private: - PrototypeBackend* backend; - }; - - class PrototypePlayground final : public QWidget { - Q_OBJECT - - public: - explicit PrototypePlayground(QWidget* inParent = nullptr); - ~PrototypePlayground() override; - - private: - PrototypeTriangleWidget* triangle; - PrototypeWebWidget* web; - }; -} diff --git a/Editor/Include/Editor/Widget/WebWidget.h b/Editor/Include/Editor/Widget/WebWidget.h deleted file mode 100644 index d7619c3f2..000000000 --- a/Editor/Include/Editor/Widget/WebWidget.h +++ /dev/null @@ -1,38 +0,0 @@ -// -// Created by johnk on 2025/8/9. -// - -#pragma once - -#include -#include -#include - -namespace Editor { - class WebPage : public QWebEnginePage { - Q_OBJECT - - public: - explicit WebPage(QWidget* inParent = nullptr); - ~WebPage() override; - - protected: - void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, const QString& message, int lineNumber, const QString& sourceID) override; - }; - - class WebWidget : public QWebEngineView { - Q_OBJECT - - public: - explicit WebWidget(QWidget* inParent = nullptr); - ~WebWidget() override; - - void Load(const std::string& inUrl); - - protected: - QWebChannel* GetWebChannel() const; - - private: - QWebChannel* webChannel; - }; -} diff --git a/Editor/Shader/ImGui.esl b/Editor/Shader/ImGui.esl new file mode 100644 index 000000000..47e6a7625 --- /dev/null +++ b/Editor/Shader/ImGui.esl @@ -0,0 +1,37 @@ +#include + +VkBinding(0, 0) Texture2D imageTex : register(t0); +VkBinding(1, 0) SamplerState imageSampler : register(s0); +VkBinding(2, 0) cbuffer passParams : register(b0) +{ + float2 displayPos; + float2 displaySize; +}; + +struct FragmentInput { + float4 position : SV_POSITION; + float2 uv : TEXCOORD; + float4 color : COLOR; +}; + +FragmentInput VSMain( + VkLocation(0) float2 position : POSITION, + VkLocation(1) float2 uv : TEXCOORD, + VkLocation(2) float4 color : COLOR) +{ + const float2 normalized = (position - displayPos) / displaySize; + + FragmentInput output; + output.position = float4(normalized.x * 2.0f - 1.0f, 1.0f - normalized.y * 2.0f, 0.0f, 1.0f); +#if VULKAN + output.position.y = -output.position.y; +#endif + output.uv = uv; + output.color = color; + return output; +} + +float4 PSMain(FragmentInput input) : SV_TARGET +{ + return input.color * imageTex.Sample(imageSampler, input.uv); +} diff --git a/Editor/Shader/Prototype.esl b/Editor/Shader/Prototype.esl deleted file mode 100644 index 08f75969a..000000000 --- a/Editor/Shader/Prototype.esl +++ /dev/null @@ -1,40 +0,0 @@ -#include - -struct FragmentInput { - float4 position : SV_POSITION; - float4 color : COLOR; -}; - -#if VERTEX_SHADER -cbuffer vsUniform { - float rotation; -}; - -FragmentInput VSMain( - uint vertexId : SV_VertexID, - VkLocation(0) float3 position : POSITION) -{ - const float sinR = sin(rotation); - const float cosR = cos(rotation); - const float2 rotated = float2( - position.x * cosR - position.y * sinR, - position.x * sinR + position.y * cosR); - - FragmentInput fragmentInput; - fragmentInput.position = float4(rotated, position.z, 1.0f); -#if VULKAN - fragmentInput.position.y = - fragmentInput.position.y; -#endif - - fragmentInput.color = float4(0.0f, 0.0f, 0.0f, 1.0f); - fragmentInput.color[vertexId % 3] = 1.0f; - return fragmentInput; -} -#endif - -#if PIXEL_SHADER -float4 PSMain(FragmentInput input) : SV_TARGET -{ - return input.color; -} -#endif diff --git a/Editor/Src/EditorApplication.cpp b/Editor/Src/EditorApplication.cpp new file mode 100644 index 000000000..12372db6c --- /dev/null +++ b/Editor/Src/EditorApplication.cpp @@ -0,0 +1,356 @@ +// +// Created by johnk on 2026/7/7. +// + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace Editor::Internal { + constexpr uint32_t defaultWindowWidth = 1600; + constexpr uint32_t defaultWindowHeight = 900; + constexpr uint32_t defaultSceneWidth = 1280; + constexpr uint32_t defaultSceneHeight = 720; + constexpr RHI::PixelFormat sceneColorFormat = RHI::PixelFormat::rgba8Unorm; + constexpr const char* editorDockSpaceName = "EditorDockSpace"; + constexpr const char* editorDockSpaceId = "EditorDockSpaceV2"; + + static Runtime::CanvasDesc CreateSceneRenderCanvasDesc() + { + Runtime::CanvasDesc result; + result.width = defaultSceneWidth; + result.height = defaultSceneHeight; + result.format = sceneColorFormat; + result.debugName = "editorSceneColor"; + return result; + } + + static std::string MakeWindowTitle(const EditorApplicationDesc& inDesc) + { + if (inDesc.mode == EditorApplicationMode::projectHub) { + return "Explosion Project Hub"; + } + const std::string projectName = Core::Paths::HasSetGameRoot() + ? Core::Paths::GameRootDir().DirName() + : "Untitled"; + return std::format("{} - Explosion Editor", projectName); + } + + static void SetDarkStyle() + { + ImGui::StyleColorsDark(); + ImGuiStyle& style = ImGui::GetStyle(); + style.WindowRounding = 0.0f; + style.ChildRounding = 2.0f; + style.FrameRounding = 2.0f; + style.PopupRounding = 2.0f; + style.ScrollbarRounding = 2.0f; + style.GrabRounding = 2.0f; + style.TabRounding = 2.0f; + } + + static void BuildInitialEditorDockLayout(ImGuiID inDockSpaceId, const ImVec2& inSize) + { + if (ImGui::DockBuilderGetNode(inDockSpaceId) != nullptr) { + return; + } + + ImGui::DockBuilderAddNode(inDockSpaceId, ImGuiDockNodeFlags_DockSpace); + ImGui::DockBuilderSetNodeSize(inDockSpaceId, inSize); + + ImGuiID sceneNodeId = inDockSpaceId; + ImGuiID outlinerNodeId = 0; + ImGuiID inspectorNodeId = 0; + ImGuiID logNodeId = 0; + sceneNodeId = ImGui::DockBuilderSplitNode(sceneNodeId, ImGuiDir_Left, 0.22f, &outlinerNodeId, &sceneNodeId); + sceneNodeId = ImGui::DockBuilderSplitNode(sceneNodeId, ImGuiDir_Right, 0.28f, &inspectorNodeId, &sceneNodeId); + sceneNodeId = ImGui::DockBuilderSplitNode(sceneNodeId, ImGuiDir_Down, 0.26f, &logNodeId, &sceneNodeId); + + ImGui::DockBuilderDockWindow("Scene", sceneNodeId); + ImGui::DockBuilderDockWindow("Outliner", outlinerNodeId); + ImGui::DockBuilderDockWindow("Inspector", inspectorNodeId); + ImGui::DockBuilderDockWindow("Log", logNodeId); + ImGui::DockBuilderFinish(inDockSpaceId); + } + + static ImDrawData* CloneDrawData(const ImDrawData& inDrawData) + { + auto* result = IM_NEW(ImDrawData)(); + result->Valid = inDrawData.Valid; + result->CmdListsCount = inDrawData.CmdListsCount; + result->TotalIdxCount = inDrawData.TotalIdxCount; + result->TotalVtxCount = inDrawData.TotalVtxCount; + result->DisplayPos = inDrawData.DisplayPos; + result->DisplaySize = inDrawData.DisplaySize; + result->FramebufferScale = inDrawData.FramebufferScale; + result->OwnerViewport = inDrawData.OwnerViewport; + result->Textures = inDrawData.Textures; + result->CmdLists.resize(inDrawData.CmdListsCount); + for (int i = 0; i < inDrawData.CmdListsCount; i++) { + result->CmdLists[i] = inDrawData.CmdLists[i]->CloneOutput(); + } + return result; + } +} + +namespace Editor { + EditorApplication::EditorApplication(EditorApplicationDesc inDesc) + : desc(std::move(inDesc)) + , editorFrame() + , lastFrameSeconds(Common::TimePoint::Now().ToSeconds()) + , lastCursorX(0.0) + , lastCursorY(0.0) + , hasLastCursor(false) + , requestQuit(false) + { + InitializeImGui(); + window = Common::MakeUnique( + EditorWindowDesc { + .title = Internal::MakeWindowTitle(desc), + .width = Internal::defaultWindowWidth, + .height = Internal::defaultWindowHeight + }); + InstallCallbacks(); + + if (desc.mode == EditorApplicationMode::editor) { + context = Common::MakeUnique(); + auto& sceneClient = context->GetSceneClient(); + sceneClient.SetEditorWindow(*window); + sceneRenderCanvas = Common::MakeUnique(window->GetDevice(), Internal::CreateSceneRenderCanvasDesc()); + sceneClient.SetRenderSurface(sceneRenderCanvas.Get()); + sceneClient.OpenProjectLevel(); + } else { + projectHubFrame = Common::MakeUnique(); + } + } + + EditorApplication::~EditorApplication() + { + if (context != nullptr) { + context->GetSceneClient().SetRenderSurface(nullptr); + } + if (window != nullptr) { + window->WaitRenderingIdle(); + } + sceneRenderCanvas.Reset(); + context.Reset(); + projectHubFrame.Reset(); + window.Reset(); + ShutdownImGui(); + } + + int EditorApplication::Run() + { + while (!window->ShouldClose()) { + window->PollEvents(); + window->RecreateSwapChainIfNeeded(); + const float deltaSeconds = NextDeltaSeconds(); + BeginImGuiFrame(deltaSeconds); + + if (desc.mode == EditorApplicationMode::editor) { + RenderEditorFrame(deltaSeconds); + } else { + RenderProjectHubFrame(); + } + } + return 0; + } + + void EditorApplication::InitializeImGui() + { + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + io.BackendPlatformName = "ExplosionGLFW"; + io.BackendRendererName = "ExplosionRHI"; + io.IniFilename = "ExplosionEditor.ini"; + Internal::SetDarkStyle(); + } + + void EditorApplication::ShutdownImGui() + { + ImGui::DestroyContext(); + } + + void EditorApplication::InstallCallbacks() + { + GLFWwindow* nativeWindow = window->GetNativeWindow(); + glfwSetWindowUserPointer(nativeWindow, this); + glfwSetKeyCallback(nativeWindow, [](GLFWwindow* inWindow, int inKey, int inScancode, int inAction, int inMods) -> void { + static_cast(glfwGetWindowUserPointer(inWindow))->HandleKey(inKey, inScancode, inAction, inMods); + }); + glfwSetCharCallback(nativeWindow, [](GLFWwindow* inWindow, unsigned int inCodepoint) -> void { + static_cast(glfwGetWindowUserPointer(inWindow))->HandleChar(inCodepoint); + }); + glfwSetMouseButtonCallback(nativeWindow, [](GLFWwindow* inWindow, int inButton, int inAction, int inMods) -> void { + static_cast(glfwGetWindowUserPointer(inWindow))->HandleMouseButton(inButton, inAction, inMods); + }); + glfwSetCursorPosCallback(nativeWindow, [](GLFWwindow* inWindow, double inX, double inY) -> void { + static_cast(glfwGetWindowUserPointer(inWindow))->HandleCursor(inX, inY); + }); + glfwSetScrollCallback(nativeWindow, [](GLFWwindow* inWindow, double inXOffset, double inYOffset) -> void { + static_cast(glfwGetWindowUserPointer(inWindow))->HandleScroll(inXOffset, inYOffset); + }); + } + + void EditorApplication::BeginImGuiFrame(float inDeltaSeconds) const + { + int windowWidth = 0; + int windowHeight = 0; + int framebufferWidth = 0; + int framebufferHeight = 0; + glfwGetWindowSize(window->GetNativeWindow(), &windowWidth, &windowHeight); + glfwGetFramebufferSize(window->GetNativeWindow(), &framebufferWidth, &framebufferHeight); + + ImGuiIO& io = ImGui::GetIO(); + io.DisplaySize = ImVec2(static_cast(windowWidth), static_cast(windowHeight)); + io.DisplayFramebufferScale = ImVec2( + windowWidth > 0 ? static_cast(framebufferWidth) / static_cast(windowWidth) : 1.0f, + windowHeight > 0 ? static_cast(framebufferHeight) / static_cast(windowHeight) : 1.0f); + io.DeltaTime = std::max(inDeltaSeconds, 1.0f / 1000.0f); + ImGui::NewFrame(); + } + + void EditorApplication::DrawDockSpace() const + { + const ImGuiViewport* mainViewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(mainViewport->WorkPos); + ImGui::SetNextWindowSize(mainViewport->WorkSize); + ImGui::SetNextWindowViewport(mainViewport->ID); + + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDocking + | ImGuiWindowFlags_NoTitleBar + | ImGuiWindowFlags_NoCollapse + | ImGuiWindowFlags_NoResize + | ImGuiWindowFlags_NoMove + | ImGuiWindowFlags_NoBringToFrontOnFocus + | ImGuiWindowFlags_NoNavFocus + | ImGuiWindowFlags_NoBackground; + + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + ImGui::Begin(Internal::editorDockSpaceName, nullptr, windowFlags); + ImGui::PopStyleVar(3); + + const ImGuiID dockSpaceId = ImGui::GetID(Internal::editorDockSpaceId); + Internal::BuildInitialEditorDockLayout(dockSpaceId, mainViewport->WorkSize); + ImGui::DockSpace(dockSpaceId, ImVec2(0.0f, 0.0f), ImGuiDockNodeFlags_None); + ImGui::End(); + } + + void EditorApplication::RenderEditorFrame(float inDeltaSeconds) + { + DrawDockSpace(); + editorFrame.Render(*context, *sceneRenderCanvas, requestQuit); + if (requestQuit) { + window->RequestClose(); + } + + if (ImGui::GetIO().KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_S, false)) { + context->GetSceneClient().SaveLevel(); + } + + ImGui::Render(); + window->SetDrawData(Internal::CloneDrawData(*ImGui::GetDrawData())); + context->GetSceneClient().TickEditorCamera(inDeltaSeconds); + Runtime::EngineHolder::Get().Tick(inDeltaSeconds); + window->RenderPendingUi(); + } + + void EditorApplication::RenderProjectHubFrame() + { + projectHubFrame->Render(*window, desc.rhiType); + ImGui::Render(); + Runtime::EngineHolder::Get().Tick(ImGui::GetIO().DeltaTime); + window->RenderUiOnly(*ImGui::GetDrawData()); + } + + void EditorApplication::HandleKey(int inKey, int inScancode, int inAction, int inMods) + { + std::ignore = inScancode; + ImGuiCompatibility::UpdateKeyModifiers(inMods); + + ImGuiIO& io = ImGui::GetIO(); + const ImGuiKey imguiKey = ImGuiCompatibility::ToImGuiKey(inKey); + if (imguiKey != ImGuiKey_None) { + io.AddKeyEvent(imguiKey, inAction != GLFW_RELEASE); + io.SetKeyEventNativeData(imguiKey, inKey, inScancode); + } + + if (context != nullptr) { + auto& sceneClient = context->GetSceneClient(); + if (inKey == GLFW_KEY_ESCAPE && inAction == GLFW_PRESS) { + sceneClient.EndCameraLook(); + } + if (inAction == GLFW_PRESS || inAction == GLFW_RELEASE) { + const bool canRouteToScene = sceneClient.IsCameraLooking() || (!io.WantCaptureKeyboard && sceneClient.IsSceneHovered()); + if (canRouteToScene) { + sceneClient.OnKey(inKey, inAction == GLFW_PRESS); + } + } + } + } + + void EditorApplication::HandleChar(uint32_t inCodepoint) const + { + ImGui::GetIO().AddInputCharacter(inCodepoint); + } + + void EditorApplication::HandleMouseButton(int inButton, int inAction, int inMods) + { + ImGuiCompatibility::UpdateKeyModifiers(inMods); + if (const auto imguiMouseButton = ImGuiCompatibility::ToImGuiMouseButton(inButton)) { + ImGui::GetIO().AddMouseButtonEvent(*imguiMouseButton, inAction == GLFW_PRESS); + } + + if (context == nullptr || inButton != GLFW_MOUSE_BUTTON_RIGHT) { + return; + } + auto& sceneClient = context->GetSceneClient(); + if (inAction == GLFW_PRESS && sceneClient.IsSceneHovered()) { + hasLastCursor = false; + sceneClient.BeginCameraLook(); + } else if (inAction == GLFW_RELEASE) { + sceneClient.EndCameraLook(); + } + } + + void EditorApplication::HandleCursor(double inX, double inY) + { + ImGui::GetIO().AddMousePosEvent(static_cast(inX), static_cast(inY)); + if (context != nullptr) { + auto& sceneClient = context->GetSceneClient(); + if (sceneClient.IsCameraLooking() && hasLastCursor) { + sceneClient.AddCameraLookDelta(static_cast(inX - lastCursorX), static_cast(inY - lastCursorY)); + } + } + lastCursorX = inX; + lastCursorY = inY; + hasLastCursor = true; + } + + void EditorApplication::HandleScroll(double inXOffset, double inYOffset) const + { + ImGui::GetIO().AddMouseWheelEvent(static_cast(inXOffset), static_cast(inYOffset)); + } + + float EditorApplication::NextDeltaSeconds() + { + const double now = Common::TimePoint::Now().ToSeconds(); + const float deltaSeconds = static_cast(now - lastFrameSeconds); + lastFrameSeconds = now; + return deltaSeconds; + } +} diff --git a/Editor/Src/EditorContext.cpp b/Editor/Src/EditorContext.cpp new file mode 100644 index 000000000..d8eade6a3 --- /dev/null +++ b/Editor/Src/EditorContext.cpp @@ -0,0 +1,97 @@ +// +// Created by johnk on 2026/7/4. +// + +#include +#include +#include + +namespace Editor { + EditorContext::EditorContext() + : sceneClient(Common::MakeUnique()) + , selectedEntity(Runtime::entityNull) + , selectionVersion(0) + , worldStructureVersion(0) + , componentsVersion(0) + { + } + + EditorContext::~EditorContext() = default; + + SceneClient& EditorContext::GetSceneClient() const + { + return *sceneClient; + } + + Runtime::Entity EditorContext::GetSelectedEntity() const + { + return selectedEntity; + } + + uint64_t EditorContext::GetSelectionVersion() const + { + return selectionVersion; + } + + uint64_t EditorContext::GetWorldStructureVersion() const + { + return worldStructureVersion; + } + + uint64_t EditorContext::GetComponentsVersion() const + { + return componentsVersion; + } + + void EditorContext::SetSelectedEntity(Runtime::Entity inEntity) + { + if (selectedEntity == inEntity) { + return; + } + selectedEntity = inEntity; + selectionVersion++; + } + + Runtime::Entity EditorContext::CreateEntity(const std::string& inName) + { + auto& registry = sceneClient->GetWorld().GetRegistry(); + const auto entity = registry.Create(); + // Name's reflected constructor takes a std::string. + registry.Emplace(entity, inName); + registry.Emplace(entity); + worldStructureVersion++; + return entity; + } + + void EditorContext::DestroyEntity(Runtime::Entity inEntity) + { + auto& registry = sceneClient->GetWorld().GetRegistry(); + if (!registry.Valid(inEntity)) { + return; + } + registry.Destroy(inEntity); + if (selectedEntity == inEntity) { + SetSelectedEntity(Runtime::entityNull); + } + worldStructureVersion++; + } + + void EditorContext::RenameEntity(Runtime::Entity inEntity, const std::string& inName) + { + auto& registry = sceneClient->GetWorld().GetRegistry(); + if (!registry.Valid(inEntity)) { + return; + } + if (registry.Has(inEntity)) { + registry.Update(inEntity, [&](Runtime::Name& name) -> void { name.value = inName; }); + } else { + registry.Emplace(inEntity, inName); + } + worldStructureVersion++; + } + + void EditorContext::NotifyComponentsChanged(Runtime::Entity) + { + componentsVersion++; + } +} diff --git a/Editor/Src/EditorLog.cpp b/Editor/Src/EditorLog.cpp new file mode 100644 index 000000000..1c3cdd364 --- /dev/null +++ b/Editor/Src/EditorLog.cpp @@ -0,0 +1,60 @@ +// +// Created by johnk on 2026/7/5. +// + +#include + +#include + +namespace Editor { + EditorLogStream& EditorLogStream::Get() + { + static EditorLogStream* stream = []() -> EditorLogStream* { + auto* result = new EditorLogStream(); + Core::Logger::Get().Attach(Common::UniquePtr(result)); + return result; + }(); + return *stream; + } + + EditorLogStream::EditorLogStream() + : nextListenerHandle(0) + { + } + + void EditorLogStream::Write(const Core::LogEntry& inEntry) + { + std::unique_lock lock(mutex); + history.emplace_back(inEntry); + if (history.size() > maxHistoryEntries) { + history.pop_front(); + } + for (const auto& listener : listeners | std::views::values) { + listener(inEntry); + } + } + + void EditorLogStream::Flush() + { + } + + std::vector EditorLogStream::Snapshot() const + { + std::unique_lock lock(mutex); + return { history.begin(), history.end() }; + } + + EditorLogStream::ListenerHandle EditorLogStream::AddListener(EntryListener inListener) + { + std::unique_lock lock(mutex); + const ListenerHandle handle = nextListenerHandle++; + listeners.emplace(handle, std::move(inListener)); + return handle; + } + + void EditorLogStream::RemoveListener(ListenerHandle inHandle) + { + std::unique_lock lock(mutex); + listeners.erase(inHandle); + } +} diff --git a/Editor/Src/EditorWindow.cpp b/Editor/Src/EditorWindow.cpp new file mode 100644 index 000000000..bbb223f54 --- /dev/null +++ b/Editor/Src/EditorWindow.cpp @@ -0,0 +1,570 @@ +// +// Created by johnk on 2026/7/7. +// + +#include +#include +#include +#include +#include + +#if PLATFORM_WINDOWS +#define GLFW_EXPOSE_NATIVE_WIN32 +#elif PLATFORM_MACOS +#define GLFW_EXPOSE_NATIVE_COCOA +#endif +#include +#include + +#if PLATFORM_WINDOWS +#ifdef CreateSemaphore +#undef CreateSemaphore +#endif +#endif + +#include + +#include +#include +#include + +namespace Editor::Internal { + const Common::LinearColor uiOnlyClearColor = Common::LinearColor(0.08f, 0.08f, 0.09f, 1.0f); + constexpr int imguiVertexBufferInitialCapacity = 5000; + constexpr int imguiIndexBufferInitialCapacity = 10000; + constexpr RHI::IndexFormat imguiIndexFormat = sizeof(ImDrawIdx) == 2 + ? RHI::IndexFormat::uint16 + : RHI::IndexFormat::uint32; + + static uint32_t ClampExtent(int inValue) + { + return static_cast(std::max(1, inValue)); + } + + static Render::ShaderCompileOutput CompileImGuiShader( + RHI::Device& inDevice, + const std::string& inEntryPoint, + RHI::ShaderStageBits inStage) + { + Render::ShaderCompileInput input; + input.source = Common::FileUtils::ReadTextFile("../Shader/Editor/ImGui.esl").Unwrap(); + input.entryPoint = inEntryPoint; + input.stage = inStage; + input.includeDirectories.emplace_back("../Shader/Explosion"); + + Render::ShaderCompileOptions options; + options.byteCodeType = inDevice.GetGpu().GetInstance().GetRHIType() == RHI::RHIType::directX12 + ? Render::ShaderByteCodeType::dxil + : Render::ShaderByteCodeType::spirv; + options.withDebugInfo = static_cast(BUILD_CONFIG_DEBUG); // NOLINT + + auto future = Render::ShaderCompiler::Get().Compile(input, options); + future.wait(); + return future.get(); + } +} + +namespace Editor { + EditorWindow::EditorWindow(const EditorWindowDesc& inDesc) + : Runtime::Window(*Runtime::EngineHolder::Get().GetRenderModule().GetDevice()) + , window(nullptr) + , uiFrameFence(GetDevice().CreateFence(true)) + , uiCommandBuffer(GetDevice().CreateCommandBuffer()) + , imguiVertexBufferCapacity(0) + , imguiIndexBufferCapacity(0) + , framebufferWidth(inDesc.width) + , framebufferHeight(inDesc.height) + , pendingDrawData(nullptr) + { + glfwInit(); + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + window = glfwCreateWindow( + static_cast(inDesc.width), + static_cast(inDesc.height), + inDesc.title.c_str(), + nullptr, + nullptr); + Assert(window != nullptr); + + int fbWidth = 0; + int fbHeight = 0; + glfwGetFramebufferSize(window, &fbWidth, &fbHeight); + framebufferWidth = Internal::ClampExtent(fbWidth); + framebufferHeight = Internal::ClampExtent(fbHeight); + + InitializeSurface( + GetDevice().CreateSurface(RHI::SurfaceCreateInfo(GetPlatformWindow())), + framebufferWidth, + framebufferHeight); + CreateImGuiDeviceObjects(); + CreateImGuiDynamicBuffers(Internal::imguiVertexBufferInitialCapacity, Internal::imguiIndexBufferInitialCapacity); + } + + EditorWindow::~EditorWindow() + { + DestroyDrawData(pendingDrawData); + pendingDrawData = nullptr; + WaitRenderingIdle(); + imguiFrameTextureBindGroups.clear(); + DestroySurface(); + glfwDestroyWindow(window); + glfwTerminate(); + } + + GLFWwindow* EditorWindow::GetNativeWindow() const + { + return window; + } + + uint32_t EditorWindow::GetWidth() const + { + return GetFramebufferWidth(); + } + + uint32_t EditorWindow::GetHeight() const + { + return GetFramebufferHeight(); + } + + uint32_t EditorWindow::GetFramebufferWidth() const + { + return framebufferWidth; + } + + uint32_t EditorWindow::GetFramebufferHeight() const + { + return framebufferHeight; + } + + bool EditorWindow::ShouldClose() const + { + return static_cast(glfwWindowShouldClose(window)); + } + + void EditorWindow::RequestClose() const + { + glfwSetWindowShouldClose(window, GLFW_TRUE); + } + + void EditorWindow::PollEvents() const + { + glfwPollEvents(); + } + + void EditorWindow::SetTitle(const std::string& inTitle) const + { + glfwSetWindowTitle(window, inTitle.c_str()); + } + + void EditorWindow::SetDrawData(ImDrawData* inDrawData) + { + std::unique_lock lock(drawDataMutex); + DestroyDrawData(pendingDrawData); + pendingDrawData = inDrawData; + } + + void EditorWindow::RecreateSwapChainIfNeeded() + { + int fbWidth = 0; + int fbHeight = 0; + glfwGetFramebufferSize(window, &fbWidth, &fbHeight); + const uint32_t newWidth = Internal::ClampExtent(fbWidth); + const uint32_t newHeight = Internal::ClampExtent(fbHeight); + if (newWidth == framebufferWidth && newHeight == framebufferHeight) { + return; + } + + framebufferWidth = newWidth; + framebufferHeight = newHeight; + const RHI::PixelFormat previousFormat = GetSwapChainFormat(); + WaitRenderingIdle(); + Runtime::Window::Resize(framebufferWidth, framebufferHeight); + if (GetSwapChainFormat() != previousFormat) { + CreateImGuiDeviceObjects(); + } + } + + void EditorWindow::WaitRenderingIdle() const + { + Runtime::EngineHolder::Get().GetRenderModule().GetRenderThread().Flush(); + Runtime::Window::WaitRenderingIdle(); + } + + void EditorWindow::RenderPendingUi() + { + ImDrawData* drawData = TakeDrawData(); + Runtime::EngineHolder::Get().GetRenderModule().GetRenderThread().EmplaceTask([this, drawData]() -> void { + uiFrameFence->Wait(); + AcquireBackTexture(); + uiFrameFence->Reset(); + + RenderUiPass(drawData, RHI::LoadOp::clear, Internal::uiOnlyClearColor); + GetDevice().GetQueue(RHI::QueueType::graphics, 0)->Submit( + uiCommandBuffer.Get(), + RHI::QueueSubmitInfo() + .AddWaitSemaphore(GetImageReadySemaphore()) + .AddSignalSemaphore(GetRenderFinishedSemaphore()) + .SetSignalFence(uiFrameFence.Get())); + Present(); + DestroyDrawData(drawData); + }); + } + + void EditorWindow::RenderUiOnly(ImDrawData& inDrawData) + { + uiFrameFence->Wait(); + AcquireBackTexture(); + uiFrameFence->Reset(); + + RenderUiPass(&inDrawData, RHI::LoadOp::clear, Internal::uiOnlyClearColor); + GetDevice().GetQueue(RHI::QueueType::graphics, 0)->Submit( + uiCommandBuffer.Get(), + RHI::QueueSubmitInfo() + .AddWaitSemaphore(GetImageReadySemaphore()) + .AddSignalSemaphore(GetRenderFinishedSemaphore()) + .SetSignalFence(uiFrameFence.Get())); + Present(); + } + + void* EditorWindow::GetPlatformWindow() const + { +#if PLATFORM_WINDOWS + return glfwGetWin32Window(window); +#elif PLATFORM_MACOS + return glfwGetCocoaView(window); +#else + Unimplement(); + return nullptr; +#endif + } + + void EditorWindow::RenderUiPass( + ImDrawData* inDrawData, + RHI::LoadOp inLoadOp, + const Common::LinearColor& inClearValue) + { + auto* target = GetTexture(); + auto* targetView = GetRenderTargetView(); + Assert(target != nullptr && targetView != nullptr); + + const auto commandRecorder = uiCommandBuffer->Begin(); + { + commandRecorder->ResourceBarrier(RHI::Barrier::Transition(target, GetCurrentBackTextureState(), RHI::TextureState::renderTarget)); + const auto rasterRecorder = commandRecorder->BeginRasterPass( + RHI::RasterPassBeginInfo() + .AddColorAttachment(RHI::ColorAttachment(targetView, inLoadOp, RHI::StoreOp::store, inClearValue))); + if (inDrawData != nullptr) { + RenderImGuiDrawData(*inDrawData, *rasterRecorder); + } + rasterRecorder->EndPass(); + commandRecorder->ResourceBarrier(RHI::Barrier::Transition(target, RHI::TextureState::renderTarget, RHI::TextureState::present)); + } + commandRecorder->End(); + } + + void EditorWindow::RenderImGuiDrawData(ImDrawData& inDrawData, RHI::RasterPassCommandRecorder& inRecorder) + { + imguiFrameTextureBindGroups.clear(); + if (inDrawData.TotalVtxCount <= 0 || inDrawData.TotalIdxCount <= 0) { + return; + } + + if (inDrawData.TotalVtxCount > imguiVertexBufferCapacity || inDrawData.TotalIdxCount > imguiIndexBufferCapacity) { + CreateImGuiDynamicBuffers( + std::max(inDrawData.TotalVtxCount, imguiVertexBufferCapacity * 2), + std::max(inDrawData.TotalIdxCount, imguiIndexBufferCapacity * 2)); + } + + UpdateImGuiBuffers(inDrawData); + UpdateImGuiPassParams(inDrawData); + + const auto framebufferWidth = static_cast(inDrawData.DisplaySize.x * inDrawData.FramebufferScale.x); + const auto framebufferHeight = static_cast(inDrawData.DisplaySize.y * inDrawData.FramebufferScale.y); + if (framebufferWidth == 0 || framebufferHeight == 0) { + return; + } + + inRecorder.SetPipeline(imguiPipeline.Get()); + inRecorder.SetVertexBuffer(0, imguiVertexBufferView.Get()); + inRecorder.SetIndexBuffer(imguiIndexBufferView.Get()); + inRecorder.SetPrimitiveTopology(RHI::PrimitiveTopology::triangleList); + inRecorder.SetViewport(0.0f, 0.0f, static_cast(framebufferWidth), static_cast(framebufferHeight), 0.0f, 1.0f); + + const ImVec2 clipOffset = inDrawData.DisplayPos; + const ImVec2 clipScale = inDrawData.FramebufferScale; + int globalVertexOffset = 0; + int globalIndexOffset = 0; + for (int commandListIndex = 0; commandListIndex < inDrawData.CmdListsCount; commandListIndex++) { + const ImDrawList* commandList = inDrawData.CmdLists[commandListIndex]; + for (int commandIndex = 0; commandIndex < commandList->CmdBuffer.Size; commandIndex++) { + const ImDrawCmd* command = &commandList->CmdBuffer[commandIndex]; + if (command->UserCallback != nullptr) { + command->UserCallback(commandList, command); + continue; + } + + ImVec2 clipMin( + (command->ClipRect.x - clipOffset.x) * clipScale.x, + (command->ClipRect.y - clipOffset.y) * clipScale.y); + ImVec2 clipMax( + (command->ClipRect.z - clipOffset.x) * clipScale.x, + (command->ClipRect.w - clipOffset.y) * clipScale.y); + clipMin.x = std::clamp(clipMin.x, 0.0f, static_cast(framebufferWidth)); + clipMin.y = std::clamp(clipMin.y, 0.0f, static_cast(framebufferHeight)); + clipMax.x = std::clamp(clipMax.x, 0.0f, static_cast(framebufferWidth)); + clipMax.y = std::clamp(clipMax.y, 0.0f, static_cast(framebufferHeight)); + if (clipMax.x <= clipMin.x || clipMax.y <= clipMin.y) { + continue; + } + + inRecorder.SetScissor( + static_cast(clipMin.x), + static_cast(clipMin.y), + static_cast(clipMax.x), + static_cast(clipMax.y)); + ImTextureID textureId = command->GetTexID(); + auto* textureView = textureId == ImTextureID_Invalid + ? imguiFontTextureView.Get() + : reinterpret_cast(static_cast(textureId)); + Assert(textureView != nullptr); + inRecorder.SetBindGroup(0, GetOrCreateImGuiTextureBindGroup(*textureView)); + inRecorder.DrawIndexed( + command->ElemCount, + 1, + command->IdxOffset + globalIndexOffset, + command->VtxOffset + globalVertexOffset, + 0); + } + globalIndexOffset += commandList->IdxBuffer.Size; + globalVertexOffset += commandList->VtxBuffer.Size; + } + } + + void EditorWindow::CreateImGuiDeviceObjects() + { + RHI::Device& device = GetDevice(); + imguiFrameTextureBindGroups.clear(); + imguiVertexShaderCompileOutput = Internal::CompileImGuiShader(device, "VSMain", RHI::ShaderStageBits::sVertex); + imguiPixelShaderCompileOutput = Internal::CompileImGuiShader(device, "PSMain", RHI::ShaderStageBits::sPixel); + imguiVertexShader = device.CreateShaderModule(RHI::ShaderModuleCreateInfo("VSMain", imguiVertexShaderCompileOutput.byteCode)); + imguiPixelShader = device.CreateShaderModule(RHI::ShaderModuleCreateInfo("PSMain", imguiPixelShaderCompileOutput.byteCode)); + + CreateImGuiFontTexture(); + + imguiPassParamsBuffer = device.CreateBuffer( + RHI::BufferCreateInfo() + .SetSize(sizeof(ImGuiPassParams)) + .SetUsages(RHI::BufferUsageBits::uniform | RHI::BufferUsageBits::mapWrite) + .SetInitialState(RHI::BufferState::staging) + .SetDebugName("imguiPassParams")); + imguiPassParamsBufferView = imguiPassParamsBuffer->CreateBufferView( + RHI::BufferViewCreateInfo() + .SetType(RHI::BufferViewType::uniformBinding) + .SetSize(sizeof(ImGuiPassParams)) + .SetOffset(0)); + + imguiBindGroupLayout = device.CreateBindGroupLayout( + RHI::BindGroupLayoutCreateInfo(0, "imguiBindGroupLayout") + .AddEntry(RHI::BindGroupLayoutEntry(imguiPixelShaderCompileOutput.reflectionData.QueryResourceBindingChecked("imageTex").second, RHI::ShaderStageBits::sPixel)) + .AddEntry(RHI::BindGroupLayoutEntry(imguiPixelShaderCompileOutput.reflectionData.QueryResourceBindingChecked("imageSampler").second, RHI::ShaderStageBits::sPixel)) + .AddEntry(RHI::BindGroupLayoutEntry(imguiVertexShaderCompileOutput.reflectionData.QueryResourceBindingChecked("passParams").second, RHI::ShaderStageBits::sVertex))); + imguiPipelineLayout = device.CreatePipelineLayout( + RHI::PipelineLayoutCreateInfo() + .AddBindGroupLayout(imguiBindGroupLayout.Get())); + + const RHI::BlendComponent colorBlend(RHI::BlendOp::opAdd, RHI::BlendFactor::srcAlpha, RHI::BlendFactor::oneMinusSrcAlpha); + const RHI::BlendComponent alphaBlend(RHI::BlendOp::opAdd, RHI::BlendFactor::one, RHI::BlendFactor::oneMinusSrcAlpha); + const RHI::ColorTargetState colorTarget(GetSwapChainFormat(), RHI::ColorWriteBits::all, true, colorBlend, alphaBlend); + + imguiPipeline = device.CreateRasterPipeline( + RHI::RasterPipelineCreateInfo(imguiPipelineLayout.Get()) + .SetVertexShader(imguiVertexShader.Get()) + .SetPixelShader(imguiPixelShader.Get()) + .SetVertexState( + RHI::VertexState() + .AddVertexBufferLayout( + RHI::VertexBufferLayout(RHI::VertexStepMode::perVertex, sizeof(ImDrawVert)) + .AddAttribute(RHI::VertexAttribute(imguiVertexShaderCompileOutput.reflectionData.QueryVertexBindingChecked("POSITION"), RHI::VertexFormat::float32X2, offsetof(ImDrawVert, pos))) + .AddAttribute(RHI::VertexAttribute(imguiVertexShaderCompileOutput.reflectionData.QueryVertexBindingChecked("TEXCOORD"), RHI::VertexFormat::float32X2, offsetof(ImDrawVert, uv))) + .AddAttribute(RHI::VertexAttribute(imguiVertexShaderCompileOutput.reflectionData.QueryVertexBindingChecked("COLOR"), RHI::VertexFormat::unorm8X4, offsetof(ImDrawVert, col))))) + .SetFragmentState(RHI::FragmentState().AddColorTarget(colorTarget)) + .SetPrimitiveState(RHI::PrimitiveState(RHI::PrimitiveTopologyType::triangle, RHI::FillMode::solid, Internal::imguiIndexFormat, RHI::FrontFace::ccw, RHI::CullMode::none)) + .SetDepthStencilState(RHI::DepthStencilState())); + } + + void EditorWindow::CreateImGuiFontTexture() + { + RHI::Device& device = GetDevice(); + + unsigned char* pixels = nullptr; + int width = 0; + int height = 0; + ImGui::GetIO().Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + + imguiFontTexture = device.CreateTexture( + RHI::TextureCreateInfo() + .SetDimension(RHI::TextureDimension::t2D) + .SetWidth(static_cast(width)) + .SetHeight(static_cast(height)) + .SetDepthOrArraySize(1) + .SetFormat(RHI::PixelFormat::rgba8Unorm) + .SetUsages(RHI::TextureUsageBits::copyDst | RHI::TextureUsageBits::textureBinding) + .SetMipLevels(1) + .SetSamples(1) + .SetInitialState(RHI::TextureState::undefined) + .SetDebugName("imguiFontTexture")); + imguiFontTextureView = imguiFontTexture->CreateTextureView( + RHI::TextureViewCreateInfo() + .SetDimension(RHI::TextureViewDimension::tv2D) + .SetMipLevels(0, 1) + .SetArrayLayers(0, 1) + .SetAspect(RHI::TextureAspect::color) + .SetType(RHI::TextureViewType::textureBinding)); + imguiImageSampler = device.CreateSampler( + RHI::SamplerCreateInfo() + .SetAddressModeU(RHI::AddressMode::clampToEdge) + .SetAddressModeV(RHI::AddressMode::clampToEdge) + .SetAddressModeW(RHI::AddressMode::clampToEdge) + .SetMagFilter(RHI::FilterMode::linear) + .SetMinFilter(RHI::FilterMode::linear) + .SetMipFilter(RHI::FilterMode::linear) + .SetDebugName("imguiImageSampler")); + + const auto copyFootprint = device.GetTextureSubResourceCopyFootprint(*imguiFontTexture, RHI::TextureSubResourceInfo()); + auto stagingBuffer = device.CreateBuffer( + RHI::BufferCreateInfo() + .SetSize(static_cast(copyFootprint.totalBytes)) + .SetUsages(RHI::BufferUsageBits::mapWrite | RHI::BufferUsageBits::copySrc) + .SetInitialState(RHI::BufferState::staging) + .SetDebugName("imguiFontUpload")); + auto* mapped = stagingBuffer->Map(RHI::MapMode::write, 0, copyFootprint.totalBytes); + const size_t srcRowPitch = static_cast(width) * copyFootprint.bytesPerPixel; + for (int row = 0; row < height; row++) { + const auto* src = pixels + static_cast(row) * srcRowPitch; + auto* dst = static_cast(mapped) + static_cast(row) * copyFootprint.rowPitch; + memcpy(dst, src, srcRowPitch); + } + stagingBuffer->Unmap(); + + const auto commandBuffer = device.CreateCommandBuffer(); + const auto commandRecorder = commandBuffer->Begin(); + { + const auto copyRecorder = commandRecorder->BeginCopyPass(); + copyRecorder->ResourceBarrier(RHI::Barrier::Transition(imguiFontTexture.Get(), RHI::TextureState::undefined, RHI::TextureState::copyDst)); + copyRecorder->CopyBufferToTexture( + stagingBuffer.Get(), + imguiFontTexture.Get(), + RHI::BufferTextureCopyInfo( + 0, + RHI::TextureSubResourceInfo(), + Common::UVec3Consts::zero, + Common::UVec3(static_cast(width), static_cast(height), 1))); + copyRecorder->ResourceBarrier(RHI::Barrier::Transition(imguiFontTexture.Get(), RHI::TextureState::copyDst, RHI::TextureState::shaderReadOnly)); + copyRecorder->EndPass(); + } + commandRecorder->End(); + + const auto fence = device.CreateFence(false); + device.GetQueue(RHI::QueueType::graphics, 0)->Submit(commandBuffer.Get(), RHI::QueueSubmitInfo().SetSignalFence(fence.Get())); + fence->Wait(); + ImGui::GetIO().Fonts->SetTexID(static_cast(reinterpret_cast(imguiFontTextureView.Get()))); + } + + void EditorWindow::CreateImGuiDynamicBuffers(int inVertexCount, int inIndexCount) + { + RHI::Device& device = GetDevice(); + imguiVertexBufferCapacity = std::max(1, inVertexCount); + imguiIndexBufferCapacity = std::max(1, inIndexCount); + + const uint32_t vertexBufferSize = static_cast(imguiVertexBufferCapacity * sizeof(ImDrawVert)); + imguiVertexBuffer = device.CreateBuffer( + RHI::BufferCreateInfo() + .SetSize(vertexBufferSize) + .SetUsages(RHI::BufferUsageBits::vertex | RHI::BufferUsageBits::mapWrite | RHI::BufferUsageBits::copySrc) + .SetInitialState(RHI::BufferState::staging) + .SetDebugName("imguiVertexBuffer")); + imguiVertexBufferView = imguiVertexBuffer->CreateBufferView( + RHI::BufferViewCreateInfo() + .SetType(RHI::BufferViewType::vertex) + .SetSize(vertexBufferSize) + .SetOffset(0) + .SetExtendVertex(sizeof(ImDrawVert))); + + const uint32_t indexBufferSize = static_cast(imguiIndexBufferCapacity * sizeof(ImDrawIdx)); + imguiIndexBuffer = device.CreateBuffer( + RHI::BufferCreateInfo() + .SetSize(indexBufferSize) + .SetUsages(RHI::BufferUsageBits::index | RHI::BufferUsageBits::mapWrite | RHI::BufferUsageBits::copySrc) + .SetInitialState(RHI::BufferState::staging) + .SetDebugName("imguiIndexBuffer")); + imguiIndexBufferView = imguiIndexBuffer->CreateBufferView( + RHI::BufferViewCreateInfo() + .SetType(RHI::BufferViewType::index) + .SetSize(indexBufferSize) + .SetOffset(0) + .SetExtendIndex(Internal::imguiIndexFormat)); + } + + void EditorWindow::UpdateImGuiBuffers(ImDrawData& inDrawData) + { + auto* vertexDst = static_cast(imguiVertexBuffer->Map(RHI::MapMode::write, 0, inDrawData.TotalVtxCount * sizeof(ImDrawVert))); + auto* indexDst = static_cast(imguiIndexBuffer->Map(RHI::MapMode::write, 0, inDrawData.TotalIdxCount * sizeof(ImDrawIdx))); + for (int i = 0; i < inDrawData.CmdListsCount; i++) { + const ImDrawList* commandList = inDrawData.CmdLists[i]; + memcpy(vertexDst, commandList->VtxBuffer.Data, static_cast(commandList->VtxBuffer.Size) * sizeof(ImDrawVert)); + memcpy(indexDst, commandList->IdxBuffer.Data, static_cast(commandList->IdxBuffer.Size) * sizeof(ImDrawIdx)); + vertexDst += commandList->VtxBuffer.Size; + indexDst += commandList->IdxBuffer.Size; + } + imguiVertexBuffer->Unmap(); + imguiIndexBuffer->Unmap(); + } + + void EditorWindow::UpdateImGuiPassParams(const ImDrawData& inDrawData) + { + const ImGuiPassParams params { + Common::FVec2(inDrawData.DisplayPos.x, inDrawData.DisplayPos.y), + Common::FVec2(inDrawData.DisplaySize.x, inDrawData.DisplaySize.y) + }; + auto* data = imguiPassParamsBuffer->Map(RHI::MapMode::write, 0, sizeof(ImGuiPassParams)); + memcpy(data, ¶ms, sizeof(ImGuiPassParams)); + imguiPassParamsBuffer->Unmap(); + } + + RHI::BindGroup* EditorWindow::GetOrCreateImGuiTextureBindGroup(RHI::TextureView& inTextureView) + { + if (const auto iter = imguiFrameTextureBindGroups.find(&inTextureView); + iter != imguiFrameTextureBindGroups.end()) { + return iter->second.Get(); + } + + auto bindGroup = GetDevice().CreateBindGroup( + RHI::BindGroupCreateInfo(imguiBindGroupLayout.Get(), "imguiBindGroup") + .AddEntry(RHI::BindGroupEntry(imguiPixelShaderCompileOutput.reflectionData.QueryResourceBindingChecked("imageTex").second, &inTextureView)) + .AddEntry(RHI::BindGroupEntry(imguiPixelShaderCompileOutput.reflectionData.QueryResourceBindingChecked("imageSampler").second, imguiImageSampler.Get())) + .AddEntry(RHI::BindGroupEntry(imguiVertexShaderCompileOutput.reflectionData.QueryResourceBindingChecked("passParams").second, imguiPassParamsBufferView.Get()))); + auto* result = bindGroup.Get(); + imguiFrameTextureBindGroups.emplace(&inTextureView, std::move(bindGroup)); + return result; + } + + ImDrawData* EditorWindow::TakeDrawData() + { + std::unique_lock lock(drawDataMutex); + ImDrawData* result = pendingDrawData; + pendingDrawData = nullptr; + return result; + } + + void EditorWindow::DestroyDrawData(ImDrawData* inDrawData) + { + if (inDrawData == nullptr) { + return; + } + for (int i = 0; i < inDrawData->CmdLists.Size; i++) { + IM_DELETE(inDrawData->CmdLists[i]); + } + inDrawData->CmdLists.clear(); + IM_DELETE(inDrawData); + } +} diff --git a/Editor/Src/Frame/EditorFrame.cpp b/Editor/Src/Frame/EditorFrame.cpp new file mode 100644 index 000000000..4bcfea5ba --- /dev/null +++ b/Editor/Src/Frame/EditorFrame.cpp @@ -0,0 +1,248 @@ +// +// Created by johnk on 2026/7/7. +// + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace Editor::Internal { + static std::string EntityDisplayName(const Runtime::ECRegistry& inRegistry, Runtime::Entity inEntity) + { + const auto* name = inRegistry.Find(inEntity); + return name != nullptr && !name->value.empty() + ? name->value + : std::format("Entity {}", inEntity); + } + + static std::string LevelString(Core::LogLevel inLevel) + { + switch (inLevel) { + case Core::LogLevel::verbose: + return "verbose"; + case Core::LogLevel::info: + return "info"; + case Core::LogLevel::warning: + return "warning"; + case Core::LogLevel::error: + return "error"; + default: + return "unknown"; + } + } +} + +namespace Editor { + EditorFrame::EditorFrame() + : createEntityName("Entity") + , selectedAddComponentIndex(0) + { + } + + EditorFrame::~EditorFrame() = default; + + void EditorFrame::Render(EditorContext& inContext, Runtime::Canvas& inSceneRenderCanvas, bool& outRequestQuit) + { + RenderMenuBar(inContext, outRequestQuit); + RenderSceneTab(inContext, inSceneRenderCanvas); + RenderOutlinerTab(inContext); + RenderInspectorTab(inContext); + RenderLogTab(); + } + + void EditorFrame::RenderMenuBar(EditorContext& inContext, bool& outRequestQuit) + { + if (!ImGui::BeginMainMenuBar()) { + return; + } + if (ImGui::BeginMenu("File")) { + if (ImGui::MenuItem("Save Level", "Ctrl+S")) { + inContext.GetSceneClient().SaveLevel(); + } + ImGui::Separator(); + if (ImGui::MenuItem("Exit")) { + outRequestQuit = true; + } + ImGui::EndMenu(); + } + ImGui::EndMainMenuBar(); + } + + void EditorFrame::RenderSceneTab(EditorContext& inContext, Runtime::Canvas& inSceneRenderCanvas) + { + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + ImGui::Begin("Scene", nullptr, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + ImVec2 sceneSize = ImGui::GetContentRegionAvail(); + sceneSize.x = std::max(sceneSize.x, 1.0f); + sceneSize.y = std::max(sceneSize.y, 1.0f); + + const ImVec2 framebufferScale = ImGui::GetIO().DisplayFramebufferScale; + auto& sceneClient = inContext.GetSceneClient(); + sceneClient.ResizeRenderSurface( + std::max(1u, static_cast(sceneSize.x * framebufferScale.x)), + std::max(1u, static_cast(sceneSize.y * framebufferScale.y))); + ImGui::Image(static_cast(reinterpret_cast(inSceneRenderCanvas.GetTextureView())), sceneSize); + sceneClient.SetSceneHovered(ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)); + sceneClient.SetSceneFocused(ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)); + ImGui::End(); + ImGui::PopStyleVar(); + } + + void EditorFrame::RenderOutlinerTab(EditorContext& inContext) + { + ImGui::Begin("Outliner"); + ImGui::InputText("New Entity", &createEntityName); + ImGui::SameLine(); + if (ImGui::Button("Create")) { + const auto entity = inContext.CreateEntity(createEntityName); + inContext.SetSelectedEntity(entity); + } + ImGui::Separator(); + + auto& registry = inContext.GetSceneClient().GetWorld().GetRegistry(); + registry.Each([&](Runtime::Entity entity) -> void { + if (registry.Has(entity)) { + return; + } + const std::string label = Internal::EntityDisplayName(registry, entity); + const bool selected = inContext.GetSelectedEntity() == entity; + ImGui::PushID(static_cast(entity)); + if (ImGui::Selectable(label.c_str(), selected)) { + inContext.SetSelectedEntity(entity); + } + ImGui::PopID(); + }); + ImGui::End(); + } + + void EditorFrame::RenderInspectorTab(EditorContext& inContext) + { + ImGui::Begin("Inspector"); + const Runtime::Entity selectedEntity = inContext.GetSelectedEntity(); + auto& registry = inContext.GetSceneClient().GetWorld().GetRegistry(); + if (selectedEntity == Runtime::entityNull || !registry.Valid(selectedEntity)) { + ImGui::TextDisabled("No entity selected"); + ImGui::End(); + return; + } + + std::string entityName = Internal::EntityDisplayName(registry, selectedEntity); + if (InputWidget::Render("Name", entityName)) { + inContext.RenameEntity(selectedEntity, entityName); + } + if (ImGui::Button("Destroy Entity")) { + inContext.DestroyEntity(selectedEntity); + ImGui::End(); + return; + } + ImGui::Separator(); + + std::vector addableComponents; + for (const auto* clazz : Mirror::Class::GetAll()) { + if (clazz->HasMeta("comp") && !registry.HasDyn(clazz, selectedEntity) && clazz->HasDefaultConstructor()) { + addableComponents.emplace_back(clazz); + } + } + std::ranges::sort(addableComponents, [](const Mirror::Class* lhs, const Mirror::Class* rhs) -> bool { + return lhs->GetName() < rhs->GetName(); + }); + + if (!addableComponents.empty()) { + selectedAddComponentIndex = std::clamp(selectedAddComponentIndex, 0, static_cast(addableComponents.size() - 1)); + if (ImGui::BeginCombo("Add Component", addableComponents[selectedAddComponentIndex]->GetName().c_str())) { + for (int i = 0; i < static_cast(addableComponents.size()); i++) { + const bool selected = i == selectedAddComponentIndex; + if (ImGui::Selectable(addableComponents[i]->GetName().c_str(), selected)) { + selectedAddComponentIndex = i; + } + if (selected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + ImGui::SameLine(); + if (ImGui::Button("Add")) { + registry.EmplaceDyn(addableComponents[selectedAddComponentIndex], selectedEntity, {}); + inContext.NotifyComponentsChanged(selectedEntity); + } + } + + Runtime::CompClass componentToRemove = nullptr; + registry.CompEach(selectedEntity, [&](Runtime::CompClass compClass) -> void { + if (compClass->HasMeta("transient")) { + return; + } + ImGui::PushID(compClass->GetName().c_str()); + const bool open = ImGui::CollapsingHeader(compClass->GetName().c_str(), ImGuiTreeNodeFlags_DefaultOpen); + if (ImGui::BeginPopupContextItem("ComponentMenu")) { + if (ImGui::MenuItem("Remove")) { + componentToRemove = compClass; + } + ImGui::EndPopup(); + } + if (open && componentToRemove != compClass) { + const Mirror::Any compRef = registry.GetDyn(compClass, selectedEntity); + for (const auto& memberVariable : compClass->GetMemberVariables() | std::views::values) { + if (memberVariable.IsTransient()) { + continue; + } + Mirror::Any memberRef = memberVariable.GetDyn(compRef); + if (RenderInputWidget(memberVariable.GetName(), memberRef)) { + inContext.NotifyComponentsChanged(selectedEntity); + } + } + } + ImGui::PopID(); + }); + if (componentToRemove != nullptr) { + registry.RemoveDyn(componentToRemove, selectedEntity); + inContext.NotifyComponentsChanged(selectedEntity); + } + ImGui::End(); + } + + void EditorFrame::RenderLogTab() + { + ImGui::Begin("Log"); + const auto entries = EditorLogStream::Get().Snapshot(); + if (ImGui::Button("Copy")) { + std::string text; + for (const auto& entry : entries) { + text += std::format("[{}][{}][{}] {}\n", entry.time, Internal::LevelString(entry.level), entry.tag, entry.content); + } + ImGui::SetClipboardText(text.c_str()); + } + ImGui::Separator(); + ImGui::BeginChild("LogEntries", ImVec2(0.0f, 0.0f), false, ImGuiWindowFlags_HorizontalScrollbar); + for (const auto& entry : entries) { + ImVec4 color = ImGui::GetStyleColorVec4(ImGuiCol_Text); + if (entry.level == Core::LogLevel::warning) { + color = ImVec4(1.0f, 0.78f, 0.25f, 1.0f); + } else if (entry.level == Core::LogLevel::error) { + color = ImVec4(1.0f, 0.35f, 0.32f, 1.0f); + } + ImGui::PushStyleColor(ImGuiCol_Text, color); + ImGui::TextUnformatted(std::format("[{}][{}][{}] {}", entry.time, Internal::LevelString(entry.level), entry.tag, entry.content).c_str()); + ImGui::PopStyleColor(); + } + if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) { + ImGui::SetScrollHereY(1.0f); + } + ImGui::EndChild(); + ImGui::End(); + } +} diff --git a/Editor/Src/Frame/ProjectHubFrame.cpp b/Editor/Src/Frame/ProjectHubFrame.cpp new file mode 100644 index 000000000..3fee4e338 --- /dev/null +++ b/Editor/Src/Frame/ProjectHubFrame.cpp @@ -0,0 +1,227 @@ +// +// Created by johnk on 2026/7/8. +// + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Editor::Internal { + constexpr std::string_view templateFileExtension = ".tpl"; + constexpr std::string_view cmakeMinVersion = "3.25"; + + static Common::Path StripTemplateExtension(const Common::Path& inRelativePath) + { + const std::string str = inRelativePath.String(); + return { str.substr(0, str.size() - templateFileExtension.size()) }; + } + + static Common::Result RenderProjectTemplate( + const Common::Path& inTemplateDir, + const Common::Path& inProjectDir, + const std::string& inProjectName) + { + Common::TemplateEngine templateEngine; + templateEngine + .Set("projectName", inProjectName) + .Set("cmakeMinVersion", std::string(cmakeMinVersion)); + + Common::Result result = Common::Ok(); + inTemplateDir.TraverseRecurse([&](const Common::Path& inPath) -> bool { + if (inPath.IsDirectory()) { + return true; + } + + const Common::Path relativePath = inPath.Relative(inTemplateDir); + const std::string extension = inPath.Extension(); + const bool isTemplate = std::string_view(extension) == templateFileExtension; + const Common::Path dstPath = inProjectDir / (isTemplate ? StripTemplateExtension(relativePath) : relativePath); + dstPath.Parent().MakeDir(); + + if (!isTemplate) { + inPath.CopyTo(dstPath); + return true; + } + if (auto renderResult = templateEngine.RenderFileTo(inPath.String(), dstPath.String()); + renderResult.IsErr()) { + result = Common::Err(renderResult.Error()); + return false; + } + return true; + }); + return result; + } + + static std::string LaunchCommand(const std::string& inExecutable, const std::string& inProjectPath, const std::string& inRhiType) + { +#if PLATFORM_WINDOWS + return std::format("start \"\" \"{}\" -project \"{}\" -rhi {}", inExecutable, inProjectPath, inRhiType); +#else + return std::format("\"{}\" -project \"{}\" -rhi {} &", inExecutable, inProjectPath, inRhiType); +#endif + } +} + +namespace Editor { + ProjectHubFrame::ProjectHubFrame() + : recentProjectsFile(Core::Paths::EngineCacheDir() / "Editor" / "ProjectHub" / "RecentProjects.json") + , engineVersion(std::format("v{}.{}.{}", ENGINE_VERSION_MAJOR, ENGINE_VERSION_MINOR, ENGINE_VERSION_PATCH)) + , projectName() + , directory() + , selectedTemplateIndex(0) + , lastCreatedProjectPath() + { + const Common::Path projectTemplatesRoot = Core::Paths::EngineResDir() / "Editor" / "ProjectTemplates"; + (void) projectTemplatesRoot.Traverse([this](const Common::Path& inPath) -> bool { + if (inPath.IsFile()) { + return true; + } + projectTemplates.emplace_back(ProjectTemplateInfo { inPath.DirName(), inPath.String() }); + return true; + }); + + if (recentProjectsFile.Exists()) { + Common::JsonDeserializeFromFile(recentProjectsFile.String(), recentProjects); + } + } + + ProjectHubFrame::~ProjectHubFrame() + { + SaveRecentProjects(); + } + + void ProjectHubFrame::Render(EditorWindow& inWindow, const std::string& inRhiType) + { + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->WorkPos); + ImGui::SetNextWindowSize(viewport->WorkSize); + ImGui::Begin("Project Hub", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove); + + ImGui::TextUnformatted("Explosion Editor"); + ImGui::SameLine(); + ImGui::TextDisabled("%s", engineVersion.c_str()); + ImGui::Separator(); + + ImGui::Columns(2, "ProjectHubColumns", true); + ImGui::TextUnformatted("Recent Projects"); + ImGui::BeginChild("RecentProjects", ImVec2(0.0f, -1.0f), true); + for (const auto& project : std::views::reverse(recentProjects)) { + ImGui::PushID(project.path.c_str()); + if (ImGui::Selectable(project.name.c_str())) { + OpenProject(inWindow, project.path, inRhiType); + } + ImGui::TextDisabled("%s", project.path.c_str()); + ImGui::PopID(); + } + ImGui::EndChild(); + + ImGui::NextColumn(); + ImGui::TextUnformatted("Create Project"); + ImGui::InputText("Name", &projectName); + ImGui::InputText("Directory", &directory); + + const char* selectedTemplate = projectTemplates.empty() ? "None" : projectTemplates[selectedTemplateIndex].name.c_str(); + if (ImGui::BeginCombo("Template", selectedTemplate)) { + for (int i = 0; i < static_cast(projectTemplates.size()); i++) { + const bool selected = i == selectedTemplateIndex; + if (ImGui::Selectable(projectTemplates[i].name.c_str(), selected)) { + selectedTemplateIndex = i; + } + if (selected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + + if (ImGui::Button("Create")) { + const CreateProjectResult result = CreateProject(); + lastCreatedProjectPath = result.success ? result.projectPath : std::string(); + statusMessage = result.success ? std::format("Created {}", result.projectPath) : result.error; + } + ImGui::SameLine(); + if (ImGui::Button("Open Created") && !lastCreatedProjectPath.empty()) { + OpenProject(inWindow, lastCreatedProjectPath, inRhiType); + } + if (!statusMessage.empty()) { + ImGui::TextWrapped("%s", statusMessage.c_str()); + } + ImGui::Columns(1); + ImGui::End(); + } + + CreateProjectResult ProjectHubFrame::CreateProject() + { + if (projectName.empty() || directory.empty() || projectTemplates.empty()) { + return { .success = false, .error = "Project name, directory and template must not be empty.", .projectPath = {} }; + } + + const std::string templatePath = projectTemplates[selectedTemplateIndex].path; + const Common::Path templateDir(templatePath); + if (!templateDir.Exists() || !templateDir.IsDirectory()) { + return { .success = false, .error = std::format("Project template '{}' does not exist.", templatePath), .projectPath = {} }; + } + + const Common::Path projectDir = Common::Path(directory) / projectName; + if (projectDir.Exists()) { + return { .success = false, .error = std::format("Target directory '{}' already exists.", projectDir.String()), .projectPath = {} }; + } + + if (const auto result = Internal::RenderProjectTemplate(templateDir, projectDir, projectName); + result.IsErr()) { + return { .success = false, .error = result.Error(), .projectPath = {} }; + } + + recentProjects.emplace_back(RecentProjectInfo { projectName, projectDir.String() }); + SaveRecentProjects(); + LogInfo(ProjectHub, "created project '{}' at '{}'", projectName, projectDir.String()); + return { .success = true, .error = {}, .projectPath = projectDir.String() }; + } + + void ProjectHubFrame::OpenProject(EditorWindow& inWindow, const std::string& inProjectPath, const std::string& inRhiType) + { + const Common::Path projectDir(inProjectPath); + if (!projectDir.Exists() || !projectDir.IsDirectory()) { + statusMessage = std::format("Project '{}' does not exist.", inProjectPath); + return; + } + + TouchRecentProject(inProjectPath); + SaveRecentProjects(); + + const std::string command = Internal::LaunchCommand(Core::Paths::ExecutablePath().String(), inProjectPath, inRhiType); + std::ignore = std::system(command.c_str()); + inWindow.RequestClose(); + } + + void ProjectHubFrame::SaveRecentProjects() const + { + recentProjectsFile.Parent().MakeDir(); + Common::JsonSerializeToFile(recentProjectsFile.String(), recentProjects); + } + + void ProjectHubFrame::TouchRecentProject(const std::string& inProjectPath) + { + std::string touchedProjectName = Common::Path(inProjectPath).DirName(); + if (const auto iter = std::ranges::find_if(recentProjects, [&](const RecentProjectInfo& info) -> bool { return info.path == inProjectPath; }); + iter != recentProjects.end()) { + touchedProjectName = iter->name; + recentProjects.erase(iter); + } + recentProjects.emplace_back(RecentProjectInfo { touchedProjectName, inProjectPath }); + } +} diff --git a/Editor/Src/Main.cpp b/Editor/Src/Main.cpp index 4feceea18..769557436 100644 --- a/Editor/Src/Main.cpp +++ b/Editor/Src/Main.cpp @@ -2,19 +2,10 @@ // Created by johnk on 2024/3/31. // -#include -#include - #include +#include +#include #include -#include -#include -#include -#include - -static Core::CmdlineArgValue caPrototype( - "prototype", "-prototype", false, - "Whether to run the prototype playground (native graphics widget mixed with web widget) instead of editor"); static Core::CmdlineArgValue caRhiType( "rhiType", "-rhi", RHI::GetPlatformDefaultRHIAbbrString(), @@ -24,86 +15,39 @@ static Core::CmdlineArgValue caProjectRoot( "projectRoot", "-project", "", "project root path"); -enum class EditorApplicationModel : uint8_t { - prototype, - projectHub, - editor, - max -}; - -static EditorApplicationModel GetAppModel() -{ - if (caPrototype.GetValue()) { - return EditorApplicationModel::prototype; - } - if (caProjectRoot.GetValue().empty()) { - return EditorApplicationModel::projectHub; - } - return EditorApplicationModel::editor; -} - -static bool NeedInitCore(EditorApplicationModel inModel) +static Editor::EditorApplicationMode GetAppMode() { - return inModel == EditorApplicationModel::editor - || inModel == EditorApplicationModel::prototype; + return caProjectRoot.GetValue().empty() + ? Editor::EditorApplicationMode::projectHub + : Editor::EditorApplicationMode::editor; } -static void InitializePreQtApp(EditorApplicationModel inModel) +static void InitializeEngine() { - Editor::WebUIServer::Get().Start(); - - if (!NeedInitCore(inModel)) { - return; - } + Editor::EditorLogStream::Get(); Runtime::EngineInitParams params {}; params.logToFile = true; params.gameRoot = caProjectRoot.GetValue(); params.rhiType = caRhiType.GetValue(); - Runtime::EngineHolder::Load("Editor", params); } -static void InitializePostQtApp() -{ - QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy); -} - -static void Cleanup(EditorApplicationModel inModel) -{ - Editor::WebUIServer::Get().Stop(); - - if (!NeedInitCore(inModel)) { - return; - } - Runtime::EngineHolder::Unload(); -} - -static Common::UniquePtr CreateMainWidget(EditorApplicationModel inModel) -{ - if (inModel == EditorApplicationModel::prototype) { - return new Editor::PrototypePlayground(); - } - if (inModel == EditorApplicationModel::projectHub) { // NOLINT - return new Editor::ProjectHub(); - } - return new Editor::ExplosionEditor(); -} - int main(int argc, char* argv[]) { Core::Cli::Get().Parse(argc, argv); - const auto appModel = GetAppModel(); - - InitializePreQtApp(appModel); - QApplication qtApplication(argc, argv); - InitializePostQtApp(); - Common::UniquePtr mainWidget = CreateMainWidget(appModel); - mainWidget->show(); - - const int execRes = QApplication::exec(); - mainWidget.Reset(); - Cleanup(appModel); - return execRes; + InitializeEngine(); + const Editor::EditorApplicationDesc appDesc { + .mode = GetAppMode(), + .rhiType = caRhiType.GetValue(), + .projectRoot = caProjectRoot.GetValue() + }; + int result = 0; + { + Editor::EditorApplication application(appDesc); + result = application.Run(); + } + Runtime::EngineHolder::Unload(); + return result; } diff --git a/Editor/Src/Qt/MirrorTemplateView.cpp b/Editor/Src/Qt/MirrorTemplateView.cpp deleted file mode 100644 index 573970db8..000000000 --- a/Editor/Src/Qt/MirrorTemplateView.cpp +++ /dev/null @@ -1,141 +0,0 @@ -// -// Created by Kindem on 2025/8/26. -// - -#include - -namespace Editor { - QListMetaView::QListMetaView(const Mirror::Any& inRef) - : ref(inRef) - { - Assert(inRef.IsRef() && ref.CanAsTemplateView()); - rtti = static_cast(ref.GetTemplateViewRtti()); - } - - const Mirror::TypeInfo* QListMetaView::ElementType() const - { - return rtti->getElementType(); - } - - size_t QListMetaView::Size() const - { - return rtti->getSize(ref); - } - - void QListMetaView::Reserve(size_t inSize) const - { - rtti->reserve(ref, inSize); - } - - void QListMetaView::Resize(size_t inSize) const - { - rtti->resize(ref, inSize); - } - - void QListMetaView::Clear() const - { - rtti->clear(ref); - } - - Mirror::Any QListMetaView::At(size_t inIndex) const - { - return rtti->getElement(ref, inIndex); - } - - Mirror::Any QListMetaView::ConstAt(size_t inIndex) const - { - return rtti->getConstElement(ref, inIndex); - } - - Mirror::Any QListMetaView::EmplaceBack(const Mirror::Argument& inTempObj) const - { - return rtti->emplaceBack(ref, inTempObj); - } - - Mirror::Any QListMetaView::EmplaceDefaultBack() const - { - return rtti->emplaceDefaultBack(ref); - } - - void QListMetaView::Remove(size_t inIndex) const - { - rtti->remove(ref, inIndex); - } - - QMapMetaView::QMapMetaView(const Mirror::Any& inRef) - : ref(inRef) - { - Assert(inRef.IsRef() && ref.CanAsTemplateView()); - rtti = static_cast(ref.GetTemplateViewRtti()); - } - - const Mirror::TypeInfo* QMapMetaView::GetKeyType() const - { - return rtti->getKeyType(); - } - - const Mirror::TypeInfo* QMapMetaView::GetValueType() const - { - return rtti->getValueType(); - } - - Mirror::Any QMapMetaView::CreateKey() const - { - return rtti->createKey(); - } - - Mirror::Any QMapMetaView::CreateValue() const - { - return rtti->createValue(); - } - - size_t QMapMetaView::GetSize() const - { - return rtti->getSize(ref); - } - - void QMapMetaView::Reserve(size_t inSize) const - { - rtti->reserve(ref, inSize); - } - - void QMapMetaView::Clear() const - { - rtti->clear(ref); - } - - Mirror::Any QMapMetaView::GetOrAdd(const Mirror::Argument& inKey) const - { - return rtti->getOrAdd(ref, inKey); - } - - Mirror::Any QMapMetaView::ConstAt(const Mirror::Argument& inKey) const - { - return rtti->constAt(ref, inKey); - } - - void QMapMetaView::Traverse(const PairTraverser& inTraverser) const - { - rtti->traverse(ref, inTraverser); - } - - void QMapMetaView::ConstTraverse(const PairTraverser& inTraverser) const - { - rtti->constTraverse(ref, inTraverser); - } - - bool QMapMetaView::Contains(const Mirror::Argument& inKey) const - { - return rtti->contains(ref, inKey); - } - - void QMapMetaView::Emplace(const Mirror::Argument& inTempKey, const Mirror::Argument& inTempValue) const - { - rtti->emplace(ref, inTempKey, inTempValue); - } - - void QMapMetaView::Erase(const Mirror::Argument& inKey) const - { - rtti->erase(ref, inKey); - } -} // namespace Editor diff --git a/Editor/Src/SceneClient.cpp b/Editor/Src/SceneClient.cpp new file mode 100644 index 000000000..55b30861f --- /dev/null +++ b/Editor/Src/SceneClient.cpp @@ -0,0 +1,356 @@ +// +// Created by johnk on 2026/7/4. +// + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Editor::Internal { + const Core::Uri mainLevelUri("asset://Game/Maps/Main"); + const Core::Uri defaultUnlitMaterialUri("asset://Game/Materials/DefaultUnlit"); + const Core::Uri defaultUnlitInstanceUri("asset://Game/Materials/DefaultUnlitInstance"); + const Core::Uri cubeMeshUri("asset://Game/Meshes/Cube"); + constexpr float cameraMoveSpeed = 5.0f; + constexpr float cameraLookSpeedDegrees = 0.2f; + constexpr float maxCameraPitchDegrees = 89.0f; + constexpr float degToRad = 3.14159265358979323846f / 180.0f; + + static bool AssetExists(const Core::Uri& inUri) + { + return Core::AssetUriParser(inUri).Parse().Exists(); + } + + template + static void SaveAsset(const Runtime::AssetPtr& inAsset) + { + if (const Common::Path parentDir = Core::AssetUriParser(inAsset->Uri()).Parse().Parent(); + !parentDir.Exists()) { + parentDir.MakeDir(); + } + Runtime::AssetManager::Get().Save(inAsset); + } + + static Runtime::StaticMeshVertices BuildCubeVertices() + { + // 24 vertices (4 per face) so each face gets its own uv space, z-up unit cube centered at origin + static constexpr float h = 0.5f; + static const std::array, 6> facePositions = {{ + {{ { -h, -h, h }, { h, -h, h }, { h, h, h }, { -h, h, h } }}, // +z + {{ { -h, h, -h }, { h, h, -h }, { h, -h, -h }, { -h, -h, -h } }}, // -z + {{ { h, -h, -h }, { h, h, -h }, { h, h, h }, { h, -h, h } }}, // +x + {{ { -h, h, -h }, { -h, -h, -h }, { -h, -h, h }, { -h, h, h } }}, // -x + {{ { h, h, -h }, { -h, h, -h }, { -h, h, h }, { h, h, h } }}, // +y + {{ { -h, -h, -h }, { h, -h, -h }, { h, -h, h }, { -h, -h, h } }} // -y + }}; + + Runtime::StaticMeshVertices result; + for (const auto& face : facePositions) { + const auto baseVertex = static_cast(result.positions.size()); + for (size_t corner = 0; corner < 4; corner++) { + result.positions.emplace_back(face[corner]); + result.tangents.emplace_back(0.0f, 0.0f, 1.0f); + result.uv0.emplace_back(corner == 1 || corner == 2 ? 1.0f : 0.0f, corner >= 2 ? 1.0f : 0.0f); + } + for (const uint32_t index : { 0u, 1u, 2u, 0u, 2u, 3u }) { + result.indices.emplace_back(baseVertex + index); + } + } + result.vertexCount = static_cast(result.positions.size()); + result.indexCount = static_cast(result.indices.size()); + return result; + } + + static Runtime::AssetPtr EnsureDefaultCubeMesh() + { + auto& assetManager = Runtime::AssetManager::Get(); + if (AssetExists(cubeMeshUri)) { + return assetManager.SyncLoad(cubeMeshUri, Mirror::Class::Get()); + } + + Runtime::AssetPtr material = new Runtime::Material(defaultUnlitMaterialUri); + material->SetType(Runtime::MaterialType::surface); + material->SetSource("float4 GetBaseColor()\n{\n return baseColor;\n}\n"); + Runtime::MaterialFVec4ParameterField baseColorField; + baseColorField.defaultValue = Common::FVec4(1.0f, 1.0f, 1.0f, 1.0f); + material->EmplaceParameterField("baseColor") = baseColorField; + material->Update(); + SaveAsset(material); + + Runtime::AssetPtr materialInstance = new Runtime::MaterialInstance(defaultUnlitInstanceUri); + materialInstance->SetMaterial(material); + materialInstance->SetParameter("baseColor", Common::FVec4(0.9f, 0.6f, 0.2f, 1.0f)); + SaveAsset(materialInstance); + + Runtime::AssetPtr cubeMesh = new Runtime::StaticMesh(cubeMeshUri); + cubeMesh->SetMaterial(materialInstance); + cubeMesh->EmplaceLOD().vertices = BuildCubeVertices(); + SaveAsset(cubeMesh); + return cubeMesh; + } + + static void AuthorDefaultLevelContent(Runtime::ECRegistry& inRegistry) + { + const Runtime::AssetPtr cubeMesh = EnsureDefaultCubeMesh(); + + // WorldTransform's reflected constructor takes an FTransform, other argument shapes would not match it + const auto ground = inRegistry.Create(); + Common::FTransform groundTransform; + groundTransform.scale = Common::FVec3(10.0f, 10.0f, 0.2f); + groundTransform.translation = Common::FVec3(0.0f, 0.0f, -0.1f); + inRegistry.Emplace(ground, groundTransform); + auto& groundPrimitive = inRegistry.Emplace(ground); + groundPrimitive.mesh = cubeMesh; + + const auto cube = inRegistry.Create(); + Common::FTransform cubeTransform; + cubeTransform.translation = Common::FVec3(0.0f, 0.0f, 0.5f); + inRegistry.Emplace(cube, cubeTransform); + auto& cubePrimitive = inRegistry.Emplace(cube); + cubePrimitive.mesh = cubeMesh; + + const auto playerStart = inRegistry.Create(); + const Common::FTransform playerStartTransform = Common::FTransform::LookAt(Common::FVec3(-5.0f, -6.0f, 4.0f), Common::FVec3(0.0f, 0.0f, 0.5f)); + inRegistry.Emplace(playerStart, playerStartTransform); + inRegistry.Emplace(playerStart); + } +} + +namespace Editor { + SceneClient::SceneClient() + : levelUri(Internal::mainLevelUri) + , world("EditorWorld", this, Runtime::PlayType::editor) + , window(nullptr) + , renderSurface(nullptr) + , editorCamera(Runtime::entityNull) + , sceneHovered(false) + , sceneFocused(false) + , cameraLooking(false) + , cameraAnglesInitialized(false) + , cameraYaw(0.0f) + , cameraPitch(0.0f) + , pendingLookDeltaX(0.0f) + , pendingLookDeltaY(0.0f) + { + world.SetSystemGraph(Runtime::SystemGraphPresets::Default3DWorld()); + } + + SceneClient::~SceneClient() + { + EndCameraLook(); + if (!world.Stopped()) { + world.Stop(); + } + } + + Runtime::World& SceneClient::GetWorld() + { + return world; + } + + Runtime::RenderSurface* SceneClient::GetRenderSurface() + { + return renderSurface; + } + + void SceneClient::SetEditorWindow(EditorWindow& inWindow) + { + window = &inWindow; + } + + void SceneClient::SetRenderSurface(Runtime::RenderSurface* inRenderSurface) + { + renderSurface = inRenderSurface; + } + + void SceneClient::ResizeRenderSurface(uint32_t inWidth, uint32_t inHeight) + { + if (renderSurface == nullptr) { + return; + } + if (window != nullptr) { + window->WaitRenderingIdle(); + } + renderSurface->Resize(inWidth, inHeight); + } + + void SceneClient::OpenProjectLevel() + { + Assert(world.Stopped()); + if (Internal::AssetExists(levelUri)) { + const auto level = Runtime::AssetManager::Get().SyncLoad(levelUri, Mirror::Class::Get()); + world.LoadFrom(level); + } else { + Internal::AuthorDefaultLevelContent(world.GetRegistry()); + SaveLevel(); + } + CreateEditorCamera(); + world.Play(); + } + + void SceneClient::SaveLevel() + { + Runtime::AssetPtr level = new Runtime::Level(levelUri); + world.SaveTo(level); + Internal::SaveAsset(level); + } + + Runtime::Entity SceneClient::GetEditorCamera() const + { + return editorCamera; + } + + void SceneClient::TickEditorCamera(float inDeltaSeconds) + { + if (!world.Playing() || editorCamera == Runtime::entityNull) { + return; + } + auto& registry = world.GetRegistry(); + + if (!cameraAnglesInitialized) { + const auto forward = registry.Get(editorCamera).localToWorld.GetRotationMatrix().Col(0); + cameraYaw = std::atan2(-forward.y, forward.x); + const float maxPitch = Internal::maxCameraPitchDegrees * Internal::degToRad; + cameraPitch = std::clamp(std::asin(std::clamp(forward.z, -1.0f, 1.0f)), -maxPitch, maxPitch); + cameraAnglesInitialized = true; + } + + if (cameraLooking) { + cameraYaw -= pendingLookDeltaX * Internal::cameraLookSpeedDegrees * Internal::degToRad; + cameraPitch -= pendingLookDeltaY * Internal::cameraLookSpeedDegrees * Internal::degToRad; + const float maxPitch = Internal::maxCameraPitchDegrees * Internal::degToRad; + cameraPitch = std::clamp(cameraPitch, -maxPitch, maxPitch); + pendingLookDeltaX = 0.0f; + pendingLookDeltaY = 0.0f; + } + + const Common::FVec3 moveInput = cameraLooking ? CameraMoveInput() : Common::FVec3Consts::zero; + const bool moving = moveInput.x != 0.0f || moveInput.y != 0.0f || moveInput.z != 0.0f; + if (!moving && !cameraLooking) { + return; + } + + const Common::FQuat orientation = + Common::FQuat(Common::FVec3Consts::unitY, Common::FRadian(cameraPitch)) + * Common::FQuat(Common::FVec3Consts::unitZ, Common::FRadian(cameraYaw)); + const Common::FVec3 lookForward = orientation.RotateVector(Common::FVec3Consts::unitX); + const Common::FVec3 lookRight = orientation.RotateVector(Common::FVec3Consts::unitY); + Common::FVec3 moveForward(lookForward.x, lookForward.y, 0.0f); + Common::FVec3 moveRight(lookRight.x, lookRight.y, 0.0f); + moveForward.Normalize(); + moveRight.Normalize(); + + registry.Update(editorCamera, [&](Runtime::WorldTransform& transform) -> void { + const float moveDelta = Internal::cameraMoveSpeed * inDeltaSeconds; + transform.localToWorld.translation += moveForward * (moveInput.x * moveDelta); + transform.localToWorld.translation += moveRight * (moveInput.y * moveDelta); + transform.localToWorld.translation += Common::FVec3(0.0f, 0.0f, 1.0f) * (moveInput.z * moveDelta); + transform.localToWorld.rotation = orientation; + }); + } + + void SceneClient::SetSceneHovered(bool inHovered) + { + sceneHovered = inHovered; + } + + void SceneClient::SetSceneFocused(bool inFocused) + { + sceneFocused = inFocused; + if (!sceneFocused) { + pressedKeys.clear(); + } + } + + bool SceneClient::IsSceneHovered() const + { + return sceneHovered; + } + + bool SceneClient::IsCameraLooking() const + { + return cameraLooking; + } + + void SceneClient::OnKey(int inKey, bool inPressed) + { + if (inPressed) { + pressedKeys.insert(inKey); + } else { + pressedKeys.erase(inKey); + } + } + + void SceneClient::BeginCameraLook() + { + if (cameraLooking || !sceneHovered) { + return; + } + cameraLooking = true; + if (window != nullptr) { + glfwSetInputMode(window->GetNativeWindow(), GLFW_CURSOR, GLFW_CURSOR_DISABLED); + } + } + + void SceneClient::EndCameraLook() + { + if (!cameraLooking) { + return; + } + cameraLooking = false; + pendingLookDeltaX = 0.0f; + pendingLookDeltaY = 0.0f; + pressedKeys.clear(); + if (window != nullptr) { + glfwSetInputMode(window->GetNativeWindow(), GLFW_CURSOR, GLFW_CURSOR_NORMAL); + } + } + + void SceneClient::AddCameraLookDelta(float inDeltaX, float inDeltaY) + { + if (!cameraLooking) { + return; + } + pendingLookDeltaX += inDeltaX; + pendingLookDeltaY += inDeltaY; + } + + void SceneClient::CreateEditorCamera() + { + auto& registry = world.GetRegistry(); + editorCamera = registry.Create(); + registry.Emplace(editorCamera); + registry.Emplace(editorCamera); + // WorldTransform's reflected constructor takes an FTransform, other argument shapes would not match it + const Common::FTransform cameraTransform = Common::FTransform::LookAt(Common::FVec3(-5.0f, -6.0f, 4.0f), Common::FVec3(0.0f, 0.0f, 0.5f)); + registry.Emplace(editorCamera, cameraTransform); + } + + Common::FVec3 SceneClient::CameraMoveInput() const + { + Common::FVec3 result(0.0f, 0.0f, 0.0f); + if (pressedKeys.contains(GLFW_KEY_W)) { result.x += 1.0f; } + if (pressedKeys.contains(GLFW_KEY_S)) { result.x -= 1.0f; } + if (pressedKeys.contains(GLFW_KEY_D)) { result.y += 1.0f; } + if (pressedKeys.contains(GLFW_KEY_A)) { result.y -= 1.0f; } + if (pressedKeys.contains(GLFW_KEY_E)) { result.z += 1.0f; } + if (pressedKeys.contains(GLFW_KEY_Q)) { result.z -= 1.0f; } + if (result.Model() > 1.0f) { + result.Normalize(); + } + return result; + } +} diff --git a/Editor/Src/Utils/ImGuiCompatibility.cpp b/Editor/Src/Utils/ImGuiCompatibility.cpp new file mode 100644 index 000000000..ec2192fc5 --- /dev/null +++ b/Editor/Src/Utils/ImGuiCompatibility.cpp @@ -0,0 +1,99 @@ +#include +#include + +#include + +namespace Editor { + ImGuiKey ImGuiCompatibility::ToImGuiKey(int inGlfwKey) + { + switch (inGlfwKey) { + case GLFW_KEY_TAB: return ImGuiKey_Tab; + case GLFW_KEY_LEFT: return ImGuiKey_LeftArrow; + case GLFW_KEY_RIGHT: return ImGuiKey_RightArrow; + case GLFW_KEY_UP: return ImGuiKey_UpArrow; + case GLFW_KEY_DOWN: return ImGuiKey_DownArrow; + case GLFW_KEY_PAGE_UP: return ImGuiKey_PageUp; + case GLFW_KEY_PAGE_DOWN: return ImGuiKey_PageDown; + case GLFW_KEY_HOME: return ImGuiKey_Home; + case GLFW_KEY_END: return ImGuiKey_End; + case GLFW_KEY_INSERT: return ImGuiKey_Insert; + case GLFW_KEY_DELETE: return ImGuiKey_Delete; + case GLFW_KEY_BACKSPACE: return ImGuiKey_Backspace; + case GLFW_KEY_SPACE: return ImGuiKey_Space; + case GLFW_KEY_ENTER: return ImGuiKey_Enter; + case GLFW_KEY_ESCAPE: return ImGuiKey_Escape; + case GLFW_KEY_APOSTROPHE: return ImGuiKey_Apostrophe; + case GLFW_KEY_COMMA: return ImGuiKey_Comma; + case GLFW_KEY_MINUS: return ImGuiKey_Minus; + case GLFW_KEY_PERIOD: return ImGuiKey_Period; + case GLFW_KEY_SLASH: return ImGuiKey_Slash; + case GLFW_KEY_SEMICOLON: return ImGuiKey_Semicolon; + case GLFW_KEY_EQUAL: return ImGuiKey_Equal; + case GLFW_KEY_LEFT_BRACKET: return ImGuiKey_LeftBracket; + case GLFW_KEY_BACKSLASH: return ImGuiKey_Backslash; + case GLFW_KEY_RIGHT_BRACKET: return ImGuiKey_RightBracket; + case GLFW_KEY_GRAVE_ACCENT: return ImGuiKey_GraveAccent; + case GLFW_KEY_CAPS_LOCK: return ImGuiKey_CapsLock; + case GLFW_KEY_SCROLL_LOCK: return ImGuiKey_ScrollLock; + case GLFW_KEY_NUM_LOCK: return ImGuiKey_NumLock; + case GLFW_KEY_PRINT_SCREEN: return ImGuiKey_PrintScreen; + case GLFW_KEY_PAUSE: return ImGuiKey_Pause; + case GLFW_KEY_KP_0: return ImGuiKey_Keypad0; + case GLFW_KEY_KP_1: return ImGuiKey_Keypad1; + case GLFW_KEY_KP_2: return ImGuiKey_Keypad2; + case GLFW_KEY_KP_3: return ImGuiKey_Keypad3; + case GLFW_KEY_KP_4: return ImGuiKey_Keypad4; + case GLFW_KEY_KP_5: return ImGuiKey_Keypad5; + case GLFW_KEY_KP_6: return ImGuiKey_Keypad6; + case GLFW_KEY_KP_7: return ImGuiKey_Keypad7; + case GLFW_KEY_KP_8: return ImGuiKey_Keypad8; + case GLFW_KEY_KP_9: return ImGuiKey_Keypad9; + case GLFW_KEY_KP_DECIMAL: return ImGuiKey_KeypadDecimal; + case GLFW_KEY_KP_DIVIDE: return ImGuiKey_KeypadDivide; + case GLFW_KEY_KP_MULTIPLY: return ImGuiKey_KeypadMultiply; + case GLFW_KEY_KP_SUBTRACT: return ImGuiKey_KeypadSubtract; + case GLFW_KEY_KP_ADD: return ImGuiKey_KeypadAdd; + case GLFW_KEY_KP_ENTER: return ImGuiKey_KeypadEnter; + case GLFW_KEY_KP_EQUAL: return ImGuiKey_KeypadEqual; + case GLFW_KEY_LEFT_SHIFT: return ImGuiKey_LeftShift; + case GLFW_KEY_LEFT_CONTROL: return ImGuiKey_LeftCtrl; + case GLFW_KEY_LEFT_ALT: return ImGuiKey_LeftAlt; + case GLFW_KEY_LEFT_SUPER: return ImGuiKey_LeftSuper; + case GLFW_KEY_RIGHT_SHIFT: return ImGuiKey_RightShift; + case GLFW_KEY_RIGHT_CONTROL: return ImGuiKey_RightCtrl; + case GLFW_KEY_RIGHT_ALT: return ImGuiKey_RightAlt; + case GLFW_KEY_RIGHT_SUPER: return ImGuiKey_RightSuper; + case GLFW_KEY_MENU: return ImGuiKey_Menu; + default: + break; + } + if (inGlfwKey >= GLFW_KEY_0 && inGlfwKey <= GLFW_KEY_9) { + return static_cast(ImGuiKey_0 + (inGlfwKey - GLFW_KEY_0)); + } + if (inGlfwKey >= GLFW_KEY_A && inGlfwKey <= GLFW_KEY_Z) { + return static_cast(ImGuiKey_A + (inGlfwKey - GLFW_KEY_A)); + } + if (inGlfwKey >= GLFW_KEY_F1 && inGlfwKey <= GLFW_KEY_F24) { + return static_cast(ImGuiKey_F1 + (inGlfwKey - GLFW_KEY_F1)); + } + return ImGuiKey_None; + } + + std::optional ImGuiCompatibility::ToImGuiMouseButton(int inGlfwButton) + { + if (inGlfwButton < 0 || inGlfwButton >= ImGuiMouseButton_COUNT) { + return {}; + } + return inGlfwButton; + } + + void ImGuiCompatibility::UpdateKeyModifiers(int inGlfwMods) + { + ImGuiIO& io = ImGui::GetIO(); + io.AddKeyEvent(ImGuiMod_Ctrl, (inGlfwMods & GLFW_MOD_CONTROL) != 0); + io.AddKeyEvent(ImGuiMod_Shift, (inGlfwMods & GLFW_MOD_SHIFT) != 0); + io.AddKeyEvent(ImGuiMod_Alt, (inGlfwMods & GLFW_MOD_ALT) != 0); + io.AddKeyEvent(ImGuiMod_Super, (inGlfwMods & GLFW_MOD_SUPER) != 0); + } + +} diff --git a/Editor/Src/WebUIServer.cpp b/Editor/Src/WebUIServer.cpp deleted file mode 100644 index cd31f55c9..000000000 --- a/Editor/Src/WebUIServer.cpp +++ /dev/null @@ -1,97 +0,0 @@ -// -// Created by johnk on 2025/8/8. -// - -#include -#include - -#include -#include -#include -#include - -static Core::CmdlineArgValue caWebUIPort( - "webUIPort", "-webUIPort", 10907, - "WebUI port"); - -static Core::CmdlineArgValue caWebUIDev( - "webUIDev", "-webUIDev", false, - "Whether to enable hot reload for web UI"); - -static Core::CmdlineArgValue caWebUIDevServerPort( - "webUIDevServerPort", "-webUIDevServerPort", 5173, - "Port of web ui dev server, which works only when dev mode enabled"); - -static Core::CmdlineArgValue caWebUIDebug( - "webUIDebug", "-webUIDebug", false, - "Whether to enable web ui debug (you can attach debugger to qt web engine process)."); - -static Core::CmdlineArgValue caWebUIRemoteDebugPort( - "webUIRemoveDebugPort", "-webUIRemoveDebugPort", 5174, - "Port of web ui debug port, you can attach to the url printed in log to create debug process."); - -namespace Editor { - WebUIServer& WebUIServer::Get() - { - static WebUIServer webUIServer; - return webUIServer; - } - - WebUIServer::WebUIServer() = default; - - void WebUIServer::Start() - { - uint32_t serverPort; - std::string serverMode; - - if (caWebUIDev.GetValue()) { - serverPort = caWebUIDevServerPort.GetValue(); - serverMode = "development"; - - httplib::Client client(std::format("http://localhost:{}", serverPort)); - auto res = client.Get("/"); - AssertWithReason( - res->status == 200, - "did you forget to start dev server, just call Script/start_editor_web_dev_server.py manually"); - } else { - serverPort = caWebUIPort.GetValue(); - serverMode = "product"; - productServerThread = std::make_unique("WebUIServerThread", [this, serverPort]() -> void { - const auto webRoot = Core::Paths::ExecutablePath().Parent() / "Web"; - const auto indexHtmlFile = webRoot / "index.html"; - - productServer = Common::MakeUnique(); - productServer->set_mount_point("/", webRoot.Absolute().String()); - productServer->Get("/(.+)", [indexHtmlFile](const httplib::Request&, httplib::Response& res) { - res.set_file_content(indexHtmlFile.Absolute().String()); - }); - productServer->listen("localhost", static_cast(serverPort)); - }); - } - - baseUrl = std::format("http://localhost:{}", serverPort); - LogInfo(WebUI, "{} web ui server listening on {}", serverMode, baseUrl); - - if (caWebUIDebug.GetValue()) { - const auto flags = std::format("--remote-debugging-port={}", caWebUIRemoteDebugPort.GetValue()); - qputenv("QTWEBENGINE_CHROMIUM_FLAGS", QByteArrayView(flags.c_str(), static_cast(flags.length()))); - } - } - - void WebUIServer::Stop() - { - if (productServer.Valid()) { - productServer->stop(); - } - if (productServerThread.Valid()) { - productServerThread->Join(); - } - productServer.Reset(); - productServerThread.Reset(); - } - - const std::string& WebUIServer::BaseUrl() const - { - return baseUrl; - } -} // namespace Editor diff --git a/Editor/Src/Widget/Editor.cpp b/Editor/Src/Widget/Editor.cpp deleted file mode 100644 index e788102b4..000000000 --- a/Editor/Src/Widget/Editor.cpp +++ /dev/null @@ -1,10 +0,0 @@ -// -// Created by Kindem on 2025/3/22. -// - -#include -#include - -namespace Editor { - ExplosionEditor::ExplosionEditor() = default; -} // namespace Editor diff --git a/Editor/Src/Widget/GraphicsWidget.cpp b/Editor/Src/Widget/GraphicsWidget.cpp deleted file mode 100644 index e1a7820c9..000000000 --- a/Editor/Src/Widget/GraphicsWidget.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// -// Created by Kindem on 2025/3/16. -// - -#include -#include // NOLINT -#include - -namespace Editor { - GraphicsWidget::GraphicsWidget(QWidget* inParent) - : QWidget(inParent) - { - setAttribute(Qt::WA_NativeWindow); - setAttribute(Qt::WA_PaintOnScreen); - setAttribute(Qt::WA_NoSystemBackground); - - const Render::RenderModule& renderModule = Core::ModuleManager::Get().GetTyped("Render"); - device = renderModule.GetDevice(); - Assert(device != nullptr); - - surface = device->CreateSurface( - RHI::SurfaceCreateInfo() - .SetWindow(reinterpret_cast(winId()))); // NOLINT - } - - GraphicsWidget::~GraphicsWidget() = default; - - RHI::Device& GraphicsWidget::GetDevice() const - { - return *device; - } - - RHI::Surface& GraphicsWidget::GetSurface() const - { - return *surface; - } - - QPaintEngine* GraphicsWidget::paintEngine() const - { - return nullptr; - } - - void GraphicsWidget::WaitDeviceIdle() const - { - const Common::UniquePtr fence = device->CreateFence(false); - device->GetQueue(RHI::QueueType::graphics, 0)->Flush(fence.Get()); - fence->Wait(); - } -} // namespace Editor diff --git a/Editor/Src/Widget/InputWidgets.cpp b/Editor/Src/Widget/InputWidgets.cpp new file mode 100644 index 000000000..7e8cf9a10 --- /dev/null +++ b/Editor/Src/Widget/InputWidgets.cpp @@ -0,0 +1,201 @@ +#include + +#include +#include + +#include + +namespace Editor::Internal { + template + static bool RenderScalarValue(const std::string& inLabel, ImGuiDataType inDataType, T& inValue, float inSpeed) + { + return ImGui::DragScalar(inLabel.c_str(), inDataType, &inValue, inSpeed); + } + + template + static bool RenderUnsignedScalarValue(const std::string& inLabel, ImGuiDataType inDataType, T& inValue) + { + const T minValue = 0; + return ImGui::DragScalar(inLabel.c_str(), inDataType, &inValue, 1.0f, &minValue); + } +} + +namespace Editor { + bool InputWidget::Render(const std::string& inLabel, bool& inValue) + { + return ImGui::Checkbox(inLabel.c_str(), &inValue); + } + + bool InputWidget::Render(const std::string& inLabel, int8_t& inValue) + { + return Internal::RenderScalarValue(inLabel, ImGuiDataType_S8, inValue, 1.0f); + } + + bool InputWidget::Render(const std::string& inLabel, uint8_t& inValue) + { + return Internal::RenderUnsignedScalarValue(inLabel, ImGuiDataType_U8, inValue); + } + + bool InputWidget::Render(const std::string& inLabel, int16_t& inValue) + { + return Internal::RenderScalarValue(inLabel, ImGuiDataType_S16, inValue, 1.0f); + } + + bool InputWidget::Render(const std::string& inLabel, uint16_t& inValue) + { + return Internal::RenderUnsignedScalarValue(inLabel, ImGuiDataType_U16, inValue); + } + + bool InputWidget::Render(const std::string& inLabel, int32_t& inValue) + { + return Internal::RenderScalarValue(inLabel, ImGuiDataType_S32, inValue, 1.0f); + } + + bool InputWidget::Render(const std::string& inLabel, uint32_t& inValue) + { + return Internal::RenderUnsignedScalarValue(inLabel, ImGuiDataType_U32, inValue); + } + + bool InputWidget::Render(const std::string& inLabel, int64_t& inValue) + { + return Internal::RenderScalarValue(inLabel, ImGuiDataType_S64, inValue, 1.0f); + } + + bool InputWidget::Render(const std::string& inLabel, uint64_t& inValue) + { + return Internal::RenderUnsignedScalarValue(inLabel, ImGuiDataType_U64, inValue); + } + + bool InputWidget::Render(const std::string& inLabel, float& inValue) + { + return Internal::RenderScalarValue(inLabel, ImGuiDataType_Float, inValue, 0.05f); + } + + bool InputWidget::Render(const std::string& inLabel, double& inValue) + { + return Internal::RenderScalarValue(inLabel, ImGuiDataType_Double, inValue, 0.05f); + } + + bool InputWidget::Render(const std::string& inLabel, std::string& inValue) + { + return ImGui::InputText(inLabel.c_str(), &inValue); + } + + bool InputWidget::Render(const std::string& inLabel, Core::Uri& inValue) + { + std::string value = inValue.Str(); + if (!InputWidget::Render(inLabel, value)) { + return false; + } + inValue = value; + return true; + } + + bool InputWidget::Render(const std::string& inLabel, Common::FVec2& inValue) + { + float value[2] = { inValue.x, inValue.y }; + if (!ImGui::DragFloat2(inLabel.c_str(), value, 0.05f)) { + return false; + } + inValue = Common::FVec2(value[0], value[1]); + return true; + } + + bool InputWidget::Render(const std::string& inLabel, Common::FVec3& inValue) + { + float value[3] = { inValue.x, inValue.y, inValue.z }; + if (!ImGui::DragFloat3(inLabel.c_str(), value, 0.05f)) { + return false; + } + inValue = Common::FVec3(value[0], value[1], value[2]); + return true; + } + + bool InputWidget::Render(const std::string& inLabel, Common::FVec4& inValue) + { + float value[4] = { inValue.x, inValue.y, inValue.z, inValue.w }; + if (!ImGui::DragFloat4(inLabel.c_str(), value, 0.05f)) { + return false; + } + inValue = Common::FVec4(value[0], value[1], value[2], value[3]); + return true; + } + + bool InputWidget::Render(const std::string& inLabel, Common::FQuat& inValue) + { + float value[4] = { inValue.w, inValue.x, inValue.y, inValue.z }; + if (!ImGui::DragFloat4(inLabel.c_str(), value, 0.01f)) { + return false; + } + inValue = Common::FQuat(value[0], value[1], value[2], value[3]); + return true; + } + + bool InputWidget::Render(const std::string& inLabel, Common::FTransform& inValue) + { + bool changed = false; + if (ImGui::TreeNodeEx(inLabel.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { + changed |= InputWidget::Render("Scale", inValue.scale); + changed |= InputWidget::Render("Rotation", inValue.rotation); + changed |= InputWidget::Render("Translation", inValue.translation); + ImGui::TreePop(); + } + return changed; + } + + bool InputWidget::Render(const std::string& inLabel, Common::LinearColor& inValue) + { + float value[4] = { inValue.r, inValue.g, inValue.b, inValue.a }; + if (!ImGui::ColorEdit4(inLabel.c_str(), value)) { + return false; + } + inValue = Common::LinearColor(value[0], value[1], value[2], value[3]); + return true; + } + + bool InputWidget::Render(const std::string& inLabel, Common::Color& inValue) + { + float value[4] = { + static_cast(inValue.r) / 255.0f, + static_cast(inValue.g) / 255.0f, + static_cast(inValue.b) / 255.0f, + static_cast(inValue.a) / 255.0f + }; + if (!ImGui::ColorEdit4(inLabel.c_str(), value)) { + return false; + } + inValue = Common::Color( + static_cast(std::clamp(value[0], 0.0f, 1.0f) * 255.0f), + static_cast(std::clamp(value[1], 0.0f, 1.0f) * 255.0f), + static_cast(std::clamp(value[2], 0.0f, 1.0f) * 255.0f), + static_cast(std::clamp(value[3], 0.0f, 1.0f) * 255.0f)); + return true; + } + + bool RenderInputWidget(const std::string& inLabel, Mirror::Any inValue) + { + if (auto* value = inValue.TryAs()) { return InputWidget::Render(inLabel, *value); } + if (auto* value = inValue.TryAs()) { return InputWidget::Render(inLabel, *value); } + if (auto* value = inValue.TryAs()) { return InputWidget::Render(inLabel, *value); } + if (auto* value = inValue.TryAs()) { return InputWidget::Render(inLabel, *value); } + if (auto* value = inValue.TryAs()) { return InputWidget::Render(inLabel, *value); } + if (auto* value = inValue.TryAs()) { return InputWidget::Render(inLabel, *value); } + if (auto* value = inValue.TryAs()) { return InputWidget::Render(inLabel, *value); } + if (auto* value = inValue.TryAs()) { return InputWidget::Render(inLabel, *value); } + if (auto* value = inValue.TryAs()) { return InputWidget::Render(inLabel, *value); } + if (auto* value = inValue.TryAs()) { return InputWidget::Render(inLabel, *value); } + if (auto* value = inValue.TryAs()) { return InputWidget::Render(inLabel, *value); } + if (auto* value = inValue.TryAs()) { return InputWidget::Render(inLabel, *value); } + if (auto* value = inValue.TryAs()) { return InputWidget::Render(inLabel, *value); } + if (auto* value = inValue.TryAs()) { return InputWidget::Render(inLabel, *value); } + if (auto* value = inValue.TryAs()) { return InputWidget::Render(inLabel, *value); } + if (auto* value = inValue.TryAs()) { return InputWidget::Render(inLabel, *value); } + if (auto* value = inValue.TryAs()) { return InputWidget::Render(inLabel, *value); } + if (auto* value = inValue.TryAs()) { return InputWidget::Render(inLabel, *value); } + if (auto* value = inValue.TryAs()) { return InputWidget::Render(inLabel, *value); } + if (auto* value = inValue.TryAs()) { return InputWidget::Render(inLabel, *value); } + + ImGui::Text("%s: %s", inLabel.c_str(), inValue.ToString().c_str()); + return false; + } +} diff --git a/Editor/Src/Widget/ProjectHub.cpp b/Editor/Src/Widget/ProjectHub.cpp deleted file mode 100644 index 8bfbd3907..000000000 --- a/Editor/Src/Widget/ProjectHub.cpp +++ /dev/null @@ -1,169 +0,0 @@ -// -// Created by johnk on 2025/8/3. -// - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Editor::Internal { - constexpr std::string_view templateFileExtension = ".tpl"; - constexpr std::string_view cmakeMinVersion = "3.25"; - - static Common::Path StripTemplateExtension(const Common::Path& inRelativePath) - { - const std::string str = inRelativePath.String(); - return { str.substr(0, str.size() - templateFileExtension.size()) }; - } - - static QJsonValue ToJsonValue(const CreateProjectResult& inResult) - { - QJsonValue value; - QtJsonSerialize(value, inResult); - return value; - } - - static Common::Result RenderProjectTemplate( - const Common::Path& inTemplateDir, const Common::Path& inProjectDir, const std::string& inProjectName) - { - Common::TemplateEngine templateEngine; - templateEngine - .Set("projectName", inProjectName) - .Set("cmakeMinVersion", std::string(cmakeMinVersion)); - - Common::Result result = Common::Ok(); - inTemplateDir.TraverseRecurse([&](const Common::Path& inPath) -> bool { - if (inPath.IsDirectory()) { - return true; - } - - const Common::Path relativePath = inPath.Relative(inTemplateDir); - const std::string extension = inPath.Extension(); - const bool isTemplate = std::string_view(extension) == templateFileExtension; - const Common::Path dstPath = inProjectDir / (isTemplate ? StripTemplateExtension(relativePath) : relativePath); - dstPath.Parent().MakeDir(); - - if (!isTemplate) { - inPath.CopyTo(dstPath); - return true; - } - if (auto renderResult = templateEngine.RenderFileTo(inPath.String(), dstPath.String()); - renderResult.IsErr()) { - result = Common::Err(renderResult.Error()); - return false; - } - return true; - }); - return result; - } -} - -namespace Editor { - ProjectHubBackend::ProjectHubBackend(ProjectHub* parent) - : QObject(parent) - , recentProjectsFile(Core::Paths::EngineCacheDir() / "Editor" / "ProjectHub" / "RecentProjects.json") - , engineVersion(std::format("v{}.{}.{}", ENGINE_VERSION_MAJOR, ENGINE_VERSION_MINOR, ENGINE_VERSION_PATCH)) - { - const Common::Path projectTemplatesRoot = Core::Paths::EngineResDir() / "Editor" / "ProjectTemplates"; - (void) projectTemplatesRoot.Traverse([this](const Common::Path& inPath) -> bool { - if (inPath.IsFile()) { - return true; - } - projectTemplates.emplace_back(ProjectTemplateInfo { inPath.DirName(), inPath.String() }); - return true; - }); - - if (recentProjectsFile.Exists()) { - Common::JsonDeserializeFromFile(recentProjectsFile.String(), recentProjects); - } - } - - ProjectHubBackend::~ProjectHubBackend() - { - Common::JsonSerializeToFile(recentProjectsFile.String(), recentProjects); - } - - QJsonValue ProjectHubBackend::CreateProject(const QString& inName, const QString& inDirectory, const QString& inTemplatePath) - { - const std::string name = inName.toStdString(); - const std::string directory = inDirectory.toStdString(); - const std::string templatePath = inTemplatePath.toStdString(); - - if (name.empty() || directory.empty() || templatePath.empty()) { - return Internal::ToJsonValue({ .success = false, .error = "Project name, directory and template must not be empty.", .projectPath = {} }); - } - - const Common::Path templateDir(templatePath); - if (!templateDir.Exists() || !templateDir.IsDirectory()) { - return Internal::ToJsonValue({ .success = false, .error = std::format("Project template '{}' does not exist.", templatePath), .projectPath = {} }); - } - - const Common::Path projectDir = Common::Path(directory) / name; - if (projectDir.Exists()) { - return Internal::ToJsonValue({ .success = false, .error = std::format("Target directory '{}' already exists.", projectDir.String()), .projectPath = {} }); - } - - if (const auto result = Internal::RenderProjectTemplate(templateDir, projectDir, name); - result.IsErr()) { - return Internal::ToJsonValue({ .success = false, .error = result.Error(), .projectPath = {} }); - } - - recentProjects.emplace_back(RecentProjectInfo { name, projectDir.String() }); - Common::JsonSerializeToFile(recentProjectsFile.String(), recentProjects); - emit RecentProjectsChanged(); - - LogInfo(ProjectHub, "created project '{}' at '{}'", name, projectDir.String()); - return Internal::ToJsonValue({ .success = true, .error = {}, .projectPath = projectDir.String() }); - } - - void ProjectHubBackend::OpenProject(const QString& inProjectPath) // NOLINT - { - // TODO: launch the editor for the project at inProjectPath - LogInfo(ProjectHub, "open project '{}'", inProjectPath.toStdString()); - } - - QString ProjectHubBackend::BrowseDirectory() const // NOLINT - { - return QFileDialog::getExistingDirectory(nullptr, "Select Project Directory", QDir::rootPath()); - } - - QString ProjectHubBackend::GetEngineVersion() const - { - return QString::fromStdString(engineVersion); - } - - QJsonValue ProjectHubBackend::GetProjectTemplates() const - { - QJsonValue value; - QtJsonSerialize(value, projectTemplates); - return value; - } - - QJsonValue ProjectHubBackend::GetRecentProjects() const - { - QJsonValue value; - QtJsonSerialize(value, recentProjects); - return value; - } - - ProjectHub::ProjectHub(QWidget* inParent) - : WebWidget(inParent) - { - setFixedSize(800, 600); - Load("/project-hub"); - - backend = new ProjectHubBackend(this); - GetWebChannel()->registerObject("backend", backend); - } -} // namespace Editor diff --git a/Editor/Src/Widget/Prototype.cpp b/Editor/Src/Widget/Prototype.cpp deleted file mode 100644 index c202d5e0e..000000000 --- a/Editor/Src/Widget/Prototype.cpp +++ /dev/null @@ -1,324 +0,0 @@ -// -// Created by johnk on 2026/6/21. -// - -#include - -#include -#include - -#include -#include -#include -#include // NOLINT - -// In a unity build may be pulled in by an earlier translation unit after RHI's own headers were already -// included (and thus their `#undef CreateSemaphore` skipped via #pragma once), leaving the Win32 `CreateSemaphore` -// macro active. Drop it so the RHI::Device::CreateSemaphore() calls below resolve to the real method. -#undef CreateSemaphore - -namespace Editor::Internal { - constexpr float pi = 3.14159265358979323846f; - constexpr float twoPi = 2.0f * pi; - constexpr float degToRad = pi / 180.0f; - constexpr float radToDeg = 180.0f / pi; - constexpr float defaultRotationSpeedDegrees = 90.0f; - - struct PrototypeVertex { - Common::FVec3 position; - }; - - struct PrototypeVsUniform { - float rotation; - float padding0; - float padding1; - float padding2; - }; -} - -namespace Editor { - PrototypeTriangleWidget::PrototypeTriangleWidget(QWidget* inParent) - : GraphicsWidget(inParent) - , rotationSpeed(Internal::defaultRotationSpeedDegrees * Internal::degToRad) - , rotation(0.0f) - , lastFrameSeconds(-1.0) - , imageReadySemaphore(GetDevice().CreateSemaphore()) - , renderFinishedSemaphore(GetDevice().CreateSemaphore()) - , frameFence(GetDevice().CreateFence(true)) - , drawThread(Common::MakeUnique("PrototypeDrawThread")) - { - setMinimumSize(320, 240); - resize(640, 480); - RecreateSwapChain(width(), height()); - - Render::ShaderCompileOptions shaderCompileOptions; - shaderCompileOptions.byteCodeType = GetDevice().GetGpu().GetInstance().GetRHIType() == RHI::RHIType::directX12 ? Render::ShaderByteCodeType::dxil : Render::ShaderByteCodeType::spirv; - shaderCompileOptions.withDebugInfo = static_cast(BUILD_CONFIG_DEBUG); // NOLINT - - { - Render::ShaderCompileInput shaderCompileInput; - shaderCompileInput.source = Common::FileUtils::ReadTextFile("../Shader/Editor/Prototype.esl").Unwrap(); - shaderCompileInput.stage = RHI::ShaderStageBits::sVertex; - shaderCompileInput.entryPoint = "VSMain"; - shaderCompileInput.includeDirectories.emplace_back("../Shader/Explosion"); - vsCompileOutput = Render::ShaderCompiler::Get().Compile(shaderCompileInput, shaderCompileOptions).get(); - } - - { - Render::ShaderCompileInput shaderCompileInput; - shaderCompileInput.source = Common::FileUtils::ReadTextFile("../Shader/Editor/Prototype.esl").Unwrap(); - shaderCompileInput.stage = RHI::ShaderStageBits::sPixel; - shaderCompileInput.entryPoint = "PSMain"; - shaderCompileInput.includeDirectories.emplace_back("../Shader/Explosion"); - psCompileOutput = Render::ShaderCompiler::Get().Compile(shaderCompileInput, shaderCompileOptions).get(); - } - - vsModule = GetDevice().CreateShaderModule(RHI::ShaderModuleCreateInfo("VSMain", vsCompileOutput.byteCode)); - psModule = GetDevice().CreateShaderModule(RHI::ShaderModuleCreateInfo("PSMain", psCompileOutput.byteCode)); - - bindGroupLayout = GetDevice().CreateBindGroupLayout( - RHI::BindGroupLayoutCreateInfo(0) - .AddEntry(RHI::BindGroupLayoutEntry(vsCompileOutput.reflectionData.QueryResourceBindingChecked("vsUniform").second, RHI::ShaderStageBits::sVertex))); - - pipelineLayout = GetDevice().CreatePipelineLayout( - RHI::PipelineLayoutCreateInfo() - .AddBindGroupLayout(bindGroupLayout.Get())); - - pipeline = GetDevice().CreateRasterPipeline( - RHI::RasterPipelineCreateInfo(pipelineLayout.Get()) - .SetVertexShader(vsModule.Get()) - .SetPixelShader(psModule.Get()) - .SetVertexState( - RHI::VertexState() - .AddVertexBufferLayout( - RHI::VertexBufferLayout(RHI::VertexStepMode::perVertex, sizeof(Internal::PrototypeVertex)) - .AddAttribute(RHI::VertexAttribute(vsCompileOutput.reflectionData.QueryVertexBindingChecked("POSITION"), RHI::VertexFormat::float32X3, 0)))) - .SetFragmentState( - RHI::FragmentState() - .AddColorTarget(RHI::ColorTargetState(swapChainTextures[0]->GetCreateInfo().format, RHI::ColorWriteBits::all))) - .SetPrimitiveState(RHI::PrimitiveState(RHI::PrimitiveTopologyType::triangle, RHI::FillMode::solid, RHI::IndexFormat::uint16, RHI::FrontFace::ccw, RHI::CullMode::none))); - - { - const std::vector vertices = { - {{-.5f, -.5f, 0.f}}, - {{.5f, -.5f, 0.f}}, - {{0.f, .5f, 0.f}}, - }; - const auto bufferSize = vertices.size() * sizeof(Internal::PrototypeVertex); - - vertexBuffer = GetDevice().CreateBuffer( - RHI::BufferCreateInfo() - .SetSize(bufferSize) - .SetUsages(RHI::BufferUsageBits::vertex | RHI::BufferUsageBits::mapWrite | RHI::BufferUsageBits::copySrc) - .SetInitialState(RHI::BufferState::staging) - .SetDebugName("vertexBuffer")); - - auto* data = vertexBuffer->Map(RHI::MapMode::write, 0, bufferSize); - memcpy(data, vertices.data(), bufferSize); - vertexBuffer->Unmap(); - - vertexBufferView = vertexBuffer->CreateBufferView( - RHI::BufferViewCreateInfo() - .SetType(RHI::BufferViewType::vertex) - .SetSize(bufferSize) - .SetOffset(0) - .SetExtendVertex(sizeof(Internal::PrototypeVertex))); - } - - { - uniformBuffer = GetDevice().CreateBuffer( - RHI::BufferCreateInfo() - .SetSize(sizeof(Internal::PrototypeVsUniform)) - .SetUsages(RHI::BufferUsageBits::uniform | RHI::BufferUsageBits::mapWrite | RHI::BufferUsageBits::copySrc) - .SetInitialState(RHI::BufferState::staging) - .SetDebugName("vsUniform")); - - uniformBufferView = uniformBuffer->CreateBufferView( - RHI::BufferViewCreateInfo() - .SetType(RHI::BufferViewType::uniformBinding) - .SetSize(sizeof(Internal::PrototypeVsUniform)) - .SetOffset(0)); - } - - bindGroup = GetDevice().CreateBindGroup( - RHI::BindGroupCreateInfo(bindGroupLayout.Get()) - .AddEntry(RHI::BindGroupEntry(vsCompileOutput.reflectionData.QueryResourceBindingChecked("vsUniform").second, uniformBufferView.Get()))); - - commandBuffer = GetDevice().CreateCommandBuffer(); - - running = true; - DispatchFrame(); - } - - PrototypeTriangleWidget::~PrototypeTriangleWidget() - { - running = false; - drawThread.Reset(); - WaitDeviceIdle(); - } - - void PrototypeTriangleWidget::SetRotationSpeed(float inRadiansPerSecond) - { - rotationSpeed.store(inRadiansPerSecond); - } - - float PrototypeTriangleWidget::GetRotationSpeed() const - { - return rotationSpeed.load(); - } - - void PrototypeTriangleWidget::resizeEvent(QResizeEvent* event) - { - GraphicsWidget::resizeEvent(event); - - drawThread->EmplaceTask([this, size = event->size()]() -> void { - RecreateSwapChain(size.width(), size.height()); - }); - } - - void PrototypeTriangleWidget::RecreateSwapChain(uint32_t inWidth, uint32_t inHeight) - { - static std::vector formatQualifiers = { - RHI::PixelFormat::rgba8Unorm, - RHI::PixelFormat::bgra8Unorm - }; - - if (swapChain != nullptr) { - WaitDeviceIdle(); - swapChain.Reset(); - } - - std::optional pixelFormat = {}; - for (const auto format : formatQualifiers) { - if (device->CheckSwapChainFormatSupport(surface.Get(), format)) { - pixelFormat = format; - break; - } - } - Assert(pixelFormat.has_value()); - - swapChain = device->CreateSwapChain( - RHI::SwapChainCreateInfo() - .SetPresentQueue(device->GetQueue(RHI::QueueType::graphics, 0)) - .SetSurface(surface.Get()) - .SetTextureNum(2) - .SetFormat(pixelFormat.value()) - .SetWidth(inWidth) - .SetHeight(inHeight) - .SetPresentMode(RHI::PresentMode::immediately)); - - for (auto i = 0; i < swapChainTextureNum; i++) { - swapChainTextures[i] = swapChain->GetTexture(i); - swapChainTextureViews[i] = swapChainTextures[i]->CreateTextureView( - RHI::TextureViewCreateInfo() - .SetDimension(RHI::TextureViewDimension::tv2D) - .SetMipLevels(0, 1) - .SetArrayLayers(0, 1) - .SetAspect(RHI::TextureAspect::color) - .SetType(RHI::TextureViewType::colorAttachment)); - } - } - - void PrototypeTriangleWidget::DispatchFrame() - { - if (!running) { - return; - } - drawThread->EmplaceTask([this]() -> void { DrawFrame(); }); - } - - void PrototypeTriangleWidget::DrawFrame() - { - frameFence->Wait(); - frameFence->Reset(); - - const double currentTimeSeconds = Common::TimePoint::Now().ToSeconds(); - if (lastFrameSeconds >= 0.0) { - rotation += static_cast(currentTimeSeconds - lastFrameSeconds) * rotationSpeed.load(); - rotation = std::fmod(rotation, Internal::twoPi); - } - lastFrameSeconds = currentTimeSeconds; - - const Internal::PrototypeVsUniform uniform { rotation, 0.0f, 0.0f, 0.0f }; - auto* uniformData = uniformBuffer->Map(RHI::MapMode::write, 0, sizeof(Internal::PrototypeVsUniform)); - memcpy(uniformData, &uniform, sizeof(Internal::PrototypeVsUniform)); - uniformBuffer->Unmap(); - - const auto backTextureIndex = swapChain->AcquireBackTexture(imageReadySemaphore.Get()); - const Common::UniquePtr commandRecorder = commandBuffer->Begin(); - { - commandRecorder->ResourceBarrier(RHI::Barrier::Transition(swapChainTextures[backTextureIndex], RHI::TextureState::present, RHI::TextureState::renderTarget)); - const Common::UniquePtr rasterRecorder = commandRecorder->BeginRasterPass( - RHI::RasterPassBeginInfo() - .AddColorAttachment(RHI::ColorAttachment(swapChainTextureViews[backTextureIndex].Get(), RHI::LoadOp::clear, RHI::StoreOp::store, Common::LinearColorConsts::black))); - { - rasterRecorder->SetPipeline(pipeline.Get()); - rasterRecorder->SetBindGroup(0, bindGroup.Get()); - rasterRecorder->SetScissor(0, 0, width(), height()); - rasterRecorder->SetViewport(0, 0, static_cast(width()), static_cast(height()), 0, 1); - rasterRecorder->SetPrimitiveTopology(RHI::PrimitiveTopology::triangleList); - rasterRecorder->SetVertexBuffer(0, vertexBufferView.Get()); - rasterRecorder->Draw(3, 1, 0, 0); - } - rasterRecorder->EndPass(); - commandRecorder->ResourceBarrier(RHI::Barrier::Transition(swapChainTextures[backTextureIndex], RHI::TextureState::renderTarget, RHI::TextureState::present)); - } - commandRecorder->End(); - - GetDevice().GetQueue(RHI::QueueType::graphics, 0)->Submit( - commandBuffer.Get(), - RHI::QueueSubmitInfo() - .AddWaitSemaphore(imageReadySemaphore.Get()) - .AddSignalSemaphore(renderFinishedSemaphore.Get()) - .SetSignalFence(frameFence.Get())); - swapChain->Present(renderFinishedSemaphore.Get()); - - DispatchFrame(); - } - - PrototypeBackend::PrototypeBackend(PrototypeTriangleWidget* inTriangle, QObject* inParent) - : QObject(inParent) - , triangle(inTriangle) - { - } - - PrototypeBackend::~PrototypeBackend() = default; - - void PrototypeBackend::SetRotationSpeed(double inDegreesPerSecond) - { - triangle->SetRotationSpeed(static_cast(inDegreesPerSecond) * Internal::degToRad); - } - - double PrototypeBackend::GetRotationSpeed() const - { - return static_cast(triangle->GetRotationSpeed() * Internal::radToDeg); - } - - PrototypeWebWidget::PrototypeWebWidget(PrototypeTriangleWidget* inTriangle, QWidget* inParent) - : WebWidget(inParent) - { - backend = new PrototypeBackend(inTriangle, this); - GetWebChannel()->registerObject("backend", backend); - Load("/prototype"); - } - - PrototypeWebWidget::~PrototypeWebWidget() = default; - - PrototypePlayground::PrototypePlayground(QWidget* inParent) - : QWidget(inParent) - { - setWindowTitle("Explosion Prototype Playground"); - resize(1280, 720); - - triangle = new PrototypeTriangleWidget(this); - web = new PrototypeWebWidget(triangle, this); - - auto* layout = new QHBoxLayout(this); - layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(0); - layout->addWidget(triangle, 1); - layout->addWidget(web, 1); - } - - PrototypePlayground::~PrototypePlayground() = default; -} // namespace Editor diff --git a/Editor/Src/Widget/WebWidget.cpp b/Editor/Src/Widget/WebWidget.cpp deleted file mode 100644 index 5871b83e1..000000000 --- a/Editor/Src/Widget/WebWidget.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// -// Created by johnk on 2025/8/9. -// - -#include -#include -#include -#include -#include - -namespace Editor { - WebPage::WebPage(QWidget* inParent) - : QWebEnginePage(inParent) - { - } - - WebPage::~WebPage() = default; - - void WebPage::javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, const QString& message, int lineNumber, const QString& sourceID) - { - if (level == InfoMessageLevel) - { - LogInfo(WebUIJavaScript, "{}", message.toStdString()); - } - else if (level == WarningMessageLevel) - { - LogWarning(WebUIJavaScript, "{}", message.toStdString()); - } - else if (level == ErrorMessageLevel) - { - LogError(WebUIJavaScript, "{}", message.toStdString()); - } - } - - WebWidget::WebWidget(QWidget* inParent) - : QWebEngineView(inParent) - { - webChannel = new QWebChannel(this); - setPage(new WebPage(this)); - page()->setWebChannel(webChannel); - } - - WebWidget::~WebWidget() = default; - - void WebWidget::Load(const std::string& inUrl) - { - static Core::CmdlineArg& caWebUIPort = Core::Cli::Get().GetArg("webUIPort"); - - Assert(inUrl.starts_with("/")); - const auto& baseUrl = WebUIServer::Get().BaseUrl(); - const auto fullUrl = baseUrl + inUrl; - load(QUrl(fullUrl.c_str())); - } - - QWebChannel* WebWidget::GetWebChannel() const - { - return webChannel; - } -} // namespace Editor diff --git a/Editor/Web/.gitignore b/Editor/Web/.gitignore deleted file mode 100644 index c64d8f8f2..000000000 --- a/Editor/Web/.gitignore +++ /dev/null @@ -1,29 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - - -pnpm-lock.yaml -yarn.lock -package-lock.json -bun.lockb \ No newline at end of file diff --git a/Editor/Web/.npmrc b/Editor/Web/.npmrc deleted file mode 100644 index 1e54ebc8e..000000000 --- a/Editor/Web/.npmrc +++ /dev/null @@ -1 +0,0 @@ -package-lock=true \ No newline at end of file diff --git a/Editor/Web/LICENSE b/Editor/Web/LICENSE deleted file mode 100644 index d3006aadc..000000000 --- a/Editor/Web/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Next UI - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/Editor/Web/eslint.config.mjs b/Editor/Web/eslint.config.mjs deleted file mode 100644 index d2f5ad282..000000000 --- a/Editor/Web/eslint.config.mjs +++ /dev/null @@ -1,167 +0,0 @@ -import path from "node:path"; -import { fileURLToPath } from "node:url"; - -import { defineConfig, globalIgnores } from "eslint/config"; -import { fixupConfigRules, fixupPluginRules } from "@eslint/compat"; -import react from "eslint-plugin-react"; -import unusedImports from "eslint-plugin-unused-imports"; -import _import from "eslint-plugin-import"; -import typescriptEslint from "@typescript-eslint/eslint-plugin"; -import jsxA11Y from "eslint-plugin-jsx-a11y"; -import prettier from "eslint-plugin-prettier"; -import globals from "globals"; -import tsParser from "@typescript-eslint/parser"; -import js from "@eslint/js"; -import { FlatCompat } from "@eslint/eslintrc"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - allConfig: js.configs.all, -}); - -export default defineConfig([ - globalIgnores([ - ".now/*", - "**/*.css", - "**/.changeset", - "**/dist", - "esm/*", - "public/*", - "tests/*", - "scripts/*", - "**/*.config.js", - "**/.DS_Store", - "**/node_modules", - "**/coverage", - "**/.next", - "**/build", - "!**/.commitlintrc.cjs", - "!**/.lintstagedrc.cjs", - "!**/jest.config.js", - "!**/plopfile.js", - "!**/react-shim.js", - "!**/tsup.config.ts", - ]), - { - extends: fixupConfigRules( - compat.extends( - "plugin:react/recommended", - "plugin:prettier/recommended", - "plugin:react-hooks/recommended", - "plugin:jsx-a11y/recommended", - ), - ), - - plugins: { - react: fixupPluginRules(react), - "unused-imports": unusedImports, - import: fixupPluginRules(_import), - "@typescript-eslint": typescriptEslint, - "jsx-a11y": fixupPluginRules(jsxA11Y), - prettier: fixupPluginRules(prettier), - }, - - languageOptions: { - globals: { - ...Object.fromEntries( - Object.entries(globals.browser).map(([key]) => [key, "off"]), - ), - ...globals.node, - }, - - parser: tsParser, - ecmaVersion: 12, - sourceType: "module", - - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - }, - }, - - settings: { - react: { - version: "detect", - }, - }, - - - files: ["**/*.ts", "**/*.tsx"], - - rules: { - "no-console": "warn", - "react/prop-types": "off", - "react/jsx-uses-react": "off", - "react/react-in-jsx-scope": "off", - "react-hooks/exhaustive-deps": "off", - "jsx-a11y/click-events-have-key-events": "warn", - "jsx-a11y/interactive-supports-focus": "warn", - "prettier/prettier": [ - "warn", - { - "endOfLine": "auto", - "singleQuote": true, - "jsxSingleQuote": true, - "semi": true, - "printWidth": 200, - "tabWidth": 2, - "useTabs": false, - "objectWrap": "collapse", - "bracketSameLine": true, - "arrowParens": "always", - "singleAttributePerLine": false, - } - ], - "no-unused-vars": "off", - "unused-imports/no-unused-vars": "off", - "unused-imports/no-unused-imports": "warn", - "@typescript-eslint/no-unused-vars": [ - "warn", - { - args: "after-used", - ignoreRestSiblings: false, - argsIgnorePattern: "^_.*?$", - }, - ], - "import/order": [ - "warn", - { - groups: [ - "type", - "builtin", - "object", - "external", - "internal", - "parent", - "sibling", - "index", - ], - - pathGroups: [ - { - pattern: "~/**", - group: "external", - position: "after", - }, - ], - - "newlines-between": "never", - }, - ], - "react/self-closing-comp": "warn", - "react/jsx-sort-props": [ - "warn", - { - callbacksLast: true, - shorthandFirst: true, - noSortAlphabetically: false, - reservedFirst: true, - }, - ] - }, - }, -]); diff --git a/Editor/Web/favicon.ico b/Editor/Web/favicon.ico deleted file mode 100644 index e38d32ab7..000000000 Binary files a/Editor/Web/favicon.ico and /dev/null differ diff --git a/Editor/Web/index.html b/Editor/Web/index.html deleted file mode 100644 index 102806f02..000000000 --- a/Editor/Web/index.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - Vite + HeroUI - - - - - - - -
- - - diff --git a/Editor/Web/package.json b/Editor/Web/package.json deleted file mode 100644 index 560a7c3e6..000000000 --- a/Editor/Web/package.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "name": "vite-template", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "lint": "eslint --fix", - "preview": "vite preview" - }, - "dependencies": { - "@heroui/avatar": "^2.2.20", - "@heroui/breadcrumbs": "^2.2.20", - "@heroui/button": "^2.2.24", - "@heroui/chip": "^2.2.20", - "@heroui/code": "^2.2.18", - "@heroui/dropdown": "^2.3.24", - "@heroui/form": "^2.1.24", - "@heroui/input": "^2.4.25", - "@heroui/kbd": "^2.2.19", - "@heroui/link": "^2.2.21", - "@heroui/navbar": "^2.2.22", - "@heroui/react": "^2.8.2", - "@heroui/snippet": "^2.2.25", - "@heroui/switch": "^2.2.22", - "@heroui/system": "^2.4.20", - "@heroui/tabs": "^2.2.21", - "@heroui/theme": "^2.4.20", - "@heroui/use-theme": "2.1.10", - "@heroui/user": "^2.2.20", - "@react-aria/visually-hidden": "3.8.26", - "@react-types/shared": "3.31.0", - "@tailwindcss/postcss": "4.1.11", - "@tailwindcss/vite": "4.1.11", - "clsx": "2.1.1", - "framer-motion": "11.18.2", - "react": "18.3.1", - "react-dom": "18.3.1", - "react-router-dom": "6.23.0", - "tailwind-variants": "2.0.1", - "tailwindcss": "4.1.11" - }, - "devDependencies": { - "@eslint/compat": "1.2.8", - "@eslint/eslintrc": "3.3.1", - "@eslint/js": "9.25.1", - "@types/node": "20.5.7", - "@types/react": "18.3.3", - "@types/react-dom": "18.3.0", - "@typescript-eslint/eslint-plugin": "8.31.1", - "@typescript-eslint/parser": "8.31.1", - "@vitejs/plugin-react": "4.7.0", - "eslint": "9.25.1", - "eslint-config-prettier": "9.1.0", - "eslint-plugin-import": "2.31.0", - "eslint-plugin-jsx-a11y": "6.10.2", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "5.2.1", - "eslint-plugin-react": "7.37.5", - "eslint-plugin-react-hooks": "5.2.0", - "eslint-plugin-unused-imports": "4.1.4", - "globals": "16.0.0", - "postcss": "8.5.6", - "prettier": "3.5.3", - "typescript": "5.6.3", - "vite": "6.0.11", - "vite-tsconfig-paths": "5.1.4" - } -} diff --git a/Editor/Web/postcss.config.js b/Editor/Web/postcss.config.js deleted file mode 100644 index fe1e17ec2..000000000 --- a/Editor/Web/postcss.config.js +++ /dev/null @@ -1,5 +0,0 @@ -export default { - plugins: { - "@tailwindcss/postcss": {}, - }, -}; \ No newline at end of file diff --git a/Editor/Web/public/logo.png b/Editor/Web/public/logo.png deleted file mode 100644 index ffd4edeee..000000000 Binary files a/Editor/Web/public/logo.png and /dev/null differ diff --git a/Editor/Web/public/vite.svg b/Editor/Web/public/vite.svg deleted file mode 100644 index e7b8dfb1b..000000000 --- a/Editor/Web/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/Editor/Web/src/App.tsx b/Editor/Web/src/App.tsx deleted file mode 100644 index 6c3d38191..000000000 --- a/Editor/Web/src/App.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Route, Routes } from 'react-router-dom'; -import ProjectHubPage from '@/pages/project-hub'; -import PrototypePage from '@/pages/prototype'; - -function App() { - return ( - - } path='/project-hub' /> - } path='/prototype' /> - - ); -} - -export default App; diff --git a/Editor/Web/src/main.tsx b/Editor/Web/src/main.tsx deleted file mode 100644 index 70805e993..000000000 --- a/Editor/Web/src/main.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import { BrowserRouter } from 'react-router-dom'; -import { Provider } from './provider.tsx'; -import App from './App.tsx'; -import '@/styles/globals.css'; - -ReactDOM.createRoot(document.getElementById('root')!).render( - - - -
- -
-
-
-
, -); diff --git a/Editor/Web/src/pages/project-hub.tsx b/Editor/Web/src/pages/project-hub.tsx deleted file mode 100644 index f03f68fa3..000000000 --- a/Editor/Web/src/pages/project-hub.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import { useEffect, useState } from 'react'; -import { Tabs, Tab } from '@heroui/tabs'; -import { User } from '@heroui/user'; -import { Form } from '@heroui/form'; -import { Button, PressEvent } from '@heroui/button'; -import { Input } from '@heroui/input'; -import { Chip } from '@heroui/chip'; -import { Listbox, ListboxItem } from '@heroui/listbox'; -import { Avatar } from '@heroui/avatar'; -import { ScrollShadow } from '@heroui/scroll-shadow'; -import { Select, SelectItem, addToast } from '@heroui/react'; -import { QWebChannel } from '@/qwebchannel'; - -interface RecentProjectInfo { - name: string; - path: string; -} - -interface ProjectTemplateInfo { - name: string; - path: string; -} - -interface CreateProjectResult { - success: boolean; - error: string; - projectPath: string; -} - -export default function ProjectHubPage() { - const [engineVersion, setEngineVersion] = useState(''); - const [recentProjects, setRecentProjects] = useState(Array); - const [projectTemplates, setProjectTemplates] = useState(Array); - const [projectName, setProjectName] = useState(''); - const [projectPath, setProjectPath] = useState(''); - const [projectTemplate, setProjectTemplate] = useState('0'); - const [isCreating, setIsCreating] = useState(false); - - useEffect(() => { - new QWebChannel(window.qt.webChannelTransport, (channel: QWebChannel): void => { - window.backend = channel.objects.backend; - setEngineVersion(window.backend.engineVersion); - setRecentProjects(window.backend.recentProjects); - setProjectTemplates(window.backend.projectTemplates); - window.backend.RecentProjectsChanged.connect(() => { - setRecentProjects(window.backend.recentProjects); - }); - }); - }, []); - - function onCreateProject(): void { - const template = projectTemplates[parseInt(projectTemplate)]; - if (!projectName || !projectPath || !template) { - addToast({ title: 'Cannot create project', description: 'Please fill in the project name, path and template.', color: 'warning' }); - return; - } - - setIsCreating(true); - window.backend.CreateProject(projectName, projectPath, template.path, (result: CreateProjectResult) => { - setIsCreating(false); - if (!result.success) { - addToast({ title: 'Failed to create project', description: result.error, color: 'danger' }); - return; - } - addToast({ title: 'Project created', description: `${projectName} has been created.`, color: 'success' }); - setProjectName(''); - setProjectPath(''); - openProject(result.projectPath); - }); - } - - function onOpenProject(e: PressEvent): void { - const index = parseInt(e.target.getAttribute('data-key') as string); - openProject(recentProjects[index].path); - } - - function openProject(path: string): void { - window.backend.OpenProject(path); - } - - function onBrowseProjectPath(): void { - window.backend.BrowseDirectory((path: string) => { - if (path) { - setProjectPath(path); - } - }); - } - - return ( -
-
- - - {engineVersion} - -
- } - name='Explosion Game Engine' - /> -
- - - - - - {recentProjects.map((item, i) => ( - -
- -
- {item.name} - {item.path} -
-
-
- ))} -
-
-
- -
- -
- - -
- - -
-
-
- - ); -} diff --git a/Editor/Web/src/pages/prototype.tsx b/Editor/Web/src/pages/prototype.tsx deleted file mode 100644 index 968f78810..000000000 --- a/Editor/Web/src/pages/prototype.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { useEffect, useState } from 'react'; -import { Card, CardBody, CardHeader, Slider, Button, Chip } from '@heroui/react'; -import { QWebChannel } from '@/qwebchannel'; - -const minRotationSpeed = -360; -const maxRotationSpeed = 360; - -export default function PrototypePage() { - const [ready, setReady] = useState(false); - const [rotationSpeed, setRotationSpeed] = useState(90); - - useEffect(() => { - new QWebChannel(window.qt.webChannelTransport, (channel: QWebChannel): void => { - window.backend = channel.objects.backend; - setRotationSpeed(window.backend.rotationSpeed); - setReady(true); - }); - }, []); - - function onRotationSpeedChange(value: number | number[]): void { - const speed = Array.isArray(value) ? value[0] : value; - setRotationSpeed(speed); - if (ready) { - window.backend.SetRotationSpeed(speed); - } - } - - return ( -
-
- Prototype Playground - - native graphics + web - -
- - - - Triangle Rotation - - {Math.round(rotationSpeed)} °/s - - - - `${v} °/s`} - maxValue={maxRotationSpeed} - minValue={minRotationSpeed} - step={1} - value={rotationSpeed} - onChange={onRotationSpeedChange} - /> -
- - - -
-
-
- -

- The triangle on the left is rendered through the native graphics API (RHI). This panel is a web widget; both - live inside the same Qt layout. -

-
- ); -} diff --git a/Editor/Web/src/provider.tsx b/Editor/Web/src/provider.tsx deleted file mode 100644 index f9c2c0541..000000000 --- a/Editor/Web/src/provider.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import type { NavigateOptions } from 'react-router-dom'; -import { HeroUIProvider } from '@heroui/system'; -import { ToastProvider } from '@heroui/react'; -import { useHref, useNavigate } from 'react-router-dom'; - -declare module '@react-types/shared' { - interface RouterConfig { - routerOptions: NavigateOptions; - } -} - -export function Provider({ children }: { children: React.ReactNode }) { - const navigate = useNavigate(); - - return ( - - - {children} - - ); -} diff --git a/Editor/Web/src/qwebchannel.d.ts b/Editor/Web/src/qwebchannel.d.ts deleted file mode 100644 index 541c4dc34..000000000 --- a/Editor/Web/src/qwebchannel.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -export { QWebChannel } from './qwebchannel.js'; - -declare global { - interface Window { - qt: any; - backend: any; - showDirectoryPicker: any; - } -} diff --git a/Editor/Web/src/qwebchannel.js b/Editor/Web/src/qwebchannel.js deleted file mode 100644 index 13a7dab75..000000000 --- a/Editor/Web/src/qwebchannel.js +++ /dev/null @@ -1,455 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -"use strict"; - -var QWebChannelMessageTypes = { - signal: 1, - propertyUpdate: 2, - init: 3, - idle: 4, - debug: 5, - invokeMethod: 6, - connectToSignal: 7, - disconnectFromSignal: 8, - setProperty: 9, - response: 10, -}; - -var QWebChannel = function(transport, initCallback, converters) -{ - if (typeof transport !== "object" || typeof transport.send !== "function") { - console.error("The QWebChannel expects a transport object with a send function and onmessage callback property." + - " Given is: transport: " + typeof(transport) + ", transport.send: " + typeof(transport.send)); - return; - } - - var channel = this; - this.transport = transport; - - var converterRegistry = - { - Date : function(response) { - if (typeof response === "string" - && response.match( - /^-?\d+-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d*)?([-+\u2212](\d{2}):(\d{2})|Z)?$/)) { - var date = new Date(response); - if (!isNaN(date)) - return date; - } - return undefined; // Return undefined if current converter is not applicable - } - }; - - this.usedConverters = []; - - this.addConverter = function(converter) - { - if (typeof converter === "string") { - if (converterRegistry.hasOwnProperty(converter)) - this.usedConverters.push(converterRegistry[converter]); - else - console.error("Converter '" + converter + "' not found"); - } else if (typeof converter === "function") { - this.usedConverters.push(converter); - } else { - console.error("Invalid converter object type " + typeof converter); - } - } - - if (Array.isArray(converters)) { - for (const converter of converters) - this.addConverter(converter); - } else if (converters !== undefined) { - this.addConverter(converters); - } - - this.send = function(data) - { - if (typeof(data) !== "string") { - data = JSON.stringify(data); - } - channel.transport.send(data); - } - - this.transport.onmessage = function(message) - { - var data = message.data; - if (typeof data === "string") { - data = JSON.parse(data); - } - switch (data.type) { - case QWebChannelMessageTypes.signal: - channel.handleSignal(data); - break; - case QWebChannelMessageTypes.response: - channel.handleResponse(data); - break; - case QWebChannelMessageTypes.propertyUpdate: - channel.handlePropertyUpdate(data); - break; - default: - console.error("invalid message received:", message.data); - break; - } - } - - this.execCallbacks = {}; - this.execId = 0; - this.exec = function(data, callback) - { - if (!callback) { - // if no callback is given, send directly - channel.send(data); - return; - } - if (channel.execId === Number.MAX_VALUE) { - // wrap - channel.execId = Number.MIN_VALUE; - } - if (data.hasOwnProperty("id")) { - console.error("Cannot exec message with property id: " + JSON.stringify(data)); - return; - } - data.id = channel.execId++; - channel.execCallbacks[data.id] = callback; - channel.send(data); - }; - - this.objects = {}; - - this.handleSignal = function(message) - { - var object = channel.objects[message.object]; - if (object) { - object.signalEmitted(message.signal, message.args); - } else { - console.warn("Unhandled signal: " + message.object + "::" + message.signal); - } - } - - this.handleResponse = function(message) - { - if (!message.hasOwnProperty("id")) { - console.error("Invalid response message received: ", JSON.stringify(message)); - return; - } - channel.execCallbacks[message.id](message.data); - delete channel.execCallbacks[message.id]; - } - - this.handlePropertyUpdate = function(message) - { - message.data.forEach(data => { - var object = channel.objects[data.object]; - if (object) { - object.propertyUpdate(data.signals, data.properties); - } else { - console.warn("Unhandled property update: " + data.object + "::" + data.signal); - } - }); - channel.exec({type: QWebChannelMessageTypes.idle}); - } - - this.debug = function(message) - { - channel.send({type: QWebChannelMessageTypes.debug, data: message}); - }; - - channel.exec({type: QWebChannelMessageTypes.init}, function(data) { - for (const objectName of Object.keys(data)) { - new QObject(objectName, data[objectName], channel); - } - - // now unwrap properties, which might reference other registered objects - for (const objectName of Object.keys(channel.objects)) { - channel.objects[objectName].unwrapProperties(); - } - - if (initCallback) { - initCallback(channel); - } - channel.exec({type: QWebChannelMessageTypes.idle}); - }); -}; - -function QObject(name, data, webChannel) -{ - this.__id__ = name; - webChannel.objects[name] = this; - - // List of callbacks that get invoked upon signal emission - this.__objectSignals__ = {}; - - // Cache of all properties, updated when a notify signal is emitted - this.__propertyCache__ = {}; - - var object = this; - - // ---------------------------------------------------------------------- - - this.unwrapQObject = function(response) - { - for (const converter of webChannel.usedConverters) { - var result = converter(response); - if (result !== undefined) - return result; - } - - if (response instanceof Array) { - // support list of objects - return response.map(qobj => object.unwrapQObject(qobj)) - } - if (!(response instanceof Object)) - return response; - - if (!response["__QObject*__"] || response.id === undefined) { - var jObj = {}; - for (const propName of Object.keys(response)) { - jObj[propName] = object.unwrapQObject(response[propName]); - } - return jObj; - } - - var objectId = response.id; - if (webChannel.objects[objectId]) - return webChannel.objects[objectId]; - - if (!response.data) { - console.error("Cannot unwrap unknown QObject " + objectId + " without data."); - return; - } - - var qObject = new QObject( objectId, response.data, webChannel ); - qObject.destroyed.connect(function() { - if (webChannel.objects[objectId] === qObject) { - delete webChannel.objects[objectId]; - // reset the now deleted QObject to an empty {} object - // just assigning {} though would not have the desired effect, but the - // below also ensures all external references will see the empty map - // NOTE: this detour is necessary to workaround QTBUG-40021 - Object.keys(qObject).forEach(name => delete qObject[name]); - } - }); - // here we are already initialized, and thus must directly unwrap the properties - qObject.unwrapProperties(); - return qObject; - } - - this.unwrapProperties = function() - { - for (const propertyIdx of Object.keys(object.__propertyCache__)) { - object.__propertyCache__[propertyIdx] = object.unwrapQObject(object.__propertyCache__[propertyIdx]); - } - } - - function addSignal(signalData, isPropertyNotifySignal) - { - var signalName = signalData[0]; - var signalIndex = signalData[1]; - object[signalName] = { - connect: function(callback) { - if (typeof(callback) !== "function") { - console.error("Bad callback given to connect to signal " + signalName); - return; - } - - object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || []; - object.__objectSignals__[signalIndex].push(callback); - - // only required for "pure" signals, handled separately for properties in propertyUpdate - if (isPropertyNotifySignal) - return; - - // also note that we always get notified about the destroyed signal - if (signalName === "destroyed" || signalName === "destroyed()" || signalName === "destroyed(QObject*)") - return; - - // and otherwise we only need to be connected only once - if (object.__objectSignals__[signalIndex].length == 1) { - webChannel.exec({ - type: QWebChannelMessageTypes.connectToSignal, - object: object.__id__, - signal: signalIndex - }); - } - }, - disconnect: function(callback) { - if (typeof(callback) !== "function") { - console.error("Bad callback given to disconnect from signal " + signalName); - return; - } - // This makes a new list. This is important because it won't interfere with - // signal processing if a disconnection happens while emittig a signal - object.__objectSignals__[signalIndex] = (object.__objectSignals__[signalIndex] || []).filter(function(c) { - return c != callback; - }); - if (!isPropertyNotifySignal && object.__objectSignals__[signalIndex].length === 0) { - // only required for "pure" signals, handled separately for properties in propertyUpdate - webChannel.exec({ - type: QWebChannelMessageTypes.disconnectFromSignal, - object: object.__id__, - signal: signalIndex - }); - } - } - }; - } - - /** - * Invokes all callbacks for the given signalname. Also works for property notify callbacks. - */ - function invokeSignalCallbacks(signalName, signalArgs) - { - var connections = object.__objectSignals__[signalName]; - if (connections) { - connections.forEach(function(callback) { - callback.apply(callback, signalArgs); - }); - } - } - - this.propertyUpdate = function(signals, propertyMap) - { - // update property cache - for (const propertyIndex of Object.keys(propertyMap)) { - var propertyValue = propertyMap[propertyIndex]; - object.__propertyCache__[propertyIndex] = this.unwrapQObject(propertyValue); - } - - for (const signalName of Object.keys(signals)) { - // Invoke all callbacks, as signalEmitted() does not. This ensures the - // property cache is updated before the callbacks are invoked. - invokeSignalCallbacks(signalName, signals[signalName]); - } - } - - this.signalEmitted = function(signalName, signalArgs) - { - invokeSignalCallbacks(signalName, this.unwrapQObject(signalArgs)); - } - - function addMethod(methodData) - { - var methodName = methodData[0]; - var methodIdx = methodData[1]; - - // Fully specified methods are invoked by id, others by name for host-side overload resolution - var invokedMethod = methodName[methodName.length - 1] === ')' ? methodIdx : methodName - - object[methodName] = function() { - var args = []; - var callback; - var errCallback; - for (var i = 0; i < arguments.length; ++i) { - var argument = arguments[i]; - if (typeof argument === "function") - callback = argument; - else - args.push(argument); - } - - var result; - // during test, webChannel.exec synchronously calls the callback - // therefore, the promise must be constucted before calling - // webChannel.exec to ensure the callback is set up - if (!callback && (typeof(Promise) === 'function')) { - result = new Promise(function(resolve, reject) { - callback = resolve; - errCallback = reject; - }); - } - - webChannel.exec({ - "type": QWebChannelMessageTypes.invokeMethod, - "object": object.__id__, - "method": invokedMethod, - "args": args - }, function(response) { - if (response !== undefined) { - var result = object.unwrapQObject(response); - if (callback) { - (callback)(result); - } - } else if (errCallback) { - (errCallback)(); - } - }); - - return result; - }; - } - - function bindGetterSetter(propertyInfo) - { - var propertyIndex = propertyInfo[0]; - var propertyName = propertyInfo[1]; - var notifySignalData = propertyInfo[2]; - // initialize property cache with current value - // NOTE: if this is an object, it is not directly unwrapped as it might - // reference other QObject that we do not know yet - object.__propertyCache__[propertyIndex] = propertyInfo[3]; - - if (notifySignalData) { - if (notifySignalData[0] === 1) { - // signal name is optimized away, reconstruct the actual name - notifySignalData[0] = propertyName + "Changed"; - } - addSignal(notifySignalData, true); - } - - Object.defineProperty(object, propertyName, { - configurable: true, - get: function () { - var propertyValue = object.__propertyCache__[propertyIndex]; - if (propertyValue === undefined) { - // This shouldn't happen - console.warn("Undefined value in property cache for property \"" + propertyName + "\" in object " + object.__id__); - } - - return propertyValue; - }, - set: function(value) { - if (value === undefined) { - console.warn("Property setter for " + propertyName + " called with undefined value!"); - return; - } - object.__propertyCache__[propertyIndex] = value; - var valueToSend = value; - webChannel.exec({ - "type": QWebChannelMessageTypes.setProperty, - "object": object.__id__, - "property": propertyIndex, - "value": valueToSend - }); - } - }); - - } - - // ---------------------------------------------------------------------- - - data.methods.forEach(addMethod); - - data.properties.forEach(bindGetterSetter); - - data.signals.forEach(function(signal) { addSignal(signal, false); }); - - Object.assign(object, data.enums); -} - -QObject.prototype.toJSON = function() { - if (this.__id__ === undefined) return {}; - return { - id: this.__id__, - "__QObject*__": true - }; -}; - -//required for use with nodejs -//++[kindem] use ES6 export -export { - QWebChannel -} -//--[kindem] diff --git a/Editor/Web/src/styles/globals.css b/Editor/Web/src/styles/globals.css deleted file mode 100644 index e012273ba..000000000 --- a/Editor/Web/src/styles/globals.css +++ /dev/null @@ -1,3 +0,0 @@ -@import "tailwindcss"; - -@config "../../tailwind.config.js" \ No newline at end of file diff --git a/Editor/Web/src/vite-env.d.ts b/Editor/Web/src/vite-env.d.ts deleted file mode 100644 index 11f02fe2a..000000000 --- a/Editor/Web/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/Editor/Web/tailwind.config.js b/Editor/Web/tailwind.config.js deleted file mode 100644 index db470c45c..000000000 --- a/Editor/Web/tailwind.config.js +++ /dev/null @@ -1,17 +0,0 @@ -import {heroui} from "@heroui/theme" - -/** @type {import('tailwindcss').Config} */ -export default { - content: [ - "./index.html", - './src/layouts/**/*.{js,ts,jsx,tsx,mdx}', - './src/pages/**/*.{js,ts,jsx,tsx,mdx}', - './src/components/**/*.{js,ts,jsx,tsx,mdx}', - "./node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}", - ], - theme: { - extend: {}, - }, - darkMode: "class", - plugins: [heroui()], -} diff --git a/Editor/Web/tsconfig.json b/Editor/Web/tsconfig.json deleted file mode 100644 index 456359a35..000000000 --- a/Editor/Web/tsconfig.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - "paths": { - "@/*": ["./src/*"] - }, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"], - "references": [{ "path": "./tsconfig.node.json" }] -} diff --git a/Editor/Web/tsconfig.node.json b/Editor/Web/tsconfig.node.json deleted file mode 100644 index 97ede7ee6..000000000 --- a/Editor/Web/tsconfig.node.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "composite": true, - "skipLibCheck": true, - "module": "ESNext", - "moduleResolution": "bundler", - "allowSyntheticDefaultImports": true, - "strict": true - }, - "include": ["vite.config.ts"] -} diff --git a/Editor/Web/vercel.json b/Editor/Web/vercel.json deleted file mode 100644 index 1db5d80ea..000000000 --- a/Editor/Web/vercel.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "rewrites": [ - { "source": "/(.*)", "destination": "/" } - ] -} \ No newline at end of file diff --git a/Editor/Web/vite.config.ts b/Editor/Web/vite.config.ts deleted file mode 100644 index 655ffd7d4..000000000 --- a/Editor/Web/vite.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react"; -import tsconfigPaths from "vite-tsconfig-paths"; -import tailwindcss from "@tailwindcss/vite"; - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [react(), tsconfigPaths(), tailwindcss()], -}); diff --git a/Engine/Shader/BasePassPS.esl b/Engine/Shader/BasePassPS.esl index e69de29bb..1c6a17afd 100644 --- a/Engine/Shader/BasePassPS.esl +++ b/Engine/Shader/BasePassPS.esl @@ -0,0 +1,17 @@ +#include + +VkBinding(1, 0) cbuffer materialUniform : register(b0) { + float4 baseColor; +}; + +#include + +struct FragmentInput { + float4 position : SV_POSITION; + float2 uv0 : TEXCOORD; +}; + +float4 PSMain(FragmentInput input) : SV_TARGET +{ + return GetBaseColor(); +} diff --git a/Engine/Shader/BasePassVS.esl b/Engine/Shader/BasePassVS.esl index e69de29bb..8189d167e 100644 --- a/Engine/Shader/BasePassVS.esl +++ b/Engine/Shader/BasePassVS.esl @@ -0,0 +1,25 @@ +#include +#include + +VkBinding(0, 0) cbuffer vsUniform : register(b0) { + row_major float4x4 localToWorld; + row_major float4x4 worldToClip; +}; + +struct FragmentInput { + float4 position : SV_POSITION; + float2 uv0 : TEXCOORD; +}; + +FragmentInput VSMain(VertexFactoryInput vfInput) +{ + const float4 worldPosition = mul(localToWorld, float4(GetLocalPosition(vfInput), 1.0f)); + + FragmentInput output; + output.position = mul(worldToClip, worldPosition); +#if VULKAN + output.position.y = - output.position.y; +#endif + output.uv0 = GetUv0(vfInput); + return output; +} diff --git a/Engine/Shader/VertexFactory/StaticMesh/VertexFactory.esh b/Engine/Shader/VertexFactory/StaticMesh/VertexFactory.esh new file mode 100644 index 000000000..dcadaf34f --- /dev/null +++ b/Engine/Shader/VertexFactory/StaticMesh/VertexFactory.esh @@ -0,0 +1,17 @@ +#include + +// bare semantics (implicit index 0) keep the dxil and spirv reflection keys identical +struct VertexFactoryInput { + VkLocation(0) float3 position : POSITION; + VkLocation(1) float2 uv0 : TEXCOORD; +}; + +float3 GetLocalPosition(VertexFactoryInput input) +{ + return input.position; +} + +float2 GetUv0(VertexFactoryInput input) +{ + return input.uv0; +} diff --git a/Engine/Source/Common/Include/Common/Math/Transform.h b/Engine/Source/Common/Include/Common/Math/Transform.h index d3fb18e4c..de2884ccd 100644 --- a/Engine/Source/Common/Include/Common/Math/Transform.h +++ b/Engine/Source/Common/Include/Common/Math/Transform.h @@ -296,22 +296,17 @@ namespace Common { template Transform& Transform::UpdateRotation(const Vec& forward, const Vec& side, const Vec& up) { - // Transform of an object(camera) is the inverse of transform represented in lookAtMatrix - // Mat lookAtMat { - // s.x, s.y, s.z, -s.Dot(inPosition), - // u.x, u.y, u.z, -u.Dot(inPosition), - // f.x, f.y. f.z, -f.Dot(inPosition), - // 0, 0, 0, 1 - // }; - - // Rotaion Mat is orthogonal, its inverse equals to its transpose - // So, we get quaternion from the transposed rotation part of lookAtMatrix + // the resulting rotation must map the local axes onto the given frame following the engine convention + // (local +x -> forward, +y -> side, +z -> up), i.e. GetRotationMatrix() columns must be [forward, side, up]; + // engine quaternions apply as the transpose of the textbook rotation matrix (see + // Quaternion::GetRotationMatrix), so the textbook extraction below runs on the transposed target, whose + // rows are forward/side/up // Algorithm: https://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/ Mat rotMat { - side.x, up.x, forward.x, - side.y, up.y, forward.y, - side.z, up.z, forward.z + forward.x, forward.y, forward.z, + side.x, side.y, side.z, + up.x, up.y, up.z }; T trace = rotMat.At(0, 0) + rotMat.At(1, 1) + rotMat.At(2, 2); diff --git a/Engine/Source/Common/Test/MathTest.cpp b/Engine/Source/Common/Test/MathTest.cpp index 55089f96c..093a8cd54 100644 --- a/Engine/Source/Common/Test/MathTest.cpp +++ b/Engine/Source/Common/Test/MathTest.cpp @@ -873,6 +873,8 @@ TEST(MathTest, TransformLookAtTest) FTransform v0; v0.MoveAndLookTo(FVec3(1, 2, 3), FVec3(4, 2, 3)); ASSERT_TRUE(v0.translation == FVec3(1, 2, 3)); + // +x is the entity forward axis, so looking straight down +x is the identity orientation + ASSERT_TRUE(v0.rotation == FQuatConsts::identity); const FTransform v1 = FTransform::LookAt(FVec3(1, 2, 3), FVec3(4, 2, 3)); ASSERT_TRUE(v0 == v1); @@ -880,7 +882,15 @@ TEST(MathTest, TransformLookAtTest) FTransform v2(FQuatConsts::identity, FVec3(0, 0, 0)); v2.LookTo(FVec3(1, 0, 0)); ASSERT_TRUE(v2.translation == FVec3(0, 0, 0)); - ASSERT_TRUE(v2.rotation == FQuat(0.5f, 0.5f, 0.5f, 0.5f)); + ASSERT_TRUE(v2.rotation == FQuatConsts::identity); + + // a yawed look direction must keep the local up axis on the world up: look-at never introduces roll + FTransform v3; + v3.LookTo(FVec3(1, 1, 0)); + const FMat4x4 r3 = v3.GetRotationMatrix(); + const float invSqrt2 = std::sqrt(0.5f); + ASSERT_TRUE((r3 * FVec4(1, 0, 0, 0)) == FVec4(invSqrt2, invSqrt2, 0, 0)); + ASSERT_TRUE((r3 * FVec4(0, 0, 1, 0)) == FVec4(0, 0, 1, 0)); } TEST(MathTest, TransformCastToTest) @@ -1038,9 +1048,11 @@ TEST(MathTest, ViewMatrixTest) ASSERT_NEAR(camInView.y, 0.0f, 1e-4f); ASSERT_NEAR(camInView.z, 0.0f, 1e-4f); + // the looked-at target must sit straight ahead on the view space depth axis const FVec4 targetInView = vm1 * FVec4(0, 0, 0, 1); - const FVec3 targetXyz = targetInView.SubVec<0, 1, 2>(); - ASSERT_NEAR(targetXyz.Model(), std::sqrt(50.0f), 1e-4f); + ASSERT_NEAR(targetInView.x, 0.0f, 1e-4f); + ASSERT_NEAR(targetInView.y, 0.0f, 1e-4f); + ASSERT_NEAR(targetInView.z, std::sqrt(50.0f), 1e-4f); } // ==================================== Projection ==================================== diff --git a/Engine/Source/Core/Include/Core/Log.h b/Engine/Source/Core/Include/Core/Log.h index 8e94e24d9..b8d2801f8 100644 --- a/Engine/Source/Core/Include/Core/Log.h +++ b/Engine/Source/Core/Include/Core/Log.h @@ -19,10 +19,25 @@ #define LogError(tag, ...) Core::Logger::Get().Log(#tag, Core::LogLevel::error, std::format(__VA_ARGS__)) namespace Core { - class LogStream { + enum class LogLevel : uint8_t { + verbose, + info, + warning, + error, + max + }; + + struct LogEntry { + std::string time; + std::string tag; + LogLevel level; + std::string content; + }; + + class CORE_API LogStream { public: virtual ~LogStream() = default; - virtual void Write(const std::string& inString) = 0; + virtual void Write(const LogEntry& inEntry) = 0; virtual void Flush() = 0; }; @@ -34,7 +49,7 @@ namespace Core { NonCopyable(COutLogStream); NonMovable(COutLogStream); - void Write(const std::string& inString) override; + void Write(const LogEntry& inEntry) override; void Flush() override; }; @@ -46,21 +61,13 @@ namespace Core { NonCopyable(FileLogStream) NonMovable(FileLogStream) - void Write(const std::string& inString) override; + void Write(const LogEntry& inEntry) override; void Flush() override; private: std::ofstream file; }; - enum class LogLevel : uint8_t { - verbose, - info, - warning, - error, - max - }; - class CORE_API Logger { public: static Logger& Get(); @@ -76,7 +83,7 @@ namespace Core { private: Logger(); - void LogInternal(const std::string& inString); + void LogInternal(const LogEntry& inEntry); float lastFlushTimeSec; std::vector> streams; diff --git a/Engine/Source/Core/Src/Log.cpp b/Engine/Source/Core/Src/Log.cpp index 84b63b4d0..6d397c668 100644 --- a/Engine/Source/Core/Src/Log.cpp +++ b/Engine/Source/Core/Src/Log.cpp @@ -6,6 +6,27 @@ #include #include +namespace Core::Internal { + static std::string FormatLogEntry(const LogEntry& inEntry) + { + static std::unordered_map logLevelStringMap = { + { LogLevel::verbose, "Verbose" }, + { LogLevel::info, "Info" }, + { LogLevel::warning, "Warning" }, + { LogLevel::error, "Error" } + }; + + static std::unordered_map logLevelColorStr = { + { LogLevel::verbose, "" }, + { LogLevel::info, "" }, + { LogLevel::warning, "\033[33m" }, + { LogLevel::error, "\033[31m" } + }; + + return std::format("{}[{}][{}][{}] {}\033[0m", logLevelColorStr.at(inEntry.level), inEntry.time, inEntry.tag, logLevelStringMap.at(inEntry.level), inEntry.content); + } +} + namespace Core { COutLogStream::COutLogStream() = default; @@ -14,9 +35,9 @@ namespace Core { Flush(); } - void COutLogStream::Write(const std::string& inString) + void COutLogStream::Write(const LogEntry& inEntry) { - std::cout << inString << Common::newline; + std::cout << Internal::FormatLogEntry(inEntry) << Common::newline; } void COutLogStream::Flush() @@ -38,9 +59,9 @@ namespace Core { Flush(); } - void FileLogStream::Write(const std::string& inString) + void FileLogStream::Write(const LogEntry& inEntry) { - file << inString << Common::newline; + file << Internal::FormatLogEntry(inEntry) << Common::newline; } void FileLogStream::Flush() @@ -61,22 +82,12 @@ namespace Core { void Logger::Log(const std::string& inTag, LogLevel inLevel, const std::string& inContent) { - static std::unordered_map LogLevelStringMap = { - { LogLevel::verbose, "Verbose" }, - { LogLevel::info, "Info" }, - { LogLevel::warning, "Warning" }, - { LogLevel::error, "Error" } - }; - - static std::unordered_map LogLevelColorStr = { - { LogLevel::verbose, "" }, - { LogLevel::info, "" }, - { LogLevel::warning, "\033[33m" }, - { LogLevel::error, "\033[31m" } - }; - - const auto time = Common::AccurateTime(Common::TimePoint::Now()); - LogInternal(std::format("{}[{}][{}][{}] {}\033[0m", LogLevelColorStr.at(inLevel), time.ToString("hh-mm-ss:mss"), inTag, LogLevelStringMap.at(inLevel), inContent)); + LogEntry entry; + entry.time = Common::AccurateTime(Common::TimePoint::Now()).ToString("hh-mm-ss:mss"); + entry.tag = inTag; + entry.level = inLevel; + entry.content = inContent; + LogInternal(entry); } void Logger::Attach(Common::UniquePtr&& inStream) @@ -97,7 +108,7 @@ namespace Core { Attach(new COutLogStream()); } - void Logger::LogInternal(const std::string& inString) // NOLINT + void Logger::LogInternal(const LogEntry& inEntry) // NOLINT { #if BUILD_CONFIG_DEBUG const bool needFlush = true; // NOLINT @@ -108,7 +119,7 @@ namespace Core { #endif for (const auto& stream : streams) { - stream->Write(inString); + stream->Write(inEntry); if (needFlush) { stream->Flush(); } diff --git a/Engine/Source/Launch/Include/Launch/GameApplication.h b/Engine/Source/Launch/Include/Launch/GameApplication.h index 3a749ea49..37e5b8600 100644 --- a/Engine/Source/Launch/Include/Launch/GameApplication.h +++ b/Engine/Source/Launch/Include/Launch/GameApplication.h @@ -5,7 +5,8 @@ #pragma once #include -#include +#include +#include #include #include @@ -22,7 +23,8 @@ namespace Launch { double lastFrameTimeSeconds; double thisFrameTimeSeconds; float deltaTimeSeconds; - Common::UniquePtr viewport; + Common::UniquePtr window; + Common::UniquePtr client; Runtime::Engine* engine; Runtime::GameModule* gameModule; }; diff --git a/Engine/Source/Launch/Include/Launch/GameClient.h b/Engine/Source/Launch/Include/Launch/GameClient.h index 76829934b..e21b08d65 100644 --- a/Engine/Source/Launch/Include/Launch/GameClient.h +++ b/Engine/Source/Launch/Include/Launch/GameClient.h @@ -8,18 +8,18 @@ #include namespace Launch { - class GameViewport; + class GameWindow; class GameClient final : public Runtime::Client { public: - explicit GameClient(GameViewport& inViewport); + explicit GameClient(GameWindow& inWindow); ~GameClient() override; - Runtime::Viewport& GetViewport() override; Runtime::World& GetWorld() override; + Runtime::RenderSurface* GetRenderSurface() override; private: - GameViewport& viewport; + GameWindow& window; Runtime::World world; }; } diff --git a/Engine/Source/Launch/Include/Launch/GameViewport.h b/Engine/Source/Launch/Include/Launch/GameViewport.h deleted file mode 100644 index c053993b8..000000000 --- a/Engine/Source/Launch/Include/Launch/GameViewport.h +++ /dev/null @@ -1,53 +0,0 @@ -// -// Created by johnk on 2025/2/19. -// - -#pragma once - -#include -#if PLATFORM_WINDOWS -#define GLFW_EXPOSE_NATIVE_WIN32 -#elif PLATFORM_MACOS -#define GLFW_EXPOSE_NATIVE_COCOA -#endif -#include - -#include -#include -#include -#include - -namespace Launch { - struct GameViewportDesc { - std::string title; - uint32_t width; - uint32_t height; - }; - - class GameViewport final : public Runtime::Viewport { - public: - explicit GameViewport(const GameViewportDesc& inDesc); - ~GameViewport() override; - - Runtime::Client& GetClient() override; - Runtime::PresentInfo GetNextPresentInfo() override; - uint32_t GetWidth() const override; - uint32_t GetHeight() const override; - void Resize(uint32_t inWidth, uint32_t inHeight) override; - - bool ShouldClose() const; - void PollEvents() const; - void RecreateSwapChain(uint32_t inWidth, uint32_t inHeight); - void WaitDeviceIdle() const; - - private: - Render::RenderModule& renderModule; - GameClient client; - GLFWwindow* window; - RHI::Device* device; - Common::UniquePtr imageReadySemaphore; - Common::UniquePtr renderFinishedSemaphore; - Common::UniquePtr surface; - Common::UniquePtr swapChain; - }; -} diff --git a/Engine/Source/Launch/Include/Launch/GameWindow.h b/Engine/Source/Launch/Include/Launch/GameWindow.h new file mode 100644 index 000000000..63ab54326 --- /dev/null +++ b/Engine/Source/Launch/Include/Launch/GameWindow.h @@ -0,0 +1,40 @@ +// +// Created by johnk on 2025/2/19. +// + +#pragma once + +#include +#if PLATFORM_WINDOWS +#define GLFW_EXPOSE_NATIVE_WIN32 +#elif PLATFORM_MACOS +#define GLFW_EXPOSE_NATIVE_COCOA +#endif +#include + +#include +#include + +namespace Launch { + struct GameWindowDesc { + std::string title; + uint32_t width; + uint32_t height; + }; + + class GameWindow final : public Runtime::Window { + public: + explicit GameWindow(const GameWindowDesc& inDesc); + ~GameWindow() override; + + void Resize(uint32_t inWidth, uint32_t inHeight) override; + + bool ShouldClose() const; + void PollEvents() const; + void WaitRenderingIdle() const; + + private: + Render::RenderModule& renderModule; + GLFWwindow* window; + }; +} diff --git a/Engine/Source/Launch/Src/GameApplication.cpp b/Engine/Source/Launch/Src/GameApplication.cpp index a4aa9aee9..a346f4bb2 100644 --- a/Engine/Source/Launch/Src/GameApplication.cpp +++ b/Engine/Source/Launch/Src/GameApplication.cpp @@ -32,23 +32,29 @@ namespace Launch { engine = &Runtime::EngineHolder::Get(); gameModule = &Core::ModuleManager::Get().GetTyped(gameModuleName); - GameViewportDesc viewportDesc; - viewportDesc.title = gameModule->GetGameName(); - // TODO load from config - viewportDesc.width = 1024; - viewportDesc.height = 768; - viewport = Common::MakeUnique(viewportDesc); + GameWindowDesc windowDesc; + windowDesc.title = gameModule->GetGameName(); + windowDesc.width = 1024; + windowDesc.height = 768; + window = Common::MakeUnique(windowDesc); + client = Common::MakeUnique(*window); auto& settingRegistry = Runtime::SettingsRegistry::Get(); settingRegistry.LoadAllSettings(); const auto& gameSettings = Runtime::SettingsRegistry::Get().GetSettings(); const auto startupLevel = Runtime::AssetManager::Get().SyncLoad(gameSettings.gameStartupLevel, Runtime::Level::GetStaticClass()); - viewport->GetClient().GetWorld().LoadFrom(startupLevel); + client->GetWorld().LoadFrom(startupLevel); + client->GetWorld().Play(); } GameApplication::~GameApplication() { + if (client.Valid() && !client->GetWorld().Stopped()) { + client->GetWorld().Stop(); + } + client.Reset(); + window.Reset(); Runtime::EngineHolder::Unload(); } @@ -59,11 +65,11 @@ namespace Launch { lastFrameTimeSeconds = thisFrameTimeSeconds; engine->Tick(deltaTimeSeconds); - viewport->PollEvents(); + window->PollEvents(); } bool GameApplication::ShouldClose() const { - return viewport->ShouldClose(); + return window->ShouldClose(); } } diff --git a/Engine/Source/Launch/Src/GameClient.cpp b/Engine/Source/Launch/Src/GameClient.cpp index eadd1b7c9..715ae3155 100644 --- a/Engine/Source/Launch/Src/GameClient.cpp +++ b/Engine/Source/Launch/Src/GameClient.cpp @@ -3,24 +3,26 @@ // #include -#include +#include +#include namespace Launch { - GameClient::GameClient(GameViewport& inViewport) - : viewport(inViewport) + GameClient::GameClient(GameWindow& inWindow) + : window(inWindow) , world("GameWorld", this, Runtime::PlayType::game) { + world.SetSystemGraph(Runtime::SystemGraphPresets::Default3DWorld()); } GameClient::~GameClient() = default; - Runtime::Viewport& GameClient::GetViewport() + Runtime::World& GameClient::GetWorld() { - return viewport; + return world; } - Runtime::World& GameClient::GetWorld() + Runtime::RenderSurface* GameClient::GetRenderSurface() { - return world; + return &window; } } diff --git a/Engine/Source/Launch/Src/GameViewport.cpp b/Engine/Source/Launch/Src/GameViewport.cpp deleted file mode 100644 index 5d7ca22eb..000000000 --- a/Engine/Source/Launch/Src/GameViewport.cpp +++ /dev/null @@ -1,131 +0,0 @@ -// -// Created by johnk on 2025/2/19. -// - -#include -#include - -namespace Launch::Internal { - static void* GetGlfwPlatformWindow(GLFWwindow* inWindow) - { -#if PLATFORM_WINDOWS - return glfwGetWin32Window(inWindow); -#elif PLATFORM_MACOS - return glfwGetCocoaView(inWindow); -#else - Unimplement(); - return nullptr; -#endif - } -} - -namespace Launch { - GameViewport::GameViewport(const GameViewportDesc& inDesc) - : renderModule(Runtime::EngineHolder::Get().GetRenderModule()) - , client(*this) - { - glfwInit(); - window = glfwCreateWindow(static_cast(inDesc.width), static_cast(inDesc.height), inDesc.title.c_str(), nullptr, nullptr); - - device = renderModule.GetDevice(); - imageReadySemaphore = device->CreateSemaphore(); - renderFinishedSemaphore = device->CreateSemaphore(); - - surface = device->CreateSurface(RHI::SurfaceCreateInfo(Internal::GetGlfwPlatformWindow(window))); - RecreateSwapChain(inDesc.width, inDesc.height); - } - - GameViewport::~GameViewport() - { - WaitDeviceIdle(); - glfwDestroyWindow(window); - glfwTerminate(); - } - - Runtime::Client& GameViewport::GetClient() - { - return client; - } - - Runtime::PresentInfo GameViewport::GetNextPresentInfo() - { - const auto backTextureIndex = swapChain->AcquireBackTexture(imageReadySemaphore.Get()); - - Runtime::PresentInfo result; - result.backTexture = swapChain->GetTexture(backTextureIndex); - result.imageReadySemaphore = imageReadySemaphore.Get(); - result.renderFinishedSemaphore = renderFinishedSemaphore.Get(); - return result; - } - - uint32_t GameViewport::GetWidth() const - { - int width; - glfwGetWindowSize(window, &width, nullptr); - return static_cast(width); - } - - uint32_t GameViewport::GetHeight() const - { - int height; - glfwGetWindowSize(window, nullptr, &height); - return static_cast(height); - } - - void GameViewport::Resize(uint32_t inWidth, uint32_t inHeight) - { - glfwSetWindowSize(window, static_cast(inWidth), static_cast(inHeight)); - RecreateSwapChain(inWidth, inHeight); - } - - bool GameViewport::ShouldClose() const - { - return static_cast(glfwWindowShouldClose(window)); - } - - void GameViewport::PollEvents() const // NOLINT - { - glfwPollEvents(); - } - - void GameViewport::RecreateSwapChain(uint32_t inWidth, uint32_t inHeight) - { - static std::vector formatQualifiers = { - RHI::PixelFormat::rgba8Unorm, - RHI::PixelFormat::bgra8Unorm - }; - - WaitDeviceIdle(); - if (swapChain.Valid()) { - swapChain.Reset(); - } - - std::optional pixelFormat = {}; - for (const auto format : formatQualifiers) { - if (device->CheckSwapChainFormatSupport(surface.Get(), format)) { - pixelFormat = format; - break; - } - } - Assert(pixelFormat.has_value()); - - swapChain = device->CreateSwapChain( - RHI::SwapChainCreateInfo() - .SetPresentQueue(device->GetQueue(RHI::QueueType::graphics, 0)) - .SetSurface(surface.Get()) - .SetTextureNum(2) - .SetFormat(pixelFormat.value()) - .SetWidth(inWidth) - .SetHeight(inHeight) - .SetPresentMode(RHI::PresentMode::immediately)); - } - - void GameViewport::WaitDeviceIdle() const - { - renderModule.GetRenderThread().Flush(); - - const Common::UniquePtr fence = device->CreateFence(false); - device->GetQueue(RHI::QueueType::graphics, 0)->Flush(fence.Get()); - fence->Wait(); - } -} diff --git a/Engine/Source/Launch/Src/GameWindow.cpp b/Engine/Source/Launch/Src/GameWindow.cpp new file mode 100644 index 000000000..666c2c304 --- /dev/null +++ b/Engine/Source/Launch/Src/GameWindow.cpp @@ -0,0 +1,74 @@ +// +// Created by johnk on 2025/2/19. +// + +#include +#include + +namespace Launch::Internal { + static void* GetGlfwPlatformWindow(GLFWwindow* inWindow) + { +#if PLATFORM_WINDOWS + return glfwGetWin32Window(inWindow); +#elif PLATFORM_MACOS + return glfwGetCocoaView(inWindow); +#else + Unimplement(); + return nullptr; +#endif + } +} + +namespace Launch { + GameWindow::GameWindow(const GameWindowDesc& inDesc) + : Runtime::Window(*Runtime::EngineHolder::Get().GetRenderModule().GetDevice()) + , renderModule(Runtime::EngineHolder::Get().GetRenderModule()) + , window(nullptr) + { + glfwInit(); + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + window = glfwCreateWindow( + static_cast(inDesc.width), + static_cast(inDesc.height), + inDesc.title.c_str(), + nullptr, + nullptr); + Assert(window != nullptr); + + InitializeSurface( + GetDevice().CreateSurface(RHI::SurfaceCreateInfo(Internal::GetGlfwPlatformWindow(window))), + inDesc.width, + inDesc.height); + } + + GameWindow::~GameWindow() + { + WaitRenderingIdle(); + DestroySurface(); + glfwDestroyWindow(window); + glfwTerminate(); + } + + void GameWindow::Resize(uint32_t inWidth, uint32_t inHeight) + { + glfwSetWindowSize(window, static_cast(inWidth), static_cast(inHeight)); + WaitRenderingIdle(); + Runtime::Window::Resize(inWidth, inHeight); + } + + bool GameWindow::ShouldClose() const + { + return static_cast(glfwWindowShouldClose(window)); + } + + void GameWindow::PollEvents() const + { + glfwPollEvents(); + } + + void GameWindow::WaitRenderingIdle() const + { + renderModule.GetRenderThread().Flush(); + Runtime::Window::WaitRenderingIdle(); + } +} diff --git a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/BindGroupLayout.h b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/BindGroupLayout.h index 9042fd2b6..85234dde4 100644 --- a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/BindGroupLayout.h +++ b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/BindGroupLayout.h @@ -11,6 +11,8 @@ namespace RHI::DirectX12 { class DX12Device; + D3D12_SHADER_VISIBILITY GetShaderVisibility(ShaderStageFlags shaderStageFlags); + struct RootParameterKeyInfo { BindingType bindingType; uint8_t layoutIndex; diff --git a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/CommandRecorder.h b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/CommandRecorder.h index ed512d168..b0fd0dc70 100644 --- a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/CommandRecorder.h +++ b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/CommandRecorder.h @@ -12,6 +12,7 @@ namespace RHI::DirectX12 { class DX12ComputePipeline; class DX12RasterPipeline; class DX12PipelineLayout; + class DX12QuerySet; class DX12CommandRecorder final : public CommandRecorder { public: @@ -25,6 +26,9 @@ namespace RHI::DirectX12 { Common::UniquePtr BeginCopyPass() override; Common::UniquePtr BeginComputePass() override; Common::UniquePtr BeginRasterPass(const RasterPassBeginInfo& inBeginInfo) override; + void WriteTimestamp(QuerySet* inQuerySet, uint32_t inQueryIndex) override; + void ResetQuerySet(QuerySet* inQuerySet, uint32_t inFirstQuery, uint32_t inQueryCount) override; + void ResolveQuery(QuerySet* inQuerySet, uint32_t inFirstQuery, uint32_t inQueryCount, Buffer* inDstBuffer, size_t inDstOffset) override; void End() override; private: @@ -70,7 +74,9 @@ namespace RHI::DirectX12 { // ComputePassCommandRecorder void SetPipeline(ComputePipeline* inPipeline) override; void SetBindGroup(uint8_t inLayoutIndex, BindGroup* inBindGroup) override; + void SetPipelineConstants(uint32_t inPipelineConstantIndex, const void* inData, uint32_t inSize) override; void Dispatch(size_t inGroupCountX, size_t inGroupCountY, size_t inGroupCountZ) override; + void DispatchIndirect(Buffer* inIndirectBuffer, size_t inOffset) override; void EndPass() override; private: @@ -94,6 +100,7 @@ namespace RHI::DirectX12 { // RasterPassCommandRecorder void SetPipeline(RasterPipeline* inPipeline) override; void SetBindGroup(uint8_t inLayoutIndex, BindGroup* inBindGroup) override; + void SetPipelineConstants(uint32_t inPipelineConstantIndex, const void* inData, uint32_t inSize) override; void SetIndexBuffer(BufferView* inBufferView) override; void SetVertexBuffer(size_t inSlot, BufferView* inBufferView) override; void Draw(size_t inVertexCount, size_t inInstanceCount, size_t inFirstVertex, size_t inFirstInstance) override; @@ -107,6 +114,8 @@ namespace RHI::DirectX12 { void DrawIndexedIndirect(Buffer* inIndirectBuffer, size_t inOffset) override; void MultiDrawIndirect(Buffer* inIndirectBuffer, size_t inOffset, size_t inDrawCount) override; void MultiDrawIndexedIndirect(Buffer* inIndirectBuffer, size_t inOffset, size_t inDrawCount) override; + void BeginOcclusionQuery(QuerySet* inQuerySet, uint32_t inQueryIndex) override; + void EndOcclusionQuery() override; void EndPass() override; private: @@ -114,5 +123,7 @@ namespace RHI::DirectX12 { DX12CommandRecorder& commandRecorder; DX12RasterPipeline* rasterPipeline; DX12CommandBuffer& commandBuffer; + DX12QuerySet* activeOcclusionQuerySet; + uint32_t activeOcclusionQueryIndex; }; } diff --git a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/Common.h b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/Common.h index e6e399948..a2e49ae66 100644 --- a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/Common.h +++ b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/Common.h @@ -267,12 +267,28 @@ namespace RHI::DirectX12 { ECIMPL_ITEM(TextureState::copyDst, D3D12_RESOURCE_STATE_COPY_DEST) ECIMPL_ITEM(TextureState::shaderReadOnly, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) ECIMPL_ITEM(TextureState::renderTarget, D3D12_RESOURCE_STATE_RENDER_TARGET) - ECIMPL_ITEM(TextureState::storage, D3D12_RESOURCE_STATE_UNORDERED_ACCESS) + ECIMPL_ITEM(TextureState::storage, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) + ECIMPL_ITEM(TextureState::rwStorage, D3D12_RESOURCE_STATE_UNORDERED_ACCESS) ECIMPL_ITEM(TextureState::depthStencilReadonly, D3D12_RESOURCE_STATE_DEPTH_READ) ECIMPL_ITEM(TextureState::depthStencilWrite, D3D12_RESOURCE_STATE_DEPTH_WRITE) ECIMPL_ITEM(TextureState::present, D3D12_RESOURCE_STATE_PRESENT) ECIMPL_END(D3D12_RESOURCE_STATES) + ECIMPL_BEGIN(QueryType, D3D12_QUERY_HEAP_TYPE) + ECIMPL_ITEM(QueryType::occlusion, D3D12_QUERY_HEAP_TYPE_OCCLUSION) + ECIMPL_ITEM(QueryType::timestamp, D3D12_QUERY_HEAP_TYPE_TIMESTAMP) + ECIMPL_END(D3D12_QUERY_HEAP_TYPE) + + ECIMPL_BEGIN(QueryType, D3D12_QUERY_TYPE) + ECIMPL_ITEM(QueryType::occlusion, D3D12_QUERY_TYPE_OCCLUSION) + ECIMPL_ITEM(QueryType::timestamp, D3D12_QUERY_TYPE_TIMESTAMP) + ECIMPL_END(D3D12_QUERY_TYPE) + + ECIMPL_BEGIN(ColorSpace, DXGI_COLOR_SPACE_TYPE) + ECIMPL_ITEM(ColorSpace::srgbNonLinear, DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709) + ECIMPL_ITEM(ColorSpace::hdr10St2084, DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) + ECIMPL_END(DXGI_COLOR_SPACE_TYPE) + FCIMPL_BEGIN(ColorWriteFlags, uint8_t) FCIMPL_ITEM(ColorWriteBits::red, D3D12_COLOR_WRITE_ENABLE_RED) FCIMPL_ITEM(ColorWriteBits::green, D3D12_COLOR_WRITE_ENABLE_GREEN) @@ -285,6 +301,7 @@ namespace RHI::DirectX12 { FCIMPL_ITEM(TextureUsageBits::copyDst, D3D12_RESOURCE_FLAG_NONE) FCIMPL_ITEM(TextureUsageBits::textureBinding, D3D12_RESOURCE_FLAG_NONE) FCIMPL_ITEM(TextureUsageBits::storageBinding, D3D12_RESOURCE_FLAG_NONE) + FCIMPL_ITEM(TextureUsageBits::rwStorageBinding, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS) FCIMPL_ITEM(TextureUsageBits::renderAttachment, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET) FCIMPL_ITEM(TextureUsageBits::depthStencilAttachment, D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL) FCIMPL_END(D3D12_RESOURCE_FLAGS) diff --git a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/Device.h b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/Device.h index 35692a15b..010b32112 100644 --- a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/Device.h +++ b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/Device.h @@ -85,18 +85,21 @@ namespace RHI::DirectX12 { Common::UniquePtr CreateBindGroup(const BindGroupCreateInfo& inCreateInfo) override; Common::UniquePtr CreatePipelineLayout(const PipelineLayoutCreateInfo& inCreateInfo) override; Common::UniquePtr CreateShaderModule(const ShaderModuleCreateInfo& inCreateInfo) override; + Common::UniquePtr CreatePipelineCache(const PipelineCacheCreateInfo& inCreateInfo) override; Common::UniquePtr CreateComputePipeline(const ComputePipelineCreateInfo& inCreateInfo) override; Common::UniquePtr CreateRasterPipeline(const RasterPipelineCreateInfo& inCreateInfo) override; Common::UniquePtr CreateCommandBuffer() override; Common::UniquePtr CreateFence(bool inInitAsSignaled) override; Common::UniquePtr CreateSemaphore() override; + Common::UniquePtr CreateQuerySet(const QuerySetCreateInfo& inCreateInfo) override; - bool CheckSwapChainFormatSupport(Surface* inSurface, PixelFormat inFormat) override; + bool CheckSwapChainFormatSupport(Surface* inSurface, PixelFormat inFormat, ColorSpace inColorSpace) override; TextureSubResourceCopyFootprint GetTextureSubResourceCopyFootprint(const Texture& texture, const TextureSubResourceInfo& subResourceInfo) override; ID3D12Device* GetNative() const; ID3D12CommandSignature* GetDrawIndirectCommandSignature() const; ID3D12CommandSignature* GetDrawIndexedIndirectCommandSignature() const; + ID3D12CommandSignature* GetDispatchIndirectCommandSignature() const; Common::UniquePtr AllocateRtvDescriptor() const; Common::UniquePtr AllocateCbvSrvUavDescriptor() const; Common::UniquePtr AllocateSamplerDescriptor() const; @@ -107,7 +110,7 @@ namespace RHI::DirectX12 { void CreateNativeQueues(const DeviceCreateInfo& inCreateInfo); void QueryNativeDescriptorSize(); void CreateDescriptorPools(); - void CreateDrawIndirectCommandSignatures(); + void CreateIndirectCommandSignatures(); #if BUILD_CONFIG_DEBUG void RegisterNativeDebugLayerExceptionHandler(); void UnregisterNativeDebugLayerExceptionHandler(); @@ -127,5 +130,6 @@ namespace RHI::DirectX12 { ComPtr nativeDevice; ComPtr drawIndirectCommandSignature; ComPtr drawIndexedIndirectCommandSignature; + ComPtr dispatchIndirectCommandSignature; }; } diff --git a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/PipelineCache.h b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/PipelineCache.h new file mode 100644 index 000000000..6ae57793a --- /dev/null +++ b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/PipelineCache.h @@ -0,0 +1,34 @@ +// +// Created by johnk on 2026/7/2. +// + +#pragma once + +#include + +#include +#include + +#include + +using Microsoft::WRL::ComPtr; + +namespace RHI::DirectX12 { + class DX12Device; + + class DX12PipelineCache final : public PipelineCache { + public: + NonCopyable(DX12PipelineCache) + DX12PipelineCache(DX12Device& inDevice, const PipelineCacheCreateInfo& inCreateInfo); + ~DX12PipelineCache() override; + + std::vector GetData() override; + ID3D12PipelineLibrary* GetNative() const; + + private: + void CreateNativePipelineLibrary(DX12Device& inDevice, const PipelineCacheCreateInfo& inCreateInfo); + + std::vector initialData; + ComPtr nativePipelineLibrary; + }; +} diff --git a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/PipelineLayout.h b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/PipelineLayout.h index f6144c690..8be0ace68 100644 --- a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/PipelineLayout.h +++ b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/PipelineLayout.h @@ -6,6 +6,7 @@ #include #include +#include #include @@ -38,6 +39,7 @@ namespace RHI::DirectX12 { ~DX12PipelineLayout() override; std::optional QueryRootDescriptorParameterIndex(uint8_t inLayoutIndex, const HlslBinding& inBinding); + RootParameterIndex QueryRootConstantParameterIndex(uint32_t inPipelineConstantIndex) const; ID3D12RootSignature* GetNative() const; private: @@ -45,5 +47,6 @@ namespace RHI::DirectX12 { ComPtr nativeRootSignature; RootParameterIndexMap rootParameterIndexMap; + std::vector rootConstantParameterIndices; }; } diff --git a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/QuerySet.h b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/QuerySet.h new file mode 100644 index 000000000..267b876c2 --- /dev/null +++ b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/QuerySet.h @@ -0,0 +1,30 @@ +// +// Created by johnk on 2026/6/30. +// + +#pragma once + +#include +#include + +#include + +using Microsoft::WRL::ComPtr; + +namespace RHI::DirectX12 { + class DX12Device; + + class DX12QuerySet final : public QuerySet { + public: + NonCopyable(DX12QuerySet) + DX12QuerySet(DX12Device& inDevice, const QuerySetCreateInfo& inCreateInfo); + ~DX12QuerySet() override; + + ID3D12QueryHeap* GetNative() const; + + private: + void CreateNativeQueryHeap(DX12Device& inDevice, const QuerySetCreateInfo& inCreateInfo); + + ComPtr nativeQueryHeap; + }; +} diff --git a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/Queue.h b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/Queue.h index f2b165d92..875f8aa9a 100644 --- a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/Queue.h +++ b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/Queue.h @@ -20,6 +20,7 @@ namespace RHI::DirectX12 { void Submit(CommandBuffer* inCmdBuffer, const QueueSubmitInfo& inSubmitInfo) override; void Flush(Fence* inFenceToSignal) override; + float GetTimestampPeriod() override; ID3D12CommandQueue* GetNative() const; diff --git a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/SwapChain.h b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/SwapChain.h index eb2a20d23..48d0d3af2 100644 --- a/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/SwapChain.h +++ b/Engine/Source/RHI-DirectX12/Include/RHI/DirectX12/SwapChain.h @@ -33,6 +33,7 @@ namespace RHI::DirectX12 { DX12Queue& queue; uint8_t textureNum; PresentMode presentMode; + bool allowTearing; ComPtr nativeSwapChain; std::vector> textures; }; diff --git a/Engine/Source/RHI-DirectX12/Src/BindGroup.cpp b/Engine/Source/RHI-DirectX12/Src/BindGroup.cpp index 8985bcdcf..844384149 100644 --- a/Engine/Source/RHI-DirectX12/Src/BindGroup.cpp +++ b/Engine/Source/RHI-DirectX12/Src/BindGroup.cpp @@ -19,7 +19,8 @@ namespace RHI::DirectX12 { return bufferView->GetNativeCpuDescriptorHandle(); } if (entry.binding.type == BindingType::texture || - entry.binding.type == BindingType::storageTexture) { + entry.binding.type == BindingType::storageTexture || + entry.binding.type == BindingType::rwStorageTexture) { const auto* textureView = static_cast(std::get(entry.entity)); return textureView->GetNativeCpuDescriptorHandle(); } diff --git a/Engine/Source/RHI-DirectX12/Src/CommandRecorder.cpp b/Engine/Source/RHI-DirectX12/Src/CommandRecorder.cpp index 9dd0c1297..8d196ffeb 100644 --- a/Engine/Source/RHI-DirectX12/Src/CommandRecorder.cpp +++ b/Engine/Source/RHI-DirectX12/Src/CommandRecorder.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include namespace RHI::DirectX12 { @@ -224,11 +225,24 @@ namespace RHI::DirectX12 { } } + void DX12ComputePassCommandRecorder::SetPipelineConstants(const uint32_t inPipelineConstantIndex, const void* inData, const uint32_t inSize) + { + Assert(inSize % 4 == 0); + const auto rootParameterIndex = computePipeline->GetPipelineLayout().QueryRootConstantParameterIndex(inPipelineConstantIndex); + commandBuffer.GetNativeCmdList()->SetComputeRoot32BitConstants(rootParameterIndex, inSize / 4, inData, 0); + } + void DX12ComputePassCommandRecorder::Dispatch(const size_t inGroupCountX, const size_t inGroupCountY, const size_t inGroupCountZ) { commandBuffer.GetNativeCmdList()->Dispatch(inGroupCountX, inGroupCountY, inGroupCountZ); } + void DX12ComputePassCommandRecorder::DispatchIndirect(Buffer* inIndirectBuffer, const size_t inOffset) + { + const auto* indirectBuffer = static_cast(inIndirectBuffer); + commandBuffer.GetNativeCmdList()->ExecuteIndirect(device.GetDispatchIndirectCommandSignature(), 1, indirectBuffer->GetNative(), inOffset, nullptr, 0); + } + void DX12ComputePassCommandRecorder::EndPass() { } @@ -238,6 +252,8 @@ namespace RHI::DirectX12 { , commandRecorder(inCmdRecorder) , rasterPipeline(nullptr) , commandBuffer(inCmdBuffer) + , activeOcclusionQuerySet(nullptr) + , activeOcclusionQueryIndex(0) { // set render targets std::vector rtvHandles(inBeginInfo.colorAttachments.size()); @@ -317,6 +333,13 @@ namespace RHI::DirectX12 { } } + void DX12RasterPassCommandRecorder::SetPipelineConstants(const uint32_t inPipelineConstantIndex, const void* inData, const uint32_t inSize) + { + Assert(inSize % 4 == 0); + const auto rootParameterIndex = rasterPipeline->GetPipelineLayout().QueryRootConstantParameterIndex(inPipelineConstantIndex); + commandBuffer.GetNativeCmdList()->SetGraphicsRoot32BitConstants(rootParameterIndex, inSize / 4, inData, 0); + } + void DX12RasterPassCommandRecorder::SetIndexBuffer(BufferView* inBufferView) { const auto* bufferView = static_cast(inBufferView); @@ -389,6 +412,19 @@ namespace RHI::DirectX12 { commandBuffer.GetNativeCmdList()->ExecuteIndirect(device.GetDrawIndexedIndirectCommandSignature(), inDrawCount, indirectBuffer->GetNative(), inOffset, nullptr, 0); } + void DX12RasterPassCommandRecorder::BeginOcclusionQuery(QuerySet* inQuerySet, const uint32_t inQueryIndex) + { + activeOcclusionQuerySet = static_cast(inQuerySet); + activeOcclusionQueryIndex = inQueryIndex; + commandBuffer.GetNativeCmdList()->BeginQuery(activeOcclusionQuerySet->GetNative(), D3D12_QUERY_TYPE_OCCLUSION, inQueryIndex); + } + + void DX12RasterPassCommandRecorder::EndOcclusionQuery() + { + Assert(activeOcclusionQuerySet != nullptr); + commandBuffer.GetNativeCmdList()->EndQuery(activeOcclusionQuerySet->GetNative(), D3D12_QUERY_TYPE_OCCLUSION, activeOcclusionQueryIndex); + } + void DX12RasterPassCommandRecorder::EndPass() { } @@ -472,6 +508,26 @@ namespace RHI::DirectX12 { return Common::UniquePtr(new DX12RasterPassCommandRecorder(device, *this, commandBuffer, inBeginInfo)); } + void DX12CommandRecorder::WriteTimestamp(QuerySet* inQuerySet, const uint32_t inQueryIndex) + { + const auto* querySet = static_cast(inQuerySet); + const auto queryType = EnumCast(querySet->GetCreateInfo().type); + commandBuffer.GetNativeCmdList()->EndQuery(querySet->GetNative(), queryType, inQueryIndex); + } + + void DX12CommandRecorder::ResetQuerySet(QuerySet* inQuerySet, uint32_t inFirstQuery, uint32_t inQueryCount) + { + // DirectX12 query heaps need no reset before reuse. + } + + void DX12CommandRecorder::ResolveQuery(QuerySet* inQuerySet, const uint32_t inFirstQuery, const uint32_t inQueryCount, Buffer* inDstBuffer, const size_t inDstOffset) + { + const auto* querySet = static_cast(inQuerySet); + const auto* dstBuffer = static_cast(inDstBuffer); + const auto queryType = EnumCast(querySet->GetCreateInfo().type); + commandBuffer.GetNativeCmdList()->ResolveQueryData(querySet->GetNative(), queryType, inFirstQuery, inQueryCount, dstBuffer->GetNative(), inDstOffset); + } + void DX12CommandRecorder::End() { Assert(SUCCEEDED(commandBuffer.GetNativeCmdList()->Close())); diff --git a/Engine/Source/RHI-DirectX12/Src/Device.cpp b/Engine/Source/RHI-DirectX12/Src/Device.cpp index eb07ff72d..28d05f67a 100644 --- a/Engine/Source/RHI-DirectX12/Src/Device.cpp +++ b/Engine/Source/RHI-DirectX12/Src/Device.cpp @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include #include @@ -153,7 +155,7 @@ namespace RHI::DirectX12 { CreateNativeQueues(inCreateInfo); QueryNativeDescriptorSize(); CreateDescriptorPools(); - CreateDrawIndirectCommandSignatures(); + CreateIndirectCommandSignatures(); #if BUILD_CONFIG_DEBUG RegisterNativeDebugLayerExceptionHandler(); #endif @@ -253,14 +255,24 @@ namespace RHI::DirectX12 { return { new DX12Semaphore(*this) }; } - bool DX12Device::CheckSwapChainFormatSupport(Surface* inSurface, PixelFormat inFormat) + Common::UniquePtr DX12Device::CreateQuerySet(const QuerySetCreateInfo& inCreateInfo) { - static std::unordered_set supportedFormats = { - PixelFormat::rgba8Unorm, - PixelFormat::bgra8Unorm, - // TODO HDR + return { new DX12QuerySet(*this, inCreateInfo) }; + } + + Common::UniquePtr DX12Device::CreatePipelineCache(const PipelineCacheCreateInfo& inCreateInfo) + { + return { new DX12PipelineCache(*this, inCreateInfo) }; + } + + bool DX12Device::CheckSwapChainFormatSupport(Surface* inSurface, PixelFormat inFormat, ColorSpace inColorSpace) + { + static std::unordered_map> supportedFormats = { + { ColorSpace::srgbNonLinear, { PixelFormat::rgba8Unorm, PixelFormat::bgra8Unorm } }, + { ColorSpace::hdr10St2084, { PixelFormat::rgb10A2Unorm } } }; - return supportedFormats.contains(inFormat); + const auto iter = supportedFormats.find(inColorSpace); + return iter != supportedFormats.end() && iter->second.contains(inFormat); } TextureSubResourceCopyFootprint DX12Device::GetTextureSubResourceCopyFootprint(const Texture& texture, const TextureSubResourceInfo& subResourceInfo) @@ -299,6 +311,11 @@ namespace RHI::DirectX12 { return drawIndexedIndirectCommandSignature.Get(); } + ID3D12CommandSignature* DX12Device::GetDispatchIndirectCommandSignature() const + { + return dispatchIndirectCommandSignature.Get(); + } + Common::UniquePtr DX12Device::AllocateRtvDescriptor() const { return rtvDescriptorPool->Allocate(); @@ -368,7 +385,7 @@ namespace RHI::DirectX12 { dsvDescriptorPool = Common::MakeUnique(*this, D3D12_DESCRIPTOR_HEAP_TYPE_DSV, nativeDsvDescriptorSize, 16); } - void DX12Device::CreateDrawIndirectCommandSignatures() + void DX12Device::CreateIndirectCommandSignatures() { const auto createSignature = [this](const D3D12_INDIRECT_ARGUMENT_TYPE inArgumentType, const uint32_t inStride) -> ComPtr { D3D12_INDIRECT_ARGUMENT_DESC argumentDesc {}; @@ -388,6 +405,7 @@ namespace RHI::DirectX12 { drawIndirectCommandSignature = createSignature(D3D12_INDIRECT_ARGUMENT_TYPE_DRAW, sizeof(DrawIndirectArguments)); drawIndexedIndirectCommandSignature = createSignature(D3D12_INDIRECT_ARGUMENT_TYPE_DRAW_INDEXED, sizeof(DrawIndexedIndirectArguments)); + dispatchIndirectCommandSignature = createSignature(D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH, sizeof(DispatchIndirectArguments)); } #if BUILD_CONFIG_DEBUG diff --git a/Engine/Source/RHI-DirectX12/Src/Pipeline.cpp b/Engine/Source/RHI-DirectX12/Src/Pipeline.cpp index 43083881d..cee31a71a 100644 --- a/Engine/Source/RHI-DirectX12/Src/Pipeline.cpp +++ b/Engine/Source/RHI-DirectX12/Src/Pipeline.cpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace RHI::DirectX12 { D3D12_RENDER_TARGET_BLEND_DESC GetDX12RenderTargetBlendDesc(const ColorTargetState& colorTargetState) @@ -224,8 +225,18 @@ namespace RHI::DirectX12 { auto inputElements = GetDX12InputElements(inCreateInfo); UpdateDX12InputLayoutDesc(desc, inputElements); - bool success = SUCCEEDED(inDevice.GetNative()->CreateGraphicsPipelineState(&desc, IID_PPV_ARGS(&nativePipelineState))); - Assert(success); + auto* pipelineCache = static_cast(inCreateInfo.pipelineCache); + ID3D12PipelineLibrary* nativePipelineLibrary = pipelineCache != nullptr ? pipelineCache->GetNative() : nullptr; + const bool cacheable = nativePipelineLibrary != nullptr && !inCreateInfo.debugName.empty(); + const std::wstring cacheKey = cacheable ? Common::StringUtils::ToWideString(inCreateInfo.debugName) : std::wstring {}; + + bool loaded = cacheable && SUCCEEDED(nativePipelineLibrary->LoadGraphicsPipeline(cacheKey.c_str(), &desc, IID_PPV_ARGS(&nativePipelineState))); + if (!loaded) { + Assert(SUCCEEDED(inDevice.GetNative()->CreateGraphicsPipelineState(&desc, IID_PPV_ARGS(&nativePipelineState)))); + if (cacheable) { + nativePipelineLibrary->StorePipeline(cacheKey.c_str(), nativePipelineState.Get()); + } + } #if BUILD_CONFIG_DEBUG if (!inCreateInfo.debugName.empty()) { diff --git a/Engine/Source/RHI-DirectX12/Src/PipelineCache.cpp b/Engine/Source/RHI-DirectX12/Src/PipelineCache.cpp new file mode 100644 index 000000000..140d2019a --- /dev/null +++ b/Engine/Source/RHI-DirectX12/Src/PipelineCache.cpp @@ -0,0 +1,56 @@ +// +// Created by johnk on 2026/7/2. +// + +#include + +#include +#include +#include + +namespace RHI::DirectX12 { + DX12PipelineCache::DX12PipelineCache(DX12Device& inDevice, const PipelineCacheCreateInfo& inCreateInfo) + : PipelineCache(inCreateInfo) + { + CreateNativePipelineLibrary(inDevice, inCreateInfo); + } + + DX12PipelineCache::~DX12PipelineCache() = default; + + std::vector DX12PipelineCache::GetData() + { + if (nativePipelineLibrary == nullptr) { + return {}; + } + + const SIZE_T size = nativePipelineLibrary->GetSerializedSize(); + std::vector result(size); + Assert(SUCCEEDED(nativePipelineLibrary->Serialize(result.data(), size))); + return result; + } + + ID3D12PipelineLibrary* DX12PipelineCache::GetNative() const + { + return nativePipelineLibrary.Get(); + } + + void DX12PipelineCache::CreateNativePipelineLibrary(DX12Device& inDevice, const PipelineCacheCreateInfo& inCreateInfo) + { + ComPtr nativeDevice1; + if (FAILED(inDevice.GetNative()->QueryInterface(IID_PPV_ARGS(&nativeDevice1)))) { + return; + } + + // CreatePipelineLibrary does not copy the blob, so it must outlive the library object. + if (inCreateInfo.initialDataSize > 0) { + initialData.resize(inCreateInfo.initialDataSize); + memcpy(initialData.data(), inCreateInfo.initialData, inCreateInfo.initialDataSize); + } + + // A stale or driver-incompatible blob makes CreatePipelineLibrary fail; fall back to an empty library then. + if (FAILED(nativeDevice1->CreatePipelineLibrary(initialData.data(), initialData.size(), IID_PPV_ARGS(&nativePipelineLibrary)))) { + initialData.clear(); + Assert(SUCCEEDED(nativeDevice1->CreatePipelineLibrary(nullptr, 0, IID_PPV_ARGS(&nativePipelineLibrary)))); + } + } +} diff --git a/Engine/Source/RHI-DirectX12/Src/PipelineLayout.cpp b/Engine/Source/RHI-DirectX12/Src/PipelineLayout.cpp index 1f21de048..4948da8c3 100644 --- a/Engine/Source/RHI-DirectX12/Src/PipelineLayout.cpp +++ b/Engine/Source/RHI-DirectX12/Src/PipelineLayout.cpp @@ -40,6 +40,11 @@ namespace RHI::DirectX12 { return iter->second; } + RootParameterIndex DX12PipelineLayout::QueryRootConstantParameterIndex(const uint32_t inPipelineConstantIndex) const + { + return rootConstantParameterIndices[inPipelineConstantIndex]; + } + ID3D12RootSignature* DX12PipelineLayout::GetNative() const { return nativeRootSignature.Get(); @@ -66,7 +71,21 @@ namespace RHI::DirectX12 { rootParameterIndexMap[rootParameterKey] = { keyInfo.bindingType, index }; } } - // TODO root constants + rootConstantParameterIndices.reserve(inCreateInfo.pipelineConstantLayouts.size()); + for (const auto& pipelineConstantLayout : inCreateInfo.pipelineConstantLayouts) { + Assert(pipelineConstantLayout.size % 4 == 0); + const auto& hlslBinding = std::get(pipelineConstantLayout.platformBinding); + const auto index = static_cast(rootParameters.size()); + + rootParameters.emplace_back(); + rootParameters.back().InitAsConstants( + pipelineConstantLayout.size / 4, + hlslBinding.binding, + hlslBinding.bindingSpace, + GetShaderVisibility(pipelineConstantLayout.stageFlags)); + + rootConstantParameterIndices.emplace_back(index); + } CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc; rootSignatureDesc.Init_1_1(rootParameters.size(), rootParameters.data(), 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT); diff --git a/Engine/Source/RHI-DirectX12/Src/QuerySet.cpp b/Engine/Source/RHI-DirectX12/Src/QuerySet.cpp new file mode 100644 index 000000000..b4d3149e6 --- /dev/null +++ b/Engine/Source/RHI-DirectX12/Src/QuerySet.cpp @@ -0,0 +1,33 @@ +// +// Created by johnk on 2026/6/30. +// + +#include +#include +#include +#include + +namespace RHI::DirectX12 { + DX12QuerySet::DX12QuerySet(DX12Device& inDevice, const QuerySetCreateInfo& inCreateInfo) + : QuerySet(inCreateInfo) + { + CreateNativeQueryHeap(inDevice, inCreateInfo); + } + + DX12QuerySet::~DX12QuerySet() = default; + + ID3D12QueryHeap* DX12QuerySet::GetNative() const + { + return nativeQueryHeap.Get(); + } + + void DX12QuerySet::CreateNativeQueryHeap(DX12Device& inDevice, const QuerySetCreateInfo& inCreateInfo) + { + D3D12_QUERY_HEAP_DESC desc = {}; + desc.Type = EnumCast(inCreateInfo.type); + desc.Count = inCreateInfo.count; + desc.NodeMask = 0; + + Assert(SUCCEEDED(inDevice.GetNative()->CreateQueryHeap(&desc, IID_PPV_ARGS(&nativeQueryHeap)))); + } +} diff --git a/Engine/Source/RHI-DirectX12/Src/Queue.cpp b/Engine/Source/RHI-DirectX12/Src/Queue.cpp index ca700b262..cd4d5c653 100644 --- a/Engine/Source/RHI-DirectX12/Src/Queue.cpp +++ b/Engine/Source/RHI-DirectX12/Src/Queue.cpp @@ -44,6 +44,13 @@ namespace RHI::DirectX12 { } } + float DX12Queue::GetTimestampPeriod() + { + UINT64 frequency; + Assert(SUCCEEDED(nativeCmdQueue->GetTimestampFrequency(&frequency))); + return 1.0e9f / static_cast(frequency); + } + ID3D12CommandQueue* DX12Queue::GetNative() const { return nativeCmdQueue.Get(); diff --git a/Engine/Source/RHI-DirectX12/Src/SwapChain.cpp b/Engine/Source/RHI-DirectX12/Src/SwapChain.cpp index 2c20adbe5..972f4fd52 100644 --- a/Engine/Source/RHI-DirectX12/Src/SwapChain.cpp +++ b/Engine/Source/RHI-DirectX12/Src/SwapChain.cpp @@ -3,6 +3,7 @@ // #include +#include #include #include @@ -15,10 +16,23 @@ #include namespace RHI::DirectX12 { - static uint8_t GetSyncInterval(PresentMode presentMode) + // DXGI only exposes the vblank sync interval as a knob, so every vsynced mode (vsync/mailbox/fifoRelaxed) maps to 1 and + // only immediately maps to 0. Tearing (true uncapped present) is then opted into separately via the ALLOW_TEARING flags. + static uint8_t GetSyncInterval(const PresentMode presentMode) { return presentMode == PresentMode::immediately ? 0 : 1; } + + static bool CheckAllowTearingSupport(IDXGIFactory4* inFactory) + { + ComPtr factory5; + if (FAILED(inFactory->QueryInterface(IID_PPV_ARGS(&factory5)))) { + return false; + } + BOOL allowTearing = FALSE; + return SUCCEEDED(factory5->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allowTearing, sizeof(allowTearing))) + && allowTearing == TRUE; + } } namespace RHI::DirectX12 { @@ -26,8 +40,9 @@ namespace RHI::DirectX12 { : SwapChain(inCreateInfo) , device(inDevice) , queue(static_cast(*inCreateInfo.presentQueue)) - , presentMode(inCreateInfo.presentMode) , textureNum(inCreateInfo.textureNum) + , presentMode(inCreateInfo.presentMode) + , allowTearing(false) { CreateDX12SwapChain(inCreateInfo); FetchTextures(inCreateInfo); @@ -59,7 +74,11 @@ namespace RHI::DirectX12 { { auto& dx12WaitSemaphore = static_cast(*inWaitSemaphore); Assert(SUCCEEDED(queue.GetNative()->Wait(dx12WaitSemaphore.GetNative(), 1))); - Assert(SUCCEEDED(nativeSwapChain->Present(GetSyncInterval(presentMode), false))); + + const auto syncInterval = GetSyncInterval(presentMode); + // DXGI_PRESENT_ALLOW_TEARING is only legal with a sync interval of 0 and an ALLOW_TEARING swap chain. + const UINT presentFlags = syncInterval == 0 && allowTearing ? DXGI_PRESENT_ALLOW_TEARING : 0; + Assert(SUCCEEDED(nativeSwapChain->Present(syncInterval, presentFlags))); } void DX12SwapChain::CreateDX12SwapChain(const SwapChainCreateInfo& inCreateInfo) @@ -70,6 +89,10 @@ namespace RHI::DirectX12 { const auto* dx12Surface = static_cast(inCreateInfo.surface); Assert(dx12Surface != nullptr); + // Tearing (uncapped, IMMEDIATE-like present) needs both the ALLOW_TEARING swap-chain flag here and the matching + // present flag later, and is only usable when the DXGI factory reports support for it. + allowTearing = inCreateInfo.presentMode == PresentMode::immediately && CheckAllowTearingSupport(instance.GetNative()); + DXGI_SWAP_CHAIN_DESC1 desc {}; desc.BufferCount = inCreateInfo.textureNum; desc.Width = inCreateInfo.width; @@ -78,6 +101,7 @@ namespace RHI::DirectX12 { desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; desc.SampleDesc.Count = 1; + desc.Flags = allowTearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0; ComPtr dx12SwapChain1; bool success = SUCCEEDED(instance.GetNative()->CreateSwapChainForHwnd( @@ -91,6 +115,13 @@ namespace RHI::DirectX12 { success = SUCCEEDED(dx12SwapChain1.As(&nativeSwapChain)); Assert(success); + + const auto dxgiColorSpace = EnumCast(inCreateInfo.colorSpace); + UINT colorSpaceSupport = 0; + if (SUCCEEDED(nativeSwapChain->CheckColorSpaceSupport(dxgiColorSpace, &colorSpaceSupport)) + && (colorSpaceSupport & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT)) { + Assert(SUCCEEDED(nativeSwapChain->SetColorSpace1(dxgiColorSpace))); + } } void DX12SwapChain::FetchTextures(const SwapChainCreateInfo& inCreateInfo) diff --git a/Engine/Source/RHI-DirectX12/Src/TextureView.cpp b/Engine/Source/RHI-DirectX12/Src/TextureView.cpp index 6f83a4571..164dec0c3 100644 --- a/Engine/Source/RHI-DirectX12/Src/TextureView.cpp +++ b/Engine/Source/RHI-DirectX12/Src/TextureView.cpp @@ -11,12 +11,12 @@ namespace RHI::DirectX12 { static bool IsShaderResource(const TextureViewType type) { - return (type == TextureViewType::textureBinding); + return (type == TextureViewType::textureBinding) || (type == TextureViewType::storageBinding); } static bool IsUnorderedAccess(const TextureViewType type) { - return (type == TextureViewType::storageBinding); + return (type == TextureViewType::rwStorageBinding); } static bool IsRenderTarget(const TextureViewType type) diff --git a/Engine/Source/RHI-Dummy/Include/RHI/Dummy/CommandRecorder.h b/Engine/Source/RHI-Dummy/Include/RHI/Dummy/CommandRecorder.h index 0d6b007e4..a4687454c 100644 --- a/Engine/Source/RHI-Dummy/Include/RHI/Dummy/CommandRecorder.h +++ b/Engine/Source/RHI-Dummy/Include/RHI/Dummy/CommandRecorder.h @@ -21,6 +21,9 @@ namespace RHI::Dummy { Common::UniquePtr BeginCopyPass() override; Common::UniquePtr BeginComputePass() override; Common::UniquePtr BeginRasterPass(const RasterPassBeginInfo& beginInfo) override; + void WriteTimestamp(QuerySet* querySet, uint32_t queryIndex) override; + void ResetQuerySet(QuerySet* querySet, uint32_t firstQuery, uint32_t queryCount) override; + void ResolveQuery(QuerySet* querySet, uint32_t firstQuery, uint32_t queryCount, Buffer* dstBuffer, size_t dstOffset) override; void End() override; private: @@ -60,10 +63,12 @@ namespace RHI::Dummy { // ComputePassCommandRecorder void SetPipeline(ComputePipeline* pipeline) override; void SetBindGroup(uint8_t layoutIndex, BindGroup *bindGroup) override; + void SetPipelineConstants(uint32_t pipelineConstantIndex, const void* data, uint32_t size) override; void Dispatch(size_t groupCountX, size_t groupCountY, size_t groupCountZ) override; + void DispatchIndirect(Buffer* indirectBuffer, size_t offset) override; void EndPass() override; }; - + class DummyRasterPassCommandRecorder final : public RasterPassCommandRecorder { public: NonCopyable(DummyRasterPassCommandRecorder) @@ -78,6 +83,7 @@ namespace RHI::Dummy { // RasterPassCommandRecorder void SetPipeline(RasterPipeline* pipeline) override; void SetBindGroup(uint8_t layoutIndex, BindGroup* bindGroup) override; + void SetPipelineConstants(uint32_t pipelineConstantIndex, const void* data, uint32_t size) override; void SetIndexBuffer(BufferView* bufferView) override; void SetVertexBuffer(size_t slot, BufferView* bufferView) override; void Draw(size_t vertexCount, size_t instanceCount, size_t firstVertex, size_t firstInstance) override; @@ -91,6 +97,8 @@ namespace RHI::Dummy { void DrawIndexedIndirect(Buffer* indirectBuffer, size_t offset) override; void MultiDrawIndirect(Buffer* indirectBuffer, size_t offset, size_t drawCount) override; void MultiDrawIndexedIndirect(Buffer* indirectBuffer, size_t offset, size_t drawCount) override; + void BeginOcclusionQuery(QuerySet* querySet, uint32_t queryIndex) override; + void EndOcclusionQuery() override; void EndPass() override; }; } diff --git a/Engine/Source/RHI-Dummy/Include/RHI/Dummy/Device.h b/Engine/Source/RHI-Dummy/Include/RHI/Dummy/Device.h index 2f9268f42..9ecd45b72 100644 --- a/Engine/Source/RHI-Dummy/Include/RHI/Dummy/Device.h +++ b/Engine/Source/RHI-Dummy/Include/RHI/Dummy/Device.h @@ -28,13 +28,15 @@ namespace RHI::Dummy { Common::UniquePtr CreateBindGroup(const BindGroupCreateInfo& createInfo) override; Common::UniquePtr CreatePipelineLayout(const PipelineLayoutCreateInfo& createInfo) override; Common::UniquePtr CreateShaderModule(const ShaderModuleCreateInfo& createInfo) override; + Common::UniquePtr CreatePipelineCache(const PipelineCacheCreateInfo& createInfo) override; Common::UniquePtr CreateComputePipeline(const ComputePipelineCreateInfo& createInfo) override; Common::UniquePtr CreateRasterPipeline(const RasterPipelineCreateInfo& createInfo) override; Common::UniquePtr CreateCommandBuffer() override; Common::UniquePtr CreateFence(bool bInitAsSignaled) override; Common::UniquePtr CreateSemaphore() override; + Common::UniquePtr CreateQuerySet(const QuerySetCreateInfo& createInfo) override; - bool CheckSwapChainFormatSupport(Surface *surface, PixelFormat format) override; + bool CheckSwapChainFormatSupport(Surface *surface, PixelFormat format, ColorSpace colorSpace) override; TextureSubResourceCopyFootprint GetTextureSubResourceCopyFootprint(const Texture& texture, const TextureSubResourceInfo& subResourceInfo) override; private: diff --git a/Engine/Source/RHI-Dummy/Include/RHI/Dummy/PipelineCache.h b/Engine/Source/RHI-Dummy/Include/RHI/Dummy/PipelineCache.h new file mode 100644 index 000000000..7286fa127 --- /dev/null +++ b/Engine/Source/RHI-Dummy/Include/RHI/Dummy/PipelineCache.h @@ -0,0 +1,18 @@ +// +// Created by johnk on 2026/7/2. +// + +#pragma once + +#include + +namespace RHI::Dummy { + class DummyPipelineCache final : public PipelineCache { + public: + NonCopyable(DummyPipelineCache) + explicit DummyPipelineCache(const PipelineCacheCreateInfo& createInfo); + ~DummyPipelineCache() override; + + std::vector GetData() override; + }; +} diff --git a/Engine/Source/RHI-Dummy/Include/RHI/Dummy/QuerySet.h b/Engine/Source/RHI-Dummy/Include/RHI/Dummy/QuerySet.h new file mode 100644 index 000000000..c9c3fac37 --- /dev/null +++ b/Engine/Source/RHI-Dummy/Include/RHI/Dummy/QuerySet.h @@ -0,0 +1,16 @@ +// +// Created by johnk on 2026/6/30. +// + +#pragma once + +#include + +namespace RHI::Dummy { + class DummyQuerySet final : public QuerySet { + public: + NonCopyable(DummyQuerySet) + explicit DummyQuerySet(const QuerySetCreateInfo& createInfo); + ~DummyQuerySet() override; + }; +} diff --git a/Engine/Source/RHI-Dummy/Include/RHI/Dummy/Queue.h b/Engine/Source/RHI-Dummy/Include/RHI/Dummy/Queue.h index 24aa57c6b..47a5def2a 100644 --- a/Engine/Source/RHI-Dummy/Include/RHI/Dummy/Queue.h +++ b/Engine/Source/RHI-Dummy/Include/RHI/Dummy/Queue.h @@ -15,5 +15,6 @@ namespace RHI::Dummy { void Submit(RHI::CommandBuffer* commandBuffer, const RHI::QueueSubmitInfo& submitInfo) override; void Flush(RHI::Fence* fenceToSignal) override; + float GetTimestampPeriod() override; }; } diff --git a/Engine/Source/RHI-Dummy/Src/CommandRecorder.cpp b/Engine/Source/RHI-Dummy/Src/CommandRecorder.cpp index 4d891b08b..ada6bbdb2 100644 --- a/Engine/Source/RHI-Dummy/Src/CommandRecorder.cpp +++ b/Engine/Source/RHI-Dummy/Src/CommandRecorder.cpp @@ -71,10 +71,18 @@ namespace RHI::Dummy { { } + void DummyComputePassCommandRecorder::SetPipelineConstants(uint32_t pipelineConstantIndex, const void* data, uint32_t size) + { + } + void DummyComputePassCommandRecorder::Dispatch(size_t groupCountX, size_t groupCountY, size_t groupCountZ) { } + void DummyComputePassCommandRecorder::DispatchIndirect(Buffer* indirectBuffer, size_t offset) + { + } + void DummyComputePassCommandRecorder::EndPass() { } @@ -105,6 +113,10 @@ namespace RHI::Dummy { { } + void DummyRasterPassCommandRecorder::SetPipelineConstants(uint32_t pipelineConstantIndex, const void* data, uint32_t size) + { + } + void DummyRasterPassCommandRecorder::SetIndexBuffer(BufferView* bufferView) { } @@ -157,6 +169,14 @@ namespace RHI::Dummy { { } + void DummyRasterPassCommandRecorder::BeginOcclusionQuery(QuerySet* querySet, uint32_t queryIndex) + { + } + + void DummyRasterPassCommandRecorder::EndOcclusionQuery() + { + } + void DummyRasterPassCommandRecorder::EndPass() { } @@ -195,6 +215,18 @@ namespace RHI::Dummy { return Common::UniquePtr(new DummyRasterPassCommandRecorder(dummyCommandBuffer)); } + void DummyCommandRecorder::WriteTimestamp(QuerySet* querySet, uint32_t queryIndex) + { + } + + void DummyCommandRecorder::ResetQuerySet(QuerySet* querySet, uint32_t firstQuery, uint32_t queryCount) + { + } + + void DummyCommandRecorder::ResolveQuery(QuerySet* querySet, uint32_t firstQuery, uint32_t queryCount, Buffer* dstBuffer, size_t dstOffset) + { + } + void DummyCommandRecorder::End() { } diff --git a/Engine/Source/RHI-Dummy/Src/Device.cpp b/Engine/Source/RHI-Dummy/Src/Device.cpp index c4cbd46d4..6c1a0b06c 100644 --- a/Engine/Source/RHI-Dummy/Src/Device.cpp +++ b/Engine/Source/RHI-Dummy/Src/Device.cpp @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include namespace RHI::Dummy { @@ -114,7 +116,17 @@ namespace RHI::Dummy { return { new DummySemaphore(*this) }; } - bool DummyDevice::CheckSwapChainFormatSupport(Surface* surface, PixelFormat format) + Common::UniquePtr DummyDevice::CreateQuerySet(const QuerySetCreateInfo& createInfo) + { + return { new DummyQuerySet(createInfo) }; + } + + Common::UniquePtr DummyDevice::CreatePipelineCache(const PipelineCacheCreateInfo& createInfo) + { + return { new DummyPipelineCache(createInfo) }; + } + + bool DummyDevice::CheckSwapChainFormatSupport(Surface* surface, PixelFormat format, ColorSpace colorSpace) { return true; } diff --git a/Engine/Source/RHI-Dummy/Src/PipelineCache.cpp b/Engine/Source/RHI-Dummy/Src/PipelineCache.cpp new file mode 100644 index 000000000..b4b4f5068 --- /dev/null +++ b/Engine/Source/RHI-Dummy/Src/PipelineCache.cpp @@ -0,0 +1,19 @@ +// +// Created by johnk on 2026/7/2. +// + +#include + +namespace RHI::Dummy { + DummyPipelineCache::DummyPipelineCache(const PipelineCacheCreateInfo& createInfo) + : PipelineCache(createInfo) + { + } + + DummyPipelineCache::~DummyPipelineCache() = default; + + std::vector DummyPipelineCache::GetData() + { + return {}; + } +} diff --git a/Engine/Source/RHI-Dummy/Src/QuerySet.cpp b/Engine/Source/RHI-Dummy/Src/QuerySet.cpp new file mode 100644 index 000000000..577b4809b --- /dev/null +++ b/Engine/Source/RHI-Dummy/Src/QuerySet.cpp @@ -0,0 +1,14 @@ +// +// Created by johnk on 2026/6/30. +// + +#include + +namespace RHI::Dummy { + DummyQuerySet::DummyQuerySet(const QuerySetCreateInfo& createInfo) + : QuerySet(createInfo) + { + } + + DummyQuerySet::~DummyQuerySet() = default; +} diff --git a/Engine/Source/RHI-Dummy/Src/Queue.cpp b/Engine/Source/RHI-Dummy/Src/Queue.cpp index 48d3a8662..3f6a3660b 100644 --- a/Engine/Source/RHI-Dummy/Src/Queue.cpp +++ b/Engine/Source/RHI-Dummy/Src/Queue.cpp @@ -16,4 +16,9 @@ namespace RHI::Dummy { void DummyQueue::Flush(RHI::Fence* fenceToSignal) { } + + float DummyQueue::GetTimestampPeriod() + { + return 1.0f; + } } diff --git a/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/CommandRecorder.h b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/CommandRecorder.h index 8a297530e..ff8a83fcf 100644 --- a/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/CommandRecorder.h +++ b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/CommandRecorder.h @@ -14,6 +14,7 @@ namespace RHI::Vulkan { class VulkanCommandBuffer; class VulkanRasterPipeline; class VulkanComputePipeline; + class VulkanQuerySet; class VulkanCommandRecorder final : public CommandRecorder { public: @@ -27,6 +28,9 @@ namespace RHI::Vulkan { Common::UniquePtr BeginCopyPass() override; Common::UniquePtr BeginComputePass() override; Common::UniquePtr BeginRasterPass(const RasterPassBeginInfo& inBeginInfo) override; + void WriteTimestamp(QuerySet* inQuerySet, uint32_t inQueryIndex) override; + void ResetQuerySet(QuerySet* inQuerySet, uint32_t inFirstQuery, uint32_t inQueryCount) override; + void ResolveQuery(QuerySet* inQuerySet, uint32_t inFirstQuery, uint32_t inQueryCount, Buffer* inDstBuffer, size_t inDstOffset) override; void End() override; private: @@ -72,7 +76,9 @@ namespace RHI::Vulkan { // ComputePassCommandRecorder void SetPipeline(ComputePipeline* inPipeline) override; void SetBindGroup(uint8_t inLayoutIndex, BindGroup* inBindGroup) override; + void SetPipelineConstants(uint32_t inPipelineConstantIndex, const void* inData, uint32_t inSize) override; void Dispatch(size_t inGroupCountX, size_t inGroupCountY, size_t inGroupCountZ) override; + void DispatchIndirect(Buffer* inIndirectBuffer, size_t inOffset) override; void EndPass() override; private: @@ -96,6 +102,7 @@ namespace RHI::Vulkan { // RasterPassCommandRecorder void SetPipeline(RasterPipeline* inPipeline) override; void SetBindGroup(uint8_t inLayoutIndex, BindGroup* inBindGroup) override; + void SetPipelineConstants(uint32_t inPipelineConstantIndex, const void* inData, uint32_t inSize) override; void SetIndexBuffer(BufferView* inBufferView) override; void SetVertexBuffer(size_t inSlot, BufferView* inBufferView) override; void Draw(size_t inVertexCount, size_t inInstanceCount, size_t inFirstVertex, size_t inFirstInstance) override; @@ -109,6 +116,8 @@ namespace RHI::Vulkan { void DrawIndexedIndirect(Buffer* inIndirectBuffer, size_t inOffset) override; void MultiDrawIndirect(Buffer* inIndirectBuffer, size_t inOffset, size_t inDrawCount) override; void MultiDrawIndexedIndirect(Buffer* inIndirectBuffer, size_t inOffset, size_t inDrawCount) override; + void BeginOcclusionQuery(QuerySet* inQuerySet, uint32_t inQueryIndex) override; + void EndOcclusionQuery() override; void EndPass() override; private: @@ -116,6 +125,8 @@ namespace RHI::Vulkan { VulkanCommandRecorder& commandRecorder; VulkanCommandBuffer& commandBuffer; VulkanRasterPipeline* rasterPipeline; + VulkanQuerySet* activeOcclusionQuerySet; + uint32_t activeOcclusionQueryIndex; }; } diff --git a/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Common.h b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Common.h index 105f980c6..7454d1ad7 100644 --- a/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Common.h +++ b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Common.h @@ -193,6 +193,7 @@ namespace RHI::Vulkan { ECIMPL_ITEM(BindingType::sampler, VK_DESCRIPTOR_TYPE_SAMPLER) ECIMPL_ITEM(BindingType::texture, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE) ECIMPL_ITEM(BindingType::storageTexture, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) + ECIMPL_ITEM(BindingType::rwStorageTexture, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) ECIMPL_END(VkDescriptorType) ECIMPL_BEGIN(AddressMode, VkSamplerAddressMode) @@ -261,9 +262,20 @@ namespace RHI::Vulkan { ECIMPL_BEGIN(PresentMode, VkPresentModeKHR) ECIMPL_ITEM(PresentMode::immediately, VK_PRESENT_MODE_IMMEDIATE_KHR) ECIMPL_ITEM(PresentMode::vsync, VK_PRESENT_MODE_FIFO_KHR) - ECIMPL_ITEM(PresentMode::max, VK_PRESENT_MODE_IMMEDIATE_KHR) // TODO Set the default present mode to immediate? + ECIMPL_ITEM(PresentMode::mailbox, VK_PRESENT_MODE_MAILBOX_KHR) + ECIMPL_ITEM(PresentMode::fifoRelaxed, VK_PRESENT_MODE_FIFO_RELAXED_KHR) ECIMPL_END(VkPresentModeKHR) + ECIMPL_BEGIN(ColorSpace, VkColorSpaceKHR) + ECIMPL_ITEM(ColorSpace::srgbNonLinear, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) + ECIMPL_ITEM(ColorSpace::hdr10St2084, VK_COLOR_SPACE_HDR10_ST2084_EXT) + ECIMPL_END(VkColorSpaceKHR) + + ECIMPL_BEGIN(QueryType, VkQueryType) + ECIMPL_ITEM(QueryType::occlusion, VK_QUERY_TYPE_OCCLUSION) + ECIMPL_ITEM(QueryType::timestamp, VK_QUERY_TYPE_TIMESTAMP) + ECIMPL_END(VkQueryType) + ECIMPL_BEGIN(TextureAspect, VkImageAspectFlags) ECIMPL_ITEM(TextureAspect::color, VK_IMAGE_ASPECT_COLOR_BIT) ECIMPL_ITEM(TextureAspect::depth, VK_IMAGE_ASPECT_DEPTH_BIT) @@ -289,6 +301,7 @@ namespace RHI::Vulkan { FCIMPL_ITEM(BufferUsageBits::storage, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) FCIMPL_ITEM(BufferUsageBits::rwStorage, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) FCIMPL_ITEM(BufferUsageBits::indirect, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT) + FCIMPL_ITEM(BufferUsageBits::queryResolve, VK_BUFFER_USAGE_TRANSFER_DST_BIT) FCIMPL_END(VkBufferUsageFlagBits) FCIMPL_BEGIN(TextureUsageFlags, VkImageUsageFlags) @@ -296,6 +309,7 @@ namespace RHI::Vulkan { FCIMPL_ITEM(TextureUsageBits::copyDst, VK_IMAGE_USAGE_TRANSFER_DST_BIT) FCIMPL_ITEM(TextureUsageBits::textureBinding, VK_IMAGE_USAGE_SAMPLED_BIT) FCIMPL_ITEM(TextureUsageBits::storageBinding, VK_IMAGE_USAGE_STORAGE_BIT) + FCIMPL_ITEM(TextureUsageBits::rwStorageBinding, VK_IMAGE_USAGE_STORAGE_BIT) FCIMPL_ITEM(TextureUsageBits::renderAttachment, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) FCIMPL_ITEM(TextureUsageBits::depthStencilAttachment, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) FCIMPL_END(VkImageUsageFlagBits) diff --git a/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Device.h b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Device.h index 7071a00fa..b79759d98 100644 --- a/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Device.h +++ b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Device.h @@ -35,13 +35,15 @@ namespace RHI::Vulkan { Common::UniquePtr CreateBindGroup(const BindGroupCreateInfo& inCreateInfo) override; Common::UniquePtr CreatePipelineLayout(const PipelineLayoutCreateInfo& inCreateInfo) override; Common::UniquePtr CreateShaderModule(const ShaderModuleCreateInfo& inCreateInfo) override; + Common::UniquePtr CreatePipelineCache(const PipelineCacheCreateInfo& inCreateInfo) override; Common::UniquePtr CreateComputePipeline(const ComputePipelineCreateInfo& inCreateInfo) override; Common::UniquePtr CreateRasterPipeline(const RasterPipelineCreateInfo& inCreateInfo) override; Common::UniquePtr CreateCommandBuffer() override; Common::UniquePtr CreateFence(bool initAsSignaled) override; Common::UniquePtr CreateSemaphore() override; + Common::UniquePtr CreateQuerySet(const QuerySetCreateInfo& inCreateInfo) override; - bool CheckSwapChainFormatSupport(Surface* inSurface, PixelFormat inFormat) override; + bool CheckSwapChainFormatSupport(Surface* inSurface, PixelFormat inFormat, ColorSpace inColorSpace) override; TextureSubResourceCopyFootprint GetTextureSubResourceCopyFootprint(const Texture& texture, const TextureSubResourceInfo& subResourceInfo) override; VkDevice GetNative() const; diff --git a/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Instance.h b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Instance.h index 3ff02b2b8..c844928c6 100644 --- a/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Instance.h +++ b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Instance.h @@ -53,6 +53,7 @@ namespace RHI::Vulkan { VkDebugUtilsMessengerEXT nativeDebugMessenger; #endif VkInstance nativeInstance; + std::vector enabledExtensionNames; std::vector nativePhysicalDevices; std::vector> gpus; std::unordered_map dynamicFuncPointers; diff --git a/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/PipelineCache.h b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/PipelineCache.h new file mode 100644 index 000000000..0f05c1dc6 --- /dev/null +++ b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/PipelineCache.h @@ -0,0 +1,29 @@ +// +// Created by johnk on 2026/7/2. +// + +#pragma once + +#include + +#include + +namespace RHI::Vulkan { + class VulkanDevice; + + class VulkanPipelineCache final : public PipelineCache { + public: + NonCopyable(VulkanPipelineCache) + VulkanPipelineCache(VulkanDevice& inDevice, const PipelineCacheCreateInfo& inCreateInfo); + ~VulkanPipelineCache() override; + + std::vector GetData() override; + VkPipelineCache GetNative() const; + + private: + void CreateNativePipelineCache(const PipelineCacheCreateInfo& inCreateInfo); + + VulkanDevice& device; + VkPipelineCache nativePipelineCache; + }; +} diff --git a/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/PipelineLayout.h b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/PipelineLayout.h index 68f3a1243..6cd70ad80 100644 --- a/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/PipelineLayout.h +++ b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/PipelineLayout.h @@ -4,6 +4,8 @@ #pragma once +#include + #include #include @@ -18,11 +20,13 @@ namespace RHI::Vulkan { ~VulkanPipelineLayout() override; VkPipelineLayout GetNative() const; + const VkPushConstantRange& GetPushConstantRange(uint32_t inPipelineConstantIndex) const; private: void CreateNativePipelineLayout(const PipelineLayoutCreateInfo& inCreateInfo); VulkanDevice& device; VkPipelineLayout nativePipelineLayout; + std::vector pushConstantRanges; }; } \ No newline at end of file diff --git a/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/QuerySet.h b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/QuerySet.h new file mode 100644 index 000000000..86b9b3aee --- /dev/null +++ b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/QuerySet.h @@ -0,0 +1,28 @@ +// +// Created by johnk on 2026/6/30. +// + +#pragma once + +#include + +#include + +namespace RHI::Vulkan { + class VulkanDevice; + + class VulkanQuerySet final : public QuerySet { + public: + NonCopyable(VulkanQuerySet) + VulkanQuerySet(VulkanDevice& inDevice, const QuerySetCreateInfo& inCreateInfo); + ~VulkanQuerySet() override; + + VkQueryPool GetNative() const; + + private: + void CreateNativeQueryPool(const QuerySetCreateInfo& inCreateInfo); + + VulkanDevice& device; + VkQueryPool nativeQueryPool; + }; +} diff --git a/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Queue.h b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Queue.h index 082d32d82..6c288ea2b 100644 --- a/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Queue.h +++ b/Engine/Source/RHI-Vulkan/Include/RHI/Vulkan/Queue.h @@ -20,10 +20,12 @@ namespace RHI::Vulkan { void Submit(CommandBuffer* inCmdBuffer, const QueueSubmitInfo& inSubmitInfo) override; void Flush(Fence* inFenceToSignal) override; + float GetTimestampPeriod() override; VkQueue GetNative() const; private: + VulkanDevice& device; VkQueue nativeQueue; }; } diff --git a/Engine/Source/RHI-Vulkan/Src/BindGroup.cpp b/Engine/Source/RHI-Vulkan/Src/BindGroup.cpp index a0d24505f..008b97929 100644 --- a/Engine/Source/RHI-Vulkan/Src/BindGroup.cpp +++ b/Engine/Source/RHI-Vulkan/Src/BindGroup.cpp @@ -87,7 +87,8 @@ namespace RHI::Vulkan { bufferInfosNum++; } else if (entry.binding.type == BindingType::sampler || entry.binding.type == BindingType::texture - ||entry.binding.type == BindingType::storageTexture) { + || entry.binding.type == BindingType::storageTexture + || entry.binding.type == BindingType::rwStorageTexture) { imageInfosNum++; } } @@ -121,11 +122,15 @@ namespace RHI::Vulkan { imageInfos.back().sampler = sampler->GetNative(); descriptorWrites[i].pImageInfo = &imageInfos.back(); - } else if (entry.binding.type == BindingType::texture || entry.binding.type == BindingType::storageTexture) { + } else if (entry.binding.type == BindingType::texture + || entry.binding.type == BindingType::storageTexture + || entry.binding.type == BindingType::rwStorageTexture) { const auto* textureView = static_cast(std::get(entry.entity)); imageInfos.emplace_back(); - imageInfos.back().imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfos.back().imageLayout = entry.binding.type == BindingType::texture + ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL + : VK_IMAGE_LAYOUT_GENERAL; imageInfos.back().imageView = textureView->GetNative(); descriptorWrites[i].pImageInfo = &imageInfos.back(); diff --git a/Engine/Source/RHI-Vulkan/Src/CommandRecorder.cpp b/Engine/Source/RHI-Vulkan/Src/CommandRecorder.cpp index dfb093bac..457c3a05e 100644 --- a/Engine/Source/RHI-Vulkan/Src/CommandRecorder.cpp +++ b/Engine/Source/RHI-Vulkan/Src/CommandRecorder.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include namespace RHI::Vulkan { @@ -71,7 +72,8 @@ namespace RHI::Vulkan { { TextureState::copyDst, VK_ACCESS_TRANSFER_WRITE_BIT }, { TextureState::shaderReadOnly, VK_ACCESS_SHADER_READ_BIT }, { TextureState::renderTarget, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT }, - { TextureState::storage, VK_ACCESS_SHADER_WRITE_BIT }, + { TextureState::storage, VK_ACCESS_SHADER_READ_BIT }, + { TextureState::rwStorage, VK_ACCESS_SHADER_WRITE_BIT }, { TextureState::depthStencilReadonly, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT }, { TextureState::depthStencilWrite, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT }, { TextureState::present, VK_ACCESS_MEMORY_READ_BIT } @@ -88,6 +90,7 @@ namespace RHI::Vulkan { { TextureState::shaderReadOnly, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT }, { TextureState::renderTarget, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }, { TextureState::storage, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT }, + { TextureState::rwStorage, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT }, { TextureState::depthStencilReadonly, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT }, { TextureState::depthStencilWrite, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT }, { TextureState::present, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT } @@ -104,6 +107,7 @@ namespace RHI::Vulkan { { TextureState::shaderReadOnly, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT }, { TextureState::renderTarget, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }, { TextureState::storage, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT }, + { TextureState::rwStorage, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT }, { TextureState::depthStencilReadonly, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT }, { TextureState::depthStencilWrite, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT }, { TextureState::present, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT } @@ -120,6 +124,7 @@ namespace RHI::Vulkan { { TextureState::shaderReadOnly, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL }, { TextureState::renderTarget, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }, { TextureState::storage, VK_IMAGE_LAYOUT_GENERAL }, + { TextureState::rwStorage, VK_IMAGE_LAYOUT_GENERAL }, { TextureState::depthStencilReadonly, VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL }, { TextureState::depthStencilWrite, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }, { TextureState::present, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR } @@ -244,6 +249,28 @@ namespace RHI::Vulkan { return Common::UniquePtr(new VulkanRasterPassCommandRecorder(device, *this, commandBuffer, inBeginInfo)); } + void VulkanCommandRecorder::WriteTimestamp(QuerySet* inQuerySet, const uint32_t inQueryIndex) + { + const auto* querySet = static_cast(inQuerySet); + vkCmdWriteTimestamp(commandBuffer.GetNative(), VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, querySet->GetNative(), inQueryIndex); + } + + void VulkanCommandRecorder::ResetQuerySet(QuerySet* inQuerySet, const uint32_t inFirstQuery, const uint32_t inQueryCount) + { + const auto* querySet = static_cast(inQuerySet); + vkCmdResetQueryPool(commandBuffer.GetNative(), querySet->GetNative(), inFirstQuery, inQueryCount); + } + + void VulkanCommandRecorder::ResolveQuery(QuerySet* inQuerySet, const uint32_t inFirstQuery, const uint32_t inQueryCount, Buffer* inDstBuffer, const size_t inDstOffset) + { + const auto* querySet = static_cast(inQuerySet); + const auto* dstBuffer = static_cast(inDstBuffer); + vkCmdCopyQueryPoolResults( + commandBuffer.GetNative(), querySet->GetNative(), inFirstQuery, inQueryCount, + dstBuffer->GetNative(), inDstOffset, sizeof(uint64_t), + VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); + } + void VulkanCommandRecorder::End() { vkEndCommandBuffer(commandBuffer.GetNative()); @@ -365,11 +392,24 @@ namespace RHI::Vulkan { vkCmdBindDescriptorSets(commandBuffer.GetNative(), VK_PIPELINE_BIND_POINT_COMPUTE, layout, inLayoutIndex, 1, &descriptorSet, 0, nullptr); } + void VulkanComputePassCommandRecorder::SetPipelineConstants(uint32_t inPipelineConstantIndex, const void* inData, uint32_t inSize) + { + const auto* pipelineLayout = computePipeline->GetPipelineLayout(); + const auto& range = pipelineLayout->GetPushConstantRange(inPipelineConstantIndex); + vkCmdPushConstants(commandBuffer.GetNative(), pipelineLayout->GetNative(), range.stageFlags, range.offset, inSize, inData); + } + void VulkanComputePassCommandRecorder::Dispatch(size_t inGroupCountX, size_t inGroupCountY, size_t inGroupCountZ) { vkCmdDispatch(commandBuffer.GetNative(), inGroupCountX, inGroupCountY, inGroupCountZ); } + void VulkanComputePassCommandRecorder::DispatchIndirect(Buffer* inIndirectBuffer, const size_t inOffset) + { + const auto* indirectBuffer = static_cast(inIndirectBuffer); + vkCmdDispatchIndirect(commandBuffer.GetNative(), indirectBuffer->GetNative(), inOffset); + } + void VulkanComputePassCommandRecorder::EndPass() { @@ -380,6 +420,8 @@ namespace RHI::Vulkan { , commandRecorder(inCmdRecorder) , commandBuffer(inCmdBuffer) , rasterPipeline(nullptr) + , activeOcclusionQuerySet(nullptr) + , activeOcclusionQueryIndex(0) { std::vector colorAttachmentInfos(inBeginInfo.colorAttachments.size()); for (size_t i = 0; i < inBeginInfo.colorAttachments.size(); i++) @@ -425,7 +467,11 @@ namespace RHI::Vulkan { renderingInfo.pDepthAttachment = &depthAttachmentInfo; - if (!inBeginInfo.depthStencilAttachment->depthReadOnly) { + // depth-only formats must not present a stencil attachment, otherwise validation requires the + // pipeline's (undefined) stencil format to match the view format + const auto depthStencilFormat = depthStencilTextureView->GetTexture().GetCreateInfo().format; + const bool hasStencil = depthStencilFormat == PixelFormat::d32FloatS8Uint || depthStencilFormat == PixelFormat::d24UnormS8Uint; + if (hasStencil && !inBeginInfo.depthStencilAttachment->depthReadOnly) { stencilAttachmentInfo.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; stencilAttachmentInfo.imageView = depthStencilTextureView->GetNative(); stencilAttachmentInfo.imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; @@ -475,6 +521,13 @@ namespace RHI::Vulkan { vkCmdBindDescriptorSets(commandBuffer.GetNative(), VK_PIPELINE_BIND_POINT_GRAPHICS, layout, inLayoutIndex, 1, &descriptorSet, 0, nullptr); } + void VulkanRasterPassCommandRecorder::SetPipelineConstants(uint32_t inPipelineConstantIndex, const void* inData, uint32_t inSize) + { + const auto* pipelineLayout = rasterPipeline->GetPipelineLayout(); + const auto& range = pipelineLayout->GetPushConstantRange(inPipelineConstantIndex); + vkCmdPushConstants(commandBuffer.GetNative(), pipelineLayout->GetNative(), range.stageFlags, range.offset, inSize, inData); + } + void VulkanRasterPassCommandRecorder::SetIndexBuffer(BufferView *inBufferView) { const auto* mBufferView = static_cast(inBufferView); @@ -568,6 +621,19 @@ namespace RHI::Vulkan { vkCmdDrawIndexedIndirect(commandBuffer.GetNative(), indirectBuffer->GetNative(), inOffset, inDrawCount, sizeof(DrawIndexedIndirectArguments)); } + void VulkanRasterPassCommandRecorder::BeginOcclusionQuery(QuerySet* inQuerySet, const uint32_t inQueryIndex) + { + activeOcclusionQuerySet = static_cast(inQuerySet); + activeOcclusionQueryIndex = inQueryIndex; + vkCmdBeginQuery(commandBuffer.GetNative(), activeOcclusionQuerySet->GetNative(), inQueryIndex, VK_QUERY_CONTROL_PRECISE_BIT); + } + + void VulkanRasterPassCommandRecorder::EndOcclusionQuery() + { + Assert(activeOcclusionQuerySet != nullptr); + vkCmdEndQuery(commandBuffer.GetNative(), activeOcclusionQuerySet->GetNative(), activeOcclusionQueryIndex); + } + void VulkanRasterPassCommandRecorder::EndPass() { auto* pfn = device.GetGpu().GetInstance().FindOrGetTypedDynamicFuncPointer("vkCmdEndRenderingKHR"); diff --git a/Engine/Source/RHI-Vulkan/Src/Device.cpp b/Engine/Source/RHI-Vulkan/Src/Device.cpp index 1826cf856..b4e70a362 100644 --- a/Engine/Source/RHI-Vulkan/Src/Device.cpp +++ b/Engine/Source/RHI-Vulkan/Src/Device.cpp @@ -22,6 +22,8 @@ #include #include #include +#include +#include namespace RHI::Vulkan { const std::vector requiredExtensions = { @@ -35,9 +37,6 @@ namespace RHI::Vulkan { #endif }; - const std::vector requiredValidationLayers = { - "VK_LAYER_KHRONOS_validation" - }; } namespace RHI::Vulkan { @@ -147,10 +146,19 @@ namespace RHI::Vulkan { return { new VulkanSemaphore(*this) }; } - bool VulkanDevice::CheckSwapChainFormatSupport(Surface* inSurface, const PixelFormat inFormat) + Common::UniquePtr VulkanDevice::CreateQuerySet(const QuerySetCreateInfo& inCreateInfo) + { + return { new VulkanQuerySet(*this, inCreateInfo) }; + } + + Common::UniquePtr VulkanDevice::CreatePipelineCache(const PipelineCacheCreateInfo& inCreateInfo) + { + return { new VulkanPipelineCache(*this, inCreateInfo) }; + } + + bool VulkanDevice::CheckSwapChainFormatSupport(Surface* inSurface, const PixelFormat inFormat, const ColorSpace inColorSpace) { const auto* vkSurface = static_cast(inSurface); - VkColorSpaceKHR colorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; uint32_t formatCount = 0; std::vector surfaceFormats; @@ -161,7 +169,7 @@ namespace RHI::Vulkan { const auto iter = std::ranges::find_if( surfaceFormats, - [format = EnumCast(inFormat), colorSpace](const VkSurfaceFormatKHR surfaceFormat) { + [format = EnumCast(inFormat), colorSpace = EnumCast(inColorSpace)](const VkSurfaceFormatKHR surfaceFormat) { return format == surfaceFormat.format && colorSpace == surfaceFormat.colorSpace; }); return iter != surfaceFormats.end(); @@ -248,6 +256,7 @@ namespace RHI::Vulkan { VkPhysicalDeviceFeatures deviceFeatures = {}; deviceFeatures.samplerAnisotropy = VK_TRUE; + deviceFeatures.occlusionQueryPrecise = VK_TRUE; VkDeviceCreateInfo deviceCreateInfo = {}; deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; @@ -271,11 +280,6 @@ namespace RHI::Vulkan { deviceCreateInfo.ppEnabledExtensionNames = requiredExtensions.data(); deviceCreateInfo.enabledExtensionCount = static_cast(requiredExtensions.size()); -#if BUILD_CONFIG_DEBUG - deviceCreateInfo.enabledLayerCount = static_cast(requiredValidationLayers.size()); - deviceCreateInfo.ppEnabledLayerNames = requiredValidationLayers.data(); -#endif - Assert(vkCreateDevice(gpu.GetNative(), &deviceCreateInfo, nullptr, &nativeDevice) == VK_SUCCESS); } diff --git a/Engine/Source/RHI-Vulkan/Src/Instance.cpp b/Engine/Source/RHI-Vulkan/Src/Instance.cpp index 803d12e33..edcbf9718 100644 --- a/Engine/Source/RHI-Vulkan/Src/Instance.cpp +++ b/Engine/Source/RHI-Vulkan/Src/Instance.cpp @@ -31,6 +31,12 @@ namespace RHI::Vulkan { #endif }; + // Optional instance extensions: enabled only when the driver reports support. VK_EXT_swapchain_colorspace is what makes + // vkGetPhysicalDeviceSurfaceFormatsKHR report the HDR color spaces (e.g. HDR10 ST.2084) in addition to plain sRGB. + static std::vector optionalExtensionNames = { + "VK_EXT_swapchain_colorspace" + }; + #if BUILD_CONFIG_DEBUG static VKAPI_ATTR VkBool32 VKAPI_CALL DebugCallback( VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, @@ -117,14 +123,23 @@ namespace RHI::Vulkan { std::vector supportedExtensions(supportedExtensionCount); vkEnumerateInstanceExtensionProperties(nullptr, &supportedExtensionCount, supportedExtensions.data()); + const auto isExtensionSupported = [&supportedExtensions](const char* inName) -> bool { + return std::ranges::find_if( + supportedExtensions, + [inName](const auto& elem) -> bool { return std::string(inName) == elem.extensionName; } + ) != supportedExtensions.end(); + }; + for (auto&& requiredExtensionName : requiredExtensionNames) { - if (const auto iter = std::ranges::find_if( - supportedExtensions, - [&requiredExtensionName](const auto& elem) -> bool { return std::string(requiredExtensionName) == elem.extensionName; } - ); - iter == supportedExtensions.end()) { + if (!isExtensionSupported(requiredExtensionName)) { QuickFailWithReason("required vulkan extensions is not support"); } + enabledExtensionNames.emplace_back(requiredExtensionName); + } + for (auto&& optionalExtensionName : optionalExtensionNames) { + if (isExtensionSupported(optionalExtensionName)) { + enabledExtensionNames.emplace_back(optionalExtensionName); + } } } @@ -140,8 +155,8 @@ namespace RHI::Vulkan { VkInstanceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &applicationInfo; - createInfo.enabledExtensionCount = requiredExtensionNames.size(); - createInfo.ppEnabledExtensionNames = requiredExtensionNames.data(); + createInfo.enabledExtensionCount = enabledExtensionNames.size(); + createInfo.ppEnabledExtensionNames = enabledExtensionNames.data(); #if BUILD_CONFIG_DEBUG createInfo.enabledLayerCount = requiredLayerNames.size(); createInfo.ppEnabledLayerNames = requiredLayerNames.data(); diff --git a/Engine/Source/RHI-Vulkan/Src/Pipeline.cpp b/Engine/Source/RHI-Vulkan/Src/Pipeline.cpp index a1536a5c9..b7704a8dd 100644 --- a/Engine/Source/RHI-Vulkan/Src/Pipeline.cpp +++ b/Engine/Source/RHI-Vulkan/Src/Pipeline.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include namespace RHI::Vulkan { @@ -255,7 +256,10 @@ namespace RHI::Vulkan { pipelineCreateInfo.pVertexInputState = &vtxInput; pipelineCreateInfo.pNext = &pipelineRenderingCreateInfo; - Assert(vkCreateGraphicsPipelines(device.GetNative(), VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr, &nativePipeline) == VK_SUCCESS); + const VkPipelineCache nativePipelineCache = inCreateInfo.pipelineCache != nullptr + ? static_cast(inCreateInfo.pipelineCache)->GetNative() + : VK_NULL_HANDLE; + Assert(vkCreateGraphicsPipelines(device.GetNative(), nativePipelineCache, 1, &pipelineCreateInfo, nullptr, &nativePipeline) == VK_SUCCESS); #if BUILD_CONFIG_DEBUG if (!inCreateInfo.debugName.empty()) { @@ -310,7 +314,10 @@ namespace RHI::Vulkan { pipelineInfo.layout = pipelineLayout->GetNative(); pipelineInfo.stage = stageInfo; - Assert(vkCreateComputePipelines(device.GetNative(), VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &nativePipeline) == VK_SUCCESS); + const VkPipelineCache nativePipelineCache = inCreateInfo.pipelineCache != nullptr + ? static_cast(inCreateInfo.pipelineCache)->GetNative() + : VK_NULL_HANDLE; + Assert(vkCreateComputePipelines(device.GetNative(), nativePipelineCache, 1, &pipelineInfo, nullptr, &nativePipeline) == VK_SUCCESS); } VkPipeline VulkanComputePipeline::GetNative() const diff --git a/Engine/Source/RHI-Vulkan/Src/PipelineCache.cpp b/Engine/Source/RHI-Vulkan/Src/PipelineCache.cpp new file mode 100644 index 000000000..366dbdb4a --- /dev/null +++ b/Engine/Source/RHI-Vulkan/Src/PipelineCache.cpp @@ -0,0 +1,56 @@ +// +// Created by johnk on 2026/7/2. +// + +#include +#include +#include +#include + +namespace RHI::Vulkan { + VulkanPipelineCache::VulkanPipelineCache(VulkanDevice& inDevice, const PipelineCacheCreateInfo& inCreateInfo) + : PipelineCache(inCreateInfo) + , device(inDevice) + , nativePipelineCache(VK_NULL_HANDLE) + { + CreateNativePipelineCache(inCreateInfo); + } + + VulkanPipelineCache::~VulkanPipelineCache() + { + if (nativePipelineCache != VK_NULL_HANDLE) { + vkDestroyPipelineCache(device.GetNative(), nativePipelineCache, nullptr); + } + } + + std::vector VulkanPipelineCache::GetData() + { + size_t size = 0; + Assert(vkGetPipelineCacheData(device.GetNative(), nativePipelineCache, &size, nullptr) == VK_SUCCESS); + + std::vector result(size); + Assert(vkGetPipelineCacheData(device.GetNative(), nativePipelineCache, &size, result.data()) == VK_SUCCESS); + return result; + } + + VkPipelineCache VulkanPipelineCache::GetNative() const + { + return nativePipelineCache; + } + + void VulkanPipelineCache::CreateNativePipelineCache(const PipelineCacheCreateInfo& inCreateInfo) + { + VkPipelineCacheCreateInfo cacheInfo = {}; + cacheInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; + cacheInfo.initialDataSize = inCreateInfo.initialDataSize; + cacheInfo.pInitialData = inCreateInfo.initialData; + + Assert(vkCreatePipelineCache(device.GetNative(), &cacheInfo, nullptr, &nativePipelineCache) == VK_SUCCESS); + +#if BUILD_CONFIG_DEBUG + if (!inCreateInfo.debugName.empty()) { + device.SetObjectName(VK_OBJECT_TYPE_PIPELINE_CACHE, reinterpret_cast(nativePipelineCache), inCreateInfo.debugName.c_str()); + } +#endif + } +} diff --git a/Engine/Source/RHI-Vulkan/Src/PipelineLayout.cpp b/Engine/Source/RHI-Vulkan/Src/PipelineLayout.cpp index bbe9c031b..1a2ddd528 100644 --- a/Engine/Source/RHI-Vulkan/Src/PipelineLayout.cpp +++ b/Engine/Source/RHI-Vulkan/Src/PipelineLayout.cpp @@ -28,6 +28,11 @@ namespace RHI::Vulkan { return nativePipelineLayout; } + const VkPushConstantRange& VulkanPipelineLayout::GetPushConstantRange(const uint32_t inPipelineConstantIndex) const + { + return pushConstantRanges[inPipelineConstantIndex]; + } + void VulkanPipelineLayout::CreateNativePipelineLayout(const PipelineLayoutCreateInfo& inCreateInfo) { std::vector setLayouts(inCreateInfo.bindGroupLayouts.size()); @@ -36,20 +41,20 @@ namespace RHI::Vulkan { setLayouts[i] = vulkanBindGroup->GetNative(); } - std::vector pushConstants(inCreateInfo.pipelineConstantLayouts.size()); + pushConstantRanges.resize(inCreateInfo.pipelineConstantLayouts.size()); for (uint32_t i = 0; i < inCreateInfo.pipelineConstantLayouts.size(); ++i) { const auto& constantInfo = inCreateInfo.pipelineConstantLayouts[i]; - pushConstants[i].stageFlags = FlagsCast(constantInfo.stageFlags); - pushConstants[i].offset = constantInfo.offset; - pushConstants[i].size = constantInfo.size; + pushConstantRanges[i].stageFlags = FlagsCast(constantInfo.stageFlags); + pushConstantRanges[i].offset = std::get(constantInfo.platformBinding).offset; + pushConstantRanges[i].size = constantInfo.size; } VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = setLayouts.size(); pipelineLayoutInfo.pSetLayouts = setLayouts.data(); - pipelineLayoutInfo.pushConstantRangeCount = pushConstants.size(); - pipelineLayoutInfo.pPushConstantRanges = pushConstants.data(); + pipelineLayoutInfo.pushConstantRangeCount = pushConstantRanges.size(); + pipelineLayoutInfo.pPushConstantRanges = pushConstantRanges.data(); Assert(vkCreatePipelineLayout(device.GetNative(), &pipelineLayoutInfo, nullptr, &nativePipelineLayout) == VK_SUCCESS); #if BUILD_CONFIG_DEBUG diff --git a/Engine/Source/RHI-Vulkan/Src/QuerySet.cpp b/Engine/Source/RHI-Vulkan/Src/QuerySet.cpp new file mode 100644 index 000000000..e5b093c62 --- /dev/null +++ b/Engine/Source/RHI-Vulkan/Src/QuerySet.cpp @@ -0,0 +1,38 @@ +// +// Created by johnk on 2026/6/30. +// + +#include +#include +#include +#include + +namespace RHI::Vulkan { + VulkanQuerySet::VulkanQuerySet(VulkanDevice& inDevice, const QuerySetCreateInfo& inCreateInfo) + : QuerySet(inCreateInfo) + , device(inDevice) + , nativeQueryPool(VK_NULL_HANDLE) + { + CreateNativeQueryPool(inCreateInfo); + } + + VulkanQuerySet::~VulkanQuerySet() + { + vkDestroyQueryPool(device.GetNative(), nativeQueryPool, nullptr); + } + + VkQueryPool VulkanQuerySet::GetNative() const + { + return nativeQueryPool; + } + + void VulkanQuerySet::CreateNativeQueryPool(const QuerySetCreateInfo& inCreateInfo) + { + VkQueryPoolCreateInfo poolInfo = {}; + poolInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO; + poolInfo.queryType = EnumCast(inCreateInfo.type); + poolInfo.queryCount = inCreateInfo.count; + + Assert(vkCreateQueryPool(device.GetNative(), &poolInfo, nullptr, &nativeQueryPool) == VK_SUCCESS); + } +} diff --git a/Engine/Source/RHI-Vulkan/Src/Queue.cpp b/Engine/Source/RHI-Vulkan/Src/Queue.cpp index 11477f966..82a7d9fde 100644 --- a/Engine/Source/RHI-Vulkan/Src/Queue.cpp +++ b/Engine/Source/RHI-Vulkan/Src/Queue.cpp @@ -8,10 +8,13 @@ #include #include #include +#include +#include namespace RHI::Vulkan { - VulkanQueue::VulkanQueue(VulkanDevice&, const VkQueue inNativeQueue) - : nativeQueue(inNativeQueue) + VulkanQueue::VulkanQueue(VulkanDevice& inDevice, const VkQueue inNativeQueue) + : device(inDevice) + , nativeQueue(inNativeQueue) { } @@ -69,6 +72,13 @@ namespace RHI::Vulkan { Assert(vkQueueSubmit(nativeQueue, 1, &vkSubmitInfo, vkFence->GetNative()) == VK_SUCCESS); } + float VulkanQueue::GetTimestampPeriod() + { + VkPhysicalDeviceProperties properties; + vkGetPhysicalDeviceProperties(device.GetGpu().GetNative(), &properties); + return properties.limits.timestampPeriod; + } + VkQueue VulkanQueue::GetNative() const { return nativeQueue; diff --git a/Engine/Source/RHI-Vulkan/Src/SwapChain.cpp b/Engine/Source/RHI-Vulkan/Src/SwapChain.cpp index d32bbdc3d..be51c28ca 100644 --- a/Engine/Source/RHI-Vulkan/Src/SwapChain.cpp +++ b/Engine/Source/RHI-Vulkan/Src/SwapChain.cpp @@ -2,6 +2,8 @@ // Created by Zach Lee on 2022/4/4. // +#include + #include #include #include @@ -89,9 +91,9 @@ namespace RHI::Vulkan { extent.width = std::clamp(extent.width, surfaceCap.minImageExtent.width, surfaceCap.maxImageExtent.width); extent.height = std::clamp(extent.height, surfaceCap.minImageExtent.height, surfaceCap.maxImageExtent.height); - Assert(device.CheckSwapChainFormatSupport(vkSurface, inCreateInfo.format)); + Assert(device.CheckSwapChainFormatSupport(vkSurface, inCreateInfo.format, inCreateInfo.colorSpace)); auto supportedFormat = EnumCast(inCreateInfo.format); - auto colorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; + auto colorSpace = EnumCast(inCreateInfo.colorSpace); uint32_t presentModeCount = 0; std::vector presentModes; @@ -100,16 +102,16 @@ namespace RHI::Vulkan { presentModes.resize(presentModeCount); vkGetPhysicalDeviceSurfacePresentModesKHR(device.GetGpu().GetNative(), surface, &presentModeCount, presentModes.data()); - VkPresentModeKHR supportedMode = EnumCast(inCreateInfo.presentMode); - { - Assert(!presentModes.empty()); - const auto iter = std::ranges::find_if( - presentModes, - [supportedMode](const VkPresentModeKHR mode) { return mode == supportedMode; }); - Assert(iter != presentModes.end()); - } + // Only VK_PRESENT_MODE_FIFO_KHR is guaranteed by the spec, so fall back to it when the requested mode is unavailable. + const VkPresentModeKHR requestedMode = EnumCast(inCreateInfo.presentMode); + const VkPresentModeKHR supportedMode = std::ranges::find(presentModes, requestedMode) != presentModes.end() + ? requestedMode + : VK_PRESENT_MODE_FIFO_KHR; - swapChainImageCount = std::clamp(static_cast(inCreateInfo.textureNum), surfaceCap.minImageCount, surfaceCap.maxImageCount); + const uint32_t minImageCount = std::max(static_cast(inCreateInfo.textureNum), surfaceCap.minImageCount); + swapChainImageCount = surfaceCap.maxImageCount == 0 + ? minImageCount + : std::min(minImageCount, surfaceCap.maxImageCount); VkSwapchainCreateInfoKHR swapChainInfo = {}; swapChainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; swapChainInfo.surface = surface; @@ -135,7 +137,7 @@ namespace RHI::Vulkan { textureInfo.width = extent.width; textureInfo.height = extent.height; textureInfo.depthOrArraySize = 1; - textureInfo.initialState = TextureState::present; + textureInfo.initialState = TextureState::undefined; vkGetSwapchainImagesKHR(device.GetNative(), nativeSwapChain, &swapChainImageCount, nullptr); std::vector swapChainImages(swapChainImageCount); @@ -145,4 +147,4 @@ namespace RHI::Vulkan { } swapChainImageCount = static_cast(swapChainImages.size()); } -} \ No newline at end of file +} diff --git a/Engine/Source/RHI-Vulkan/Src/Texture.cpp b/Engine/Source/RHI-Vulkan/Src/Texture.cpp index 61f6163a8..3e9eb7730 100644 --- a/Engine/Source/RHI-Vulkan/Src/Texture.cpp +++ b/Engine/Source/RHI-Vulkan/Src/Texture.cpp @@ -19,7 +19,6 @@ namespace RHI::Vulkan { , nativeAspect(VK_IMAGE_ASPECT_COLOR_BIT) , ownMemory(false) { - TransitionToInitState(inCreateInfo); } VulkanTexture::VulkanTexture(VulkanDevice& inDevice, const TextureCreateInfo& inCreateInfo) @@ -118,4 +117,4 @@ namespace RHI::Vulkan { fence->Wait(); } } -} \ No newline at end of file +} diff --git a/Engine/Source/RHI/Include/RHI/CommandRecorder.h b/Engine/Source/RHI/Include/RHI/CommandRecorder.h index 3b9a4a283..160925e43 100644 --- a/Engine/Source/RHI/Include/RHI/CommandRecorder.h +++ b/Engine/Source/RHI/Include/RHI/CommandRecorder.h @@ -22,6 +22,7 @@ namespace RHI { class TextureView; class BindGroup; class SwapChain; + class QuerySet; struct Barrier; struct TextureSubResourceInfo { @@ -108,6 +109,12 @@ namespace RHI { uint32_t firstInstance = 0; }; + struct DispatchIndirectArguments { + uint32_t groupCountX = 0; + uint32_t groupCountY = 0; + uint32_t groupCountZ = 0; + }; + template struct ColorAttachmentBase { LoadOp loadOp; @@ -227,7 +234,9 @@ namespace RHI { virtual void SetPipeline(ComputePipeline* pipeline) = 0; virtual void SetBindGroup(uint8_t layoutIndex, BindGroup* bindGroup) = 0; + virtual void SetPipelineConstants(uint32_t pipelineConstantIndex, const void* data, uint32_t size) = 0; virtual void Dispatch(size_t groupCountX, size_t groupCountY, size_t groupCountZ) = 0; + virtual void DispatchIndirect(Buffer* indirectBuffer, size_t offset) = 0; virtual void EndPass() = 0; protected: @@ -241,6 +250,7 @@ namespace RHI { virtual void SetPipeline(RasterPipeline* pipeline) = 0; virtual void SetBindGroup(uint8_t layoutIndex, BindGroup* bindGroup) = 0; + virtual void SetPipelineConstants(uint32_t pipelineConstantIndex, const void* data, uint32_t size) = 0; virtual void SetIndexBuffer(BufferView* bufferView) = 0; virtual void SetVertexBuffer(size_t slot, BufferView* bufferView) = 0; virtual void Draw(size_t vertexCount, size_t instanceCount, size_t firstVertex, size_t firstInstance) = 0; @@ -254,6 +264,8 @@ namespace RHI { virtual void DrawIndexedIndirect(Buffer* indirectBuffer, size_t offset) = 0; virtual void MultiDrawIndirect(Buffer* indirectBuffer, size_t offset, size_t drawCount) = 0; virtual void MultiDrawIndexedIndirect(Buffer* indirectBuffer, size_t offset, size_t drawCount) = 0; + virtual void BeginOcclusionQuery(QuerySet* querySet, uint32_t queryIndex) = 0; + virtual void EndOcclusionQuery() = 0; virtual void EndPass() = 0; protected: @@ -268,6 +280,9 @@ namespace RHI { virtual Common::UniquePtr BeginCopyPass() = 0; virtual Common::UniquePtr BeginComputePass() = 0; virtual Common::UniquePtr BeginRasterPass(const RasterPassBeginInfo& beginInfo) = 0; + virtual void WriteTimestamp(QuerySet* querySet, uint32_t queryIndex) = 0; + virtual void ResetQuerySet(QuerySet* querySet, uint32_t firstQuery, uint32_t queryCount) = 0; + virtual void ResolveQuery(QuerySet* querySet, uint32_t firstQuery, uint32_t queryCount, Buffer* dstBuffer, size_t dstOffset) = 0; virtual void End() = 0; protected: diff --git a/Engine/Source/RHI/Include/RHI/Common.h b/Engine/Source/RHI/Include/RHI/Common.h index ea4c46cfd..d2b1dac7a 100644 --- a/Engine/Source/RHI/Include/RHI/Common.h +++ b/Engine/Source/RHI/Include/RHI/Common.h @@ -193,6 +193,7 @@ namespace RHI { enum class TextureViewType : uint8_t { textureBinding, storageBinding, + rwStorageBinding, colorAttachment, depthStencil, max @@ -247,6 +248,7 @@ namespace RHI { sampler, texture, storageTexture, + rwStorageTexture, max }; @@ -266,11 +268,6 @@ namespace RHI { max }; - enum class StorageTextureAccess : uint8_t { - writeOnly, - max - }; - enum class VertexStepMode : uint8_t { perVertex, perInstance, @@ -370,11 +367,16 @@ namespace RHI { }; enum class PresentMode : uint8_t { - // TODO check this - // 1. DirectX SwapEffect #see https://docs.microsoft.com/en-us/windows/win32/api/dxgi/ne-dxgi-dxgi_swap_effect - // 2. Vulkan VkPresentModeKHR #see https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkPresentModeKHR.html - immediately, - vsync, + immediately, // no synchronization, lowest latency, allows tearing + vsync, // hard vertical sync, no tearing, always supported + mailbox, // vertical sync without tearing, keeps only the latest frame (low latency) + fifoRelaxed, // vertical sync that allows tearing when the app misses a refresh + max + }; + + enum class ColorSpace : uint8_t { + srgbNonLinear, // standard SDR sRGB (gamma ~2.2) + hdr10St2084, // HDR10: Rec.2020 primaries with the ST.2084 (PQ) transfer function max }; @@ -403,11 +405,18 @@ namespace RHI { shaderReadOnly, renderTarget, storage, + rwStorage, depthStencilReadonly, depthStencilWrite, present, max }; + + enum class QueryType : uint8_t { + occlusion, + timestamp, + max + }; } namespace RHI { @@ -433,9 +442,10 @@ namespace RHI { copyDst = 0x2, textureBinding = 0x4, storageBinding = 0x8, - renderAttachment = 0x10, - depthStencilAttachment = 0x20, - max = 0x40 + rwStorageBinding = 0x10, + renderAttachment = 0x20, + depthStencilAttachment = 0x40, + max = 0x80 }; using TextureUsageFlags = Common::Flags; DECLARE_FLAG_BITS_OP(TextureUsageFlags, TextureUsageBits) diff --git a/Engine/Source/RHI/Include/RHI/Device.h b/Engine/Source/RHI/Include/RHI/Device.h index 816a2fea3..a95730347 100644 --- a/Engine/Source/RHI/Include/RHI/Device.h +++ b/Engine/Source/RHI/Include/RHI/Device.h @@ -24,11 +24,14 @@ namespace RHI { struct ShaderModuleCreateInfo; struct ComputePipelineCreateInfo; struct RasterPipelineCreateInfo; + struct PipelineCacheCreateInfo; struct SwapChainCreateInfo; struct SurfaceCreateInfo; + struct QuerySetCreateInfo; struct TextureSubResourceCopyFootprint; struct TextureSubResourceInfo; class Queue; + class QuerySet; class Buffer; class Texture; class Sampler; @@ -38,6 +41,7 @@ namespace RHI { class ShaderModule; class ComputePipeline; class RasterPipeline; + class PipelineCache; class CommandBuffer; class SwapChain; class Fence; @@ -75,13 +79,15 @@ namespace RHI { virtual Common::UniquePtr CreateBindGroup(const BindGroupCreateInfo& createInfo) = 0; virtual Common::UniquePtr CreatePipelineLayout(const PipelineLayoutCreateInfo& createInfo) = 0; virtual Common::UniquePtr CreateShaderModule(const ShaderModuleCreateInfo& createInfo) = 0; + virtual Common::UniquePtr CreatePipelineCache(const PipelineCacheCreateInfo& createInfo) = 0; virtual Common::UniquePtr CreateComputePipeline(const ComputePipelineCreateInfo& createInfo) = 0; virtual Common::UniquePtr CreateRasterPipeline(const RasterPipelineCreateInfo& createInfo) = 0; virtual Common::UniquePtr CreateCommandBuffer() = 0; virtual Common::UniquePtr CreateFence(bool bInitAsSignaled) = 0; virtual Common::UniquePtr CreateSemaphore() = 0; + virtual Common::UniquePtr CreateQuerySet(const QuerySetCreateInfo& createInfo) = 0; - virtual bool CheckSwapChainFormatSupport(Surface* surface, PixelFormat format) = 0; + virtual bool CheckSwapChainFormatSupport(Surface* surface, PixelFormat format, ColorSpace colorSpace) = 0; virtual TextureSubResourceCopyFootprint GetTextureSubResourceCopyFootprint(const Texture& texture, const TextureSubResourceInfo& subResourceInfo) = 0; protected: diff --git a/Engine/Source/RHI/Include/RHI/Pipeline.h b/Engine/Source/RHI/Include/RHI/Pipeline.h index ca946d9b8..574405eac 100644 --- a/Engine/Source/RHI/Include/RHI/Pipeline.h +++ b/Engine/Source/RHI/Include/RHI/Pipeline.h @@ -14,6 +14,7 @@ namespace RHI { class BindGroupLayout; class PipelineLayout; class ShaderModule; + class PipelineCache; struct HlslVertexBinding { std::string semanticName; @@ -219,10 +220,12 @@ namespace RHI { struct ComputePipelineCreateInfo { PipelineLayout* layout; ShaderModule* computeShader; + PipelineCache* pipelineCache; ComputePipelineCreateInfo(); ComputePipelineCreateInfo& SetLayout(PipelineLayout* inLayout); ComputePipelineCreateInfo& SetComputeShader(ShaderModule* inComputeShader); + ComputePipelineCreateInfo& SetPipelineCache(PipelineCache* inPipelineCache); }; struct RasterPipelineCreateInfo { @@ -240,6 +243,7 @@ namespace RHI { MultiSampleState multiSampleState; FragmentState fragmentState; + PipelineCache* pipelineCache; std::string debugName; explicit RasterPipelineCreateInfo(PipelineLayout* inLayout = nullptr); @@ -254,6 +258,7 @@ namespace RHI { RasterPipelineCreateInfo& SetDepthStencilState(const DepthStencilState& inDepthStencilState); RasterPipelineCreateInfo& SetMultiSampleState(const MultiSampleState& inMultiSampleState); RasterPipelineCreateInfo& SetFragmentState(const FragmentState& inFragmentState); + RasterPipelineCreateInfo& SetPipelineCache(PipelineCache* inPipelineCache); RasterPipelineCreateInfo& SetDebugName(std::string inDebugName); }; diff --git a/Engine/Source/RHI/Include/RHI/PipelineCache.h b/Engine/Source/RHI/Include/RHI/PipelineCache.h new file mode 100644 index 000000000..c65cc635e --- /dev/null +++ b/Engine/Source/RHI/Include/RHI/PipelineCache.h @@ -0,0 +1,38 @@ +// +// Created by johnk on 2026/7/2. +// + +#pragma once + +#include +#include +#include + +#include + +namespace RHI { + struct PipelineCacheCreateInfo { + const void* initialData; + size_t initialDataSize; + std::string debugName; + + PipelineCacheCreateInfo(); + explicit PipelineCacheCreateInfo(const void* inInitialData, size_t inInitialDataSize, std::string inDebugName = ""); + explicit PipelineCacheCreateInfo(const std::vector& inInitialData, std::string inDebugName = ""); + + PipelineCacheCreateInfo& SetInitialData(const void* inInitialData); + PipelineCacheCreateInfo& SetInitialDataSize(size_t inInitialDataSize); + PipelineCacheCreateInfo& SetDebugName(std::string inDebugName); + }; + + class PipelineCache { + public: + NonCopyable(PipelineCache) + virtual ~PipelineCache(); + + virtual std::vector GetData() = 0; + + protected: + explicit PipelineCache(const PipelineCacheCreateInfo& inCreateInfo); + }; +} diff --git a/Engine/Source/RHI/Include/RHI/PipelineLayout.h b/Engine/Source/RHI/Include/RHI/PipelineLayout.h index 7ef070357..9f1821f17 100644 --- a/Engine/Source/RHI/Include/RHI/PipelineLayout.h +++ b/Engine/Source/RHI/Include/RHI/PipelineLayout.h @@ -5,22 +5,37 @@ #pragma once #include +#include + #include #include namespace RHI { class BindGroupLayout; + struct HlslPipelineConstantBinding { + uint8_t binding; + uint8_t bindingSpace; + + HlslPipelineConstantBinding(uint8_t inBinding, uint8_t inBindingSpace); + }; + + struct GlslPipelineConstantBinding { + uint32_t offset; + + explicit GlslPipelineConstantBinding(uint32_t inOffset); + }; + struct PipelineConstantLayout { ShaderStageFlags stageFlags; - uint32_t offset; uint32_t size; + std::variant platformBinding; PipelineConstantLayout(); - PipelineConstantLayout(ShaderStageFlags inStageFlags, uint32_t inOffset, uint32_t inSize); + PipelineConstantLayout(ShaderStageFlags inStageFlags, uint32_t inSize, const std::variant& inPlatformBinding); PipelineConstantLayout& SetStageFlags(ShaderStageFlags inStageFlags); - PipelineConstantLayout& SetOffset(uint32_t inOffset); PipelineConstantLayout& SetSize(uint32_t inSize); + PipelineConstantLayout& SetPlatformBinding(const std::variant& inPlatformBinding); }; struct PipelineLayoutCreateInfo { diff --git a/Engine/Source/RHI/Include/RHI/QuerySet.h b/Engine/Source/RHI/Include/RHI/QuerySet.h new file mode 100644 index 000000000..6d42d6959 --- /dev/null +++ b/Engine/Source/RHI/Include/RHI/QuerySet.h @@ -0,0 +1,38 @@ +// +// Created by johnk on 2026/6/30. +// + +#pragma once + +#include + +#include +#include + +namespace RHI { + struct QuerySetCreateInfo { + QueryType type; + uint32_t count; + std::string debugName; + + QuerySetCreateInfo(); + QuerySetCreateInfo(QueryType inType, uint32_t inCount, std::string inDebugName = ""); + + QuerySetCreateInfo& SetType(QueryType inType); + QuerySetCreateInfo& SetCount(uint32_t inCount); + QuerySetCreateInfo& SetDebugName(std::string inDebugName); + }; + + class QuerySet { + public: + NonCopyable(QuerySet) + virtual ~QuerySet(); + + const QuerySetCreateInfo& GetCreateInfo() const; + + protected: + explicit QuerySet(const QuerySetCreateInfo& inCreateInfo); + + QuerySetCreateInfo createInfo; + }; +} diff --git a/Engine/Source/RHI/Include/RHI/Queue.h b/Engine/Source/RHI/Include/RHI/Queue.h index e3e602e9f..db80e37c1 100644 --- a/Engine/Source/RHI/Include/RHI/Queue.h +++ b/Engine/Source/RHI/Include/RHI/Queue.h @@ -34,6 +34,7 @@ namespace RHI { virtual void Submit(CommandBuffer* commandBuffer, const QueueSubmitInfo& submitInfo) = 0; virtual void Flush(Fence* fenceToSignal) = 0; + virtual float GetTimestampPeriod() = 0; protected: Queue(); diff --git a/Engine/Source/RHI/Include/RHI/RHI.h b/Engine/Source/RHI/Include/RHI/RHI.h index eb8c080c7..b5798d63b 100644 --- a/Engine/Source/RHI/Include/RHI/RHI.h +++ b/Engine/Source/RHI/Include/RHI/RHI.h @@ -24,6 +24,8 @@ #include #include #include +#include +#include #if PLATFORM_WINDOWS #undef CreateSemaphore diff --git a/Engine/Source/RHI/Include/RHI/SwapChain.h b/Engine/Source/RHI/Include/RHI/SwapChain.h index 8da93789d..edf663654 100644 --- a/Engine/Source/RHI/Include/RHI/SwapChain.h +++ b/Engine/Source/RHI/Include/RHI/SwapChain.h @@ -21,6 +21,7 @@ namespace RHI { Surface* surface; uint8_t textureNum; PixelFormat format; + ColorSpace colorSpace; uint32_t width; uint32_t height; PresentMode presentMode; @@ -30,6 +31,7 @@ namespace RHI { SwapChainCreateInfo& SetSurface(Surface* inSurface); SwapChainCreateInfo& SetTextureNum(uint8_t inTextureNum); SwapChainCreateInfo& SetFormat(PixelFormat inFormat); + SwapChainCreateInfo& SetColorSpace(ColorSpace inColorSpace); SwapChainCreateInfo& SetWidth(uint32_t inWidth); SwapChainCreateInfo& SetHeight(uint32_t inHeight); SwapChainCreateInfo& SetPresentMode(PresentMode inMode); diff --git a/Engine/Source/RHI/Src/Pipeline.cpp b/Engine/Source/RHI/Src/Pipeline.cpp index e5e09a16d..b73d53569 100644 --- a/Engine/Source/RHI/Src/Pipeline.cpp +++ b/Engine/Source/RHI/Src/Pipeline.cpp @@ -304,6 +304,7 @@ namespace RHI { ComputePipelineCreateInfo::ComputePipelineCreateInfo() : layout(nullptr) , computeShader(nullptr) + , pipelineCache(nullptr) { } @@ -319,6 +320,12 @@ namespace RHI { return *this; } + ComputePipelineCreateInfo& ComputePipelineCreateInfo::SetPipelineCache(PipelineCache* inPipelineCache) + { + pipelineCache = inPipelineCache; + return *this; + } + RasterPipelineCreateInfo::RasterPipelineCreateInfo(PipelineLayout* inLayout) : layout(inLayout) , vertexShader(nullptr) @@ -326,6 +333,7 @@ namespace RHI { , geometryShader(nullptr) , domainShader(nullptr) , hullShader(nullptr) + , pipelineCache(nullptr) { } @@ -397,6 +405,12 @@ namespace RHI { return *this; } + RasterPipelineCreateInfo& RasterPipelineCreateInfo::SetPipelineCache(PipelineCache* inPipelineCache) + { + pipelineCache = inPipelineCache; + return *this; + } + RasterPipelineCreateInfo& RasterPipelineCreateInfo::SetDebugName(std::string inDebugName) { debugName = std::move(inDebugName); diff --git a/Engine/Source/RHI/Src/PipelineCache.cpp b/Engine/Source/RHI/Src/PipelineCache.cpp new file mode 100644 index 000000000..afdad1d4b --- /dev/null +++ b/Engine/Source/RHI/Src/PipelineCache.cpp @@ -0,0 +1,51 @@ +// +// Created by johnk on 2026/7/2. +// + +#include + +#include + +namespace RHI { + PipelineCacheCreateInfo::PipelineCacheCreateInfo() + : initialData(nullptr) + , initialDataSize(0) + { + } + + PipelineCacheCreateInfo::PipelineCacheCreateInfo(const void* inInitialData, const size_t inInitialDataSize, std::string inDebugName) + : initialData(inInitialData) + , initialDataSize(inInitialDataSize) + , debugName(std::move(inDebugName)) + { + } + + PipelineCacheCreateInfo::PipelineCacheCreateInfo(const std::vector& inInitialData, std::string inDebugName) + : initialData(inInitialData.data()) + , initialDataSize(inInitialData.size()) + , debugName(std::move(inDebugName)) + { + } + + PipelineCacheCreateInfo& PipelineCacheCreateInfo::SetInitialData(const void* inInitialData) + { + initialData = inInitialData; + return *this; + } + + PipelineCacheCreateInfo& PipelineCacheCreateInfo::SetInitialDataSize(const size_t inInitialDataSize) + { + initialDataSize = inInitialDataSize; + return *this; + } + + PipelineCacheCreateInfo& PipelineCacheCreateInfo::SetDebugName(std::string inDebugName) + { + debugName = std::move(inDebugName); + return *this; + } + + PipelineCache::PipelineCache(const PipelineCacheCreateInfo&) {} + + PipelineCache::~PipelineCache() = default; +} diff --git a/Engine/Source/RHI/Src/PipelineLayout.cpp b/Engine/Source/RHI/Src/PipelineLayout.cpp index cfe456949..d2b4bcc62 100644 --- a/Engine/Source/RHI/Src/PipelineLayout.cpp +++ b/Engine/Source/RHI/Src/PipelineLayout.cpp @@ -5,17 +5,28 @@ #include namespace RHI { + HlslPipelineConstantBinding::HlslPipelineConstantBinding(const uint8_t inBinding, const uint8_t inBindingSpace) + : binding(inBinding) + , bindingSpace(inBindingSpace) + { + } + + GlslPipelineConstantBinding::GlslPipelineConstantBinding(const uint32_t inOffset) + : offset(inOffset) + { + } + PipelineConstantLayout::PipelineConstantLayout() : stageFlags(ShaderStageFlags::null) - , offset(0) , size(0) + , platformBinding(HlslPipelineConstantBinding(0, 0)) { } - PipelineConstantLayout::PipelineConstantLayout(const ShaderStageFlags inStageFlags, const uint32_t inOffset, const uint32_t inSize) + PipelineConstantLayout::PipelineConstantLayout(const ShaderStageFlags inStageFlags, const uint32_t inSize, const std::variant& inPlatformBinding) : stageFlags(inStageFlags) - , offset(inOffset) , size(inSize) + , platformBinding(inPlatformBinding) { } @@ -25,15 +36,15 @@ namespace RHI { return *this; } - PipelineConstantLayout& PipelineConstantLayout::SetOffset(const uint32_t inOffset) + PipelineConstantLayout& PipelineConstantLayout::SetSize(const uint32_t inSize) { - offset = inOffset; + size = inSize; return *this; } - PipelineConstantLayout& PipelineConstantLayout::SetSize(const uint32_t inSize) + PipelineConstantLayout& PipelineConstantLayout::SetPlatformBinding(const std::variant& inPlatformBinding) { - size = inSize; + platformBinding = inPlatformBinding; return *this; } diff --git a/Engine/Source/RHI/Src/QuerySet.cpp b/Engine/Source/RHI/Src/QuerySet.cpp new file mode 100644 index 000000000..ac67c4760 --- /dev/null +++ b/Engine/Source/RHI/Src/QuerySet.cpp @@ -0,0 +1,50 @@ +// +// Created by johnk on 2026/6/30. +// + +#include + +namespace RHI { + QuerySetCreateInfo::QuerySetCreateInfo() + : type(QueryType::max) + , count(0) + { + } + + QuerySetCreateInfo::QuerySetCreateInfo(const QueryType inType, const uint32_t inCount, std::string inDebugName) + : type(inType) + , count(inCount) + , debugName(std::move(inDebugName)) + { + } + + QuerySetCreateInfo& QuerySetCreateInfo::SetType(const QueryType inType) + { + type = inType; + return *this; + } + + QuerySetCreateInfo& QuerySetCreateInfo::SetCount(const uint32_t inCount) + { + count = inCount; + return *this; + } + + QuerySetCreateInfo& QuerySetCreateInfo::SetDebugName(std::string inDebugName) + { + debugName = std::move(inDebugName); + return *this; + } + + QuerySet::QuerySet(const QuerySetCreateInfo& inCreateInfo) + : createInfo(inCreateInfo) + { + } + + QuerySet::~QuerySet() = default; + + const QuerySetCreateInfo& QuerySet::GetCreateInfo() const + { + return createInfo; + } +} diff --git a/Engine/Source/RHI/Src/SwapChain.cpp b/Engine/Source/RHI/Src/SwapChain.cpp index 65cf8d334..28169e3c4 100644 --- a/Engine/Source/RHI/Src/SwapChain.cpp +++ b/Engine/Source/RHI/Src/SwapChain.cpp @@ -10,6 +10,7 @@ namespace RHI { , surface(nullptr) , textureNum(0) , format(PixelFormat::max) + , colorSpace(ColorSpace::srgbNonLinear) , width(0) , height(0) , presentMode(PresentMode::max) @@ -40,6 +41,12 @@ namespace RHI { return *this; } + SwapChainCreateInfo& SwapChainCreateInfo::SetColorSpace(const ColorSpace inColorSpace) + { + colorSpace = inColorSpace; + return *this; + } + SwapChainCreateInfo& SwapChainCreateInfo::SetWidth(const uint32_t inWidth) { width = inWidth; diff --git a/Engine/Source/Render/Include/Render/MeshRenderData.h b/Engine/Source/Render/Include/Render/MeshRenderData.h new file mode 100644 index 000000000..8e9209bdc --- /dev/null +++ b/Engine/Source/Render/Include/Render/MeshRenderData.h @@ -0,0 +1,41 @@ +// +// Created by johnk on 2026/7/5. +// + +#pragma once + +#include + +#include +#include +#include +#include + +namespace Render { + // gpu geometry for a single static mesh lod, vertex layout matches StaticMeshVertexFactory (position + uv0 + // interleaved), created and destroyed on the render thread and shared between scene proxies + class MeshRenderData { + public: + struct Vertex { + Common::FVec3 position; + Common::FVec2 uv0; + }; + + static constexpr size_t vertexStride = sizeof(Vertex); + + MeshRenderData(RHI::Device& inDevice, const std::vector& inVertices, const std::vector& inIndices); + ~MeshRenderData(); + + NonCopyable(MeshRenderData) + NonMovable(MeshRenderData) + + RHI::Buffer* GetVertexBuffer() const; + RHI::Buffer* GetIndexBuffer() const; + uint32_t GetIndexCount() const; + + private: + Common::UniquePtr vertexBuffer; + Common::UniquePtr indexBuffer; + uint32_t indexCount; + }; +} diff --git a/Engine/Source/Render/Include/Render/RenderGraph.h b/Engine/Source/Render/Include/Render/RenderGraph.h index 7a799ace8..2aa5ae91a 100644 --- a/Engine/Source/Render/Include/Render/RenderGraph.h +++ b/Engine/Source/Render/Include/Render/RenderGraph.h @@ -218,6 +218,7 @@ namespace Render { RGBindGroupDesc& RwStorageBuffer(std::string inName, RGBufferViewRef bufferView); RGBindGroupDesc& Texture(std::string inName, RGTextureViewRef textureView); RGBindGroupDesc& StorageTexture(std::string inName, RGTextureViewRef textureView); + RGBindGroupDesc& RwStorageTexture(std::string inName, RGTextureViewRef textureView); }; class RGBindGroup { diff --git a/Engine/Source/Render/Include/Render/Renderer.h b/Engine/Source/Render/Include/Render/Renderer.h index 200ab0a1f..a397ec211 100644 --- a/Engine/Source/Render/Include/Render/Renderer.h +++ b/Engine/Source/Render/Include/Render/Renderer.h @@ -20,8 +20,10 @@ namespace Render { struct Params { RHI::Device* device; const Scene* scene; - const RHI::Texture* surface; + RHI::Texture* surface; Common::UVec2 surfaceExtent; + RHI::TextureState surfaceBeforeRenderState; + RHI::TextureState surfaceAfterRenderState; std::vector views; RHI::Semaphore* waitSemaphore; RHI::Semaphore* signalSemaphore; @@ -36,8 +38,10 @@ namespace Render { protected: RHI::Device* device; const Scene* scene; - const RHI::Texture* surface; + RHI::Texture* surface; Common::UVec2 surfaceExtent; + RHI::TextureState surfaceBeforeRenderState; + RHI::TextureState surfaceAfterRenderState; std::vector views; RHI::Semaphore* waitSemaphore; RHI::Semaphore* signalSemaphore; diff --git a/Engine/Source/Render/Include/Render/Scene.h b/Engine/Source/Render/Include/Render/Scene.h index d2cbb599c..bd14da42a 100644 --- a/Engine/Source/Render/Include/Render/Scene.h +++ b/Engine/Source/Render/Include/Render/Scene.h @@ -7,6 +7,7 @@ #include #include #include +#include namespace Render { // Render::Scene is a container of render-thread world data copy. @@ -14,6 +15,7 @@ namespace Render { class Scene final { public: using EntityId = uint32_t; + template using SceneProxyContainer = std::unordered_map; Scene(); ~Scene(); @@ -25,14 +27,14 @@ namespace Render { template SP& Get(EntityId inEntity); template const SP& Get(EntityId inEntity) const; template void Remove(EntityId inEntity); + template const SceneProxyContainer& All() const; private: - template using SceneProxyContainer = std::unordered_map; - template SceneProxyContainer& GetSceneProxyContainer(); template const SceneProxyContainer& GetSceneProxyContainer() const; SceneProxyContainer lightSceneProxies; + SceneProxyContainer primitiveSceneProxies; }; } @@ -65,6 +67,13 @@ namespace Render { GetSceneProxyContainer().erase(inEntity); } + template + const Scene::SceneProxyContainer& Scene::All() const + { + Assert(Core::ThreadContext::IsRenderThread()); + return GetSceneProxyContainer(); + } + template Scene::SceneProxyContainer& Scene::GetSceneProxyContainer() { @@ -92,4 +101,16 @@ namespace Render { { return lightSceneProxies; } + + template <> + inline Scene::SceneProxyContainer& Scene::GetSceneProxyContainer() + { + return primitiveSceneProxies; + } + + template <> + inline const Scene::SceneProxyContainer& Scene::GetSceneProxyContainer() const + { + return primitiveSceneProxies; + } } diff --git a/Engine/Source/Render/Include/Render/SceneProxy/Primitive.h b/Engine/Source/Render/Include/Render/SceneProxy/Primitive.h index 6e3543b49..ca96a3565 100644 --- a/Engine/Source/Render/Include/Render/SceneProxy/Primitive.h +++ b/Engine/Source/Render/Include/Render/SceneProxy/Primitive.h @@ -4,12 +4,34 @@ #pragma once -#include +#include +#include +#include +#include namespace Render { + class MaterialShaderType; + class VertexFactoryType; + struct PrimitiveSceneProxy { PrimitiveSceneProxy(); - // TODO + Common::FMat4x4 localToWorld; + Common::SharedPtr mesh; + const VertexFactoryType* vertexFactoryType; + const MaterialShaderType* vertexShaderType; + const MaterialShaderType* pixelShaderType; + Common::FVec4 baseColor; }; } + +namespace Render { + inline PrimitiveSceneProxy::PrimitiveSceneProxy() + : localToWorld(Common::FMat4x4Consts::identity) + , vertexFactoryType(nullptr) + , vertexShaderType(nullptr) + , pixelShaderType(nullptr) + , baseColor(1.0f, 1.0f, 1.0f, 1.0f) + { + } +} diff --git a/Engine/Source/Render/Include/Render/Shader.h b/Engine/Source/Render/Include/Render/Shader.h index 015b5fc3e..8400ab66d 100644 --- a/Engine/Source/Render/Include/Render/Shader.h +++ b/Engine/Source/Render/Include/Render/Shader.h @@ -370,6 +370,7 @@ namespace Render { ~ShaderMap(); // render thread + bool HasShaderInstance(const ShaderType& inShaderType, const ShaderVariantValueMap& inShaderVariants) const; ShaderInstance GetShaderInstance(const ShaderType& inShaderType, const ShaderVariantValueMap& inShaderVariants); void Invalidate(); diff --git a/Engine/Source/Render/Include/Render/VertexFactory.h b/Engine/Source/Render/Include/Render/VertexFactory.h new file mode 100644 index 000000000..72f898adb --- /dev/null +++ b/Engine/Source/Render/Include/Render/VertexFactory.h @@ -0,0 +1,21 @@ +// +// Created by johnk on 2026/7/5. +// + +#pragma once + +#include + +namespace Render { + class StaticMeshVertexFactory final : public StaticVertexFactoryType { + public: + VertexFactoryTypeInfo(StaticMeshVertexFactory, "Engine/Shader/Explosion/VertexFactory/StaticMesh/VertexFactory.esh") + DeclVertexInput(PositionInput, POSITION, RHI::VertexFormat::float32X3, 0) + DeclVertexInput(Uv0Input, TEXCOORD, RHI::VertexFormat::float32X2, 12) + MakeVertexInputVec(PositionInput, Uv0Input) + EmptyVariantFieldVec + BeginSupportedMaterialTypes + MaterialType::surface, + EndSupportedMaterialTypes + }; +} diff --git a/Engine/Source/Render/Src/MeshRenderData.cpp b/Engine/Source/Render/Src/MeshRenderData.cpp new file mode 100644 index 000000000..3f7f30ba6 --- /dev/null +++ b/Engine/Source/Render/Src/MeshRenderData.cpp @@ -0,0 +1,49 @@ +// +// Created by johnk on 2026/7/5. +// + +#include + +namespace Render::Internal { + static Common::UniquePtr CreateUploadedBuffer(RHI::Device& inDevice, const void* inData, size_t inSize, RHI::BufferUsageBits inUsage, const std::string& inDebugName) + { + const RHI::BufferCreateInfo createInfo = RHI::BufferCreateInfo() + .SetSize(inSize) + .SetUsages(inUsage | RHI::BufferUsageBits::mapWrite | RHI::BufferUsageBits::copySrc) + .SetInitialState(RHI::BufferState::staging) + .SetDebugName(inDebugName); + + Common::UniquePtr result = inDevice.CreateBuffer(createInfo); + Assert(result.Valid()); + auto* data = result->Map(RHI::MapMode::write, 0, inSize); + memcpy(data, inData, inSize); + result->Unmap(); + return result; + } +} + +namespace Render { + MeshRenderData::MeshRenderData(RHI::Device& inDevice, const std::vector& inVertices, const std::vector& inIndices) + : vertexBuffer(Internal::CreateUploadedBuffer(inDevice, inVertices.data(), inVertices.size() * sizeof(Vertex), RHI::BufferUsageBits::vertex, "meshVertexBuffer")) + , indexBuffer(Internal::CreateUploadedBuffer(inDevice, inIndices.data(), inIndices.size() * sizeof(uint32_t), RHI::BufferUsageBits::index, "meshIndexBuffer")) + , indexCount(static_cast(inIndices.size())) + { + } + + MeshRenderData::~MeshRenderData() = default; + + RHI::Buffer* MeshRenderData::GetVertexBuffer() const + { + return vertexBuffer.Get(); + } + + RHI::Buffer* MeshRenderData::GetIndexBuffer() const + { + return indexBuffer.Get(); + } + + uint32_t MeshRenderData::GetIndexCount() const + { + return indexCount; + } +} diff --git a/Engine/Source/Render/Src/RenderGraph.cpp b/Engine/Source/Render/Src/RenderGraph.cpp index 247777cf7..fd1ee17cc 100644 --- a/Engine/Source/Render/Src/RenderGraph.cpp +++ b/Engine/Source/Render/Src/RenderGraph.cpp @@ -21,6 +21,8 @@ namespace Render::Internal { } else if (type == RHI::BindingType::texture) { outReads.emplace(std::get(view)->GetResource()); } else if (type == RHI::BindingType::storageTexture) { + outReads.emplace(std::get(view)->GetResource()); + } else if (type == RHI::BindingType::rwStorageTexture) { outWrites.emplace(std::get(view)->GetResource()); } else if (type == RHI::BindingType::sampler){ return; @@ -311,6 +313,17 @@ namespace Render { return *this; } + RGBindGroupDesc& RGBindGroupDesc::RwStorageTexture(std::string inName, RGTextureViewRef textureView) + { + Assert(!items.contains(inName)); + + RGBindItemDesc item; + item.type = RHI::BindingType::rwStorageTexture; + item.view = textureView; + items.emplace(std::make_pair(std::move(inName), item)); + return *this; + } + RGBindGroup::RGBindGroup(RGBindGroupDesc inDesc) : desc(std::move(inDesc)) { @@ -950,7 +963,7 @@ namespace Render { devirtualizedResourceViews.emplace(std::make_pair(bufferView, ResourceViewCache::Get(device).GetOrCreate(GetRHI(bufferView->GetBuffer()), bufferView->desc))); } createInfo.AddEntry(RHI::BindGroupEntry(*binding, GetRHI(bufferView))); - } else if (item.type == RHI::BindingType::texture || item.type == RHI::BindingType::storageTexture) { + } else if (item.type == RHI::BindingType::texture || item.type == RHI::BindingType::storageTexture || item.type == RHI::BindingType::rwStorageTexture) { auto* textureView = std::get(item.view); if (!devirtualizedResourceViews.contains(textureView)) { devirtualizedResourceViews.emplace(std::make_pair(textureView, ResourceViewCache::Get(device).GetOrCreate(GetRHI(textureView->GetTexture()), textureView->desc))); @@ -1053,6 +1066,8 @@ namespace Render { TransitionTexture(inRecoder, std::get(view)->GetTexture(), RHI::TextureState::shaderReadOnly); } else if (type == RHI::BindingType::storageTexture) { TransitionTexture(inRecoder, std::get(view)->GetTexture(), RHI::TextureState::storage); + } else if (type == RHI::BindingType::rwStorageTexture) { + TransitionTexture(inRecoder, std::get(view)->GetTexture(), RHI::TextureState::rwStorage); } else if (type == RHI::BindingType::sampler) {} else { Unimplement(); } diff --git a/Engine/Source/Render/Src/Renderer.cpp b/Engine/Source/Render/Src/Renderer.cpp index 971b2474d..6aced56f6 100644 --- a/Engine/Source/Render/Src/Renderer.cpp +++ b/Engine/Source/Render/Src/Renderer.cpp @@ -2,7 +2,44 @@ // Created by johnk on 2022/8/3. // +#include + +#include +#include #include +#include +#include + +namespace Render::Internal { + const Common::LinearColor surfaceClearColor = { 0.1f, 0.1f, 0.12f, 1.0f }; + + struct ALIGN_AS_GPU BasePassVsUniform { + Common::FMat4x4 localToWorld; + Common::FMat4x4 worldToClip; + }; + + struct ALIGN_AS_GPU BasePassPsUniform { + Common::FVec4 baseColor; + }; + + struct BasePassDraw { + size_t viewIndex; + RasterPipelineState* pipeline; + RGBindGroupRef bindGroup; + RGBufferViewRef vertexBufferView; + RGBufferViewRef indexBufferView; + uint32_t indexCount; + }; + + static RVertexState BuildVertexState(const VertexFactoryType& inVertexFactoryType) + { + RVertexBufferLayout layout(RHI::VertexStepMode::perVertex, MeshRenderData::vertexStride); + for (const auto& input : inVertexFactoryType.GetVertexInputs()) { + layout.AddAttribute(RVertexAttribute(RVertexBinding(input.name, 0), input.format, input.offset)); + } + return RVertexState().AddVertexBufferLayout(layout); + } +} namespace Render { Renderer::Renderer(const Params& inParams) @@ -10,6 +47,8 @@ namespace Render { , scene(inParams.scene) , surface(inParams.surface) , surfaceExtent(inParams.surfaceExtent) + , surfaceBeforeRenderState(inParams.surfaceBeforeRenderState) + , surfaceAfterRenderState(inParams.surfaceAfterRenderState) , views(inParams.views) , waitSemaphore(inParams.waitSemaphore) , signalSemaphore(inParams.signalSemaphore) @@ -29,7 +68,144 @@ namespace Render { void StandardRenderer::Render(float inDeltaTimeSeconds) { - // TODO + const RHI::PixelFormat colorFormat = surface->GetCreateInfo().format; + + auto* backTexture = rgBuilder.ImportTexture(surface, surfaceBeforeRenderState); + auto* backTextureView = rgBuilder.CreateTextureView(backTexture, RGTextureViewDesc(RHI::TextureViewType::colorAttachment, RHI::TextureViewDimension::tv2D)); + auto* depthTexture = rgBuilder.CreateTexture( + RGTextureDesc() + .SetDimension(RHI::TextureDimension::t2D) + .SetWidth(surfaceExtent.x) + .SetHeight(surfaceExtent.y) + .SetDepthOrArraySize(1) + .SetFormat(RHI::PixelFormat::d32Float) + .SetUsages(RHI::TextureUsageBits::depthStencilAttachment) + .SetMipLevels(1) + .SetSamples(1) + .SetInitialState(RHI::TextureState::depthStencilWrite) + .SetDebugName("sceneDepth")); + // written but never sampled, keep the graph cull from dropping the depth attachment + depthTexture->MaskAsUsed(); + auto* depthTextureView = rgBuilder.CreateTextureView(depthTexture, RGTextureViewDesc(RHI::TextureViewType::depthStencil, RHI::TextureViewDimension::tv2D, RHI::TextureAspect::depth)); + + std::vector draws; + std::vector passBindGroups; + if (scene != nullptr) { + ShaderMap& shaderMap = ShaderMap::Get(*device); + size_t drawIndex = 0; + + for (const auto& [entity, proxy] : scene->All()) { + if (!proxy.mesh.Valid() || proxy.vertexFactoryType == nullptr || proxy.vertexShaderType == nullptr || proxy.pixelShaderType == nullptr) { + continue; + } + // material shaders compile asynchronously, primitives simply do not draw until artifacts arrive + if (!shaderMap.HasShaderInstance(*proxy.vertexShaderType, {}) || !shaderMap.HasShaderInstance(*proxy.pixelShaderType, {})) { + continue; + } + + const ShaderInstance vertexShader = shaderMap.GetShaderInstance(*proxy.vertexShaderType, {}); + const ShaderInstance pixelShader = shaderMap.GetShaderInstance(*proxy.pixelShaderType, {}); + auto* pipeline = PipelineCache::Get(*device).GetOrCreate( + RasterPipelineStateDesc() + .SetVertexShader(vertexShader) + .SetPixelShader(pixelShader) + .SetVertexState(Internal::BuildVertexState(*proxy.vertexFactoryType)) + .SetPrimitiveState(RPrimitiveState().SetCullMode(RHI::CullMode::none)) + .SetDepthStencilState( + RDepthStencilState() + .SetDepthEnabled(true) + .SetFormat(RHI::PixelFormat::d32Float) + .SetDepthCompareFunc(RHI::CompareFunc::greaterEqual)) + .SetFragmentState(RFragmentState().AddColorTarget(RHI::ColorTargetState(colorFormat, RHI::ColorWriteBits::all, false)))); + + auto* vertexBuffer = rgBuilder.ImportBuffer(proxy.mesh->GetVertexBuffer(), RHI::BufferState::shaderReadOnly); + auto* vertexBufferView = rgBuilder.CreateBufferView( + vertexBuffer, RGBufferViewDesc(RHI::BufferViewType::vertex, vertexBuffer->GetDesc().size, 0, RHI::VertexBufferViewInfo(MeshRenderData::vertexStride))); + auto* indexBuffer = rgBuilder.ImportBuffer(proxy.mesh->GetIndexBuffer(), RHI::BufferState::shaderReadOnly); + auto* indexBufferView = rgBuilder.CreateBufferView( + indexBuffer, RGBufferViewDesc(RHI::BufferViewType::index, indexBuffer->GetDesc().size, 0, RHI::IndexBufferViewInfo(RHI::IndexFormat::uint32))); + + for (size_t viewIndex = 0; viewIndex < views.size(); viewIndex++) { + const View& view = views[viewIndex]; + + Internal::BasePassVsUniform vsUniform {}; + vsUniform.localToWorld = proxy.localToWorld; + vsUniform.worldToClip = view.data.projectionMatrix * view.data.viewMatrix; + + Internal::BasePassPsUniform psUniform {}; + psUniform.baseColor = proxy.baseColor; + + auto* vsUniformBuffer = rgBuilder.CreateBuffer( + RGBufferDesc(sizeof(Internal::BasePassVsUniform), RHI::BufferUsageBits::uniform | RHI::BufferUsageBits::mapWrite, RHI::BufferState::staging, std::format("basePassVsUniform{}", drawIndex))); + auto* vsUniformBufferView = rgBuilder.CreateBufferView(vsUniformBuffer, RGBufferViewDesc(RHI::BufferViewType::uniformBinding, sizeof(Internal::BasePassVsUniform))); + rgBuilder.QueueBufferUpload(vsUniformBuffer, RGBufferUploadInfo(&vsUniform, sizeof(Internal::BasePassVsUniform), 0, 0, true)); + + auto* psUniformBuffer = rgBuilder.CreateBuffer( + RGBufferDesc(sizeof(Internal::BasePassPsUniform), RHI::BufferUsageBits::uniform | RHI::BufferUsageBits::mapWrite, RHI::BufferState::staging, std::format("basePassPsUniform{}", drawIndex))); + auto* psUniformBufferView = rgBuilder.CreateBufferView(psUniformBuffer, RGBufferViewDesc(RHI::BufferViewType::uniformBinding, sizeof(Internal::BasePassPsUniform))); + rgBuilder.QueueBufferUpload(psUniformBuffer, RGBufferUploadInfo(&psUniform, sizeof(Internal::BasePassPsUniform), 0, 0, true)); + + auto* bindGroup = rgBuilder.AllocateBindGroup( + RGBindGroupDesc::Create(pipeline->GetPipelineLayout()->GetBindGroupLayout(0)) + .UniformBuffer("vsUniform", vsUniformBufferView) + .UniformBuffer("materialUniform", psUniformBufferView)); + + Internal::BasePassDraw draw {}; + draw.viewIndex = viewIndex; + draw.pipeline = pipeline; + draw.bindGroup = bindGroup; + draw.vertexBufferView = vertexBufferView; + draw.indexBufferView = indexBufferView; + draw.indexCount = proxy.mesh->GetIndexCount(); + draws.emplace_back(draw); + passBindGroups.emplace_back(bindGroup); + drawIndex++; + } + } + } + + rgBuilder.AddRasterPass( + "BasePass", + RGRasterPassDesc() + .AddColorAttachment(RGColorAttachment(backTextureView, RHI::LoadOp::clear, RHI::StoreOp::store, Internal::surfaceClearColor)) + .SetDepthStencilAttachment(RGDepthStencilAttachment(depthTextureView, false, RHI::LoadOp::clear, RHI::StoreOp::discard, 0.0f)), + passBindGroups, + [draws = std::move(draws), views = views](const RGBuilder& rg, RHI::RasterPassCommandRecorder& recorder) -> void { + for (size_t viewIndex = 0; viewIndex < views.size(); viewIndex++) { + const auto& viewport = views[viewIndex].data.viewport; + recorder.SetViewport( + static_cast(viewport.min.x), static_cast(viewport.min.y), + static_cast(viewport.ExtentX()), static_cast(viewport.ExtentY()), 0.0f, 1.0f); + recorder.SetScissor(viewport.min.x, viewport.min.y, viewport.max.x, viewport.max.y); + recorder.SetPrimitiveTopology(RHI::PrimitiveTopology::triangleList); + + for (const auto& draw : draws) { + if (draw.viewIndex != viewIndex) { + continue; + } + recorder.SetPipeline(draw.pipeline->GetRHI()); + recorder.SetBindGroup(0, rg.GetRHI(draw.bindGroup)); + recorder.SetVertexBuffer(0, rg.GetRHI(draw.vertexBufferView)); + recorder.SetIndexBuffer(rg.GetRHI(draw.indexBufferView)); + recorder.DrawIndexed(draw.indexCount, 1, 0, 0, 0); + } + } + }, + {}, + [backTexture, surfaceAfterRenderState = surfaceAfterRenderState](const RGBuilder& rg, RHI::CommandRecorder& recorder) -> void { + recorder.ResourceBarrier(RHI::Barrier::Transition(rg.GetRHI(backTexture), RHI::TextureState::renderTarget, surfaceAfterRenderState)); + }); + + RGExecuteInfo executeInfo; + if (waitSemaphore != nullptr) { + executeInfo.semaphoresToWait.emplace_back(waitSemaphore); + } + if (signalSemaphore != nullptr) { + executeInfo.semaphoresToSignal.emplace_back(signalSemaphore); + } + executeInfo.inFenceToSignal = signalFence; + rgBuilder.Execute(executeInfo); + FinalizeViews(); } diff --git a/Engine/Source/Render/Src/Shader.cpp b/Engine/Source/Render/Src/Shader.cpp index 73c510e02..89416e107 100644 --- a/Engine/Source/Render/Src/Shader.cpp +++ b/Engine/Source/Render/Src/Shader.cpp @@ -260,6 +260,8 @@ namespace Render { , includeDirectories(inIncludeDirectories) , shaderVariantFields(Common::VectorUtils::Combine(inVertexFactoryType.GetVariantFields(), inShaderVariantFields)) { + // the base pass source includes "VertexFactory.esh" resolved from the vertex factory's own directory + includeDirectories.emplace_back(Common::Path(vertexFactory.GetSourceFile()).Parent().String()); } MaterialShaderType::~MaterialShaderType() = default; @@ -428,6 +430,19 @@ namespace Render { ShaderMap::~ShaderMap() = default; + bool ShaderMap::HasShaderInstance(const ShaderType& inShaderType, const ShaderVariantValueMap& inShaderVariants) const // NOLINT + { + Assert(Core::ThreadContext::IsRenderThread()); + + const ShaderArtifactRegistry& registry = ShaderArtifactRegistry::Get(); + const auto typeIter = registry.typeArtifactsRT.find(inShaderType.GetKey()); + if (typeIter == registry.typeArtifactsRT.end()) { + return false; + } + const ShaderVariantKey variantKey = ShaderUtils::ComputeVariantKey(inShaderType.GetVariantFields(), inShaderVariants); + return typeIter->second.variantArtifacts.contains(variantKey); + } + ShaderInstance ShaderMap::GetShaderInstance(const ShaderType& inShaderType, const ShaderVariantValueMap& inShaderVariants) { Assert(Core::ThreadContext::IsRenderThread()); diff --git a/Engine/Source/Render/Src/ShaderCompiler.cpp b/Engine/Source/Render/Src/ShaderCompiler.cpp index 4ea7e9890..eb43101aa 100644 --- a/Engine/Source/Render/Src/ShaderCompiler.cpp +++ b/Engine/Source/Render/Src/ShaderCompiler.cpp @@ -61,7 +61,7 @@ namespace Render { { D3D_SIT_CBUFFER, RHI::BindingType::uniformBuffer }, { D3D_SIT_TEXTURE, RHI::BindingType::texture }, { D3D_SIT_SAMPLER, RHI::BindingType::sampler }, - { D3D_SIT_UAV_RWTYPED, RHI::BindingType::storageTexture }, + { D3D_SIT_UAV_RWTYPED, RHI::BindingType::rwStorageTexture }, { D3D_SIT_STRUCTURED, RHI::BindingType::storageBuffer }, { D3D_SIT_UAV_RWSTRUCTURED, RHI::BindingType::rwStorageBuffer } }; @@ -242,7 +242,8 @@ namespace Render { resourceBindings.emplace_back(&buffer, RHI::BindingType::storageBuffer); } for (const spirv_cross::Resource& image : shaderResources.storage_images) { - resourceBindings.emplace_back(&image, RHI::BindingType::storageTexture); + const bool readOnly = compiler.has_decoration(image.id, spv::DecorationNonWritable); + resourceBindings.emplace_back(&image, readOnly ? RHI::BindingType::storageTexture : RHI::BindingType::rwStorageTexture); } for (const auto& iter : resourceBindings) { // NOLINT diff --git a/Engine/Source/Render/Src/VertexFactory.cpp b/Engine/Source/Render/Src/VertexFactory.cpp new file mode 100644 index 000000000..e06489e9e --- /dev/null +++ b/Engine/Source/Render/Src/VertexFactory.cpp @@ -0,0 +1,9 @@ +// +// Created by johnk on 2026/7/5. +// + +#include + +namespace Render { + ImplementStaticVertexFactoryType(StaticMeshVertexFactory) +} diff --git a/Engine/Source/Render/Test/ResourcePoolTest.cpp b/Engine/Source/Render/Test/ResourcePoolTest.cpp index 471d6cfb1..627571ae1 100644 --- a/Engine/Source/Render/Test/ResourcePoolTest.cpp +++ b/Engine/Source/Render/Test/ResourcePoolTest.cpp @@ -33,7 +33,7 @@ TEST_F(ResourcePoolTest, BasicTest) textureDesc.height = 1080; textureDesc.depthOrArraySize = 1; textureDesc.format = RHI::PixelFormat::rgba8Unorm; - textureDesc.usages = RHI::TextureUsageBits::renderAttachment | RHI::TextureUsageBits::storageBinding; + textureDesc.usages = RHI::TextureUsageBits::renderAttachment | RHI::TextureUsageBits::rwStorageBinding; textureDesc.mipLevels = 1; textureDesc.samples = 1; textureDesc.initialState = RHI::TextureState::undefined; diff --git a/Engine/Source/Runtime/Include/Runtime/Asset/Material.h b/Engine/Source/Runtime/Include/Runtime/Asset/Material.h index b0420bdd3..e1fcb9f0d 100644 --- a/Engine/Source/Runtime/Include/Runtime/Asset/Material.h +++ b/Engine/Source/Runtime/Include/Runtime/Asset/Material.h @@ -179,10 +179,14 @@ namespace Runtime { EFunc() ParameterField& EmplaceParameterField(const std::string& inName); EFunc() void Update(); + void PostLoad() override; + const Render::MaterialShaderType* FindShaderType(Render::VertexFactoryTypeKey inVertexFactoryTypeKey, RHI::ShaderStageBits inStage) const; private: using StageShaderTypeMap = std::unordered_map>; + void CompileShaderTypes() const; + EProperty() MaterialType type; EProperty() std::string source; EProperty() VariantFieldMap variantFields; diff --git a/Engine/Source/Runtime/Include/Runtime/Asset/Mesh.h b/Engine/Source/Runtime/Include/Runtime/Asset/Mesh.h index 7a8578421..4215ae519 100644 --- a/Engine/Source/Runtime/Include/Runtime/Asset/Mesh.h +++ b/Engine/Source/Runtime/Include/Runtime/Asset/Mesh.h @@ -19,6 +19,7 @@ namespace Runtime { EProperty() std::vector positions; EProperty() std::vector tangents; EProperty() std::vector uv0; + EProperty() std::vector indices; // optional EProperty() std::vector uv1; EProperty() std::vector colors; @@ -39,6 +40,12 @@ namespace Runtime { explicit StaticMesh(Core::Uri inUri); ~StaticMesh() override; + EFunc() const AssetPtr& GetMaterial() const; + EFunc() void SetMaterial(const AssetPtr& inMaterial); + EFunc() size_t GetLODCount() const; + EFunc() const StaticMeshLOD& GetLOD(size_t inIndex) const; + EFunc() StaticMeshLOD& EmplaceLOD(); + private: EProperty() AssetPtr material; EProperty() std::vector lodVec; diff --git a/Engine/Source/Runtime/Include/Runtime/Canvas.h b/Engine/Source/Runtime/Include/Runtime/Canvas.h new file mode 100644 index 000000000..156049a8f --- /dev/null +++ b/Engine/Source/Runtime/Include/Runtime/Canvas.h @@ -0,0 +1,45 @@ +// +// Created by johnk on 2026/7/8. +// + +#pragma once + +#include + +#include +#include +#include + +namespace Runtime { + struct RUNTIME_API CanvasDesc { + CanvasDesc(); + + uint32_t width; + uint32_t height; + RHI::PixelFormat format; + std::string debugName; + }; + + class RUNTIME_API Canvas : public RenderSurface { + public: + Canvas(RHI::Device& inDevice, const CanvasDesc& inDesc); + ~Canvas() override; + + RHI::Texture* GetTexture() const override; + RHI::TextureView* GetRenderTargetView() const override; + RHI::TextureView* GetTextureView() const; + void Resize(uint32_t inWidth, uint32_t inHeight) override; + + private: + void CreateTarget(); + + RHI::Device& device; + uint32_t width; + uint32_t height; + RHI::PixelFormat format; + std::string debugName; + Common::UniquePtr texture; + Common::UniquePtr textureView; + Common::UniquePtr renderTargetView; + }; +} diff --git a/Engine/Source/Runtime/Include/Runtime/Client.h b/Engine/Source/Runtime/Include/Runtime/Client.h index 1c821cf63..318f6cd4e 100644 --- a/Engine/Source/Runtime/Include/Runtime/Client.h +++ b/Engine/Source/Runtime/Include/Runtime/Client.h @@ -4,17 +4,18 @@ #pragma once -#include +#include +#include namespace Runtime { class World; - class Client { + class RUNTIME_API Client { public: virtual ~Client(); virtual World& GetWorld() = 0; - virtual Viewport& GetViewport() = 0; + virtual RenderSurface* GetRenderSurface() = 0; protected: Client(); diff --git a/Engine/Source/Runtime/Include/Runtime/Component/Camera.h b/Engine/Source/Runtime/Include/Runtime/Component/Camera.h index 2dc19aa16..f7e9cd8d6 100644 --- a/Engine/Source/Runtime/Include/Runtime/Component/Camera.h +++ b/Engine/Source/Runtime/Include/Runtime/Component/Camera.h @@ -6,11 +6,13 @@ #include +#include #include #include +#include namespace Runtime { - struct RUNTIME_API EClass(transient) Camera final { + struct RUNTIME_API EClass(comp, transient) Camera final { EClassBody(Camera) Camera(); @@ -20,6 +22,9 @@ namespace Runtime { EProperty() std::optional farPlane; // only need when perspective EProperty() std::optional fov; + // cross-frame rendering history of the view rendered through this camera, allocated by RenderSystem on first + // use and released on the render thread with the component + RenderThreadPtr viewState; }; // TODO scene capture diff --git a/Engine/Source/Runtime/Include/Runtime/Component/Light.h b/Engine/Source/Runtime/Include/Runtime/Component/Light.h index efe40ba21..337b78be1 100644 --- a/Engine/Source/Runtime/Include/Runtime/Component/Light.h +++ b/Engine/Source/Runtime/Include/Runtime/Component/Light.h @@ -9,7 +9,7 @@ #include namespace Runtime { - struct RUNTIME_API EClass() DirectionalLight final { + struct RUNTIME_API EClass(comp) DirectionalLight final { EClassBody(DirectionalLight) DirectionalLight(); @@ -19,7 +19,7 @@ namespace Runtime { EProperty() bool castShadows; }; - struct RUNTIME_API EClass() PointLight final { + struct RUNTIME_API EClass(comp) PointLight final { EClassBody(PointLight) PointLight(); @@ -30,7 +30,7 @@ namespace Runtime { EProperty() float radius; }; - struct RUNTIME_API EClass() SpotLight final { + struct RUNTIME_API EClass(comp) SpotLight final { EClassBody(SpotLight) SpotLight(); diff --git a/Engine/Source/Runtime/Include/Runtime/Component/Name.h b/Engine/Source/Runtime/Include/Runtime/Component/Name.h new file mode 100644 index 000000000..cd45897a8 --- /dev/null +++ b/Engine/Source/Runtime/Include/Runtime/Component/Name.h @@ -0,0 +1,21 @@ +// +// Created by johnk on 2026/7/5. +// + +#pragma once + +#include + +#include +#include + +namespace Runtime { + struct RUNTIME_API EClass(comp) Name final { + EClassBody(Name) + + Name(); + explicit Name(std::string inValue); + + EProperty() std::string value; + }; +} diff --git a/Engine/Source/Runtime/Include/Runtime/Component/Player.h b/Engine/Source/Runtime/Include/Runtime/Component/Player.h index eae643485..a5f88f061 100644 --- a/Engine/Source/Runtime/Include/Runtime/Component/Player.h +++ b/Engine/Source/Runtime/Include/Runtime/Component/Player.h @@ -4,11 +4,9 @@ #pragma once -#include #include #include #include -#include namespace Runtime { struct RUNTIME_API EClass(globalComp, transient) PlayersInfo { @@ -25,20 +23,9 @@ namespace Runtime { LocalPlayer(); uint8_t localPlayerIndex; - RenderThreadPtr viewState; }; -#if BUILD_EDITOR - struct RUNTIME_API EClass(transient) EditorPlayer { - EClassBody(EditorVirtualPlayer) - - EditorPlayer(); - - RenderThreadPtr viewState; - }; -#endif - - struct RUNTIME_API EClass() PlayerStart { + struct RUNTIME_API EClass(comp) PlayerStart { EClassBody(PlayerStart) PlayerStart(); diff --git a/Engine/Source/Runtime/Include/Runtime/Component/Primitive.h b/Engine/Source/Runtime/Include/Runtime/Component/Primitive.h index ce553a70c..caf32ee79 100644 --- a/Engine/Source/Runtime/Include/Runtime/Component/Primitive.h +++ b/Engine/Source/Runtime/Include/Runtime/Component/Primitive.h @@ -10,7 +10,7 @@ #include namespace Runtime { - struct RUNTIME_API EClass() StaticPrimitive final { + struct RUNTIME_API EClass(comp) StaticPrimitive final { EClassBody(StaticPrimitive) StaticPrimitive(); diff --git a/Engine/Source/Runtime/Include/Runtime/Component/Transform.h b/Engine/Source/Runtime/Include/Runtime/Component/Transform.h index 05b54e94a..3a5226e56 100644 --- a/Engine/Source/Runtime/Include/Runtime/Component/Transform.h +++ b/Engine/Source/Runtime/Include/Runtime/Component/Transform.h @@ -10,7 +10,7 @@ #include namespace Runtime { - struct RUNTIME_API EClass() WorldTransform final { + struct RUNTIME_API EClass(comp) WorldTransform final { EClassBody(WorldTransform) WorldTransform(); @@ -20,7 +20,7 @@ namespace Runtime { }; // must be used with Hierarchy and WorldTransform - struct RUNTIME_API EClass() LocalTransform final { + struct RUNTIME_API EClass(comp) LocalTransform final { EClassBody(LocalTransform) LocalTransform(); @@ -29,7 +29,7 @@ namespace Runtime { EProperty() Common::FTransform localToParent; }; - struct RUNTIME_API EClass() Hierarchy final { + struct RUNTIME_API EClass(comp) Hierarchy final { EClassBody(Hierarchy) Hierarchy(); diff --git a/Engine/Source/Runtime/Include/Runtime/ECS.h b/Engine/Source/Runtime/Include/Runtime/ECS.h index 542fbb494..af7e20ba0 100644 --- a/Engine/Source/Runtime/Include/Runtime/ECS.h +++ b/Engine/Source/Runtime/Include/Runtime/ECS.h @@ -409,6 +409,12 @@ namespace Runtime { Observer removedObserver; }; + // entities carrying this component are skipped entirely by ECRegistry::Save, use it to mark runtime-created + // entities (e.g. players spawned by systems) that must not be serialized into a level + struct RUNTIME_API EClass(transient) TransientTag { + EClassBody(TransientTag) + }; + struct RUNTIME_API EClass() EntityArchive { EClassBody(EntityArchive) diff --git a/Engine/Source/Runtime/Include/Runtime/RenderSurface.h b/Engine/Source/Runtime/Include/Runtime/RenderSurface.h new file mode 100644 index 000000000..ebf4365a8 --- /dev/null +++ b/Engine/Source/Runtime/Include/Runtime/RenderSurface.h @@ -0,0 +1,28 @@ +// +// Created by johnk on 2026/7/8. +// + +#pragma once + +#include + +#include +#include +#include + +namespace Runtime { + class RUNTIME_API RenderSurface { + public: + virtual ~RenderSurface(); + + NonCopyable(RenderSurface) + NonMovable(RenderSurface) + + virtual RHI::Texture* GetTexture() const = 0; + virtual RHI::TextureView* GetRenderTargetView() const = 0; + virtual void Resize(uint32_t inWidth, uint32_t inHeight) = 0; + + protected: + RenderSurface(); + }; +} diff --git a/Engine/Source/Runtime/Include/Runtime/RenderThreadPtr.h b/Engine/Source/Runtime/Include/Runtime/RenderThreadPtr.h index 0144a9f42..2d70a44ca 100644 --- a/Engine/Source/Runtime/Include/Runtime/RenderThreadPtr.h +++ b/Engine/Source/Runtime/Include/Runtime/RenderThreadPtr.h @@ -70,14 +70,16 @@ namespace Runtime { template T* RenderThreadPtr::operator->() const noexcept { - Assert(Core::ThreadContext::IsGameThread()); + // systems are constructed and ticked on game worker threads by the system graph executor, both tags belong + // to the game-side frame timeline + Assert(Core::ThreadContext::IsGameThread() || Core::ThreadContext::IsGameWorkerThread()); return ptr.Get(); } template T& RenderThreadPtr::operator*() const noexcept { - Assert(Core::ThreadContext::IsGameThread()); + Assert(Core::ThreadContext::IsGameThread() || Core::ThreadContext::IsGameWorkerThread()); return *ptr; } @@ -108,7 +110,7 @@ namespace Runtime { template T* RenderThreadPtr::Get() const { - Assert(Core::ThreadContext::IsGameThread()); + Assert(Core::ThreadContext::IsGameThread() || Core::ThreadContext::IsGameWorkerThread()); return ptr.Get(); } diff --git a/Engine/Source/Runtime/Include/Runtime/System/Player.h b/Engine/Source/Runtime/Include/Runtime/System/Player.h index 2f0604f91..2f8b8ff8b 100644 --- a/Engine/Source/Runtime/Include/Runtime/System/Player.h +++ b/Engine/Source/Runtime/Include/Runtime/System/Player.h @@ -4,11 +4,7 @@ #pragma once -#include #include -#include -#include -#include #include #include @@ -24,48 +20,8 @@ namespace Runtime { ~PlayerSystem() override; private: - template Entity CreatePlayer(); - template void CreatePlayerSpecialPart(T& inPlayer); + Entity CreateLocalPlayer(); uint8_t activeLocalPlayerNum; - Render::RenderModule& renderModule; }; } - -namespace Runtime { - template - Entity PlayerSystem::CreatePlayer() - { - const auto& playerStartQuery = registry.View().All(); - Assert(playerStartQuery.size() == 1); - const auto& [playerStartEntity, playerStart, playerStartTransform] = playerStartQuery[0]; - - const auto playerEntity = registry.Create(); - registry.Emplace(playerEntity); - registry.Emplace(playerEntity, playerStartTransform); - - auto& player = registry.Emplace(playerEntity); - player.viewState = renderModule.NewViewState(); - CreatePlayerSpecialPart(player); - return playerEntity; - } - - template - inline void PlayerSystem::CreatePlayerSpecialPart(T& inPlayer) // NOLINT - { - Unimplement(); - } - - template <> - inline void PlayerSystem::CreatePlayerSpecialPart(LocalPlayer& inPlayer) // NOLINT - { - inPlayer.localPlayerIndex = activeLocalPlayerNum++; - } - -#if BUILD_EDITOR - template <> - inline void PlayerSystem::CreatePlayerSpecialPart(EditorPlayer& inPlayer) // NOLINT - { - } -#endif -} // namespace Runtime diff --git a/Engine/Source/Runtime/Include/Runtime/System/Render.h b/Engine/Source/Runtime/Include/Runtime/System/Render.h index 8b1dfb556..fc13dff46 100644 --- a/Engine/Source/Runtime/Include/Runtime/System/Render.h +++ b/Engine/Source/Runtime/Include/Runtime/System/Render.h @@ -5,13 +5,11 @@ #pragma once #include -#include #include #include -#include -#include -#include #include +#include +#include #include namespace Runtime { @@ -29,40 +27,11 @@ namespace Runtime { private: static Common::URect GetPlayerViewport(uint32_t inWidth, uint32_t inHeight, uint8_t inPlayerNum, uint8_t inPlayerIndex); - template Render::View BuildViewForPlayer(Entity inEntity, uint8_t inPlayerNum, uint8_t inPlayerIndex) const; + Render::View BuildViewForCamera(Entity inEntity) const; std::vector BuildViews() const; Render::RenderModule& renderModule; - PlayType playType; Client* client; RHI::Fence* lastFrameFence; }; } - -namespace Runtime { - template - Render::View RenderSystem::BuildViewForPlayer(Entity inEntity, uint8_t inPlayerNum, uint8_t inPlayerIndex) const - { - const auto& player = registry.Get(inEntity); - const auto& camera = registry.Get(inEntity); - const auto& worldTransform = registry.Get(inEntity); - const auto& clientViewport = client->GetViewport(); - - const auto width = clientViewport.GetWidth(); - const auto height = clientViewport.GetHeight(); - - Render::View view = renderModule.CreateView(); - view.state = player.viewState.Get(); - view.data.viewport = GetPlayerViewport(width, height, inPlayerNum, inPlayerIndex); - view.data.viewMatrix = worldTransform.localToWorld.GetTransformMatrixNoScale().Inverse(); - view.data.origin = worldTransform.localToWorld.translation; - if (camera.perspective) { - const Common::FReversedZPerspectiveProjection projection(camera.fov.value(), static_cast(width), static_cast(height), camera.nearPlane, camera.farPlane); - view.data.projectionMatrix = projection.GetProjectionMatrix(); - } else { - const Common::FReversedZOrthoProjection projection(static_cast(width), static_cast(height), camera.nearPlane, camera.farPlane); - view.data.projectionMatrix = projection.GetProjectionMatrix(); - } - return view; - } -} diff --git a/Engine/Source/Runtime/Include/Runtime/System/Scene.h b/Engine/Source/Runtime/Include/Runtime/System/Scene.h index 8527705de..7572cdae7 100644 --- a/Engine/Source/Runtime/Include/Runtime/System/Scene.h +++ b/Engine/Source/Runtime/Include/Runtime/System/Scene.h @@ -8,12 +8,16 @@ #include #include +#include #include #include +#include +#include #include #include #include #include +#include namespace Runtime { class RUNTIME_API EClass() SceneSystem final : public System { @@ -29,9 +33,9 @@ namespace Runtime { void Tick(float inDeltaTimeSeconds) override; private: - template void QueueCreateSceneProxy(Entity inEntity); + template void QueueCreateSceneProxy(Entity inEntity, bool inWithScale = false); template void QueueUpdateSceneProxyContent(Entity inEntity); - template void QueueUpdateSceneProxyTransform(Entity inEntity); + template void QueueUpdateSceneProxyTransform(Entity inEntity, bool inWithScale = false); template void QueueRemoveSceneProxy(Entity inEntity); Render::RenderModule& renderModule; @@ -39,7 +43,7 @@ namespace Runtime { EventsObserver directionalLightsObserver; EventsObserver pointLightsObserver; EventsObserver spotLightsObserver; - // TODO primitive + EventsObserver staticPrimitivesObserver; }; } @@ -89,21 +93,67 @@ namespace Runtime::Internal { outSceneProxy.intensity = inComponent.intensity; } - // TODO primitive + // runs on the render thread: uploads the mesh geometry and resolves the material's shader types, the asset chain + // is kept alive by the component copy captured into the render thread task + template <> + inline void UpdateSceneProxyContent(Render::PrimitiveSceneProxy& outSceneProxy, const StaticPrimitive& inComponent) + { + outSceneProxy.mesh = nullptr; + outSceneProxy.vertexFactoryType = nullptr; + outSceneProxy.vertexShaderType = nullptr; + outSceneProxy.pixelShaderType = nullptr; + + if (inComponent.mesh.Get() == nullptr || inComponent.mesh->GetLODCount() == 0) { + return; + } + const AssetPtr& materialInstance = inComponent.mesh->GetMaterial(); + if (materialInstance.Get() == nullptr || materialInstance->GetMaterial().Get() == nullptr) { + return; + } + + const StaticMeshVertices& vertices = inComponent.mesh->GetLOD(0).vertices; + if (vertices.positions.empty() || vertices.indices.empty()) { + return; + } + std::vector gpuVertices; + gpuVertices.reserve(vertices.positions.size()); + for (size_t i = 0; i < vertices.positions.size(); i++) { + Render::MeshRenderData::Vertex vertex; + vertex.position = vertices.positions[i]; + vertex.uv0 = i < vertices.uv0.size() ? vertices.uv0[i] : Common::FVec2(); + gpuVertices.emplace_back(vertex); + } + + RHI::Device* device = EngineHolder::Get().GetRenderModule().GetDevice(); + outSceneProxy.mesh = new Render::MeshRenderData(*device, gpuVertices, vertices.indices); + + const Render::VertexFactoryType& vertexFactoryType = Render::StaticMeshVertexFactory::Get(); + const Material* material = materialInstance->GetMaterial().Get(); + outSceneProxy.vertexFactoryType = &vertexFactoryType; + outSceneProxy.vertexShaderType = material->FindShaderType(vertexFactoryType.GetKey(), RHI::ShaderStageBits::sVertex); + outSceneProxy.pixelShaderType = material->FindShaderType(vertexFactoryType.GetKey(), RHI::ShaderStageBits::sPixel); + + if (materialInstance->HasParameter("baseColor")) { + if (const auto baseColorParam = materialInstance->GetParameter("baseColor"); + std::holds_alternative(baseColorParam)) { + outSceneProxy.baseColor = std::get(baseColorParam); + } + } + } } namespace Runtime { template - void SceneSystem::QueueCreateSceneProxy(Entity inEntity) + void SceneSystem::QueueCreateSceneProxy(Entity inEntity, bool inWithScale) { const auto& sceneHolder = registry.GGet(); const auto& component = registry.Get(inEntity); const auto* transform = registry.Find(inEntity); - renderModule.GetRenderThread().EmplaceTask([scene = sceneHolder.scene.Get(), inEntity, component, transform = Internal::GetOptional(transform)]() -> void { + renderModule.GetRenderThread().EmplaceTask([scene = sceneHolder.scene.Get(), inEntity, component, transform = Internal::GetOptional(transform), inWithScale]() -> void { SceneProxy sceneProxy; Internal::UpdateSceneProxyContent(sceneProxy, component); if (transform.has_value()) { - Internal::UpdateSceneProxyWorldTransform(sceneProxy, transform.value(), false); + Internal::UpdateSceneProxyWorldTransform(sceneProxy, transform.value(), inWithScale); } scene->Add(inEntity, std::move(sceneProxy)); }); @@ -121,13 +171,13 @@ namespace Runtime { } template - void SceneSystem::QueueUpdateSceneProxyTransform(Entity inEntity) + void SceneSystem::QueueUpdateSceneProxyTransform(Entity inEntity, bool inWithScale) { const auto& sceneHolder = registry.GGet(); const auto& transform = registry.Get(inEntity); - renderModule.GetRenderThread().EmplaceTask([scene = sceneHolder.scene.Get(), inEntity, transform]() -> void { + renderModule.GetRenderThread().EmplaceTask([scene = sceneHolder.scene.Get(), inEntity, transform, inWithScale]() -> void { auto& sceneProxy = scene->Get(inEntity); - Internal::UpdateSceneProxyWorldTransform(sceneProxy, transform, false); + Internal::UpdateSceneProxyWorldTransform(sceneProxy, transform, inWithScale); }); } diff --git a/Engine/Source/Runtime/Include/Runtime/Viewport.h b/Engine/Source/Runtime/Include/Runtime/Viewport.h deleted file mode 100644 index 7bb0adc14..000000000 --- a/Engine/Source/Runtime/Include/Runtime/Viewport.h +++ /dev/null @@ -1,35 +0,0 @@ -// -// Created by johnk on 2025/2/18. -// - -#pragma once - -#include -#include - -namespace Runtime { - class Client; - - struct PresentInfo { - PresentInfo(); - - RHI::Texture* backTexture; - RHI::Semaphore* imageReadySemaphore; - RHI::Semaphore* renderFinishedSemaphore; - }; - - class Viewport { - public: - virtual ~Viewport(); - - virtual Client& GetClient() = 0; - virtual PresentInfo GetNextPresentInfo() = 0; - virtual uint32_t GetWidth() const = 0; - virtual uint32_t GetHeight() const = 0; - virtual void Resize(uint32_t inWidth, uint32_t inHeight) = 0; - // TODO mouse keyboard inputs etc. - - protected: - Viewport(); - }; -} diff --git a/Engine/Source/Runtime/Include/Runtime/Window.h b/Engine/Source/Runtime/Include/Runtime/Window.h new file mode 100644 index 000000000..a89b1733c --- /dev/null +++ b/Engine/Source/Runtime/Include/Runtime/Window.h @@ -0,0 +1,55 @@ +// +// Created by johnk on 2026/7/8. +// + +#pragma once + +#include + +#include +#include +#include + +namespace Runtime { + class RUNTIME_API Window : public RenderSurface { + public: + ~Window() override; + + RHI::Texture* GetTexture() const override; + RHI::TextureView* GetRenderTargetView() const override; + void Resize(uint32_t inWidth, uint32_t inHeight) override; + void AcquireBackTexture(); + void Present(); + RHI::Device& GetDevice() const; + RHI::PixelFormat GetSwapChainFormat() const; + RHI::TextureState GetCurrentBackTextureState() const; + RHI::Semaphore* GetImageReadySemaphore() const; + RHI::Semaphore* GetRenderFinishedSemaphore() const; + void WaitRenderingIdle() const; + + protected: + explicit Window(RHI::Device& inDevice); + + void InitializeSurface(Common::UniquePtr inSurface, uint32_t inWidth, uint32_t inHeight); + void DestroySurface(); + + private: + static constexpr uint8_t swapChainTextureNum = 2; + + void ReleaseSwapChainResources(); + void RecreateSwapChain(uint32_t inWidth, uint32_t inHeight); + + RHI::Device& device; + Common::UniquePtr surface; + Common::UniquePtr swapChain; + std::vector swapChainTextures; + std::vector> swapChainTextureViews; + std::vector swapChainTextureStates; + Common::UniquePtr imageReadySemaphore; + std::vector> renderFinishedSemaphores; + RHI::PixelFormat swapChainFormat; + uint32_t currentBackTextureIndex; + uint32_t width; + uint32_t height; + }; +} diff --git a/Engine/Source/Runtime/Include/Runtime/World.h b/Engine/Source/Runtime/Include/Runtime/World.h index b229f5e24..42cabffe9 100644 --- a/Engine/Source/Runtime/Include/Runtime/World.h +++ b/Engine/Source/Runtime/Include/Runtime/World.h @@ -36,6 +36,9 @@ namespace Runtime { void Resume(); void Pause(); void Stop(); + bool ShouldTick() const; + ECRegistry& GetRegistry(); + const ECRegistry& GetRegistry() const; void LoadFrom(AssetPtr inLevel); void SaveTo(AssetPtr inLevel); diff --git a/Engine/Source/Runtime/Src/Asset/Material.cpp b/Engine/Source/Runtime/Src/Asset/Material.cpp index 509eccec7..866f2e42f 100644 --- a/Engine/Source/Runtime/Src/Asset/Material.cpp +++ b/Engine/Source/Runtime/Src/Asset/Material.cpp @@ -2,7 +2,11 @@ // Created by johnk on 2025/3/21. // +#include + +#include #include +#include namespace Runtime::Internal { static std::string GetVariantFieldMacro(const std::string& inVariantFieldName) @@ -230,8 +234,8 @@ namespace Runtime { RHI::ShaderStageBits::sVertex, RHI::ShaderStageBits::sPixel}; static std::unordered_map stageEntrySourceFileMap = { - {RHI::ShaderStageBits::sVertex, "Engine/Shader/BasePassVS.esl"}, - {RHI::ShaderStageBits::sPixel, "Engine/Shader/BasePassPS.esl"}}; + {RHI::ShaderStageBits::sVertex, "Engine/Shader/Explosion/BasePassVS.esl"}, + {RHI::ShaderStageBits::sPixel, "Engine/Shader/Explosion/BasePassPS.esl"}}; static std::unordered_map stageSimpleNameMap = { {RHI::ShaderStageBits::sVertex, "VS"}, {RHI::ShaderStageBits::sPixel, "PS"}}; @@ -272,10 +276,48 @@ namespace Runtime { stage, stageEntrySourceFileMap.at(stage), stageEntryPointMap.at(stage), - std::vector {materialRootCacheDir.String()}, + std::vector {materialRootCacheDir.String(), std::string("Engine/Shader/Explosion")}, Internal::BuildShaderVariantFieldVec(variantFields))); } } + + CompileShaderTypes(); + } + + void Material::PostLoad() + { + Update(); + } + + const Render::MaterialShaderType* Material::FindShaderType(Render::VertexFactoryTypeKey inVertexFactoryTypeKey, RHI::ShaderStageBits inStage) const + { + const auto stageMapIter = shaderTypes.find(inVertexFactoryTypeKey); + if (stageMapIter == shaderTypes.end()) { + return nullptr; + } + const auto shaderTypeIter = stageMapIter->second.find(inStage); + return shaderTypeIter == stageMapIter->second.end() ? nullptr : shaderTypeIter->second.Get(); + } + + void Material::CompileShaderTypes() const + { + std::vector typesToCompile; + for (const auto& stageShaderTypeMap : shaderTypes | std::views::values) { + for (const auto& shaderType : stageShaderTypeMap | std::views::values) { + typesToCompile.emplace_back(shaderType.Get()); + } + } + if (typesToCompile.empty()) { + return; + } + + // the compiler/artifact-registry singletons used here are this module's Render.Static copies, matching the + // ones the renderer executing in this module reads + const RHI::RHIType rhiType = EngineHolder::Get().GetRenderModule().GetDevice()->GetGpu().GetInstance().GetRHIType(); + Render::ShaderCompileOptions options; + options.byteCodeType = rhiType == RHI::RHIType::directX12 ? Render::ShaderByteCodeType::dxil : Render::ShaderByteCodeType::spirv; + options.withDebugInfo = static_cast(BUILD_CONFIG_DEBUG); // NOLINT + (void) Render::ShaderTypeCompiler::Get().Compile(typesToCompile, options); } MaterialInstance::MaterialInstance(Core::Uri inUri) diff --git a/Engine/Source/Runtime/Src/Asset/Mesh.cpp b/Engine/Source/Runtime/Src/Asset/Mesh.cpp index f2674c9f1..36e662280 100644 --- a/Engine/Source/Runtime/Src/Asset/Mesh.cpp +++ b/Engine/Source/Runtime/Src/Asset/Mesh.cpp @@ -11,4 +11,29 @@ namespace Runtime { } StaticMesh::~StaticMesh() = default; + + const AssetPtr& StaticMesh::GetMaterial() const + { + return material; + } + + void StaticMesh::SetMaterial(const AssetPtr& inMaterial) + { + material = inMaterial; + } + + size_t StaticMesh::GetLODCount() const + { + return lodVec.size(); + } + + const StaticMeshLOD& StaticMesh::GetLOD(size_t inIndex) const + { + return lodVec.at(inIndex); + } + + StaticMeshLOD& StaticMesh::EmplaceLOD() + { + return lodVec.emplace_back(); + } } diff --git a/Engine/Source/Runtime/Src/Canvas.cpp b/Engine/Source/Runtime/Src/Canvas.cpp new file mode 100644 index 000000000..be28af1af --- /dev/null +++ b/Engine/Source/Runtime/Src/Canvas.cpp @@ -0,0 +1,97 @@ +// +// Created by johnk on 2026/7/8. +// + +#include + +#include + +namespace Runtime::Internal { + static uint32_t ClampExtent(uint32_t inValue) + { + return std::max(1u, inValue); + } +} + +namespace Runtime { + CanvasDesc::CanvasDesc() + : width(1) + , height(1) + , format(RHI::PixelFormat::rgba8Unorm) + , debugName("canvas") + { + } + + Canvas::Canvas(RHI::Device& inDevice, const CanvasDesc& inDesc) + : device(inDevice) + , width(Internal::ClampExtent(inDesc.width)) + , height(Internal::ClampExtent(inDesc.height)) + , format(inDesc.format) + , debugName(inDesc.debugName) + { + CreateTarget(); + } + + Canvas::~Canvas() = default; + + RHI::Texture* Canvas::GetTexture() const + { + return texture.Get(); + } + + RHI::TextureView* Canvas::GetRenderTargetView() const + { + return renderTargetView.Get(); + } + + RHI::TextureView* Canvas::GetTextureView() const + { + return textureView.Get(); + } + + void Canvas::Resize(uint32_t inWidth, uint32_t inHeight) + { + const uint32_t newWidth = Internal::ClampExtent(inWidth); + const uint32_t newHeight = Internal::ClampExtent(inHeight); + if (newWidth == width && newHeight == height) { + return; + } + + width = newWidth; + height = newHeight; + textureView.Reset(); + renderTargetView.Reset(); + texture.Reset(); + CreateTarget(); + } + + void Canvas::CreateTarget() + { + texture = device.CreateTexture( + RHI::TextureCreateInfo() + .SetDimension(RHI::TextureDimension::t2D) + .SetWidth(width) + .SetHeight(height) + .SetDepthOrArraySize(1) + .SetFormat(format) + .SetUsages(RHI::TextureUsageBits::renderAttachment | RHI::TextureUsageBits::textureBinding) + .SetMipLevels(1) + .SetSamples(1) + .SetInitialState(RHI::TextureState::shaderReadOnly) + .SetDebugName(debugName)); + textureView = texture->CreateTextureView( + RHI::TextureViewCreateInfo() + .SetDimension(RHI::TextureViewDimension::tv2D) + .SetMipLevels(0, 1) + .SetArrayLayers(0, 1) + .SetAspect(RHI::TextureAspect::color) + .SetType(RHI::TextureViewType::textureBinding)); + renderTargetView = texture->CreateTextureView( + RHI::TextureViewCreateInfo() + .SetDimension(RHI::TextureViewDimension::tv2D) + .SetMipLevels(0, 1) + .SetArrayLayers(0, 1) + .SetAspect(RHI::TextureAspect::color) + .SetType(RHI::TextureViewType::colorAttachment)); + } +} diff --git a/Engine/Source/Runtime/Src/Component/Camera.cpp b/Engine/Source/Runtime/Src/Component/Camera.cpp index b5165f70a..72005169d 100644 --- a/Engine/Source/Runtime/Src/Component/Camera.cpp +++ b/Engine/Source/Runtime/Src/Component/Camera.cpp @@ -9,6 +9,7 @@ namespace Runtime { : perspective(true) , nearPlane(0.1f) , fov(90.0f) + , viewState(nullptr) { } } diff --git a/Engine/Source/Runtime/Src/Component/Name.cpp b/Engine/Source/Runtime/Src/Component/Name.cpp new file mode 100644 index 000000000..ec6d3edbf --- /dev/null +++ b/Engine/Source/Runtime/Src/Component/Name.cpp @@ -0,0 +1,14 @@ +// +// Created by johnk on 2026/7/5. +// + +#include + +namespace Runtime { + Name::Name() = default; + + Name::Name(std::string inValue) + : value(std::move(inValue)) + { + } +} diff --git a/Engine/Source/Runtime/Src/Component/Player.cpp b/Engine/Source/Runtime/Src/Component/Player.cpp index adf96d718..7d854a004 100644 --- a/Engine/Source/Runtime/Src/Component/Player.cpp +++ b/Engine/Source/Runtime/Src/Component/Player.cpp @@ -9,16 +9,8 @@ namespace Runtime { LocalPlayer::LocalPlayer() : localPlayerIndex(0) - , viewState(nullptr) { } -#if BUILD_EDITOR - EditorPlayer::EditorPlayer() - : viewState(nullptr) - { - } -#endif - PlayerStart::PlayerStart() = default; } // namespace Runtime diff --git a/Engine/Source/Runtime/Src/ECS.cpp b/Engine/Source/Runtime/Src/ECS.cpp index 55a4afac5..dccd8bbd1 100644 --- a/Engine/Source/Runtime/Src/ECS.cpp +++ b/Engine/Source/Runtime/Src/ECS.cpp @@ -805,6 +805,9 @@ namespace Runtime { outArchive = {}; outArchive.entities.reserve(Count()); Each([&](Entity entity) -> void { + if (Has(entity)) { + return; + } outArchive.entities.emplace(entity, EntityArchive {}); auto& comps = outArchive.entities.at(entity).comps; comps.reserve(CompCount(entity)); diff --git a/Engine/Source/Runtime/Src/Engine.cpp b/Engine/Source/Runtime/Src/Engine.cpp index 612713b6e..eb3e9eb6b 100644 --- a/Engine/Source/Runtime/Src/Engine.cpp +++ b/Engine/Source/Runtime/Src/Engine.cpp @@ -10,6 +10,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -35,6 +38,7 @@ namespace Runtime { Engine::~Engine() { + Render::RenderWorkerThreads::Get().Stop(); renderModule->DeInitialize(); ::Core::ModuleManager::Get().Unload("Render"); @@ -65,15 +69,21 @@ namespace Runtime { Core::ThreadContext::IncFrameNumber(); + // the shader artifact copy and pool forfeits intentionally run through this module's Render.Static singleton + // copies: the renderer itself executes in this module too, so these are the instances it reads auto& renderThread = renderModule->GetRenderThread(); - renderThread.EmplaceTask([]() -> void { + renderThread.EmplaceTask([device = renderModule->GetDevice()]() -> void { Core::ThreadContext::IncFrameNumber(); Core::Console::Get().PerformRenderThreadSettingsCopy(); Render::ShaderArtifactRegistry::Get().PerformThreadCopy(); + Render::BufferPool::Get(*device).Forfeit(); + Render::TexturePool::Get(*device).Forfeit(); + Render::ResourceViewCache::Get(*device).Forfeit(); + Render::BindGroupCache::Get(*device).Forfeit(); }); for (auto* world : worlds) { - if (!world->Playing()) { + if (!world->ShouldTick()) { continue; } world->Tick(inDeltaTimeSeconds); @@ -102,6 +112,9 @@ namespace Runtime { Render::RenderModuleInitParams initParams; initParams.rhiType = RHI::GetRHITypeByAbbrString(inRhiTypeStr); renderModule->Initialize(initParams); + // the render module started its own module-local worker threads, render-layer code inlined into this module + // (e.g. render graph buffer uploads) binds to this module's singleton copy which must be started as well + Render::RenderWorkerThreads::Get().Start(); LogInfo(Render, "RHI type: {}", inRhiTypeStr); } diff --git a/Engine/Source/Runtime/Src/RenderSurface.cpp b/Engine/Source/Runtime/Src/RenderSurface.cpp new file mode 100644 index 000000000..871d039f9 --- /dev/null +++ b/Engine/Source/Runtime/Src/RenderSurface.cpp @@ -0,0 +1,11 @@ +// +// Created by johnk on 2026/7/8. +// + +#include + +namespace Runtime { + RenderSurface::RenderSurface() = default; + + RenderSurface::~RenderSurface() = default; +} diff --git a/Engine/Source/Runtime/Src/System/Player.cpp b/Engine/Source/Runtime/Src/System/Player.cpp index 388c727e8..cef58dd75 100644 --- a/Engine/Source/Runtime/Src/System/Player.cpp +++ b/Engine/Source/Runtime/Src/System/Player.cpp @@ -2,32 +2,48 @@ // Created by johnk on 2025/3/13. // -#include +#include +#include +#include +#include #include #include -#include +#include namespace Runtime { PlayerSystem::PlayerSystem(ECRegistry& inRegistry, const SystemSetupContext& inContext) : System(inRegistry, inContext) , activeLocalPlayerNum(0) - , renderModule(EngineHolder::Get().GetRenderModule()) { + // an editor world renders through the editor camera instead of players, so only game worlds spawn them auto& playersInfo = registry.GEmplace(); - -#if BUILD_EDITOR - if (inContext.playType == PlayType::editor) { - playersInfo.players.emplace_back(CreatePlayer()); + if (inContext.playType != PlayType::game) { + return; } -#endif - if (inContext.playType == PlayType::game) { - const auto& gameSettings = SettingsRegistry::Get().GetSettings(); - playersInfo.players.resize(gameSettings.initialLocalPlayerNum); - for (auto& playerEntity : playersInfo.players) { - playerEntity = CreatePlayer(); - } + + const auto& gameSettings = SettingsRegistry::Get().GetSettings(); + playersInfo.players.resize(gameSettings.initialLocalPlayerNum); + for (auto& playerEntity : playersInfo.players) { + playerEntity = CreateLocalPlayer(); } } PlayerSystem::~PlayerSystem() = default; + + Entity PlayerSystem::CreateLocalPlayer() + { + // a game world requires exactly one player start, the result is copied because View() returns a temporary the + // reference would dangle into + const auto playerStartQuery = registry.View().All(); + Assert(playerStartQuery.size() == 1); + + const auto playerEntity = registry.Create(); + registry.Emplace(playerEntity); + registry.Emplace(playerEntity); + // the registered reflected constructor takes an FTransform, passing a WorldTransform copy would silently + // match the wrong constructor and lose the data + registry.Emplace(playerEntity, std::get<2>(playerStartQuery[0]).localToWorld); + registry.Emplace(playerEntity).localPlayerIndex = activeLocalPlayerNum++; + return playerEntity; + } } // namespace Runtime diff --git a/Engine/Source/Runtime/Src/System/Render.cpp b/Engine/Source/Runtime/Src/System/Render.cpp index eb3eedd63..428180a9d 100644 --- a/Engine/Source/Runtime/Src/System/Render.cpp +++ b/Engine/Source/Runtime/Src/System/Render.cpp @@ -2,17 +2,21 @@ // Created by johnk on 2025/3/4. // +#include +#include #include +#include #include #include +#include #include #include +#include namespace Runtime { RenderSystem::RenderSystem(ECRegistry& inRegistry, const SystemSetupContext& inContext) : System(inRegistry, inContext) , renderModule(EngineHolder::Get().GetRenderModule()) - , playType(inContext.playType) , client(inContext.client) , lastFrameFence(renderModule.GetDevice()->CreateFence(true).Release()) { @@ -28,32 +32,56 @@ namespace Runtime { void RenderSystem::Tick(float inDeltaTimeSeconds) { - auto& clientViewport = client->GetViewport(); + auto* target = client != nullptr ? client->GetRenderSurface() : nullptr; + if (target == nullptr) { + return; + } + auto* window = dynamic_cast(target); + + auto* texture = target->GetTexture(); + Assert(texture != nullptr); + const auto& textureDesc = texture->GetCreateInfo(); + renderModule.GetRenderThread().EmplaceTask( [ fence = lastFrameFence, views = BuildViews(), scene = registry.GGet().scene.Get(), - surfaceExtent = Common::UVec2(clientViewport.GetWidth(), clientViewport.GetHeight()), - presentInfo = clientViewport.GetNextPresentInfo(), + surfaceExtent = Common::UVec2(textureDesc.width, textureDesc.height), + target, + window, renderModule = &renderModule, inDeltaTimeSeconds ]() -> void { - fence->Wait(); fence->Reset(); + if (window != nullptr) { + window->AcquireBackTexture(); + } Render::StandardRenderer::Params rendererParams; rendererParams.device = renderModule->GetDevice(); rendererParams.scene = scene; - rendererParams.surface = presentInfo.backTexture; + rendererParams.surface = target->GetTexture(); rendererParams.surfaceExtent = surfaceExtent; + rendererParams.surfaceBeforeRenderState = window != nullptr + ? window->GetCurrentBackTextureState() + : RHI::TextureState::shaderReadOnly; + rendererParams.surfaceAfterRenderState = window != nullptr + ? RHI::TextureState::present + : RHI::TextureState::shaderReadOnly; rendererParams.views = views; - rendererParams.waitSemaphore = presentInfo.imageReadySemaphore; - rendererParams.signalSemaphore = presentInfo.renderFinishedSemaphore; + rendererParams.waitSemaphore = window != nullptr ? window->GetImageReadySemaphore() : nullptr; + rendererParams.signalSemaphore = window != nullptr ? window->GetRenderFinishedSemaphore() : nullptr; rendererParams.signalFence = fence; auto renderer = renderModule->CreateStandardRenderer(rendererParams); renderer.Render(inDeltaTimeSeconds); + if (window != nullptr) { + window->Present(); + } + // this frame's gpu work must finish before the renderer (and its recorded command buffers / + // transient resources) goes out of scope, and it keeps the shared semaphores safe to reuse + fence->Wait(); }); } @@ -76,23 +104,51 @@ namespace Runtime { return {}; } + Render::View RenderSystem::BuildViewForCamera(Entity inEntity) const + { + auto& camera = registry.Get(inEntity); + const auto& worldTransform = registry.Get(inEntity); + auto* target = client->GetRenderSurface(); + Assert(target != nullptr && target->GetTexture() != nullptr); + const auto& textureDesc = target->GetTexture()->GetCreateInfo(); + const auto width = textureDesc.width; + const auto height = textureDesc.height; + + if (camera.viewState == nullptr) { + camera.viewState = renderModule.NewViewState(); + } + + // a player camera renders into its split-screen sub-viewport, any other camera (e.g. the editor camera) + // covers the whole render surface + Common::URect viewportRect = { 0, 0, width, height }; + if (registry.Has(inEntity)) { + const auto& playersInfo = registry.GGet(); + viewportRect = GetPlayerViewport(width, height, static_cast(playersInfo.players.size()), registry.Get(inEntity).localPlayerIndex); + } + + Render::View view = renderModule.CreateView(); + view.state = camera.viewState.Get(); + view.data.viewport = viewportRect; + view.data.viewMatrix = Common::FViewTransform(worldTransform.localToWorld).GetViewMatrix(); + view.data.origin = worldTransform.localToWorld.translation; + if (camera.perspective) { + const Common::FReversedZPerspectiveProjection projection(camera.fov.value(), static_cast(width), static_cast(height), camera.nearPlane, camera.farPlane); + view.data.projectionMatrix = projection.GetProjectionMatrix(); + } else { + const Common::FReversedZOrthoProjection projection(static_cast(width), static_cast(height), camera.nearPlane, camera.farPlane); + view.data.projectionMatrix = projection.GetProjectionMatrix(); + } + return view; + } + std::vector RenderSystem::BuildViews() const { - const auto& playersInfo = registry.GGet(); - const auto playerNum = playersInfo.players.size(); + const auto cameras = registry.View().All(); std::vector result; - result.reserve(playerNum); - - for (auto i = 0; i < playerNum; i++) { - if (registry.Has(i)) { - result.emplace_back(BuildViewForPlayer(playersInfo.players[i], playerNum, i)); - } - #if BUILD_EDITOR - if (registry.Has(i)) { - result.emplace_back(BuildViewForPlayer(playersInfo.players[i], playerNum, i)); - } - #endif + result.reserve(cameras.size()); + for (const auto& camera : cameras) { + result.emplace_back(BuildViewForCamera(std::get<0>(camera))); } return result; } diff --git a/Engine/Source/Runtime/Src/System/Scene.cpp b/Engine/Source/Runtime/Src/System/Scene.cpp index 9cab2e9c0..0541b1974 100644 --- a/Engine/Source/Runtime/Src/System/Scene.cpp +++ b/Engine/Source/Runtime/Src/System/Scene.cpp @@ -16,12 +16,20 @@ namespace Runtime { , directionalLightsObserver(inRegistry.EventsObserver()) , pointLightsObserver(inRegistry.EventsObserver()) , spotLightsObserver(inRegistry.EventsObserver()) + , staticPrimitivesObserver(inRegistry.EventsObserver()) { transformUpdatedObserver .ObConstructed() .ObUpdated(); inRegistry.GEmplace(renderModule.NewScene()); + + // components created before the system graph was built (e.g. a loaded level or editor-authored defaults) + // never fire construct events, mirror them into the render scene here + inRegistry.View().Each([this](Entity e, DirectionalLight&) -> void { QueueCreateSceneProxy(e); }); + inRegistry.View().Each([this](Entity e, PointLight&) -> void { QueueCreateSceneProxy(e); }); + inRegistry.View().Each([this](Entity e, SpotLight&) -> void { QueueCreateSceneProxy(e); }); + inRegistry.View().Each([this](Entity e, StaticPrimitive&) -> void { QueueCreateSceneProxy(e, true); }); } SceneSystem::~SceneSystem() // NOLINT @@ -34,23 +42,29 @@ namespace Runtime { directionalLightsObserver.Constructed().Each([this](Entity e) -> void { QueueCreateSceneProxy(e); }); pointLightsObserver.Constructed().Each([this](Entity e) -> void { QueueCreateSceneProxy(e); }); spotLightsObserver.Constructed().Each([this](Entity e) -> void { QueueCreateSceneProxy(e); }); + staticPrimitivesObserver.Constructed().Each([this](Entity e) -> void { QueueCreateSceneProxy(e, true); }); directionalLightsObserver.Updated().Each([this](Entity e) -> void { QueueUpdateSceneProxyContent(e); }); pointLightsObserver.Updated().Each([this](Entity e) -> void { QueueUpdateSceneProxyContent(e); }); spotLightsObserver.Updated().Each([this](Entity e) -> void { QueueUpdateSceneProxyContent(e); }); + staticPrimitivesObserver.Updated().Each([this](Entity e) -> void { QueueUpdateSceneProxyContent(e); }); directionalLightsObserver.Removed().Each([this](Entity e) -> void { QueueRemoveSceneProxy(e); }); pointLightsObserver.Removed().Each([this](Entity e) -> void { QueueRemoveSceneProxy(e); }); spotLightsObserver.Removed().Each([this](Entity e) -> void { QueueRemoveSceneProxy(e); }); - // TODO primitive + staticPrimitivesObserver.Removed().Each([this](Entity e) -> void { QueueRemoveSceneProxy(e); }); transformUpdatedObserver.Each([this](Entity e) -> void { if (registry.Has(e) || registry.Has(e) || registry.Has(e)) { QueueUpdateSceneProxyTransform(e); } + if (registry.Has(e)) { + QueueUpdateSceneProxyTransform(e, true); + } }); transformUpdatedObserver.Clear(); directionalLightsObserver.Clear(); pointLightsObserver.Clear(); spotLightsObserver.Clear(); + staticPrimitivesObserver.Clear(); } } diff --git a/Engine/Source/Runtime/Src/Viewport.cpp b/Engine/Source/Runtime/Src/Viewport.cpp deleted file mode 100644 index 386e85208..000000000 --- a/Engine/Source/Runtime/Src/Viewport.cpp +++ /dev/null @@ -1,18 +0,0 @@ -// -// Created by johnk on 2025/2/18. -// - -#include - -namespace Runtime { - Viewport::~Viewport() = default; - - Viewport::Viewport() = default; - - PresentInfo::PresentInfo() - : backTexture(nullptr) - , imageReadySemaphore(nullptr) - , renderFinishedSemaphore(nullptr) - { - } -} diff --git a/Engine/Source/Runtime/Src/Window.cpp b/Engine/Source/Runtime/Src/Window.cpp new file mode 100644 index 000000000..58f90b23a --- /dev/null +++ b/Engine/Source/Runtime/Src/Window.cpp @@ -0,0 +1,167 @@ +// +// Created by johnk on 2026/7/8. +// + +#include +#include +#include + +#include + +namespace Runtime::Internal { + static uint32_t ClampExtent(uint32_t inValue) + { + return std::max(1u, inValue); + } +} + +namespace Runtime { + Window::Window(RHI::Device& inDevice) + : device(inDevice) + , imageReadySemaphore((device.CreateSemaphore)()) + , swapChainFormat(RHI::PixelFormat::max) + , currentBackTextureIndex(0) + , width(1) + , height(1) + { + } + + Window::~Window() = default; + + RHI::Texture* Window::GetTexture() const + { + Assert(currentBackTextureIndex < swapChainTextures.size()); + return swapChainTextures[currentBackTextureIndex]; + } + + RHI::TextureView* Window::GetRenderTargetView() const + { + Assert(currentBackTextureIndex < swapChainTextureViews.size()); + return swapChainTextureViews[currentBackTextureIndex].Get(); + } + + void Window::AcquireBackTexture() + { + currentBackTextureIndex = swapChain->AcquireBackTexture(imageReadySemaphore.Get()); + Assert(currentBackTextureIndex < swapChainTextures.size()); + } + + void Window::Resize(uint32_t inWidth, uint32_t inHeight) + { + RecreateSwapChain(Internal::ClampExtent(inWidth), Internal::ClampExtent(inHeight)); + } + + void Window::Present() + { + swapChain->Present(GetRenderFinishedSemaphore()); + swapChainTextureStates[currentBackTextureIndex] = RHI::TextureState::present; + } + + RHI::Device& Window::GetDevice() const + { + return device; + } + + RHI::PixelFormat Window::GetSwapChainFormat() const + { + return swapChainFormat; + } + + RHI::TextureState Window::GetCurrentBackTextureState() const + { + Assert(currentBackTextureIndex < swapChainTextureStates.size()); + return swapChainTextureStates[currentBackTextureIndex]; + } + + RHI::Semaphore* Window::GetImageReadySemaphore() const + { + return imageReadySemaphore.Get(); + } + + RHI::Semaphore* Window::GetRenderFinishedSemaphore() const + { + Assert(currentBackTextureIndex < renderFinishedSemaphores.size()); + return renderFinishedSemaphores[currentBackTextureIndex].Get(); + } + + void Window::WaitRenderingIdle() const + { + const auto fence = device.CreateFence(false); + device.GetQueue(RHI::QueueType::graphics, 0)->Flush(fence.Get()); + fence->Wait(); + } + + void Window::InitializeSurface(Common::UniquePtr inSurface, uint32_t inWidth, uint32_t inHeight) + { + surface = std::move(inSurface); + RecreateSwapChain(Internal::ClampExtent(inWidth), Internal::ClampExtent(inHeight)); + } + + void Window::DestroySurface() + { + ReleaseSwapChainResources(); + surface.Reset(); + } + + void Window::ReleaseSwapChainResources() + { + swapChainTextureViews.clear(); + swapChain.Reset(); + swapChainTextures.clear(); + swapChainTextureStates.clear(); + renderFinishedSemaphores.clear(); + currentBackTextureIndex = 0; + } + + void Window::RecreateSwapChain(uint32_t inWidth, uint32_t inHeight) + { + static std::vector formatQualifiers = { + RHI::PixelFormat::rgba8Unorm, + RHI::PixelFormat::bgra8Unorm + }; + + ReleaseSwapChainResources(); + + std::optional pixelFormat = {}; + for (const auto format : formatQualifiers) { + if (device.CheckSwapChainFormatSupport(surface.Get(), format, RHI::ColorSpace::srgbNonLinear)) { + pixelFormat = format; + break; + } + } + Assert(pixelFormat.has_value()); + swapChainFormat = pixelFormat.value(); + width = inWidth; + height = inHeight; + + swapChain = device.CreateSwapChain( + RHI::SwapChainCreateInfo() + .SetPresentQueue(device.GetQueue(RHI::QueueType::graphics, 0)) + .SetSurface(surface.Get()) + .SetTextureNum(swapChainTextureNum) + .SetFormat(swapChainFormat) + .SetWidth(width) + .SetHeight(height) + .SetPresentMode(RHI::PresentMode::immediately)); + + const auto actualTextureNum = swapChain->GetTextureNum(); + swapChainTextures.resize(actualTextureNum); + swapChainTextureViews.resize(actualTextureNum); + swapChainTextureStates.resize(actualTextureNum); + renderFinishedSemaphores.resize(actualTextureNum); + currentBackTextureIndex = 0; + + for (uint8_t i = 0; i < actualTextureNum; i++) { + swapChainTextures[i] = swapChain->GetTexture(i); + swapChainTextureStates[i] = swapChainTextures[i]->GetCreateInfo().initialState; + swapChainTextureViews[i] = swapChainTextures[i]->CreateTextureView( + RHI::TextureViewCreateInfo() + .SetDimension(RHI::TextureViewDimension::tv2D) + .SetMipLevels(0, 1) + .SetArrayLayers(0, 1) + .SetAspect(RHI::TextureAspect::color) + .SetType(RHI::TextureViewType::colorAttachment)); + renderFinishedSemaphores[i] = (device.CreateSemaphore)(); + } + } +} diff --git a/Engine/Source/Runtime/Src/World.cpp b/Engine/Source/Runtime/Src/World.cpp index 9fa201ae0..486478f05 100644 --- a/Engine/Source/Runtime/Src/World.cpp +++ b/Engine/Source/Runtime/Src/World.cpp @@ -75,6 +75,21 @@ namespace Runtime { executor.reset(); } + bool World::ShouldTick() const + { + return executor.has_value() && !Paused(); + } + + ECRegistry& World::GetRegistry() + { + return ecRegistry; + } + + const ECRegistry& World::GetRegistry() const + { + return ecRegistry; + } + void World::LoadFrom(AssetPtr inLevel) { Assert(Stopped()); @@ -83,7 +98,8 @@ namespace Runtime { void World::SaveTo(AssetPtr inLevel) { - Assert(Stopped()); + // an editor world keeps playing while editing, saving there is safe because transient entities are skipped + Assert(systemSetupContext.playType == PlayType::editor || Stopped()); ecRegistry.Save(inLevel->GetArchive()); } diff --git a/README.md b/README.md index 12f34e81c..859930019 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,6 @@ You need install those tools to system by yourself and add them to path: * [Conan](https://github.com/conan-io/conan) * [CMake](https://cmake.org/download/) * [Ninja](https://github.com/ninja-build/ninja) -* [Node.js](https://nodejs.org/en/download) build steps of engine may use them. @@ -139,6 +138,7 @@ Thanks all those following projects: * [Vulkan](https://www.vulkan.org/) * [DirectXShaderCompiler](https://github.com/microsoft/DirectXShaderCompiler) * [GLFW](https://www.glfw.org/) +* [Dear ImGui](https://github.com/ocornut/imgui) * [clipp](https://github.com/muellan/clipp) * [debugbreak](https://github.com/scottt/debugbreak) * [LLVM](https://llvm.org/) @@ -151,13 +151,8 @@ Thanks all those following projects: * [SPIRV-Cross](https://github.com/KhronosGroup/SPIRV-Cross) * [assimp](https://github.com/assimp/assimp) * [VulkanMemoryAllocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator) -* [Qt6](https://www.qt.io/product/qt6) * [rapidjson](https://github.com/Tencent/rapidjson) * [cpp-httplib](https://github.com/yhirose/cpp-httplib) -* [React](https://react.dev) -* [Vite](https://vite.dev) -* [HeroUI](https://www.heroui.com) -* [TailWindCSS](https://tailwindcss.com) # License diff --git a/Sample/Base/Application.cpp b/Sample/Base/Application.cpp index 373197913..06285f7ad 100644 --- a/Sample/Base/Application.cpp +++ b/Sample/Base/Application.cpp @@ -151,14 +151,9 @@ void Application::OnCursorActionReceived(float x, float y) // screen space of glfw: // origin in left-top, x from left to rignt, y from top to bottom if (mouseButtonsStatus[static_cast(MouseButton::left)]) { - // rotate camera with mouse's left button down (positive value represents counterclockwise rotation) - // before apply axisTransMat - // x+ -> from screen outer to inner - // y+ -> from left to right - // z+ -> from bottom to top - // horizontal mouse moving(dx) causes rotation alng z axis - // vertical mouse moving(dy) causes rotation along y axis - camera->Rotate(FVec3(0.0, -dy * camera->GetRotateSpeed(), -dx * camera->GetRotateSpeed())); + // rotate camera with mouse's left button down: horizontal mouse moving(dx) yaws, vertical mouse moving(dy) + // pitches, the camera rebuilds its orientation from the absolute angles so no roll can accumulate + camera->Rotate(-dy * camera->GetRotateSpeed(), -dx * camera->GetRotateSpeed()); } if (mouseButtonsStatus[static_cast(MouseButton::right)]) { diff --git a/Sample/Base/Camera.cpp b/Sample/Base/Camera.cpp index 19731a13a..5f6ac429a 100644 --- a/Sample/Base/Camera.cpp +++ b/Sample/Base/Camera.cpp @@ -2,16 +2,23 @@ // Created by johnk on 2024/5/20. // +#include + #include -Camera::Camera(const FVec3& inPos, const FVec3& inRotEulerZYX, const ProjectionParams& inProjParams) - : viewTransform(FQuat::FromEulerZYX(inRotEulerZYX.x, inRotEulerZYX.y, inRotEulerZYX.z), inPos) +static constexpr float maxPitchDeg = 89.0f; + +Camera::Camera(const FVec3& inPos, float inPitchDeg, float inYawDeg, const ProjectionParams& inProjParams) + : viewTransform(FQuatConsts::identity, inPos) , projection(inProjParams.fov, inProjParams.width, inProjParams.height, inProjParams.nearPlane, inProjParams.farPlane) - , lookTarget(FVec3Consts::unitZ) + , pitchDeg(inPitchDeg) + , yawDeg(inYawDeg) , movingStatus() , moveSpeed(1.0f) , rotateSpeed(1.0f) { + RebuildRotation(); + moveVectorMap = { { MoveDirection::front, FVec3Consts::unitX }, { MoveDirection::back, FVec3Consts::negaUnitX }, @@ -27,15 +34,11 @@ void Camera::SetPosition(const FVec3& inPosition) viewTransform.translation = inPosition; } -void Camera::SetRotation(const FVec3& inRotEulerZYX) +void Camera::SetRotation(float inPitchDeg, float inYawDeg) { - viewTransform.rotation = FQuat::FromEulerZYX(inRotEulerZYX.x, inRotEulerZYX.y, inRotEulerZYX.z); -} - -void Camera::SetLookTarget(const FVec3& inTarget) -{ - lookTarget = inTarget; - viewTransform.LookTo(lookTarget); + pitchDeg = inPitchDeg; + yawDeg = inYawDeg; + RebuildRotation(); } void Camera::SetTranslation(const FVec3& inTranslation) @@ -49,11 +52,11 @@ void Camera::Translate(const FVec3& inTransDelta) viewTransform.translation += viewTransform.rotation.RotateVector(inTransDelta); } -void Camera::Rotate(const FVec3& inRotDelta) +void Camera::Rotate(float inPitchDeltaDeg, float inYawDeltaDeg) { - // inRotDelta is also in camera's local, but we need't to change it to world, because previous rotation will be applied first, making the coords in camera local - // imagine putting a new camera from wolrd's origin to the position of old camera using old transform - viewTransform.rotation = FQuat::FromEulerZYX(inRotDelta.x, inRotDelta.y, inRotDelta.z) * viewTransform.rotation; + pitchDeg += inPitchDeltaDeg; + yawDeg += inYawDeltaDeg; + RebuildRotation(); } void Camera::PerformMove(MoveDirection direction) @@ -120,3 +123,9 @@ bool Camera::Moving() const } return result; } + +void Camera::RebuildRotation() +{ + pitchDeg = std::clamp(pitchDeg, -maxPitchDeg, maxPitchDeg); + viewTransform.rotation = FQuat(FVec3Consts::unitY, pitchDeg) * FQuat(FVec3Consts::unitZ, yawDeg); +} diff --git a/Sample/Base/Camera.h b/Sample/Base/Camera.h index 4fab148cf..21b9e3c1f 100644 --- a/Sample/Base/Camera.h +++ b/Sample/Base/Camera.h @@ -37,14 +37,13 @@ class Camera { // all the calculation applies to camera is in world space // transform is the operations make camera from local to world // the inverse of transform is the operations make objects in world from world to view(the local of camera) - Camera(const FVec3& inPos, const FVec3& inRotEulerZYX, const ProjectionParams& inProjParams); + Camera(const FVec3& inPos, float inPitchDeg, float inYawDeg, const ProjectionParams& inProjParams); void SetPosition(const FVec3& inPosition); - void SetRotation(const FVec3& inRotEulerZYX); - void SetLookTarget(const FVec3& inTarget); + void SetRotation(float inPitchDeg, float inYawDeg); void SetTranslation(const FVec3& inTranslation); void Translate(const FVec3& inTransDelta); - void Rotate(const FVec3& inRotDelta); + void Rotate(float inPitchDeltaDeg, float inYawDeltaDeg); void PerformMove(MoveDirection direction); void PerformStop(MoveDirection direction); void SetMoveSpeed(float inSpeed); @@ -57,13 +56,17 @@ class Camera { private: bool Moving() const; + // rebuilds the orientation from the absolute pitch/yaw pair, composing yaw about the world up axis outermost, + // so the camera never accumulates roll no matter how rotations interleave + void RebuildRotation(); // vt makes camera from local to world FViewTransform viewTransform; FReversedZPerspectiveProjection projection; - FVec3 lookTarget; + float pitchDeg; + float yawDeg; std::array(MoveDirection::max)> movingStatus; std::unordered_map moveVectorMap; float moveSpeed; float rotateSpeed; -}; \ No newline at end of file +}; diff --git a/Sample/RHI-SSAO/SSAOApplication.cpp b/Sample/RHI-SSAO/SSAOApplication.cpp index 6f6656313..580801a16 100644 --- a/Sample/RHI-SSAO/SSAOApplication.cpp +++ b/Sample/RHI-SSAO/SSAOApplication.cpp @@ -132,7 +132,7 @@ class SSAOApplication final : public Application { { // composition - commandRecorder->ResourceBarrier(Barrier::Transition(swapChainTextures[backTextureIndex], TextureState::present, TextureState::renderTarget)); + commandRecorder->ResourceBarrier(Barrier::Transition(swapChainTextures[backTextureIndex], swapChainTextureStates[backTextureIndex], TextureState::renderTarget)); const UniquePtr rasterRecorder = commandRecorder->BeginRasterPass( RasterPassBeginInfo() .AddColorAttachment(RHI::ColorAttachment(swapChainTextureViews[backTextureIndex].Get(), LoadOp::clear, StoreOp::store, LinearColorConsts::black))); @@ -155,10 +155,11 @@ class SSAOApplication final : public Application { graphicsQueue->Submit( commandBuffers[nextFrameIndex].Get(), QueueSubmitInfo() .AddWaitSemaphore(backBufferReadySemaphores[nextFrameIndex].Get()) - .AddSignalSemaphore(renderFinishedSemaphores[nextFrameIndex].Get()) + .AddSignalSemaphore(renderFinishedSemaphores[backTextureIndex].Get()) .SetSignalFence(inflightFences[nextFrameIndex].Get())); - swapChain->Present(renderFinishedSemaphores[nextFrameIndex].Get()); + swapChain->Present(renderFinishedSemaphores[backTextureIndex].Get()); + swapChainTextureStates[backTextureIndex] = TextureState::present; nextFrameIndex = (nextFrameIndex + 1) % backBufferCount; } @@ -186,6 +187,7 @@ class SSAOApplication final : public Application { UniquePtr indexBufferView = nullptr; std::array swapChainTextures {}; std::array, backBufferCount> swapChainTextureViews {}; + std::array swapChainTextureStates {}; UniquePtr quadVertexBuffer = nullptr; UniquePtr quadVertexBufferView = nullptr; @@ -414,7 +416,7 @@ class SSAOApplication final : public Application { surface = device->CreateSurface(SurfaceCreateInfo(GetPlatformWindow())); for (const auto format : swapChainFormatQualifiers) { - if (device->CheckSwapChainFormatSupport(surface.Get(), format)) { + if (device->CheckSwapChainFormatSupport(surface.Get(), format, ColorSpace::srgbNonLinear)) { swapChainFormat = format; break; } @@ -433,6 +435,7 @@ class SSAOApplication final : public Application { for (auto i = 0; i < backBufferCount; i++) { swapChainTextures[i] = swapChain->GetTexture(i); + swapChainTextureStates[i] = swapChainTextures[i]->GetCreateInfo().initialState; swapChainTextureViews[i] = swapChainTextures[i]->CreateTextureView( TextureViewCreateInfo() @@ -995,7 +998,7 @@ class SSAOApplication final : public Application { { auto* camera = new Camera( FVec3(.0f, -5.0f, 2.0f), - FVec3(.0f, .0f, -90.0f), + 0.0f, -90.0f, Camera::ProjectionParams { 60.0f, static_cast(GetWindowWidth()), diff --git a/Sample/RHI-TexSampling/TexSampling.cpp b/Sample/RHI-TexSampling/TexSampling.cpp index 96f50ef7b..99fc2712a 100644 --- a/Sample/RHI-TexSampling/TexSampling.cpp +++ b/Sample/RHI-TexSampling/TexSampling.cpp @@ -44,7 +44,7 @@ class TexSamplingApplication final : public Application { const UniquePtr commandRecorder = commandBuffers[nextFrameIndex]->Begin(); { - commandRecorder->ResourceBarrier(Barrier::Transition(swapChainTextures[backTextureIndex], TextureState::present, TextureState::renderTarget)); + commandRecorder->ResourceBarrier(Barrier::Transition(swapChainTextures[backTextureIndex], swapChainTextureStates[backTextureIndex], TextureState::renderTarget)); const UniquePtr rasterRecorder = commandRecorder->BeginRasterPass( RasterPassBeginInfo() .AddColorAttachment(ColorAttachment(swapChainTextureViews[backTextureIndex].Get(), LoadOp::clear, StoreOp::store, LinearColorConsts::black))); @@ -66,10 +66,11 @@ class TexSamplingApplication final : public Application { graphicsQueue->Submit( commandBuffers[nextFrameIndex].Get(), QueueSubmitInfo() .AddWaitSemaphore(backBufferReadySemaphores[nextFrameIndex].Get()) - .AddSignalSemaphore(renderFinishedSemaphores[nextFrameIndex].Get()) + .AddSignalSemaphore(renderFinishedSemaphores[backTextureIndex].Get()) .SetSignalFence(inflightFences[nextFrameIndex].Get())); - swapChain->Present(renderFinishedSemaphores[nextFrameIndex].Get()); + swapChain->Present(renderFinishedSemaphores[backTextureIndex].Get()); + swapChainTextureStates[backTextureIndex] = TextureState::present; nextFrameIndex = (nextFrameIndex + 1) % backBufferCount; } @@ -113,7 +114,7 @@ class TexSamplingApplication final : public Application { surface = device->CreateSurface(SurfaceCreateInfo(GetPlatformWindow())); for (const auto format : swapChainFormatQualifiers) { - if (device->CheckSwapChainFormatSupport(surface.Get(), format)) { + if (device->CheckSwapChainFormatSupport(surface.Get(), format, ColorSpace::srgbNonLinear)) { swapChainFormat = format; break; } @@ -132,6 +133,7 @@ class TexSamplingApplication final : public Application { for (auto i = 0; i < backBufferCount; i++) { swapChainTextures[i] = swapChain->GetTexture(i); + swapChainTextureStates[i] = swapChainTextures[i]->GetCreateInfo().initialState; swapChainTextureViews[i] = swapChainTextures[i]->CreateTextureView( TextureViewCreateInfo() @@ -398,6 +400,7 @@ class TexSamplingApplication final : public Application { UniquePtr pixelBuffer; std::array swapChainTextures {}; std::array, backBufferCount> swapChainTextureViews; + std::array swapChainTextureStates {}; UniquePtr pipelineLayout; UniquePtr pipeline; UniquePtr vertexShader; diff --git a/Sample/RHI-Triangle/Triangle.cpp b/Sample/RHI-Triangle/Triangle.cpp index 221d3f80c..43e00475f 100644 --- a/Sample/RHI-Triangle/Triangle.cpp +++ b/Sample/RHI-Triangle/Triangle.cpp @@ -43,7 +43,7 @@ class TriangleApplication final : public Application { const UniquePtr commandRecorder = commandBuffers[nextFrameIndex]->Begin(); { - commandRecorder->ResourceBarrier(Barrier::Transition(swapChainTextures[backTextureIndex], TextureState::present, TextureState::renderTarget)); + commandRecorder->ResourceBarrier(Barrier::Transition(swapChainTextures[backTextureIndex], swapChainTextureStates[backTextureIndex], TextureState::renderTarget)); const UniquePtr rasterRecorder = commandRecorder->BeginRasterPass( RasterPassBeginInfo() .AddColorAttachment(ColorAttachment(swapChainTextureViews[backTextureIndex].Get(), LoadOp::clear, StoreOp::store, LinearColorConsts::black))); @@ -64,10 +64,11 @@ class TriangleApplication final : public Application { commandBuffers[nextFrameIndex].Get(), QueueSubmitInfo() .AddWaitSemaphore(backBufferReadySemaphores[nextFrameIndex].Get()) - .AddSignalSemaphore(renderFinishedSemaphores[nextFrameIndex].Get()) + .AddSignalSemaphore(renderFinishedSemaphores[backTextureIndex].Get()) .SetSignalFence(inflightFences[nextFrameIndex].Get())); - swapChain->Present(renderFinishedSemaphores[nextFrameIndex].Get()); + swapChain->Present(renderFinishedSemaphores[backTextureIndex].Get()); + swapChainTextureStates[backTextureIndex] = TextureState::present; nextFrameIndex = (nextFrameIndex + 1) % backBufferCount; } @@ -104,7 +105,7 @@ class TriangleApplication final : public Application { surface = device->CreateSurface(SurfaceCreateInfo(GetPlatformWindow())); for (const auto format : swapChainFormatQualifiers) { - if (device->CheckSwapChainFormatSupport(surface.Get(), format)) { + if (device->CheckSwapChainFormatSupport(surface.Get(), format, ColorSpace::srgbNonLinear)) { swapChainFormat = format; break; } @@ -123,6 +124,7 @@ class TriangleApplication final : public Application { for (auto i = 0; i < backBufferCount; i++) { swapChainTextures[i] = swapChain->GetTexture(i); + swapChainTextureStates[i] = swapChainTextures[i]->GetCreateInfo().initialState; swapChainTextureViews[i] = swapChainTextures[i]->CreateTextureView( TextureViewCreateInfo() @@ -221,6 +223,7 @@ class TriangleApplication final : public Application { UniquePtr vertexBufferView; std::array swapChainTextures {}; std::array, backBufferCount> swapChainTextureViews; + std::array swapChainTextureStates {}; UniquePtr pipelineLayout; UniquePtr pipeline; UniquePtr vertexShader; diff --git a/Sample/Rendering-BaseTexture/BaseTexture.cpp b/Sample/Rendering-BaseTexture/BaseTexture.cpp index 931d757d6..1057cc244 100644 --- a/Sample/Rendering-BaseTexture/BaseTexture.cpp +++ b/Sample/Rendering-BaseTexture/BaseTexture.cpp @@ -84,9 +84,10 @@ class BaseTexApp final : public Application { UniquePtr texture; UniquePtr imageBuffer; std::array swapChainTextures; + std::array swapChainTextureStates; UniquePtr pipeline; UniquePtr imageReadySemaphore; - UniquePtr renderFinishedSemaphore; + std::array, backBufferCount> renderFinishedSemaphores; UniquePtr frameFence; }; @@ -94,6 +95,7 @@ BaseTexApp::BaseTexApp(const std::string& inName) : Application(inName) , swapChainFormat(PixelFormat::max) , swapChainTextures() + , swapChainTextureStates() { } @@ -123,7 +125,7 @@ void BaseTexApp::OnDrawFrame() frameFence->Reset(); const auto backTextureIndex = swapChain->AcquireBackTexture(imageReadySemaphore.Get()); - auto* pso = PipelineCache::Get(*device).GetOrCreate( + auto* pso = Render::PipelineCache::Get(*device).GetOrCreate( RasterPipelineStateDesc() .SetVertexShader(vs) .SetPixelShader(ps) @@ -139,7 +141,7 @@ void BaseTexApp::OnDrawFrame() .SetPrimitiveState(PrimitiveState(PrimitiveTopologyType::triangle, FillMode::solid, IndexFormat::uint16, FrontFace::ccw, CullMode::none))); RGBuilder builder(*device); - auto* backTexture = builder.ImportTexture(swapChainTextures[backTextureIndex], TextureState::present); + auto* backTexture = builder.ImportTexture(swapChainTextures[backTextureIndex], swapChainTextureStates[backTextureIndex]); auto* backTextureView = builder.CreateTextureView(backTexture, RGTextureViewDesc(TextureViewType::colorAttachment, TextureViewDimension::tv2D)); auto* vBuffer = builder.ImportBuffer(vertexBuffer.Get(), BufferState::shaderReadOnly); auto* vBufferView = builder.CreateBufferView(vBuffer, RGBufferViewDesc(BufferViewType::vertex, vBuffer->GetDesc().size, 0, VertexBufferViewInfo(sizeof(Vertex)))); @@ -147,7 +149,7 @@ void BaseTexApp::OnDrawFrame() auto* iBufferView = builder.CreateBufferView(iBuffer, RGBufferViewDesc(BufferViewType::index, iBuffer->GetDesc().size, 0, IndexBufferViewInfo(IndexFormat::uint32))); auto* uBuffer = builder.CreateBuffer(RGBufferDesc(sizeof(VertUniform), BufferUsageBits::uniform | BufferUsageBits::mapWrite, BufferState::staging, "psUniform")); auto* uBufferView = builder.CreateBufferView(uBuffer, RGBufferViewDesc(BufferViewType::uniformBinding, sizeof(VertUniform))); - auto* rgTexture = builder.ImportTexture(texture.Get(), TextureState::undefined); + auto* rgTexture = builder.ImportTexture(texture.Get(), TextureState::shaderReadOnly); auto* rgTextureView = builder.CreateTextureView(rgTexture, RGTextureViewDesc(TextureViewType::textureBinding, TextureViewDimension::tv2D)); auto* bindGroup = builder.AllocateBindGroup( @@ -185,10 +187,11 @@ void BaseTexApp::OnDrawFrame() RGExecuteInfo executeInfo; executeInfo.semaphoresToWait = { imageReadySemaphore.Get() }; - executeInfo.semaphoresToSignal = { renderFinishedSemaphore.Get() }; + executeInfo.semaphoresToSignal = { renderFinishedSemaphores[backTextureIndex].Get() }; executeInfo.inFenceToSignal = frameFence.Get(); builder.Execute(executeInfo); - swapChain->Present(renderFinishedSemaphore.Get()); + swapChain->Present(renderFinishedSemaphores[backTextureIndex].Get()); + swapChainTextureStates[backTextureIndex] = TextureState::present; frameFence->Wait(); Core::ThreadContext::IncFrameNumber(); @@ -210,7 +213,7 @@ void BaseTexApp::OnDestroy() fence->Wait(); BindGroupCache::Get(*device).Invalidate(); - PipelineCache::Get(*device).Invalidate(); + Render::PipelineCache::Get(*device).Invalidate(); BufferPool::Get(*device).Invalidate(); TexturePool::Get(*device).Invalidate(); ShaderMap::Get(*device).Invalidate(); @@ -263,7 +266,7 @@ void BaseTexApp::CreateSwapChain() surface = device->CreateSurface(SurfaceCreateInfo(GetPlatformWindow())); for (const auto format : swapChainFormatQualifiers) { - if (device->CheckSwapChainFormatSupport(surface.Get(), format)) { + if (device->CheckSwapChainFormatSupport(surface.Get(), format, ColorSpace::srgbNonLinear)) { swapChainFormat = format; break; } @@ -282,6 +285,7 @@ void BaseTexApp::CreateSwapChain() for (auto i = 0; i < backBufferCount; i++) { swapChainTextures[i] = swapChain->GetTexture(i); + swapChainTextureStates[i] = swapChainTextures[i]->GetCreateInfo().initialState; } } @@ -391,7 +395,9 @@ void BaseTexApp::CreateTextureAndSampler() void BaseTexApp::CreateSyncObjects() { imageReadySemaphore = device->CreateSemaphore(); - renderFinishedSemaphore = device->CreateSemaphore(); + for (auto i = 0; i < backBufferCount; i++) { + renderFinishedSemaphores[i] = device->CreateSemaphore(); + } frameFence = device->CreateFence(true); } diff --git a/Sample/Rendering-SSAO/SSAOApplication.cpp b/Sample/Rendering-SSAO/SSAOApplication.cpp index d7a9b9a36..34cebbc7f 100644 --- a/Sample/Rendering-SSAO/SSAOApplication.cpp +++ b/Sample/Rendering-SSAO/SSAOApplication.cpp @@ -36,7 +36,6 @@ struct RenderMaterial { UniquePtr diffuseTexture; }; -// Shader类型定义 class GBufferVS final : public StaticShaderType { ShaderTypeInfo( GBufferVS, @@ -168,7 +167,6 @@ class SSAOApp final : public Application { frameFence->Reset(); const auto backTextureIndex = swapChain->AcquireBackTexture(imageReadySemaphore.Get()); - // 更新Uniform Buffer if (uniformBuffers.sceneParams) { auto* uboData = uniformBuffers.sceneParams->Map(MapMode::write, 0, sizeof(UBOSceneParams)); memcpy(uboData, &uboSceneParams, sizeof(UBOSceneParams)); @@ -177,22 +175,18 @@ class SSAOApp final : public Application { RGBuilder builder(*device); - // 导入交换链纹理 - auto* backTexture = builder.ImportTexture(swapChainTextures[backTextureIndex], TextureState::present); + auto* backTexture = builder.ImportTexture(swapChainTextures[backTextureIndex], swapChainTextureStates[backTextureIndex]); auto* backTextureView = builder.CreateTextureView(backTexture, RGTextureViewDesc(TextureViewType::colorAttachment, TextureViewDimension::tv2D)); - // 导入几何缓冲区纹理 auto* gBufferPos = builder.ImportTexture(gBufferPosTex.Get(), TextureState::shaderReadOnly); auto* gBufferNormal = builder.ImportTexture(gBufferNormalTex.Get(), TextureState::shaderReadOnly); auto* gBufferAlbedo = builder.ImportTexture(gBufferAlbedoTex.Get(), TextureState::shaderReadOnly); auto* gBufferDepth = builder.ImportTexture(gBufferDepthTex.Get(), TextureState::depthStencilReadonly); - // 导入SSAO纹理 auto* ssaoTexture = builder.ImportTexture(ssaoTex.Get(), TextureState::shaderReadOnly); auto* ssaoBlurTexture = builder.ImportTexture(ssaoBlurTex.Get(), TextureState::shaderReadOnly); auto* noiseTexture = builder.ImportTexture(noiseTex.Get(), TextureState::shaderReadOnly); - // 导入缓冲区 auto* vBuffer = builder.ImportBuffer(vertexBuffer.Get(), BufferState::shaderReadOnly); auto* iBuffer = builder.ImportBuffer(indexBuffer.Get(), BufferState::shaderReadOnly); auto* quadVBuffer = builder.ImportBuffer(quadVertexBuffer.Get(), BufferState::shaderReadOnly); @@ -201,7 +195,6 @@ class SSAOApp final : public Application { auto* ssaoParamsBuffer = builder.ImportBuffer(uniformBuffers.ssaoParams.Get(), BufferState::shaderReadOnly); auto* ssaoKernelBuffer = builder.ImportBuffer(uniformBuffers.ssaoKernel.Get(), BufferState::shaderReadOnly); - // 创建视图 auto* gBufferPosRTV = builder.CreateTextureView(gBufferPos, RGTextureViewDesc(TextureViewType::colorAttachment, TextureViewDimension::tv2D)); auto* gBufferNormalRTV = builder.CreateTextureView(gBufferNormal, RGTextureViewDesc(TextureViewType::colorAttachment, TextureViewDimension::tv2D)); auto* gBufferAlbedoRTV = builder.CreateTextureView(gBufferAlbedo, RGTextureViewDesc(TextureViewType::colorAttachment, TextureViewDimension::tv2D)); @@ -227,7 +220,7 @@ class SSAOApp final : public Application { auto* ssaoKernelView = builder.CreateBufferView(ssaoKernelBuffer, RGBufferViewDesc(BufferViewType::uniformBinding, ssaoKernelSize * sizeof(FVec4))); // 1. G-Buffer Pass - auto* gBufferPipeline = PipelineCache::Get(*device).GetOrCreate( + auto* gBufferPipeline = Render::PipelineCache::Get(*device).GetOrCreate( RasterPipelineStateDesc() .SetVertexShader(gBufferVS) .SetPixelShader(gBufferPS) @@ -288,7 +281,7 @@ class SSAOApp final : public Application { }); // 2. SSAO Pass - auto* ssaoPipeline = PipelineCache::Get(*device).GetOrCreate( + auto* ssaoPipeline = Render::PipelineCache::Get(*device).GetOrCreate( RasterPipelineStateDesc() .SetVertexShader(ssaoVS) .SetPixelShader(ssaoPS) @@ -330,7 +323,7 @@ class SSAOApp final : public Application { }); // 3. SSAO Blur Pass - auto* ssaoBlurPipeline = PipelineCache::Get(*device).GetOrCreate( + auto* ssaoBlurPipeline = Render::PipelineCache::Get(*device).GetOrCreate( RasterPipelineStateDesc() .SetVertexShader(blurVS) .SetPixelShader(blurPS) @@ -367,7 +360,7 @@ class SSAOApp final : public Application { }); // 4. Composition Pass - auto* compositionPipeline = PipelineCache::Get(*device).GetOrCreate( + auto* compositionPipeline = Render::PipelineCache::Get(*device).GetOrCreate( RasterPipelineStateDesc() .SetVertexShader(compositionVS) .SetPixelShader(compositionPS) @@ -412,14 +405,14 @@ class SSAOApp final : public Application { recorder.ResourceBarrier(Barrier::Transition(rg.GetRHI(backTexture), TextureState::renderTarget, TextureState::present)); }); - // 执行渲染图 RGExecuteInfo executeInfo; executeInfo.semaphoresToWait = { imageReadySemaphore.Get() }; - executeInfo.semaphoresToSignal = { renderFinishedSemaphore.Get() }; + executeInfo.semaphoresToSignal = { renderFinishedSemaphores[backTextureIndex].Get() }; executeInfo.inFenceToSignal = frameFence.Get(); builder.Execute(executeInfo); - swapChain->Present(renderFinishedSemaphore.Get()); + swapChain->Present(renderFinishedSemaphores[backTextureIndex].Get()); + swapChainTextureStates[backTextureIndex] = TextureState::present; frameFence->Wait(); Core::ThreadContext::IncFrameNumber(); @@ -440,7 +433,7 @@ class SSAOApp final : public Application { fence->Wait(); BindGroupCache::Get(*device).Invalidate(); - PipelineCache::Get(*device).Invalidate(); + Render::PipelineCache::Get(*device).Invalidate(); BufferPool::Get(*device).Invalidate(); TexturePool::Get(*device).Invalidate(); ShaderMap::Get(*device).Invalidate(); @@ -473,6 +466,7 @@ class SSAOApp final : public Application { UniquePtr surface; UniquePtr swapChain; std::array swapChainTextures; + std::array swapChainTextureStates; UniquePtr vertexBuffer; UniquePtr indexBuffer; @@ -491,7 +485,7 @@ class SSAOApp final : public Application { UniquePtr noiseSampler; UniquePtr imageReadySemaphore; - UniquePtr renderFinishedSemaphore; + std::array, backBufferCount> renderFinishedSemaphores; UniquePtr frameFence; // Uniform buffers @@ -569,7 +563,7 @@ class SSAOApp final : public Application { }; for (const auto format : swapChainFormatQualifiers) { - if (device->CheckSwapChainFormatSupport(surface.Get(), format)) { + if (device->CheckSwapChainFormatSupport(surface.Get(), format, ColorSpace::srgbNonLinear)) { swapChainFormat = format; break; } @@ -588,6 +582,7 @@ class SSAOApp final : public Application { for (auto i = 0; i < backBufferCount; i++) { swapChainTextures[i] = swapChain->GetTexture(i); + swapChainTextureStates[i] = swapChainTextures[i]->GetCreateInfo().initialState; } } @@ -675,7 +670,9 @@ class SSAOApp final : public Application { void CreateSyncObjects() { imageReadySemaphore = device->CreateSemaphore(); - renderFinishedSemaphore = device->CreateSemaphore(); + for (auto i = 0; i < backBufferCount; i++) { + renderFinishedSemaphores[i] = device->CreateSemaphore(); + } frameFence = device->CreateFence(true); } @@ -905,7 +902,6 @@ class SSAOApp final : public Application { for (const auto& mesh : model->meshes) { UniquePtr diffuseTex = nullptr; - // 加载材质纹理 if (mesh->materialData && mesh->materialData->baseColorTexture) { const auto& texData = mesh->materialData->baseColorTexture; @@ -971,7 +967,7 @@ class SSAOApp final : public Application { { auto* camera = new Camera( FVec3(.0f, -5.0f, 2.0f), - FVec3(.0f, .0f, -90.0f), + 0.0f, -90.0f, Camera::ProjectionParams { 60.0f, static_cast(GetWindowWidth()), @@ -999,4 +995,4 @@ int main(int argc, char* argv[]) return -1; } return application.RunLoop(); -} \ No newline at end of file +} diff --git a/Sample/Rendering-Triangle/Triangle.cpp b/Sample/Rendering-Triangle/Triangle.cpp index a67f87206..4055c75be 100644 --- a/Sample/Rendering-Triangle/Triangle.cpp +++ b/Sample/Rendering-Triangle/Triangle.cpp @@ -74,9 +74,10 @@ class TriangleApplication final : public Application { UniquePtr surface; UniquePtr swapChain; std::array swapChainTextures; + std::array swapChainTextureStates; UniquePtr triangleVertexBuffer; UniquePtr imageReadySemaphore; - UniquePtr renderFinishedSemaphore; + std::array, backBufferCount> renderFinishedSemaphores; UniquePtr frameFence; }; @@ -84,6 +85,7 @@ TriangleApplication::TriangleApplication(const std::string& inName) : Application(inName) , swapChainFormat(PixelFormat::max) , swapChainTextures() + , swapChainTextureStates() { } @@ -114,7 +116,7 @@ void TriangleApplication::OnDrawFrame() frameFence->Reset(); const auto backTextureIndex = swapChain->AcquireBackTexture(imageReadySemaphore.Get()); - auto* pso = PipelineCache::Get(*device).GetOrCreate( + auto* pso = Render::PipelineCache::Get(*device).GetOrCreate( RasterPipelineStateDesc() .SetVertexShader(triangleVS) .SetPixelShader(trianglePS) @@ -128,7 +130,7 @@ void TriangleApplication::OnDrawFrame() .AddColorTarget(ColorTargetState(swapChainFormat, ColorWriteBits::all, false)))); RGBuilder builder(*device); - auto* backTexture = builder.ImportTexture(swapChainTextures[backTextureIndex], TextureState::present); + auto* backTexture = builder.ImportTexture(swapChainTextures[backTextureIndex], swapChainTextureStates[backTextureIndex]); auto* backTextureView = builder.CreateTextureView(backTexture, RGTextureViewDesc(TextureViewType::colorAttachment, TextureViewDimension::tv2D)); auto* vertexBuffer = builder.ImportBuffer(triangleVertexBuffer.Get(), BufferState::shaderReadOnly); auto* vertexBufferView = builder.CreateBufferView(vertexBuffer, RGBufferViewDesc(BufferViewType::vertex, vertexBuffer->GetDesc().size, 0, VertexBufferViewInfo(sizeof(Vertex)))); @@ -170,10 +172,11 @@ void TriangleApplication::OnDrawFrame() RGExecuteInfo executeInfo; executeInfo.semaphoresToWait = { imageReadySemaphore.Get() }; - executeInfo.semaphoresToSignal = { renderFinishedSemaphore.Get() }; + executeInfo.semaphoresToSignal = { renderFinishedSemaphores[backTextureIndex].Get() }; executeInfo.inFenceToSignal = frameFence.Get(); builder.Execute(executeInfo); - swapChain->Present(renderFinishedSemaphore.Get()); + swapChain->Present(renderFinishedSemaphores[backTextureIndex].Get()); + swapChainTextureStates[backTextureIndex] = TextureState::present; frameFence->Wait(); Core::ThreadContext::IncFrameNumber(); @@ -195,7 +198,7 @@ void TriangleApplication::OnDestroy() fence->Wait(); BindGroupCache::Get(*device).Invalidate(); - PipelineCache::Get(*device).Invalidate(); + Render::PipelineCache::Get(*device).Invalidate(); BufferPool::Get(*device).Invalidate(); TexturePool::Get(*device).Invalidate(); ShaderMap::Get(*device).Invalidate(); @@ -246,7 +249,7 @@ void TriangleApplication::CreateSwapChain() }; for (const auto format : swapChainFormatQualifiers) { - if (device->CheckSwapChainFormatSupport(surface.Get(), format)) { + if (device->CheckSwapChainFormatSupport(surface.Get(), format, ColorSpace::srgbNonLinear)) { swapChainFormat = format; break; } @@ -265,6 +268,7 @@ void TriangleApplication::CreateSwapChain() for (auto i = 0; i < backBufferCount; i++) { swapChainTextures[i] = swapChain->GetTexture(i); + swapChainTextureStates[i] = swapChainTextures[i]->GetCreateInfo().initialState; } } @@ -293,7 +297,9 @@ void TriangleApplication::CreateTriangleVertexBuffer() void TriangleApplication::CreateSyncObjects() { imageReadySemaphore = device->CreateSemaphore(); - renderFinishedSemaphore = device->CreateSemaphore(); + for (auto i = 0; i < backBufferCount; i++) { + renderFinishedSemaphores[i] = device->CreateSemaphore(); + } frameFence = device->CreateFence(true); } diff --git a/ThirdParty/ConanRecipes/qt/conandata.yml b/ThirdParty/ConanRecipes/qt/conandata.yml deleted file mode 100644 index f0bf218e3..000000000 --- a/ThirdParty/ConanRecipes/qt/conandata.yml +++ /dev/null @@ -1,6 +0,0 @@ -platforms: - - Windows-x86_64 - - Macos-armv8 - - Linux-x86_64 -versions: - - "6.10.3-exp" diff --git a/ThirdParty/ConanRecipes/qt/conanfile.py b/ThirdParty/ConanRecipes/qt/conanfile.py deleted file mode 100644 index d3ce401c8..000000000 --- a/ThirdParty/ConanRecipes/qt/conanfile.py +++ /dev/null @@ -1,166 +0,0 @@ -from conan import ConanFile -from conan.errors import ConanInvalidConfiguration -from conan.tools.build import check_min_cppstd -from conan.tools.files import copy -import os -import sys -import subprocess - -required_conan_version = ">=2.0.9" - - -class QtConan(ConanFile): - name = "qt" - description = "Qt 6 (prebuilt, repackaged from the official Qt CDN via aqtinstall)" - license = "https://github.com/qt/qt5/tree/dev/LICENSES" - url = "https://www.qt.io/" - homepage = "https://www.qt.io/" - topics = ("gui", "tool", "prebuilt") - package_type = "shared-library" - settings = "os", "arch", "compiler", "build_type" - no_copy_source = True - - # Modules added on top of aqt's default base install. The base set already - # includes qtbase, qtdeclarative, qttools, qtsvg, qttranslations, qtimageformats. - _ADDON_MODULES = ( - "qtshadertools", - "qtpositioning", - "qtwebchannel", - "qtwebengine", - "qtwebsockets", - ) - - # (os, arch) -> (aqt host, aqt target, aqt arch, install-subdir-name). - # Supported: Windows x64, Linux x64, macOS arm64. - _AQT_PLATFORM = { - ("Windows", "x86_64"): ("windows", "desktop", "win64_msvc2022_64", "msvc2022_64"), - ("Linux", "x86_64"): ("linux", "desktop", "linux_gcc_64", "gcc_64"), - ("Macos", "armv8"): ("mac", "desktop", "clang_64", "macos"), - } - - def _aqt_params(self): - return self._AQT_PLATFORM.get((str(self.settings.os), str(self.settings.arch))) - - @property - def _qt_version(self): - """Strip the -exp / +foo suffix so aqt sees a plain version like 6.10.1.""" - v = str(self.version) - for sep in ("-", "+"): - v = v.split(sep, 1)[0] - return v - - def validate(self): - check_min_cppstd(self, 17) - if self._aqt_params() is None: - raise ConanInvalidConfiguration( - f"qt/{self.version} prebuilt is not available for " - f"{self.settings.os}/{self.settings.arch}. " - f"Supported: {sorted(self._AQT_PLATFORM)}" - ) - if self.settings.os == "Windows" and str(self.settings.compiler) == "msvc": - # The win64_msvc2022_64 prebuilt links/runs against any MSVC toolset - # that is binary-compatible with v143. Microsoft's C++ binary - # compatibility guarantee covers v140-v145 (see "C++ binary - # compatibility 2015-2026" on Microsoft Learn), so VS2022 (193/194) - # and VS2026 (195+) both consume this prebuilt safely as long as Qt - # keeps shipping only win64_msvc2022_64. If Qt later adds a separate - # win64_msvc2026_64 in aqtinstall, switch _AQT_PLATFORM accordingly. - ver = int(str(self.settings.compiler.version)) - if ver < 193: - raise ConanInvalidConfiguration( - f"Qt prebuilt is built with MSVC 2022 (v143). " - f"compiler.version must be >= 193 (VS2022) for ABI " - f"compatibility, got {ver}" - ) - - def build(self): - self._ensure_aqt() - host, target, arch, dirname = self._aqt_params() - outdir = os.path.join(self.build_folder, "qt-install") - cmd = [ - sys.executable, "-m", "aqt", "install-qt", - host, target, self._qt_version, arch, - "-O", outdir, - "-m", *self._ADDON_MODULES, - ] - self.output.info(f"Running aqt: {' '.join(cmd)}") - subprocess.check_call(cmd) - self._strip_debug_artifacts(os.path.join(outdir, self._qt_version, dirname)) - - def package(self): - _, _, _, dirname = self._aqt_params() - qt_prefix = os.path.join(self.build_folder, "qt-install", self._qt_version, dirname) - if not os.path.isdir(qt_prefix): - raise RuntimeError(f"aqt did not produce the expected layout at {qt_prefix}") - copy(self, "*", qt_prefix, self.package_folder, keep_path=True) - # aqt 3.3+ already writes a relocatable bin/qt.conf; idempotent rewrite - # so we don't depend on that behavior surviving aqt upgrades. - qt_conf = os.path.join(self.package_folder, "bin", "qt.conf") - os.makedirs(os.path.dirname(qt_conf), exist_ok=True) - with open(qt_conf, "w", encoding="utf-8", newline="\n") as f: - f.write("[Paths]\nPrefix=..\n") - - def package_id(self): - # All consumer build_types map to Release in the top-level project, and - # the prebuilt is fully baked, so compiler/build_type don't affect the - # binary. validate() filters incompatible toolchains. - del self.info.settings.build_type - del self.info.settings.compiler - - def package_info(self): - # Consumers must use Qt's own Qt6Config.cmake (which is relocatable). - # We expose lib/cmake via builddirs so CMAKE_PREFIX_PATH picks it up. - self.cpp_info.libs = [] - self.cpp_info.builddirs = [os.path.join("lib", "cmake")] - self.cpp_info.set_property("cmake_find_mode", "none") - - def _ensure_aqt(self): - """pip-install aqtinstall on demand.""" - try: - import aqt # noqa: F401 - return - except ImportError: - pass - cmd = [sys.executable, "-m", "pip", "install", "aqtinstall"] - if self.settings.os != "Windows": - cmd.append("--break-system-packages") - subprocess.check_call(cmd) - - def _strip_debug_artifacts(self, root_dir): - """Drop Qt's Debug variants (~2 GiB on Windows). The top-level project - pins all Conan consumption to Release (CONAN_INSTALL_BUILD_CONFIGURATIONS - + CMAKE_MAP_IMPORTED_CONFIG_*), so debug DLLs/libs/.prl plus per-config - Targets-debug.cmake imports are dead weight. - - Patterns: - 1. ``d.`` when ``.`` exists in the same dir - (Qt's Windows debug suffix; pairs like Qt6Core.dll/Qt6Cored.dll). - The "release counterpart exists" check protects names that - genuinely end in ``d`` -- e.g. qdirect2d.dll, qopensslbackend.dll. - 2. ``Targets-debug.cmake`` -- per-config CMake target imports. - - Idempotent. - """ - debug_exts = {".dll", ".lib", ".pdb", ".prl", ".so", ".dylib", ".a"} - deleted, saved = 0, 0 - for cur, _, files in os.walk(root_dir): - names = set(files) - for fname in files: - stem, ext = os.path.splitext(fname) - ext_l = ext.lower() - is_debug_cmake = ext_l == ".cmake" and stem.endswith("-debug") - is_debug_binary = ( - ext_l in debug_exts - and stem.endswith("d") and len(stem) > 1 - and (stem[:-1] + ext) in names - ) - if not (is_debug_cmake or is_debug_binary): - continue - full = os.path.join(cur, fname) - saved += os.path.getsize(full) - os.remove(full) - deleted += 1 - if deleted: - self.output.info( - f"Stripped {deleted} debug artifacts ({saved / 1024 / 1024:.1f} MiB)" - ) diff --git a/ThirdParty/ConanRecipes/qt/debug.py b/ThirdParty/ConanRecipes/qt/debug.py deleted file mode 100644 index 9d99c2ec0..000000000 --- a/ThirdParty/ConanRecipes/qt/debug.py +++ /dev/null @@ -1,7 +0,0 @@ -from conan.api.conan_api import ConanAPI -from conan.cli.cli import Cli - -if __name__ == '__main__': - api = ConanAPI() - api.command = Cli(api) - api.command.run(["build", ".\conanfile.py", "--version=\"6.10.1-exp\""]) diff --git a/ThirdParty/ConanRecipes/qt/test_package/CMakeLists.txt b/ThirdParty/ConanRecipes/qt/test_package/CMakeLists.txt deleted file mode 100644 index 1e97895ff..000000000 --- a/ThirdParty/ConanRecipes/qt/test_package/CMakeLists.txt +++ /dev/null @@ -1,28 +0,0 @@ -cmake_minimum_required(VERSION 3.15) -project(test_package LANGUAGES CXX) - -find_package(Qt6 COMPONENTS Core Gui Widgets WebEngineWidgets REQUIRED) -set(QT_ROOT ${_Qt6_COMPONENT_PATH}/../..) - -qt_standard_project_setup() - -if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") - set(platform_executable_hint MACOSX_BUNDLE) -endif () - -qt_add_executable(${PROJECT_NAME} ${platform_executable_hint} test_package.cpp) -target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets Qt6::WebEngineWidgets) - -if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - set(qt_win_deploy_executable ${QT_ROOT}/bin/windeployqt.exe) - add_custom_command( - TARGET ${PROJECT_NAME} POST_BUILD - COMMAND ${qt_win_deploy_executable} $ - ) -elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") - set(qt_mac_deploy_executable ${QT_ROOT}/bin/macdeployqt) - add_custom_command( - TARGET ${PROJECT_NAME} POST_BUILD - COMMAND ${qt_mac_deploy_executable} $ -no-strip - ) -endif () diff --git a/ThirdParty/ConanRecipes/qt/test_package/conanfile.py b/ThirdParty/ConanRecipes/qt/test_package/conanfile.py deleted file mode 100644 index fc862d4bb..000000000 --- a/ThirdParty/ConanRecipes/qt/test_package/conanfile.py +++ /dev/null @@ -1,32 +0,0 @@ -from conan import ConanFile -from conan.tools.build import can_run -from conan.tools.cmake import cmake_layout, CMake -import os - - -class TestPackageConan(ConanFile): - settings = "os", "arch", "compiler", "build_type" - generators = "CMakeDeps", "CMakeToolchain" - - def layout(self): - cmake_layout(self) - - def requirements(self): - self.requires(self.tested_reference_str) - - def build(self): - cmake = CMake(self) - cmake.configure() - cmake.build() - - def test(self): - if can_run(self): - bin_path = os.path.join(self.cpp.build.bindir, "test_package") - if self.settings.os == "Macos": - self.run(f"open {bin_path}.app", env="conanrun") - elif self.settings.os == "Linux": - # Run the QWebEngineView smoke test headlessly: offscreen avoids needing an X display, and - # disabling Chromium's sandbox keeps it from aborting under restricted/headless environments. - self.run(f"env QT_QPA_PLATFORM=offscreen QTWEBENGINE_DISABLE_SANDBOX=1 {bin_path}", env="conanrun") - else: - self.run(bin_path, env="conanrun") diff --git a/ThirdParty/ConanRecipes/qt/test_package/test_package.cpp b/ThirdParty/ConanRecipes/qt/test_package/test_package.cpp deleted file mode 100644 index 810752554..000000000 --- a/ThirdParty/ConanRecipes/qt/test_package/test_package.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include -#include -#include - -int main(int argc, char *argv[]) -{ - QApplication app(argc, argv); - - QWebEngineView view; - view.load(QUrl("https://qt-project.org/")); - view.resize(1024, 768); - - QTimer::singleShot(100, []() -> void { QApplication::quit(); }); - return app.exec(); -} \ No newline at end of file diff --git a/ThirdParty/Registry.cmake b/ThirdParty/Registry.cmake index ef8e2a813..d1844b22f 100644 --- a/ThirdParty/Registry.cmake +++ b/ThirdParty/Registry.cmake @@ -9,8 +9,6 @@ else () set(CMAKE_PREFIX_PATH "${CMAKE_PREFIX_PATH};${CMAKE_CURRENT_LIST_DIR}/../../ThirdParty") endif () -find_package(httplib REQUIRED GLOBAL) - # glfw requires opengl/system only to propagate headers and the OpenGL framework (libs=False, it never links GL). On # Apple that package carries just cpp_info.frameworks, which the new CMakeConfigDeps generator forgets to count when # deciding whether to emit a target, so opengl::opengl is never created and glfw's link interface dangles. Recreate it. @@ -20,6 +18,7 @@ if (APPLE AND NOT TARGET opengl::opengl) endif () find_package(glfw3 REQUIRED GLOBAL) +find_package(imgui REQUIRED GLOBAL) find_package(stb REQUIRED GLOBAL) find_package(cityhash REQUIRED GLOBAL) find_package(GTest REQUIRED GLOBAL) diff --git a/conanfile.py b/conanfile.py index a841f60e5..882c47c9d 100644 --- a/conanfile.py +++ b/conanfile.py @@ -5,7 +5,6 @@ class ExplosionConan(ConanFile): settings = "os", "compiler", "build_type", "arch" def requirements(self): - self.requires("cpp-httplib/0.27.0") self.requires("stb/cci.20230920") self.requires("cityhash/1.0.1") self.requires("gtest/1.17.0") @@ -18,12 +17,12 @@ def requirements(self): self.requires("vulkan-validationlayers/1.4.350.0") self.requires("glfw/3.4") self.requires("rapidjson/cci.20250205") + self.requires("imgui/1.92.8-docking") if self.settings.os == "Windows": self.requires("directx-headers/1.610.2") # private repo self.requires("libclang/22.1.6-exp") - self.requires("qt/6.10.3-exp") self.requires("debugbreak/1.0-exp") self.requires("assimp/6.0.2-exp") self.requires("clipp/1.2.3-exp")