Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3d7daa9
refactor: copy first-party runtime deps via producer post-build, batc…
FlyAndNotDown Jun 28, 2026
507c811
feat: support push constants in rhi
FlyAndNotDown Jun 29, 2026
4d6dae3
feat: support indirect dispatch in rhi
FlyAndNotDown Jun 29, 2026
a8846ae
feat: support timestamp and occlusion queries in rhi
FlyAndNotDown Jun 30, 2026
4a0cf43
feat: support read-only and read-write storage textures in rhi
FlyAndNotDown Jul 1, 2026
4117f10
feat: support pipeline cache in rhi
FlyAndNotDown Jul 2, 2026
9b51da0
feat: support hdr color space control and fix swapchain present mode
FlyAndNotDown Jul 2, 2026
46462d1
fix: repair rendering samples and prototype widget after rhi pipeline…
FlyAndNotDown Jul 4, 2026
178350f
feat: add viewport present and editor world activation groundwork
FlyAndNotDown Jul 4, 2026
aede374
feat: add editor viewport, client and main window with engine tick loop
FlyAndNotDown Jul 4, 2026
43da34c
fix: serialize per-frame gpu work and acquire back texture on render …
FlyAndNotDown Jul 4, 2026
0989eb0
fix: delete editor viewport directly to avoid native window recreatio…
FlyAndNotDown Jul 4, 2026
889ece8
feat: implement static mesh unlit render path with vertex factory and…
FlyAndNotDown Jul 5, 2026
4727d9a
fix: repair static mesh render path bring-up issues
FlyAndNotDown Jul 5, 2026
b9cec7f
feat: add dock manager with outliner, inspector and log panels
FlyAndNotDown Jul 5, 2026
e206385
refactor: make log streams consume structured entries only
FlyAndNotDown Jul 5, 2026
ba22c53
refactor: drop world activation lifecycle in favor of play
FlyAndNotDown Jul 5, 2026
9c4c366
refactor: render through camera entities and drop editor player
FlyAndNotDown Jul 5, 2026
ef152e2
fix: repair look rotation convention mismatch causing camera roll
FlyAndNotDown Jul 5, 2026
0d66e45
fix: align editor camera to Qy*Qz rig, fixing inverted yaw
FlyAndNotDown Jul 5, 2026
a6db2f8
feat: add codex ignore
FlyAndNotDown Jul 6, 2026
b22912c
fix: improve editor camera controls
FlyAndNotDown Jul 7, 2026
388bf9c
feat: migrate editor ui to imgui
FlyAndNotDown Jul 7, 2026
8347a22
fix: track sample swapchain states
FlyAndNotDown Jul 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 0 additions & 6 deletions CMake/Common.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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 <cfg>" 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)
121 changes: 82 additions & 39 deletions CMake/Target.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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 ($<TARGET_FILE:...>) 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 $<TARGET_FILE:...> 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 $<$<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 "$<$<CONFIG:${config}>:${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)
Expand Down Expand Up @@ -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 ()
Expand Down Expand Up @@ -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 $<TARGET_FILE:${arg_NAME}> ${dist_dir}
VERBATIM
)
endif ()

foreach (inc ${arg_PUBLIC_INC})
get_filename_component(absolute_inc ${inc} ABSOLUTE)
list(APPEND public_build_inc $<BUILD_INTERFACE:${absolute_inc}>)
Expand Down
109 changes: 9 additions & 100 deletions Editor/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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} $<TARGET_FILE:Editor>
)
install(
CODE "execute_process(COMMAND ${qt_win_deploy_executable} ${CMAKE_INSTALL_PREFIX}/${SUB_PROJECT_NAME}/Binaries/$<TARGET_FILE_NAME:Editor>)"
)
elseif (APPLE)
set(qt_mac_deploy_executable ${QT_ROOT}/bin/macdeployqt)
add_custom_command(
TARGET Editor POST_BUILD
COMMAND ${qt_mac_deploy_executable} $<TARGET_PROPERTY:Editor,RUNTIME_OUTPUT_DIRECTORY>/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*)
Expand All @@ -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 $<TARGET_FILE_DIR:Editor>/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
Expand Down
63 changes: 63 additions & 0 deletions Editor/Include/Editor/EditorApplication.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//
// Created by johnk on 2026/7/7.
//

#pragma once

#include <Common/Memory.h>
#include <Editor/EditorContext.h>
#include <Editor/EditorWindow.h>
#include <Editor/Frame/EditorFrame.h>
#include <Editor/Frame/ProjectHubFrame.h>
#include <Runtime/Canvas.h>

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<EditorWindow> window;
Common::UniquePtr<EditorContext> context;
Common::UniquePtr<Runtime::Canvas> sceneRenderCanvas;
Common::UniquePtr<ProjectHubFrame> projectHubFrame;
EditorFrame editorFrame;
double lastFrameSeconds;
double lastCursorX;
double lastCursorY;
bool hasLastCursor;
bool requestQuit;
};
}
35 changes: 35 additions & 0 deletions Editor/Include/Editor/EditorContext.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// Created by johnk on 2026/7/4.
//

#pragma once

#include <Common/Memory.h>
#include <Editor/SceneClient.h>

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> sceneClient;
Runtime::Entity selectedEntity;
uint64_t selectionVersion;
uint64_t worldStructureVersion;
uint64_t componentsVersion;
};
}
Loading
Loading