From ed22b5851a16724a1001c45b9c882025be4bcfea Mon Sep 17 00:00:00 2001 From: gameKnife Date: Sun, 25 Jan 2026 23:46:59 +0800 Subject: [PATCH 01/53] fix: recognize pure emissive materials in GLTF loader - Read KHR_materials_emissive_strength extension for emissive strength - Identify pure emissive materials (emissiveFactor > 0.001 without emissiveTexture) - Set MaterialModel to DiffuseLight for pure emissive materials - Apply emissiveColor * emissiveStrength as diffuse color - Simplify emissiveTexture handling logic --- src/Assets/FSceneLoader.cpp | 39 +++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/Assets/FSceneLoader.cpp b/src/Assets/FSceneLoader.cpp index 85ed61da..d9ea8c56 100644 --- a/src/Assets/FSceneLoader.cpp +++ b/src/Assets/FSceneLoader.cpp @@ -553,13 +553,27 @@ namespace Assets : glm::vec3(mat.emissiveFactor[0], mat.emissiveFactor[1], mat.emissiveFactor[2]); glm::vec3 diffuseColor = mat.pbrMetallicRoughness.baseColorFactor.empty() - ? glm::vec3(1) - : glm::vec3(mat.pbrMetallicRoughness.baseColorFactor[0], - mat.pbrMetallicRoughness.baseColorFactor[1], - mat.pbrMetallicRoughness.baseColorFactor[2]); + ? glm::vec3(1) + : glm::vec3(mat.pbrMetallicRoughness.baseColorFactor[0], + mat.pbrMetallicRoughness.baseColorFactor[1], + mat.pbrMetallicRoughness.baseColorFactor[2]); m.Diffuse = glm::vec4(sqrt(diffuseColor), 1.0); + float emissiveStrength = 1.0f; + auto emissiveExtension = mat.extensions.find("KHR_materials_emissive_strength"); + if (emissiveExtension != mat.extensions.end()) + { + emissiveStrength = static_cast(emissiveExtension->second.Get("emissiveStrength").GetNumberAsDouble()); + } + + bool isPureEmissiveMaterial = (glm::length(emissiveColor) > 0.001f) && (mat.emissiveTexture.index == -1); + if (isPureEmissiveMaterial) + { + m.MaterialModel = Material::Enum::DiffuseLight; + m.Diffuse = glm::vec4(emissiveColor * emissiveStrength, 1.0f); + } + if (m.MRATextureId != -1) { // m.Metalness = 1.0f; that makes huge problem, first dont change this @@ -598,22 +612,9 @@ namespace Assets m.RefractionIndex2 = mat.extras.Get("ior2").GetNumberAsDouble(); } - auto emissive = mat.extensions.find("KHR_materials_emissive_strength"); - // TODO: may impl per pixel mat type - if (emissive != mat.extensions.end()) + if (mat.emissiveTexture.index != -1) { - float power = static_cast(emissive->second.Get("emissiveStrength").GetNumberAsDouble()); - if (mat.emissiveTexture.index != -1) - { - m.EmissiveTextureId = lambdaGetTexture(mat.emissiveTexture.index); - } - } - else if (glm::length(emissiveColor) > 0.0f) - { - if (mat.emissiveTexture.index != -1) - { - m.EmissiveTextureId = lambdaGetTexture(mat.emissiveTexture.index); - } + m.EmissiveTextureId = lambdaGetTexture(mat.emissiveTexture.index); } materials.push_back( { m, mat.name } ); From 58c8aec3600a611b3949b19d878a765a7120347e Mon Sep 17 00:00:00 2001 From: gameKnife Date: Tue, 27 Jan 2026 00:17:19 +0800 Subject: [PATCH 02/53] build: optimize CMake build system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Simplify CMake options: GK_ENABLE_* → ENABLE_* - Unify Vulkan SDK versions across CI (1.4.313.x) - Add incremental configure support (--reconfigure to force) - Add ENABLE_LTO option (default OFF) - Increase Unity Build batch size to 12 - Centralize source file definitions in SourceFiles.cmake Co-Authored-By: Claude Opus 4.5 --- .github/workflows/android.yml | 4 +- .github/workflows/ios.yml | 6 +-- .github/workflows/macos.yml | 6 +-- .github/workflows/windows.yml | 2 +- AGENTS.md | 4 +- AGENT_GUIDE/coding-standards.md | 2 +- CLAUDE.md | 76 +++++++++++++++++++++++++++++++ CMakeLists.txt | 34 +++++++------- CMakePresets.json | 50 ++++++++++----------- build.ps1 | 43 ++++++++++++++---- build.sh | 51 ++++++++++++++++----- cmake/ProjectOptions.cmake | 16 ++++++- cmake/SetupPlatform.cmake | 4 +- src/CMakeLists.txt | 70 ++++++++--------------------- src/cmake/SourceFiles.cmake | 79 +++++++++++++++++++++++++++++++++ 15 files changed, 318 insertions(+), 129 deletions(-) create mode 100644 CLAUDE.md create mode 100644 src/cmake/SourceFiles.cmake diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index e8946fad..4f7eeb8e 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -21,8 +21,8 @@ jobs: build: runs-on: ubuntu-22.04 - env: - SDK_VERSION: 1.4.309 + env: + SDK_VERSION: 1.4.313 steps: - uses: actions/setup-java@v4 with: diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index a24d83a9..e23873fe 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -21,8 +21,8 @@ jobs: build: runs-on: macos-latest - env: - SDK_VERSION: 1.4.313.1 + env: + SDK_VERSION: 1.4.313.2 steps: - uses: actions/checkout@v4 - name: Install Vulkan SDK @@ -32,7 +32,7 @@ jobs: - name: Download and setup slangc run: | - curl -L https://github.com/shader-slang/slang/releases/download/vulkan-sdk-1.4.313.0/slang-vulkan-sdk-1.4.313.0-macos-aarch64.zip -o slang.zip + curl -L https://github.com/shader-slang/slang/releases/download/vulkan-sdk-${SDK_VERSION}/slang-vulkan-sdk-${SDK_VERSION}-macos-aarch64.zip -o slang.zip mkdir -p slang unzip slang.zip -d slang echo "$PWD/slang/bin" >> $GITHUB_PATH diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 74e0be1d..ba8b76e3 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -21,8 +21,8 @@ jobs: build: runs-on: macos-latest - env: - SDK_VERSION: 1.4.313.1 + env: + SDK_VERSION: 1.4.313.2 steps: - uses: actions/checkout@v4 - name: Install Vulkan SDK @@ -32,7 +32,7 @@ jobs: - name: Download and setup slangc run: | - curl -L https://github.com/shader-slang/slang/releases/download/vulkan-sdk-1.4.313.0/slang-vulkan-sdk-1.4.313.0-macos-aarch64.zip -o slang.zip + curl -L https://github.com/shader-slang/slang/releases/download/vulkan-sdk-${SDK_VERSION}/slang-vulkan-sdk-${SDK_VERSION}-macos-aarch64.zip -o slang.zip mkdir -p slang unzip slang.zip -d slang echo "$PWD/slang/bin" >> $GITHUB_PATH diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 9e09beb1..526ef649 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -22,7 +22,7 @@ jobs: runs-on: windows-latest env: - SDK_VERSION: 1.4.313.1 + SDK_VERSION: 1.4.313.2 steps: - uses: actions/checkout@v4 - name: Download Vulkan SDK diff --git a/AGENTS.md b/AGENTS.md index 71d5914a..42ef5c73 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,8 +39,8 @@ Build (Android): - macOS/Linux: `./build.sh --android` Optional build flags (via CMake args): -- Example: `./build.sh --preset default-linux -- -DGK_ENABLE_AVIF=ON` -- Windows example: `./build.bat --preset default-windows -- -DGK_ENABLE_AVIF=ON` +- Example: `./build.sh --preset default-linux -- -DENABLE_AVIF=ON` +- Windows example: `./build.bat --preset default-windows -- -DENABLE_AVIF=ON` Run (native): - `./run.sh --preset ` diff --git a/AGENT_GUIDE/coding-standards.md b/AGENT_GUIDE/coding-standards.md index 0beecc0a..a5517eff 100644 --- a/AGENT_GUIDE/coding-standards.md +++ b/AGENT_GUIDE/coding-standards.md @@ -222,7 +222,7 @@ ``` - ✅ **生成 compile_commands.json**(禁用 Unity Build): ```bash - ./build.sh -DGK_ENABLE_UNITY_BUILD=OFF + ./build.sh -DENABLE_UNITY_BUILD=OFF ``` - ✅ **性能分析使用 Superluminal/Tracy**(不影响默认构建) - ✅ **提交前检查工作区**:`git status` 确认无遗漏文件 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..979fcf3e --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,76 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with this repository. + +## Communication Preference + +**Language: 中文 (Chinese)** +Always communicate with the user in Chinese (中文). + +## Project Overview + +gkNextRenderer is a cross-platform 3D game engine built with modern C++20 and Vulkan, featuring hardware/software ray tracing, real-time global illumination, and GPU-driven rendering. + +## Build Commands + +**Dependencies (vcpkg):** +- Windows: `./vcpkg.bat` +- macOS/Linux: `./vcpkg.sh` + +**Build:** +- Windows: `./build.bat --preset default-windows` +- macOS: `./build.sh --preset default-macos-arm64` +- Linux: `./build.sh --preset default-linux` +- Android: `./build.bat --android` (Windows) or `./build.sh --android` +- Clean rebuild: add `--clean` + +**Optional flags:** `--avif`, `--dlss`, `--oidn` + +## Run Commands + +- Windows: `./run.bat --preset ` +- macOS/Linux: `./run.sh --preset ` +- Specific target: `./run.sh --preset default-macos-arm64 --target gkNextEditor` + +## Testing + +**CRITICAL: Tests must be run from the bin directory.** + +```bash +# Unit tests (Catch2) +cd out/build//bin && ./gkNextUnitTests + +# Run specific test +cd out/build//bin && ./gkNextUnitTests "[Unit][RenderComponent]" + +# Visual tests +cd out/build//bin && ./gkNextVisualTest +``` + +## Code Style (Summary) + +- **First include:** `Common/CoreMinimal.hpp` +- **Platform abstraction:** Use `PlatformCommon.h`, not direct platform headers +- **Naming:** + - Types/functions: PascalCase + - Variables/parameters: camelCase + - Private members: camelCase_ (trailing underscore) + - Macros: UPPER_CASE +- **Braces:** Allman style (opening brace on new line) +- **Indentation:** 4 spaces, no tabs +- **Shaders:** Slang (`.vert.slang`, `.frag.slang`, `.rgen.slang`) + +## Architecture Overview + +- `src/Runtime/` - Core engine runtime +- `src/Runtime/Platform/` - Platform-specific code +- `src/Vulkan/` - Vulkan backend +- `src/Tests/` - Catch2 unit tests +- `assets/shaders/` - Slang shaders +- `assets/configs/` - Runtime configuration + +## Key References + +- **AGENTS.md** - Complete coding standards, build commands, and agent guidelines +- **AGENT_GUIDE/** - Layered documentation (core-patterns, contextual-rules, coding-standards, quick-commands) +- **README.en.md** - Project overview and quick start diff --git a/CMakeLists.txt b/CMakeLists.txt index 595b7575..77905c65 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,50 +5,50 @@ project(gkNextRenderer LANGUAGES CXX C) include(cmake/SetupPlatform.cmake) include(cmake/ProjectOptions.cmake) -option(GK_ENABLE_AVIF "Enable AVIF support" OFF) -if (GK_ENABLE_AVIF) +option(ENABLE_AVIF "Enable AVIF support" OFF) +if (ENABLE_AVIF) set(WITH_AVIF ON CACHE BOOL "Enable AVIF" FORCE) endif() -option(GK_ENABLE_DLSS "Enable DLSS support" OFF) +option(ENABLE_DLSS "Enable DLSS support" OFF) if (NOT WIN32) - if (GK_ENABLE_DLSS) + if (ENABLE_DLSS) message(STATUS "DLSS (Streamline) is only supported on Windows. Disabling.") endif() - set(GK_ENABLE_DLSS OFF CACHE BOOL "Enable DLSS support" FORCE) + set(ENABLE_DLSS OFF CACHE BOOL "Enable DLSS support" FORCE) endif() -if (GK_ENABLE_DLSS) +if (ENABLE_DLSS) set(WITH_STREAMLINE ON CACHE BOOL "Enable Nvidia Streamline" FORCE) endif() -option(GK_ENABLE_KTX2 "Enable KTX2 support" ON) -if (GK_ENABLE_KTX2) +option(ENABLE_KTX2 "Enable KTX2 support" ON) +if (ENABLE_KTX2) set(WITH_KTX2 ON CACHE BOOL "Enable KTX2" FORCE) endif() -option(GK_ENABLE_PHYSIC "Enable Physics support" ON) -if (GK_ENABLE_PHYSIC) +option(ENABLE_PHYSIC "Enable Physics support" ON) +if (ENABLE_PHYSIC) set(WITH_PHYSIC ON CACHE BOOL "Enable Physics" FORCE) endif() -option(GK_ENABLE_AUDIO "Enable Audio support" ON) -if (GK_ENABLE_AUDIO) +option(ENABLE_AUDIO "Enable Audio support" ON) +if (ENABLE_AUDIO) set(WITH_AUDIO ON CACHE BOOL "Enable Audio" FORCE) endif() -option(GK_ENABLE_OIDN "Enable OpenImageDenoise support" OFF) +option(ENABLE_OIDN "Enable OpenImageDenoise support" OFF) if (APPLE) message(STATUS "OIDN is not supported on macOS. Disabling.") - set(GK_ENABLE_OIDN OFF CACHE BOOL "Enable OpenImageDenoise support" FORCE) + set(ENABLE_OIDN OFF CACHE BOOL "Enable OpenImageDenoise support" FORCE) endif() -if (GK_ENABLE_OIDN) +if (ENABLE_OIDN) set(WITH_OIDN ON CACHE BOOL "Enable OIDN" FORCE) endif() -option(GK_ENABLE_QUICKJS "Enable QuickJS support" ON) -if (GK_ENABLE_QUICKJS) +option(ENABLE_QUICKJS "Enable QuickJS support" ON) +if (ENABLE_QUICKJS) set(WITH_QUICKJS ON CACHE BOOL "Enable QuickJS" FORCE) endif() diff --git a/CMakePresets.json b/CMakePresets.json index 3026bef6..53dd819d 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -24,42 +24,42 @@ "name": "features-minimal", "hidden": true, "cacheVariables": { - "GK_ENABLE_AVIF": "OFF", - "GK_ENABLE_DLSS": "OFF", - "GK_ENABLE_KTX2": "ON", - "GK_ENABLE_PHYSIC": "OFF", - "GK_ENABLE_AUDIO": "OFF", - "GK_ENABLE_OIDN": "OFF", - "GK_ENABLE_QUICKJS": "OFF", - "GK_ENABLE_UNITY_BUILD": "ON" + "ENABLE_AVIF": "OFF", + "ENABLE_DLSS": "OFF", + "ENABLE_KTX2": "ON", + "ENABLE_PHYSIC": "OFF", + "ENABLE_AUDIO": "OFF", + "ENABLE_OIDN": "OFF", + "ENABLE_QUICKJS": "OFF", + "ENABLE_UNITY_BUILD": "ON" } }, { "name": "features-default", "hidden": true, "cacheVariables": { - "GK_ENABLE_AVIF": "OFF", - "GK_ENABLE_DLSS": "OFF", - "GK_ENABLE_KTX2": "ON", - "GK_ENABLE_PHYSIC": "ON", - "GK_ENABLE_AUDIO": "ON", - "GK_ENABLE_OIDN": "OFF", - "GK_ENABLE_QUICKJS": "OFF", - "GK_ENABLE_UNITY_BUILD": "ON" + "ENABLE_AVIF": "OFF", + "ENABLE_DLSS": "OFF", + "ENABLE_KTX2": "ON", + "ENABLE_PHYSIC": "ON", + "ENABLE_AUDIO": "ON", + "ENABLE_OIDN": "OFF", + "ENABLE_QUICKJS": "OFF", + "ENABLE_UNITY_BUILD": "ON" } }, { "name": "features-full", "hidden": true, "cacheVariables": { - "GK_ENABLE_AVIF": "OFF", - "GK_ENABLE_DLSS": "ON", - "GK_ENABLE_KTX2": "ON", - "GK_ENABLE_PHYSIC": "ON", - "GK_ENABLE_AUDIO": "ON", - "GK_ENABLE_OIDN": "ON", - "GK_ENABLE_QUICKJS": "ON", - "GK_ENABLE_UNITY_BUILD": "ON" + "ENABLE_AVIF": "OFF", + "ENABLE_DLSS": "ON", + "ENABLE_KTX2": "ON", + "ENABLE_PHYSIC": "ON", + "ENABLE_AUDIO": "ON", + "ENABLE_OIDN": "ON", + "ENABLE_QUICKJS": "ON", + "ENABLE_UNITY_BUILD": "ON" } }, { @@ -176,7 +176,7 @@ "cacheVariables": { "CMAKE_SYSTEM_NAME": "iOS", "VCPKG_TARGET_TRIPLET": "arm64-ios", - "GK_IOS_SKIP_CODE_SIGN": "ON" + "IOS_SKIP_CODE_SIGN": "ON" } }, { diff --git a/build.ps1 b/build.ps1 index 4bb6b802..0957d7a9 100644 --- a/build.ps1 +++ b/build.ps1 @@ -6,6 +6,7 @@ .DESCRIPTION Standardizes the build process using CMake Presets. Allows pass-through arguments to CMake. + Supports incremental configuration (skips configure if already configured). .PARAMETER Preset The CMake preset to use. Required. @@ -13,6 +14,9 @@ .PARAMETER Clean Clean the build directory before building. +.PARAMETER Reconfigure + Force CMake reconfiguration even if already configured. + .PARAMETER Android Switch to the Android Gradle build. @@ -21,7 +25,8 @@ .EXAMPLE .\build.ps1 --preset full-windows-dev - .\build.ps1 --preset default-windows-dev -- -DGK_ENABLE_AVIF=ON + .\build.ps1 --preset default-windows-dev -- -DENABLE_AVIF=ON + .\build.ps1 --preset default-windows --reconfigure .\build.ps1 --clean .\build.ps1 --android #> @@ -39,6 +44,7 @@ $ProjectRoot = $ScriptDir # --- Defaults --- $Preset = $null $Clean = $false +$Reconfigure = $false $Android = $false $CMakeArgs = @() @@ -62,6 +68,9 @@ while ($i -lt $AllArgs.Count) { "^--clean$" { $Clean = $true } + "^--reconfigure$" { + $Reconfigure = $true + } "^--android$" { $Android = $true } @@ -70,12 +79,14 @@ while ($i -lt $AllArgs.Count) { Write-Host "Options:" Write-Host " --preset CMake preset to use [REQUIRED]" Write-Host " --clean Clean build directory before building" + Write-Host " --reconfigure Force CMake reconfiguration" Write-Host " --android Build for Android" Write-Host " -h, --help Show this help" Write-Host "" Write-Host "Examples:" Write-Host " build.ps1 --preset default-windows" - Write-Host " build.ps1 --preset full-windows -- -DGK_ENABLE_AVIF=ON" + Write-Host " build.ps1 --preset full-windows -- -DENABLE_AVIF=ON" + Write-Host " build.ps1 --preset default-windows --reconfigure" exit 0 } "^--$" { @@ -126,22 +137,38 @@ function Ensure-Vcpkg { function Build-Native { param ( [string]$Preset, - [string[]]$ExtraArgs + [string[]]$ExtraArgs, + [bool]$ForceReconfigure = $false ) Ensure-Vcpkg + $BuildDir = Join-Path $ProjectRoot "out/build/$Preset" + $CacheFile = Join-Path $BuildDir "CMakeCache.txt" + if ($Clean) { - $BuildDir = Join-Path $ProjectRoot "out/build/$Preset" if (Test-Path $BuildDir) { Write-Log "Cleaning build for preset: $Preset..." Remove-Item -Path $BuildDir -Recurse -Force } } - Write-Log "Configuring preset: $Preset with extra args: $($ExtraArgs -join ' ')" - cmake --preset $Preset $ExtraArgs - if ($LASTEXITCODE -ne 0) { throw "CMake configuration failed." } + # Check if reconfiguration is needed + $NeedsConfigure = $ForceReconfigure -or $Clean -or (-not (Test-Path $CacheFile)) + + # Check if any -D arguments are passed (requires reconfigure) + $HasDefineArgs = $ExtraArgs | Where-Object { $_ -match "^-D" } + if ($HasDefineArgs) { + $NeedsConfigure = $true + } + + if ($NeedsConfigure) { + Write-Log "Configuring preset: $Preset with extra args: $($ExtraArgs -join ' ')" + cmake --preset $Preset $ExtraArgs + if ($LASTEXITCODE -ne 0) { throw "CMake configuration failed." } + } else { + Write-Log "Skipping configure (already configured). Use --reconfigure to force." + } Write-Log "Building preset: $Preset" # Filter for args that are relevant to the build command @@ -181,7 +208,7 @@ try { if ($Android) { Build-Android } else { - Build-Native -Preset $Preset -ExtraArgs $CMakeArgs + Build-Native -Preset $Preset -ExtraArgs $CMakeArgs -ForceReconfigure $Reconfigure } $Global:StopWatch.Stop() diff --git a/build.sh b/build.sh index 957ca51b..c0c23bb2 100755 --- a/build.sh +++ b/build.sh @@ -2,8 +2,9 @@ set -euo pipefail # ### HELP_START ### # ============================================================================== -# gkNextRenderer Build Script v2.1 (Linux/macOS) +# gkNextRenderer Build Script v2.2 (Linux/macOS) # Wraps CMake Presets for a streamlined build workflow. +# Supports incremental configuration (skips configure if already configured). # # Usage: # ./build.sh [options] [-- ...] @@ -11,13 +12,15 @@ set -euo pipefail # Options: # --preset CMake configure preset (e.g., default-linux) [REQUIRED] # --clean Clean build directory before building. +# --reconfigure Force CMake reconfiguration even if already configured. # --android Switch to Android Gradle build. # --help, -h Show this help message. # # Examples: # ./build.sh --preset full-linux -# ./build.sh --preset default-linux -- -DGK_ENABLE_AVIF=ON +# ./build.sh --preset default-linux -- -DENABLE_AVIF=ON # ./build.sh --preset default-linux --clean +# ./build.sh --preset default-linux --reconfigure # ============================================================================== # ### HELP_END ### @@ -85,6 +88,7 @@ list_presets_and_exit() { CONFIGURE_PRESET="" CLEAN=0 +RECONFIGURE=0 TARGET_ANDROID=0 declare -a CMAKE_ARGS=() @@ -92,6 +96,7 @@ declare -a CMAKE_ARGS=() while [[ $# -gt 0 ]]; do case $1 in --clean) CLEAN=1; shift ;; + --reconfigure) RECONFIGURE=1; shift ;; --android) TARGET_ANDROID=1; shift ;; --preset) if [[ -z "$2" || "$2" == --* ]]; then @@ -122,23 +127,45 @@ fi ensure_vcpkg +BUILD_DIR="$PROJECT_ROOT/out/build/$CONFIGURE_PRESET" +CACHE_FILE="$BUILD_DIR/CMakeCache.txt" + if [ "$CLEAN" -eq 1 ]; then log "Cleaning build for preset: $CONFIGURE_PRESET..." - rm -rf "$PROJECT_ROOT/out/build/$CONFIGURE_PRESET" + rm -rf "$BUILD_DIR" fi -if [ ${#CMAKE_ARGS[@]} -eq 0 ]; then - log "Configuring preset: $CONFIGURE_PRESET" -else - log "Configuring preset: $CONFIGURE_PRESET with extra args: ${CMAKE_ARGS[*]}" +# Check if reconfiguration is needed +NEEDS_CONFIGURE=0 +if [ "$RECONFIGURE" -eq 1 ] || [ "$CLEAN" -eq 1 ] || [ ! -f "$CACHE_FILE" ]; then + NEEDS_CONFIGURE=1 fi -config_start=$(date +%s) -if [ ${#CMAKE_ARGS[@]} -eq 0 ]; then - cmake --preset "$CONFIGURE_PRESET" + +# Check if any -D arguments are passed (requires reconfigure) +for arg in "${CMAKE_ARGS[@]}"; do + if [[ "$arg" == -D* ]]; then + NEEDS_CONFIGURE=1 + break + fi +done + +config_time=0 +if [ "$NEEDS_CONFIGURE" -eq 1 ]; then + if [ ${#CMAKE_ARGS[@]} -eq 0 ]; then + log "Configuring preset: $CONFIGURE_PRESET" + else + log "Configuring preset: $CONFIGURE_PRESET with extra args: ${CMAKE_ARGS[*]}" + fi + config_start=$(date +%s) + if [ ${#CMAKE_ARGS[@]} -eq 0 ]; then + cmake --preset "$CONFIGURE_PRESET" + else + cmake --preset "$CONFIGURE_PRESET" "${CMAKE_ARGS[@]}" + fi + config_time=$(( $(date +%s) - config_start )) else - cmake --preset "$CONFIGURE_PRESET" "${CMAKE_ARGS[@]}" + log "Skipping configure (already configured). Use --reconfigure to force." fi -config_time=$(( $(date +%s) - config_start )) # Derive build preset name from configure preset name # In current CMakePresets.json, build presets map 1:1 to configure presets diff --git a/cmake/ProjectOptions.cmake b/cmake/ProjectOptions.cmake index 5d6c20dd..032c3f63 100644 --- a/cmake/ProjectOptions.cmake +++ b/cmake/ProjectOptions.cmake @@ -32,5 +32,17 @@ if (APPLE) target_link_options(gk_project_options INTERFACE "-Wl,-no_warn_duplicate_libraries") endif() -# LTCG -# set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) +# LTO/LTCG - Disabled by default (slows down build), enable with -DENABLE_LTO=ON +option(ENABLE_LTO "Enable Link Time Optimization (LTO/LTCG)" OFF) +if (ENABLE_LTO) + if (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + include(CheckIPOSupported) + check_ipo_supported(RESULT ipo_supported OUTPUT ipo_error) + if (ipo_supported) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) + message(STATUS "LTO/LTCG enabled for ${CMAKE_BUILD_TYPE} build") + else() + message(WARNING "LTO/LTCG not supported: ${ipo_error}") + endif() + endif() +endif() diff --git a/cmake/SetupPlatform.cmake b/cmake/SetupPlatform.cmake index d9a5c653..257c70eb 100644 --- a/cmake/SetupPlatform.cmake +++ b/cmake/SetupPlatform.cmake @@ -13,13 +13,13 @@ else() set(CMAKE_OSX_ARCHITECTURES "arm64") endif() -option(GK_IOS_SKIP_CODE_SIGN "Disable iOS code signing (useful for CI xcodebuild)" OFF) +option(IOS_SKIP_CODE_SIGN "Disable iOS code signing (useful for CI xcodebuild)" OFF) # Set iOS bundle identifier if building for iOS if (IOS) set(MACOSX_BUNDLE_DISPLAY_NAME ${PROJECT_NAME}) set(CMAKE_XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.tzy.gknext") - if (GK_IOS_SKIP_CODE_SIGN) + if (IOS_SKIP_CODE_SIGN) set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_STYLE "Manual") set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO") set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index eeecd1c4..5daf5f81 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,58 +1,26 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON) +# Include explicit source file lists (replaces GLOB_RECURSE for better incremental builds) +include(cmake/SourceFiles.cmake) - -# directory naming -file(GLOB_RECURSE src_files_assets "Assets/*.cpp" "Assets/*.hpp" "Assets/*.h" "../assets/shaders/common/*.h") -file(GLOB_RECURSE src_files_utilities "Utilities/*.cpp" "Utilities/*.hpp" "Utilities/*.h") -file(GLOB_RECURSE src_files_vulkan "Vulkan/*.cpp" "Vulkan/*.hpp") -file(GLOB_RECURSE src_files_rendering "Rendering/*.cpp" "Rendering/*.hpp") -file(GLOB_RECURSE src_files_thirdparty - "ThirdParty/json11/*.cpp" - "ThirdParty/json11/*.hpp" - "ThirdParty/mikktspace/*.c" - "ThirdParty/mikktspace/*.h" - "ThirdParty/miniaudio/*.h" - "ThirdParty/lzav/*.h" - "ThirdParty/tinybvh/*.h" - "ThirdParty/ozz/*.h" - ) -file(GLOB_RECURSE src_files_customimgui - "ThirdParty/imgui-custom/*.cpp" - "ThirdParty/imgui-custom/*.h" - ) +# Disable warnings for third-party code if (MSVC) -set_source_files_properties( - ${src_files_thirdparty} - PROPERTIES - COMPILE_FLAGS "/W0" -) + set_source_files_properties( + ${src_files_thirdparty} + PROPERTIES + COMPILE_FLAGS "/W0" + ) else() -set_source_files_properties( - ${src_files_thirdparty} - PROPERTIES - COMPILE_FLAGS "-w" -) + set_source_files_properties( + ${src_files_thirdparty} + PROPERTIES + COMPILE_FLAGS "-w" + ) endif() -file(GLOB_RECURSE src_files_engine - "Common/*.hpp" - "Runtime/*.h" - "Runtime/*.hpp" - "Runtime/*.cpp" - "Options.cpp" - "Options.hpp" -) if (IOS) set_source_files_properties(Runtime/NextAudio.cpp PROPERTIES LANGUAGE "OBJCXX") endif() -file(GLOB_RECURSE src_files_editor "Editor/*") -file(GLOB_RECURSE src_files_magicalego "Application/MagicaLego/*.cpp" "MagicaLego/*.hpp") -file(GLOB_RECURSE src_files_gkrenderer "Application/gkNextRenderer/*") -file(GLOB_RECURSE src_files_benchmarkcommon "Application/gkNextBenchmark/Common/*") -file(GLOB_RECURSE src_files_gkstillbenchmark "Application/gkNextBenchmark/gkNextStillBenchmark/*") -file(GLOB_RECURSE src_files_gkmotionbenchmark "Application/gkNextBenchmark/gkNextMotionBenchmark/*") -file(GLOB_RECURSE src_files_gkvisualtest "Application/gkNextVisualTest/*") # unit test files set(UNIT_TEST_SOURCES @@ -64,7 +32,7 @@ set(UNIT_TEST_SOURCES Tests/Test_RenderComponent.cpp ) -if (GK_ENABLE_PHYSIC) +if (ENABLE_PHYSIC) list(APPEND UNIT_TEST_SOURCES Tests/Test_PhysicsComponent.cpp) endif() @@ -180,8 +148,8 @@ gkNextUnitTests ) endif() -# 是否启用 Unity Build(默认开启以维持原有行为,可在配置时通过 -DGK_ENABLE_UNITY_BUILD=OFF 关闭) -option(GK_ENABLE_UNITY_BUILD "Enable unity builds for engine target" ON) +# Unity Build option (default ON, can be disabled with -DENABLE_UNITY_BUILD=OFF) +option(ENABLE_UNITY_BUILD "Enable unity builds for engine target" ON) # common setup foreach(target IN LISTS AllTargets) @@ -211,11 +179,11 @@ foreach(target IN LISTS AllTargets) if ( ${target} STREQUAL gkNextEngine ) target_compile_definitions(${target} PRIVATE ENGINE_EXPORTS) - # 若未显式关闭则为 gkNextEngine 启用 Unity Build(Android 默认仍禁用以保持行为) + # Enable Unity Build for gkNextEngine unless explicitly disabled (Android disabled by default) # Group files for Unity Build optimization - if ( GK_ENABLE_UNITY_BUILD AND NOT ANDROID ) + if ( ENABLE_UNITY_BUILD AND NOT ANDROID ) set_target_properties(${target} PROPERTIES UNITY_BUILD ON) - set_target_properties(${target} PROPERTIES UNITY_BUILD_BATCH_SIZE 6) + set_target_properties(${target} PROPERTIES UNITY_BUILD_BATCH_SIZE 12) set_source_files_properties(${src_files_assets} PROPERTIES UNITY_GROUP "assets") set_source_files_properties(${src_files_utilities} PROPERTIES UNITY_GROUP "utilities") diff --git a/src/cmake/SourceFiles.cmake b/src/cmake/SourceFiles.cmake new file mode 100644 index 00000000..daa46ec8 --- /dev/null +++ b/src/cmake/SourceFiles.cmake @@ -0,0 +1,79 @@ +# ============================================================================ +# SourceFiles.cmake - Source file definitions for gkNextRenderer +# ============================================================================ +# Centralized source file configuration using GLOB_RECURSE. +# ============================================================================ + +# --- Assets --- +file(GLOB_RECURSE src_files_assets + "Assets/*.cpp" + "Assets/*.hpp" + "Assets/*.h" +) + +# --- Utilities --- +file(GLOB_RECURSE src_files_utilities + "Utilities/*.cpp" + "Utilities/*.hpp" + "Utilities/*.h" +) + +# --- Vulkan Backend --- +file(GLOB_RECURSE src_files_vulkan + "Vulkan/*.cpp" + "Vulkan/*.hpp" +) + +# --- Rendering --- +file(GLOB_RECURSE src_files_rendering + "Rendering/*.cpp" + "Rendering/*.hpp" +) + +# --- ThirdParty Libraries --- +file(GLOB_RECURSE src_files_thirdparty + "ThirdParty/json11/*.cpp" + "ThirdParty/json11/*.hpp" + "ThirdParty/mikktspace/*.c" + "ThirdParty/mikktspace/*.h" + "ThirdParty/miniaudio/*.h" + "ThirdParty/lzav/*.h" + "ThirdParty/tinybvh/*.h" + "ThirdParty/ozz/*.h" +) + +# --- Custom ImGui Backend --- +file(GLOB_RECURSE src_files_customimgui + "ThirdParty/imgui-custom/*.cpp" + "ThirdParty/imgui-custom/*.h" +) + +# --- Engine Core --- +file(GLOB_RECURSE src_files_engine + "Common/*.hpp" + "Runtime/*.h" + "Runtime/*.hpp" + "Runtime/*.cpp" + "Options.cpp" + "Options.hpp" +) + +# --- Editor --- +file(GLOB_RECURSE src_files_editor "Editor/*") + +# --- Applications --- +file(GLOB_RECURSE src_files_magicalego + "Application/MagicaLego/*.cpp" + "Application/MagicaLego/*.hpp" +) + +file(GLOB_RECURSE src_files_gkrenderer "Application/gkNextRenderer/*") + +file(GLOB_RECURSE src_files_benchmarkcommon "Application/gkNextBenchmark/Common/*") + +file(GLOB_RECURSE src_files_gkstillbenchmark "Application/gkNextBenchmark/gkNextStillBenchmark/*") + +file(GLOB_RECURSE src_files_gkmotionbenchmark "Application/gkNextBenchmark/gkNextMotionBenchmark/*") + +file(GLOB_RECURSE src_files_gkvisualtest "Application/gkNextVisualTest/*" +) From a0bb186661c632d4f8ccff15d6546198d3ba001e Mon Sep 17 00:00:00 2001 From: gameKnife Date: Tue, 27 Jan 2026 00:43:15 +0800 Subject: [PATCH 03/53] fix light power --- src/Assets/FSceneLoader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Assets/FSceneLoader.cpp b/src/Assets/FSceneLoader.cpp index d9ea8c56..a5270198 100644 --- a/src/Assets/FSceneLoader.cpp +++ b/src/Assets/FSceneLoader.cpp @@ -564,7 +564,7 @@ namespace Assets auto emissiveExtension = mat.extensions.find("KHR_materials_emissive_strength"); if (emissiveExtension != mat.extensions.end()) { - emissiveStrength = static_cast(emissiveExtension->second.Get("emissiveStrength").GetNumberAsDouble()); + emissiveStrength = static_cast(emissiveExtension->second.Get("emissiveStrength").GetNumberAsDouble()) * 100.f; } bool isPureEmissiveMaterial = (glm::length(emissiveColor) > 0.001f) && (mat.emissiveTexture.index == -1); From 2c271c32fdd3e7cb1f05f14c8b9b24c78df51a15 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Wed, 28 Jan 2026 00:01:30 +0800 Subject: [PATCH 04/53] feat: add gizmo selection editing Enable ImGuizmo-based transforms and selection edge highlighting in editor and renderer, with input gating and CPU BVH refresh after edits. Co-authored-by: codex --- .../gkNextRenderer/gkNextRenderer.cpp | 51 +- .../gkNextRenderer/gkNextRenderer.hpp | 2 + src/Editor/EditorInterface.cpp | 3 + src/Editor/EditorMain.cpp | 29 +- src/Editor/EditorMain.h | 3 + src/Runtime/GizmoController.cpp | 177 + src/Runtime/GizmoController.hpp | 30 + src/ThirdParty/ImGuizmo/ImGuizmo.cpp | 3164 +++++++++++++++++ src/ThirdParty/ImGuizmo/ImGuizmo.h | 239 ++ src/cmake/SourceFiles.cmake | 2 + 10 files changed, 3689 insertions(+), 11 deletions(-) create mode 100644 src/Runtime/GizmoController.cpp create mode 100644 src/Runtime/GizmoController.hpp create mode 100644 src/ThirdParty/ImGuizmo/ImGuizmo.cpp create mode 100644 src/ThirdParty/ImGuizmo/ImGuizmo.h diff --git a/src/Application/gkNextRenderer/gkNextRenderer.cpp b/src/Application/gkNextRenderer/gkNextRenderer.cpp index 3f9b4b8f..5ef3dcac 100644 --- a/src/Application/gkNextRenderer/gkNextRenderer.cpp +++ b/src/Application/gkNextRenderer/gkNextRenderer.cpp @@ -17,6 +17,7 @@ #include "Runtime/ScreenShot.hpp" #include "Utilities/FileHelper.hpp" #include "Runtime/Components/SkinnedMeshComponent.h" +#include "Vulkan/SwapChain.hpp" extern float GAndroidMagicScale; @@ -147,6 +148,17 @@ bool NextRendererGameInstance::OnRenderUI() DrawTitleBar(); DrawSettings(); + + if (ImGui::GetCurrentContext() != nullptr) + { + auto& swapChain = GetEngine().GetRenderer().SwapChain(); + const auto offset = swapChain.OutputOffset(); + const auto extent = swapChain.OutputExtent(); + const ImVec2 viewportOrigin = ImGui::GetMainViewport()->Pos; + gizmoController_.Draw(*engine_, + glm::vec2(viewportOrigin.x + static_cast(offset.x), viewportOrigin.y + static_cast(offset.y)), + glm::vec2(static_cast(extent.width), static_cast(extent.height))); + } if (GOption->ReferenceMode) { ImGuiIO& io = ImGui::GetIO(); @@ -233,14 +245,19 @@ bool NextRendererGameInstance::OverrideRenderCamera(Assets::Camera& outRenderCam bool NextRendererGameInstance::OnKey(SDL_Event& event) { - modelViewController_.OnKey(event); + if (!gizmoController_.IsShowing()) + { + modelViewController_.OnKey(event); + } if (event.key.type == SDL_EVENT_KEY_DOWN) { switch (event.key.key) { - case SDLK_ESCAPE: GetEngine().GetScene().SetSelectedId(-1); return true; - break; + case SDLK_ESCAPE: + GetEngine().GetScene().SetSelectedId(-1); + GetEngine().GetShowFlags().ShowEdge = false; + return true; case SDLK_F1: GetEngine().GetUserSettings().ShowSettings = !GetEngine().GetUserSettings().ShowSettings; return true; break; case SDLK_F2: GetEngine().GetUserSettings().ShowOverlay = !GetEngine().GetUserSettings().ShowOverlay; return true; @@ -265,13 +282,23 @@ bool NextRendererGameInstance::OnKey(SDL_Event& event) bool NextRendererGameInstance::OnCursorPosition(double xpos, double ypos) { - modelViewController_.OnCursorPosition( xpos, ypos); + if (!gizmoController_.IsInteracting()) + { + modelViewController_.OnCursorPosition(xpos, ypos); + } return true; } bool NextRendererGameInstance::OnMouseButton(SDL_Event& event) { - modelViewController_.OnMouseButton(event); + if (!gizmoController_.IsInteracting()) + { + modelViewController_.OnMouseButton(event); + } + else + { + return true; + } if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN && event.button.button == SDL_BUTTON_LEFT) { @@ -284,7 +311,14 @@ bool NextRendererGameInstance::OnMouseButton(SDL_Event& event) if (result.Hitted) { GetEngine().GetScene().GetRenderCamera().FocalDistance = result.T; - NextEngineHelper::DrawAuxPoint( result.HitPoint, glm::vec4(0.2, 1, 0.2, 1), 2, 60 ); + NextEngineHelper::DrawAuxPoint( result.HitPoint, glm::vec4(0.2, 1, 0.2, 1), 2, 60 ); + GetEngine().GetScene().SetSelectedId(result.InstanceId); + GetEngine().GetShowFlags().ShowEdge = true; + } + else + { + GetEngine().GetScene().SetSelectedId(-1); + GetEngine().GetShowFlags().ShowEdge = false; } return true; }); @@ -296,7 +330,10 @@ bool NextRendererGameInstance::OnMouseButton(SDL_Event& event) bool NextRendererGameInstance::OnScroll(double xoffset, double yoffset) { - modelViewController_.OnScroll( xoffset, yoffset); + if (!gizmoController_.IsInteracting()) + { + modelViewController_.OnScroll(xoffset, yoffset); + } return true; } diff --git a/src/Application/gkNextRenderer/gkNextRenderer.hpp b/src/Application/gkNextRenderer/gkNextRenderer.hpp index 4d72924c..c065a652 100644 --- a/src/Application/gkNextRenderer/gkNextRenderer.hpp +++ b/src/Application/gkNextRenderer/gkNextRenderer.hpp @@ -1,6 +1,7 @@ #pragma once #include "Common/CoreMinimal.hpp" #include "Runtime/Engine.hpp" +#include "Runtime/GizmoController.hpp" #include "Runtime/ModelViewController.hpp" class NextRendererGameInstance : public NextGameInstanceBase @@ -45,6 +46,7 @@ class NextRendererGameInstance : public NextGameInstanceBase NextEngine* engine_; ModelViewController modelViewController_; + GizmoController gizmoController_; uint32_t modelId_; uint32_t boxModelId_; diff --git a/src/Editor/EditorInterface.cpp b/src/Editor/EditorInterface.cpp index d8c9377f..17c577ba 100644 --- a/src/Editor/EditorInterface.cpp +++ b/src/Editor/EditorInterface.cpp @@ -34,6 +34,7 @@ #include "Vulkan/RenderImage.hpp" #include "Rendering/VulkanBaseRenderer.hpp" #include "ThirdParty/fontawesome/IconsFontAwesome6.h" +#include "Common/CoreMinimal.hpp" extern std::unique_ptr GApplication; @@ -229,6 +230,8 @@ void EditorInterface::Render() editor_->GetEngine().GetRenderer().SwapChain().UpdateOutputViewport(Utilities::Math::floorToInt(node->Pos.x - viewport->Pos.x), Utilities::Math::floorToInt(node->Pos.y - viewport->Pos.y), Utilities::Math::ceilToInt(node->Size.x), Utilities::Math::ceilToInt(node->Size.y)); //editor_->GetEngine().GetRenderer().SwapChain().UpdateRenderViewport(0, 0, Utilities::Math::ceilToInt(node->Size.x), Utilities::Math::ceilToInt(node->Size.y)); + editor_->DrawGizmo(glm::vec2(node->Pos.x, node->Pos.y), glm::vec2(node->Size.x, node->Size.y)); + firstRun = false; GUserInterface = nullptr; } diff --git a/src/Editor/EditorMain.cpp b/src/Editor/EditorMain.cpp index 9f4075d9..104874ca 100644 --- a/src/Editor/EditorMain.cpp +++ b/src/Editor/EditorMain.cpp @@ -98,7 +98,10 @@ void EditorGameInstance::OnInitUI() bool EditorGameInstance::OnKey(SDL_Event& event) { - modelViewController_.OnKey(event); + if (!gizmoController_.IsShowing()) + { + modelViewController_.OnKey(event); + } if (event.key.type == SDL_EVENT_KEY_DOWN) { switch (event.key.key) @@ -113,13 +116,23 @@ bool EditorGameInstance::OnKey(SDL_Event& event) bool EditorGameInstance::OnCursorPosition(double xpos, double ypos) { - modelViewController_.OnCursorPosition(xpos, ypos); + if (!gizmoController_.IsInteracting()) + { + modelViewController_.OnCursorPosition(xpos, ypos); + } return true; } bool EditorGameInstance::OnMouseButton(SDL_Event& event) { - modelViewController_.OnMouseButton(event); + if (!gizmoController_.IsInteracting()) + { + modelViewController_.OnMouseButton(event); + } + else + { + return true; + } if (event.button.button == SDL_BUTTON_LEFT && event.button.type == SDL_EVENT_MOUSE_BUTTON_DOWN) { auto mousePos = GetEngine().GetMousePos(); @@ -149,6 +162,14 @@ bool EditorGameInstance::OnMouseButton(SDL_Event& event) bool EditorGameInstance::OnScroll(double xoffset, double yoffset) { - modelViewController_.OnScroll(xoffset, yoffset); + if (!gizmoController_.IsInteracting()) + { + modelViewController_.OnScroll(xoffset, yoffset); + } return true; } + +void EditorGameInstance::DrawGizmo(const glm::vec2& viewportPos, const glm::vec2& viewportSize) +{ + gizmoController_.Draw(*engine_, viewportPos, viewportSize); +} diff --git a/src/Editor/EditorMain.h b/src/Editor/EditorMain.h index 3b31c02c..445a704c 100644 --- a/src/Editor/EditorMain.h +++ b/src/Editor/EditorMain.h @@ -2,6 +2,7 @@ #include "EditorGUI.h" #include "Runtime/Engine.hpp" +#include "Runtime/GizmoController.hpp" #include "Runtime/ModelViewController.hpp" class EditorInterface; @@ -37,12 +38,14 @@ class EditorGameInstance : public NextGameInstanceBase // quick access engine NextEngine& GetEngine() { return *engine_; } + void DrawGizmo(const glm::vec2& viewportPos, const glm::vec2& viewportSize); private: NextEngine* engine_; std::unique_ptr editorUserInterface_; ModelViewController modelViewController_; + GizmoController gizmoController_; }; inline bool EditorGameInstance::OverrideRenderCamera(Assets::Camera& OutRenderCamera) const diff --git a/src/Runtime/GizmoController.cpp b/src/Runtime/GizmoController.cpp new file mode 100644 index 00000000..5d8c35fe --- /dev/null +++ b/src/Runtime/GizmoController.cpp @@ -0,0 +1,177 @@ +#include "Runtime/GizmoController.hpp" + +#include "Assets/Node.h" +#include "Assets/Scene.hpp" +#include "Runtime/Engine.hpp" +#include "ThirdParty/ImGuizmo/ImGuizmo.h" + +#include +#include +#include + +namespace +{ + constexpr float kToolbarOffsetY = 48.0f; +} + +void GizmoController::EnsureDefaults() +{ + if (operation_ == 0) + { + operation_ = static_cast(ImGuizmo::TRANSLATE); + } + if (mode_ == 0) + { + mode_ = static_cast(ImGuizmo::LOCAL); + } +} + +void GizmoController::HandleShortcuts(const ImGuiIO& io) +{ + if (io.WantTextInput) + { + return; + } + + if (ImGui::IsKeyPressed(ImGuiKey_W)) + { + operation_ = static_cast(ImGuizmo::TRANSLATE); + } + if (ImGui::IsKeyPressed(ImGuiKey_E)) + { + operation_ = static_cast(ImGuizmo::ROTATE); + } + if (ImGui::IsKeyPressed(ImGuiKey_R)) + { + operation_ = static_cast(ImGuizmo::SCALE); + } +} + +void GizmoController::ResetState() +{ + isUsing_ = false; + isOver_ = false; + isShowing_ = false; + wasUsing_ = false; +} + +void GizmoController::DrawToolbar() +{ + EnsureDefaults(); + + ImGui::SetNextWindowBgAlpha(0.65f); + ImGui::Begin("GizmoToolbar", nullptr, + ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoNav | + ImGuiWindowFlags_NoDocking); + + if (ImGui::RadioButton("Move", operation_ == static_cast(ImGuizmo::TRANSLATE))) + { + operation_ = static_cast(ImGuizmo::TRANSLATE); + } + ImGui::SameLine(); + if (ImGui::RadioButton("Rotate", operation_ == static_cast(ImGuizmo::ROTATE))) + { + operation_ = static_cast(ImGuizmo::ROTATE); + } + ImGui::SameLine(); + if (ImGui::RadioButton("Scale", operation_ == static_cast(ImGuizmo::SCALE))) + { + operation_ = static_cast(ImGuizmo::SCALE); + } + + ImGui::End(); +} + +void GizmoController::Draw(NextEngine& engine, const glm::vec2& viewportPos, const glm::vec2& viewportSize) +{ + Assets::Scene& scene = engine.GetScene(); + const uint32_t selectedId = scene.GetSelectedId(); + if (selectedId == static_cast(-1)) + { + ResetState(); + return; + } + + Assets::Node* node = scene.GetNodeByInstanceId(selectedId); + if (node == nullptr) + { + ResetState(); + return; + } + + if (viewportSize.x <= 0.0f || viewportSize.y <= 0.0f) + { + ResetState(); + return; + } + + isShowing_ = true; + + ImGuiIO& io = ImGui::GetIO(); + HandleShortcuts(io); + + ImGuiViewport* viewport = ImGui::GetMainViewport(); + const ImVec2 toolbarPos(viewportPos.x + viewportSize.x * 0.5f, viewportPos.y + kToolbarOffsetY); + ImGui::SetNextWindowViewport(viewport->ID); + ImGui::SetNextWindowPos(toolbarPos, ImGuiCond_Always, ImVec2(0.5f, 0.0f)); + DrawToolbar(); + + const auto& ubo = engine.GetUniformBufferObject(); + const glm::mat4& view = ubo.ModelView; + glm::mat4 projection = ubo.Projection; + projection[1][1] *= -1.0f; + + glm::mat4 worldMatrix = node->WorldTransform(); + + ImGuizmo::SetOrthographic(false); + ImGuizmo::BeginFrame(); + ImGuizmo::SetDrawlist(ImGui::GetForegroundDrawList()); + ImGuizmo::SetRect(viewportPos.x, viewportPos.y, viewportSize.x, viewportSize.y); + ImGuizmo::GetStyle().Colors[ImGuizmo::COLOR::SELECTION] = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + + ImGuizmo::Manipulate( + glm::value_ptr(view), + glm::value_ptr(projection), + static_cast(operation_), + static_cast(mode_), + glm::value_ptr(worldMatrix)); + + isUsing_ = ImGuizmo::IsUsing(); + isOver_ = ImGuizmo::IsOver(); + if (isUsing_) + { + glm::mat4 parentWorld(1.0f); + if (node->GetParent() != nullptr) + { + parentWorld = node->GetParent()->WorldTransform(); + } + glm::mat4 localMatrix = glm::inverse(parentWorld) * worldMatrix; + + glm::vec3 scale{}; + glm::quat rotation{}; + glm::vec3 translation{}; + glm::vec3 skew{}; + glm::vec4 perspective{}; + + if (glm::decompose(localMatrix, scale, rotation, translation, skew, perspective)) + { + node->SetTranslation(translation); + node->SetRotation(rotation); + node->SetScale(scale); + node->RecalcTransform(true); + scene.MarkDirty(); + } + } + else if (wasUsing_) + { + scene.GetCPUAccelerationStructure().UpdateBVH(scene); + } + + wasUsing_ = isUsing_; + + io.WantCaptureMouse = io.WantCaptureMouse || isOver_ || isUsing_; +} diff --git a/src/Runtime/GizmoController.hpp b/src/Runtime/GizmoController.hpp new file mode 100644 index 00000000..2f567237 --- /dev/null +++ b/src/Runtime/GizmoController.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "Common/CoreMinimal.hpp" + +#include + +class NextEngine; +struct ImGuiIO; + +class GizmoController +{ +public: + void Draw(NextEngine& engine, const glm::vec2& viewportPos, const glm::vec2& viewportSize); + bool IsUsing() const { return isUsing_; } + bool IsInteracting() const { return isUsing_ || isOver_; } + bool IsShowing() const { return isShowing_; } + +private: + void DrawToolbar(); + void EnsureDefaults(); + void HandleShortcuts(const ImGuiIO& io); + void ResetState(); + + int operation_ = 0; + int mode_ = 0; + bool isUsing_ = false; + bool isOver_ = false; + bool isShowing_ = false; + bool wasUsing_ = false; +}; diff --git a/src/ThirdParty/ImGuizmo/ImGuizmo.cpp b/src/ThirdParty/ImGuizmo/ImGuizmo.cpp new file mode 100644 index 00000000..b922e5ad --- /dev/null +++ b/src/ThirdParty/ImGuizmo/ImGuizmo.cpp @@ -0,0 +1,3164 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v1.92.5 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2016-2021 Cedric Guillemet +// +// 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. +// + +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include "imgui.h" +#include "imgui_internal.h" +#include "ImGuizmo.h" + +#if defined(_MSC_VER) || defined(__MINGW32__) +#include +#endif +#if !defined(_MSC_VER) && !defined(__MINGW64_VERSION_MAJOR) +#define _malloca(x) alloca(x) +#define _freea(x) +#endif + +// includes patches for multiview from +// https://github.com/CedricGuillemet/ImGuizmo/issues/15 + +namespace IMGUIZMO_NAMESPACE +{ + static const float ZPI = 3.14159265358979323846f; + static const float RAD2DEG = (180.f / ZPI); + static const float DEG2RAD = (ZPI / 180.f); + const float screenRotateSize = 0.06f; + // scale a bit so translate axis do not touch when in universal + const float rotationDisplayFactor = 1.2f; + + static OPERATION operator&(OPERATION lhs, OPERATION rhs) + { + return static_cast(static_cast(lhs) & static_cast(rhs)); + } + + static bool operator!=(OPERATION lhs, int rhs) + { + return static_cast(lhs) != rhs; + } + + static bool Intersects(OPERATION lhs, OPERATION rhs) + { + return (lhs & rhs) != 0; + } + + // True if lhs contains rhs + static bool Contains(OPERATION lhs, OPERATION rhs) + { + return (lhs & rhs) == rhs; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // utility and math + + void FPU_MatrixF_x_MatrixF(const float* a, const float* b, float* r) + { + r[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12]; + r[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13]; + r[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14]; + r[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15]; + + r[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12]; + r[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13]; + r[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14]; + r[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15]; + + r[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12]; + r[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13]; + r[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14]; + r[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15]; + + r[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12]; + r[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13]; + r[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14]; + r[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15]; + } + + void Frustum(float left, float right, float bottom, float top, float znear, float zfar, float* m16) + { + float temp, temp2, temp3, temp4; + temp = 2.0f * znear; + temp2 = right - left; + temp3 = top - bottom; + temp4 = zfar - znear; + m16[0] = temp / temp2; + m16[1] = 0.0; + m16[2] = 0.0; + m16[3] = 0.0; + m16[4] = 0.0; + m16[5] = temp / temp3; + m16[6] = 0.0; + m16[7] = 0.0; + m16[8] = (right + left) / temp2; + m16[9] = (top + bottom) / temp3; + m16[10] = (-zfar - znear) / temp4; + m16[11] = -1.0f; + m16[12] = 0.0; + m16[13] = 0.0; + m16[14] = (-temp * zfar) / temp4; + m16[15] = 0.0; + } + + void Perspective(float fovyInDegrees, float aspectRatio, float znear, float zfar, float* m16) + { + float ymax, xmax; + ymax = znear * tanf(fovyInDegrees * DEG2RAD); + xmax = ymax * aspectRatio; + Frustum(-xmax, xmax, -ymax, ymax, znear, zfar, m16); + } + + void Cross(const float* a, const float* b, float* r) + { + r[0] = a[1] * b[2] - a[2] * b[1]; + r[1] = a[2] * b[0] - a[0] * b[2]; + r[2] = a[0] * b[1] - a[1] * b[0]; + } + + float Dot(const float* a, const float* b) + { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + } + + void Normalize(const float* a, float* r) + { + float il = 1.f / (sqrtf(Dot(a, a)) + FLT_EPSILON); + r[0] = a[0] * il; + r[1] = a[1] * il; + r[2] = a[2] * il; + } + + void LookAt(const float* eye, const float* at, const float* up, float* m16) + { + float X[3], Y[3], Z[3], tmp[3]; + + tmp[0] = eye[0] - at[0]; + tmp[1] = eye[1] - at[1]; + tmp[2] = eye[2] - at[2]; + Normalize(tmp, Z); + Normalize(up, Y); + Cross(Y, Z, tmp); + Normalize(tmp, X); + Cross(Z, X, tmp); + Normalize(tmp, Y); + + m16[0] = X[0]; + m16[1] = Y[0]; + m16[2] = Z[0]; + m16[3] = 0.0f; + m16[4] = X[1]; + m16[5] = Y[1]; + m16[6] = Z[1]; + m16[7] = 0.0f; + m16[8] = X[2]; + m16[9] = Y[2]; + m16[10] = Z[2]; + m16[11] = 0.0f; + m16[12] = -Dot(X, eye); + m16[13] = -Dot(Y, eye); + m16[14] = -Dot(Z, eye); + m16[15] = 1.0f; + } + + template T Clamp(T x, T y, T z) { return ((x < y) ? y : ((x > z) ? z : x)); } + template T max(T x, T y) { return (x > y) ? x : y; } + template T min(T x, T y) { return (x < y) ? x : y; } + template bool IsWithin(T x, T y, T z) { return (x >= y) && (x <= z); } + + struct matrix_t; + struct vec_t + { + public: + float x, y, z, w; + + void Lerp(const vec_t& v, float t) + { + x += (v.x - x) * t; + y += (v.y - y) * t; + z += (v.z - z) * t; + w += (v.w - w) * t; + } + + void Set(float v) { x = y = z = w = v; } + void Set(float _x, float _y, float _z = 0.f, float _w = 0.f) { x = _x; y = _y; z = _z; w = _w; } + + vec_t& operator -= (const vec_t& v) { x -= v.x; y -= v.y; z -= v.z; w -= v.w; return *this; } + vec_t& operator += (const vec_t& v) { x += v.x; y += v.y; z += v.z; w += v.w; return *this; } + vec_t& operator *= (const vec_t& v) { x *= v.x; y *= v.y; z *= v.z; w *= v.w; return *this; } + vec_t& operator *= (float v) { x *= v; y *= v; z *= v; w *= v; return *this; } + + vec_t operator * (float f) const; + vec_t operator - () const; + vec_t operator - (const vec_t& v) const; + vec_t operator + (const vec_t& v) const; + vec_t operator * (const vec_t& v) const; + + const vec_t& operator + () const { return (*this); } + float Length() const { return sqrtf(x * x + y * y + z * z); }; + float LengthSq() const { return (x * x + y * y + z * z); }; + vec_t Normalize() { (*this) *= (1.f / ( Length() > FLT_EPSILON ? Length() : FLT_EPSILON ) ); return (*this); } + vec_t Normalize(const vec_t& v) { this->Set(v.x, v.y, v.z, v.w); this->Normalize(); return (*this); } + vec_t Abs() const; + + void Cross(const vec_t& v) + { + vec_t res; + res.x = y * v.z - z * v.y; + res.y = z * v.x - x * v.z; + res.z = x * v.y - y * v.x; + + x = res.x; + y = res.y; + z = res.z; + w = 0.f; + } + + void Cross(const vec_t& v1, const vec_t& v2) + { + x = v1.y * v2.z - v1.z * v2.y; + y = v1.z * v2.x - v1.x * v2.z; + z = v1.x * v2.y - v1.y * v2.x; + w = 0.f; + } + + float Dot(const vec_t& v) const + { + return (x * v.x) + (y * v.y) + (z * v.z) + (w * v.w); + } + + float Dot3(const vec_t& v) const + { + return (x * v.x) + (y * v.y) + (z * v.z); + } + + void Transform(const matrix_t& matrix); + void Transform(const vec_t& s, const matrix_t& matrix); + + void TransformVector(const matrix_t& matrix); + void TransformPoint(const matrix_t& matrix); + void TransformVector(const vec_t& v, const matrix_t& matrix) { (*this) = v; this->TransformVector(matrix); } + void TransformPoint(const vec_t& v, const matrix_t& matrix) { (*this) = v; this->TransformPoint(matrix); } + + float& operator [] (size_t index) { return ((float*)&x)[index]; } + const float& operator [] (size_t index) const { return ((float*)&x)[index]; } + bool operator!=(const vec_t& other) const { return memcmp(this, &other, sizeof(vec_t)) != 0; } + }; + + vec_t makeVect(float _x, float _y, float _z = 0.f, float _w = 0.f) { vec_t res; res.x = _x; res.y = _y; res.z = _z; res.w = _w; return res; } + vec_t makeVect(ImVec2 v) { vec_t res; res.x = v.x; res.y = v.y; res.z = 0.f; res.w = 0.f; return res; } + vec_t vec_t::operator * (float f) const { return makeVect(x * f, y * f, z * f, w * f); } + vec_t vec_t::operator - () const { return makeVect(-x, -y, -z, -w); } + vec_t vec_t::operator - (const vec_t& v) const { return makeVect(x - v.x, y - v.y, z - v.z, w - v.w); } + vec_t vec_t::operator + (const vec_t& v) const { return makeVect(x + v.x, y + v.y, z + v.z, w + v.w); } + vec_t vec_t::operator * (const vec_t& v) const { return makeVect(x * v.x, y * v.y, z * v.z, w * v.w); } + vec_t vec_t::Abs() const { return makeVect(fabsf(x), fabsf(y), fabsf(z)); } + + vec_t Normalized(const vec_t& v) { vec_t res; res = v; res.Normalize(); return res; } + vec_t Cross(const vec_t& v1, const vec_t& v2) + { + vec_t res; + res.x = v1.y * v2.z - v1.z * v2.y; + res.y = v1.z * v2.x - v1.x * v2.z; + res.z = v1.x * v2.y - v1.y * v2.x; + res.w = 0.f; + return res; + } + + float Dot(const vec_t& v1, const vec_t& v2) + { + return (v1.x * v2.x) + (v1.y * v2.y) + (v1.z * v2.z); + } + + vec_t BuildPlan(const vec_t& p_point1, const vec_t& p_normal) + { + vec_t normal, res; + normal.Normalize(p_normal); + res.w = normal.Dot(p_point1); + res.x = normal.x; + res.y = normal.y; + res.z = normal.z; + return res; + } + + struct matrix_t + { + public: + + union + { + float m[4][4]; + float m16[16]; + struct + { + vec_t right, up, dir, position; + } v; + vec_t component[4]; + }; + + operator float* () { return m16; } + operator const float* () const { return m16; } + void Translation(float _x, float _y, float _z) { this->Translation(makeVect(_x, _y, _z)); } + + void Translation(const vec_t& vt) + { + v.right.Set(1.f, 0.f, 0.f, 0.f); + v.up.Set(0.f, 1.f, 0.f, 0.f); + v.dir.Set(0.f, 0.f, 1.f, 0.f); + v.position.Set(vt.x, vt.y, vt.z, 1.f); + } + + void Scale(float _x, float _y, float _z) + { + v.right.Set(_x, 0.f, 0.f, 0.f); + v.up.Set(0.f, _y, 0.f, 0.f); + v.dir.Set(0.f, 0.f, _z, 0.f); + v.position.Set(0.f, 0.f, 0.f, 1.f); + } + void Scale(const vec_t& s) { Scale(s.x, s.y, s.z); } + + matrix_t& operator *= (const matrix_t& mat) + { + matrix_t tmpMat; + tmpMat = *this; + tmpMat.Multiply(mat); + *this = tmpMat; + return *this; + } + matrix_t operator * (const matrix_t& mat) const + { + matrix_t matT; + matT.Multiply(*this, mat); + return matT; + } + + void Multiply(const matrix_t& matrix) + { + matrix_t tmp; + tmp = *this; + + FPU_MatrixF_x_MatrixF((float*)&tmp, (float*)&matrix, (float*)this); + } + + void Multiply(const matrix_t& m1, const matrix_t& m2) + { + FPU_MatrixF_x_MatrixF((float*)&m1, (float*)&m2, (float*)this); + } + + float GetDeterminant() const + { + return m[0][0] * m[1][1] * m[2][2] + m[0][1] * m[1][2] * m[2][0] + m[0][2] * m[1][0] * m[2][1] - + m[0][2] * m[1][1] * m[2][0] - m[0][1] * m[1][0] * m[2][2] - m[0][0] * m[1][2] * m[2][1]; + } + + float Inverse(const matrix_t& srcMatrix, bool affine = false); + void SetToIdentity() + { + v.right.Set(1.f, 0.f, 0.f, 0.f); + v.up.Set(0.f, 1.f, 0.f, 0.f); + v.dir.Set(0.f, 0.f, 1.f, 0.f); + v.position.Set(0.f, 0.f, 0.f, 1.f); + } + void Transpose() + { + matrix_t tmpm; + for (int l = 0; l < 4; l++) + { + for (int c = 0; c < 4; c++) + { + tmpm.m[l][c] = m[c][l]; + } + } + (*this) = tmpm; + } + + void RotationAxis(const vec_t& axis, float angle); + + void OrthoNormalize() + { + v.right.Normalize(); + v.up.Normalize(); + v.dir.Normalize(); + } + }; + + void vec_t::Transform(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + w * matrix.m[3][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + w * matrix.m[3][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + w * matrix.m[3][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + w * matrix.m[3][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + void vec_t::Transform(const vec_t& s, const matrix_t& matrix) + { + *this = s; + Transform(matrix); + } + + void vec_t::TransformPoint(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + matrix.m[3][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + matrix.m[3][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + matrix.m[3][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + matrix.m[3][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + void vec_t::TransformVector(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + float matrix_t::Inverse(const matrix_t& srcMatrix, bool affine) + { + float det = 0; + + if (affine) + { + det = GetDeterminant(); + float s = 1 / det; + m[0][0] = (srcMatrix.m[1][1] * srcMatrix.m[2][2] - srcMatrix.m[1][2] * srcMatrix.m[2][1]) * s; + m[0][1] = (srcMatrix.m[2][1] * srcMatrix.m[0][2] - srcMatrix.m[2][2] * srcMatrix.m[0][1]) * s; + m[0][2] = (srcMatrix.m[0][1] * srcMatrix.m[1][2] - srcMatrix.m[0][2] * srcMatrix.m[1][1]) * s; + m[1][0] = (srcMatrix.m[1][2] * srcMatrix.m[2][0] - srcMatrix.m[1][0] * srcMatrix.m[2][2]) * s; + m[1][1] = (srcMatrix.m[2][2] * srcMatrix.m[0][0] - srcMatrix.m[2][0] * srcMatrix.m[0][2]) * s; + m[1][2] = (srcMatrix.m[0][2] * srcMatrix.m[1][0] - srcMatrix.m[0][0] * srcMatrix.m[1][2]) * s; + m[2][0] = (srcMatrix.m[1][0] * srcMatrix.m[2][1] - srcMatrix.m[1][1] * srcMatrix.m[2][0]) * s; + m[2][1] = (srcMatrix.m[2][0] * srcMatrix.m[0][1] - srcMatrix.m[2][1] * srcMatrix.m[0][0]) * s; + m[2][2] = (srcMatrix.m[0][0] * srcMatrix.m[1][1] - srcMatrix.m[0][1] * srcMatrix.m[1][0]) * s; + m[3][0] = -(m[0][0] * srcMatrix.m[3][0] + m[1][0] * srcMatrix.m[3][1] + m[2][0] * srcMatrix.m[3][2]); + m[3][1] = -(m[0][1] * srcMatrix.m[3][0] + m[1][1] * srcMatrix.m[3][1] + m[2][1] * srcMatrix.m[3][2]); + m[3][2] = -(m[0][2] * srcMatrix.m[3][0] + m[1][2] * srcMatrix.m[3][1] + m[2][2] * srcMatrix.m[3][2]); + } + else + { + // transpose matrix + float src[16]; + for (int i = 0; i < 4; ++i) + { + src[i] = srcMatrix.m16[i * 4]; + src[i + 4] = srcMatrix.m16[i * 4 + 1]; + src[i + 8] = srcMatrix.m16[i * 4 + 2]; + src[i + 12] = srcMatrix.m16[i * 4 + 3]; + } + + // calculate pairs for first 8 elements (cofactors) + float tmp[12]; // temp array for pairs + tmp[0] = src[10] * src[15]; + tmp[1] = src[11] * src[14]; + tmp[2] = src[9] * src[15]; + tmp[3] = src[11] * src[13]; + tmp[4] = src[9] * src[14]; + tmp[5] = src[10] * src[13]; + tmp[6] = src[8] * src[15]; + tmp[7] = src[11] * src[12]; + tmp[8] = src[8] * src[14]; + tmp[9] = src[10] * src[12]; + tmp[10] = src[8] * src[13]; + tmp[11] = src[9] * src[12]; + + // calculate first 8 elements (cofactors) + m16[0] = (tmp[0] * src[5] + tmp[3] * src[6] + tmp[4] * src[7]) - (tmp[1] * src[5] + tmp[2] * src[6] + tmp[5] * src[7]); + m16[1] = (tmp[1] * src[4] + tmp[6] * src[6] + tmp[9] * src[7]) - (tmp[0] * src[4] + tmp[7] * src[6] + tmp[8] * src[7]); + m16[2] = (tmp[2] * src[4] + tmp[7] * src[5] + tmp[10] * src[7]) - (tmp[3] * src[4] + tmp[6] * src[5] + tmp[11] * src[7]); + m16[3] = (tmp[5] * src[4] + tmp[8] * src[5] + tmp[11] * src[6]) - (tmp[4] * src[4] + tmp[9] * src[5] + tmp[10] * src[6]); + m16[4] = (tmp[1] * src[1] + tmp[2] * src[2] + tmp[5] * src[3]) - (tmp[0] * src[1] + tmp[3] * src[2] + tmp[4] * src[3]); + m16[5] = (tmp[0] * src[0] + tmp[7] * src[2] + tmp[8] * src[3]) - (tmp[1] * src[0] + tmp[6] * src[2] + tmp[9] * src[3]); + m16[6] = (tmp[3] * src[0] + tmp[6] * src[1] + tmp[11] * src[3]) - (tmp[2] * src[0] + tmp[7] * src[1] + tmp[10] * src[3]); + m16[7] = (tmp[4] * src[0] + tmp[9] * src[1] + tmp[10] * src[2]) - (tmp[5] * src[0] + tmp[8] * src[1] + tmp[11] * src[2]); + + // calculate pairs for second 8 elements (cofactors) + tmp[0] = src[2] * src[7]; + tmp[1] = src[3] * src[6]; + tmp[2] = src[1] * src[7]; + tmp[3] = src[3] * src[5]; + tmp[4] = src[1] * src[6]; + tmp[5] = src[2] * src[5]; + tmp[6] = src[0] * src[7]; + tmp[7] = src[3] * src[4]; + tmp[8] = src[0] * src[6]; + tmp[9] = src[2] * src[4]; + tmp[10] = src[0] * src[5]; + tmp[11] = src[1] * src[4]; + + // calculate second 8 elements (cofactors) + m16[8] = (tmp[0] * src[13] + tmp[3] * src[14] + tmp[4] * src[15]) - (tmp[1] * src[13] + tmp[2] * src[14] + tmp[5] * src[15]); + m16[9] = (tmp[1] * src[12] + tmp[6] * src[14] + tmp[9] * src[15]) - (tmp[0] * src[12] + tmp[7] * src[14] + tmp[8] * src[15]); + m16[10] = (tmp[2] * src[12] + tmp[7] * src[13] + tmp[10] * src[15]) - (tmp[3] * src[12] + tmp[6] * src[13] + tmp[11] * src[15]); + m16[11] = (tmp[5] * src[12] + tmp[8] * src[13] + tmp[11] * src[14]) - (tmp[4] * src[12] + tmp[9] * src[13] + tmp[10] * src[14]); + m16[12] = (tmp[2] * src[10] + tmp[5] * src[11] + tmp[1] * src[9]) - (tmp[4] * src[11] + tmp[0] * src[9] + tmp[3] * src[10]); + m16[13] = (tmp[8] * src[11] + tmp[0] * src[8] + tmp[7] * src[10]) - (tmp[6] * src[10] + tmp[9] * src[11] + tmp[1] * src[8]); + m16[14] = (tmp[6] * src[9] + tmp[11] * src[11] + tmp[3] * src[8]) - (tmp[10] * src[11] + tmp[2] * src[8] + tmp[7] * src[9]); + m16[15] = (tmp[10] * src[10] + tmp[4] * src[8] + tmp[9] * src[9]) - (tmp[8] * src[9] + tmp[11] * src[10] + tmp[5] * src[8]); + + // calculate determinant + det = src[0] * m16[0] + src[1] * m16[1] + src[2] * m16[2] + src[3] * m16[3]; + + // calculate matrix inverse + float invdet = 1 / det; + for (int j = 0; j < 16; ++j) + { + m16[j] *= invdet; + } + } + + return det; + } + + void matrix_t::RotationAxis(const vec_t& axis, float angle) + { + float length2 = axis.LengthSq(); + if (length2 < FLT_EPSILON) + { + SetToIdentity(); + return; + } + + vec_t n = axis * (1.f / sqrtf(length2)); + float s = sinf(angle); + float c = cosf(angle); + float k = 1.f - c; + + float xx = n.x * n.x * k + c; + float yy = n.y * n.y * k + c; + float zz = n.z * n.z * k + c; + float xy = n.x * n.y * k; + float yz = n.y * n.z * k; + float zx = n.z * n.x * k; + float xs = n.x * s; + float ys = n.y * s; + float zs = n.z * s; + + m[0][0] = xx; + m[0][1] = xy + zs; + m[0][2] = zx - ys; + m[0][3] = 0.f; + m[1][0] = xy - zs; + m[1][1] = yy; + m[1][2] = yz + xs; + m[1][3] = 0.f; + m[2][0] = zx + ys; + m[2][1] = yz - xs; + m[2][2] = zz; + m[2][3] = 0.f; + m[3][0] = 0.f; + m[3][1] = 0.f; + m[3][2] = 0.f; + m[3][3] = 1.f; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + + enum MOVETYPE + { + MT_NONE, + MT_MOVE_X, + MT_MOVE_Y, + MT_MOVE_Z, + MT_MOVE_YZ, + MT_MOVE_ZX, + MT_MOVE_XY, + MT_MOVE_SCREEN, + MT_ROTATE_X, + MT_ROTATE_Y, + MT_ROTATE_Z, + MT_ROTATE_SCREEN, + MT_SCALE_X, + MT_SCALE_Y, + MT_SCALE_Z, + MT_SCALE_XYZ + }; + + static bool IsTranslateType(int type) + { + return type >= MT_MOVE_X && type <= MT_MOVE_SCREEN; + } + + static bool IsRotateType(int type) + { + return type >= MT_ROTATE_X && type <= MT_ROTATE_SCREEN; + } + + static bool IsScaleType(int type) + { + return type >= MT_SCALE_X && type <= MT_SCALE_XYZ; + } + + // Matches MT_MOVE_AB order + static const OPERATION TRANSLATE_PLANS[3] = { TRANSLATE_Y | TRANSLATE_Z, TRANSLATE_X | TRANSLATE_Z, TRANSLATE_X | TRANSLATE_Y }; + + Style::Style() + { + // default values + TranslationLineThickness = 3.0f; + TranslationLineArrowSize = 6.0f; + RotationLineThickness = 2.0f; + RotationOuterLineThickness = 3.0f; + ScaleLineThickness = 3.0f; + ScaleLineCircleSize = 6.0f; + HatchedAxisLineThickness = 6.0f; + CenterCircleSize = 6.0f; + + // initialize default colors + Colors[DIRECTION_X] = ImVec4(0.666f, 0.000f, 0.000f, 1.000f); + Colors[DIRECTION_Y] = ImVec4(0.000f, 0.666f, 0.000f, 1.000f); + Colors[DIRECTION_Z] = ImVec4(0.000f, 0.000f, 0.666f, 1.000f); + Colors[PLANE_X] = ImVec4(0.666f, 0.000f, 0.000f, 0.380f); + Colors[PLANE_Y] = ImVec4(0.000f, 0.666f, 0.000f, 0.380f); + Colors[PLANE_Z] = ImVec4(0.000f, 0.000f, 0.666f, 0.380f); + Colors[SELECTION] = ImVec4(1.000f, 0.500f, 0.062f, 0.541f); + Colors[INACTIVE] = ImVec4(0.600f, 0.600f, 0.600f, 0.600f); + Colors[TRANSLATION_LINE] = ImVec4(0.666f, 0.666f, 0.666f, 0.666f); + Colors[SCALE_LINE] = ImVec4(0.250f, 0.250f, 0.250f, 1.000f); + Colors[ROTATION_USING_BORDER] = ImVec4(1.000f, 0.500f, 0.062f, 1.000f); + Colors[ROTATION_USING_FILL] = ImVec4(1.000f, 0.500f, 0.062f, 0.500f); + Colors[HATCHED_AXIS_LINES] = ImVec4(0.000f, 0.000f, 0.000f, 0.500f); + Colors[TEXT] = ImVec4(1.000f, 1.000f, 1.000f, 1.000f); + Colors[TEXT_SHADOW] = ImVec4(0.000f, 0.000f, 0.000f, 1.000f); + } + + struct Context + { + Context() : mbUsing(false), mbUsingViewManipulate(false), mbEnable(true), mIsViewManipulatorHovered(false), mbUsingBounds(false) + { + } + + ImDrawList* mDrawList; + Style mStyle; + + MODE mMode; + matrix_t mViewMat; + matrix_t mProjectionMat; + matrix_t mModel; + matrix_t mModelLocal; // orthonormalized model + matrix_t mModelInverse; + matrix_t mModelSource; + matrix_t mModelSourceInverse; + matrix_t mMVP; + matrix_t mMVPLocal; // MVP with full model matrix whereas mMVP's model matrix might only be translation in case of World space edition + matrix_t mViewProjection; + + vec_t mModelScaleOrigin; + vec_t mCameraEye; + vec_t mCameraRight; + vec_t mCameraDir; + vec_t mCameraUp; + vec_t mRayOrigin; + vec_t mRayVector; + + float mRadiusSquareCenter; + ImVec2 mScreenSquareCenter; + ImVec2 mScreenSquareMin; + ImVec2 mScreenSquareMax; + + float mScreenFactor; + vec_t mRelativeOrigin; + + bool mbUsing; + bool mbUsingViewManipulate; + bool mbEnable; + bool mbMouseOver; + bool mReversed; // reversed projection matrix + bool mIsViewManipulatorHovered; + + // translation + vec_t mTranslationPlan; + vec_t mTranslationPlanOrigin; + vec_t mMatrixOrigin; + vec_t mTranslationLastDelta; + + // rotation + vec_t mRotationVectorSource; + float mRotationAngle; + float mRotationAngleOrigin; + //vec_t mWorldToLocalAxis; + + // scale + vec_t mScale; + vec_t mScaleValueOrigin; + vec_t mScaleLast; + float mSaveMousePosx; + + // save axis factor when using gizmo + bool mBelowAxisLimit[3]; + int mAxisMask = 0; + bool mBelowPlaneLimit[3]; + float mAxisFactor[3]; + + float mAxisLimit=0.0025f; + float mPlaneLimit=0.02f; + + // bounds stretching + vec_t mBoundsPivot; + vec_t mBoundsAnchor; + vec_t mBoundsPlan; + vec_t mBoundsLocalPivot; + int mBoundsBestAxis; + int mBoundsAxis[2]; + bool mbUsingBounds; + matrix_t mBoundsMatrix; + + // + int mCurrentOperation; + + float mX = 0.f; + float mY = 0.f; + float mWidth = 0.f; + float mHeight = 0.f; + float mXMax = 0.f; + float mYMax = 0.f; + float mDisplayRatio = 1.f; + + bool mIsOrthographic = false; + // check to not have multiple gizmo highlighted at the same time + bool mbOverGizmoHotspot = false; + + ImGuiWindow* mAlternativeWindow = nullptr; + ImVector mIDStack; + ImGuiID mEditingID = -1; + OPERATION mOperation = OPERATION(-1); + + bool mAllowAxisFlip = true; + float mGizmoSizeClipSpace = 0.1f; + + inline ImGuiID GetCurrentID() + { + if (mIDStack.empty()) + { + mIDStack.push_back(-1); + } + return mIDStack.back(); + } + }; + + static Context gContext; + + static const vec_t directionUnary[3] = { makeVect(1.f, 0.f, 0.f), makeVect(0.f, 1.f, 0.f), makeVect(0.f, 0.f, 1.f) }; + static const char* translationInfoMask[] = { "X : %5.3f", "Y : %5.3f", "Z : %5.3f", + "Y : %5.3f Z : %5.3f", "X : %5.3f Z : %5.3f", "X : %5.3f Y : %5.3f", + "X : %5.3f Y : %5.3f Z : %5.3f" }; + static const char* scaleInfoMask[] = { "X : %5.2f", "Y : %5.2f", "Z : %5.2f", "XYZ : %5.2f" }; + static const char* rotationInfoMask[] = { "X : %5.2f deg %5.2f rad", "Y : %5.2f deg %5.2f rad", "Z : %5.2f deg %5.2f rad", "Screen : %5.2f deg %5.2f rad" }; + static const int translationInfoIndex[] = { 0,0,0, 1,0,0, 2,0,0, 1,2,0, 0,2,0, 0,1,0, 0,1,2 }; + static const float quadMin = 0.5f; + static const float quadMax = 0.8f; + static const float quadUV[8] = { quadMin, quadMin, quadMin, quadMax, quadMax, quadMax, quadMax, quadMin }; + static const int halfCircleSegmentCount = 64; + static const float snapTension = 0.5f; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + static int GetMoveType(OPERATION op, vec_t* gizmoHitProportion); + static int GetRotateType(OPERATION op); + static int GetScaleType(OPERATION op); + + Style& GetStyle() + { + return gContext.mStyle; + } + + static ImU32 GetColorU32(int idx) + { + IM_ASSERT(idx < COLOR::COUNT); + return ImGui::ColorConvertFloat4ToU32(gContext.mStyle.Colors[idx]); + } + + static ImVec2 worldToPos(const vec_t& worldPos, const matrix_t& mat, ImVec2 position = ImVec2(gContext.mX, gContext.mY), ImVec2 size = ImVec2(gContext.mWidth, gContext.mHeight)) + { + vec_t trans; + trans.TransformPoint(worldPos, mat); + trans *= 0.5f / trans.w; + trans += makeVect(0.5f, 0.5f); + trans.y = 1.f - trans.y; + trans.x *= size.x; + trans.y *= size.y; + trans.x += position.x; + trans.y += position.y; + return ImVec2(trans.x, trans.y); + } + + static void ComputeCameraRay(vec_t& rayOrigin, vec_t& rayDir, ImVec2 position = ImVec2(gContext.mX, gContext.mY), ImVec2 size = ImVec2(gContext.mWidth, gContext.mHeight)) + { + ImGuiIO& io = ImGui::GetIO(); + + matrix_t mViewProjInverse; + mViewProjInverse.Inverse(gContext.mViewMat * gContext.mProjectionMat); + + const float mox = ((io.MousePos.x - position.x) / size.x) * 2.f - 1.f; + const float moy = (1.f - ((io.MousePos.y - position.y) / size.y)) * 2.f - 1.f; + + const float zNear = gContext.mReversed ? (1.f - FLT_EPSILON) : 0.f; + const float zFar = gContext.mReversed ? 0.f : (1.f - FLT_EPSILON); + + rayOrigin.Transform(makeVect(mox, moy, zNear, 1.f), mViewProjInverse); + rayOrigin *= 1.f / rayOrigin.w; + vec_t rayEnd; + rayEnd.Transform(makeVect(mox, moy, zFar, 1.f), mViewProjInverse); + rayEnd *= 1.f / rayEnd.w; + rayDir = Normalized(rayEnd - rayOrigin); + } + + static float GetSegmentLengthClipSpace(const vec_t& start, const vec_t& end, const bool localCoordinates = false) + { + vec_t startOfSegment = start; + const matrix_t& mvp = localCoordinates ? gContext.mMVPLocal : gContext.mMVP; + startOfSegment.TransformPoint(mvp); + if (fabsf(startOfSegment.w) > FLT_EPSILON) // check for axis aligned with camera direction + { + startOfSegment *= 1.f / startOfSegment.w; + } + + vec_t endOfSegment = end; + endOfSegment.TransformPoint(mvp); + if (fabsf(endOfSegment.w) > FLT_EPSILON) // check for axis aligned with camera direction + { + endOfSegment *= 1.f / endOfSegment.w; + } + + vec_t clipSpaceAxis = endOfSegment - startOfSegment; + if (gContext.mDisplayRatio < 1.0) + clipSpaceAxis.x *= gContext.mDisplayRatio; + else + clipSpaceAxis.y /= gContext.mDisplayRatio; + float segmentLengthInClipSpace = sqrtf(clipSpaceAxis.x * clipSpaceAxis.x + clipSpaceAxis.y * clipSpaceAxis.y); + return segmentLengthInClipSpace; + } + + static float GetParallelogram(const vec_t& ptO, const vec_t& ptA, const vec_t& ptB) + { + vec_t pts[] = { ptO, ptA, ptB }; + for (unsigned int i = 0; i < 3; i++) + { + pts[i].TransformPoint(gContext.mMVP); + if (fabsf(pts[i].w) > FLT_EPSILON) // check for axis aligned with camera direction + { + pts[i] *= 1.f / pts[i].w; + } + } + vec_t segA = pts[1] - pts[0]; + vec_t segB = pts[2] - pts[0]; + segA.y /= gContext.mDisplayRatio; + segB.y /= gContext.mDisplayRatio; + vec_t segAOrtho = makeVect(-segA.y, segA.x); + segAOrtho.Normalize(); + float dt = segAOrtho.Dot3(segB); + float surface = sqrtf(segA.x * segA.x + segA.y * segA.y) * fabsf(dt); + return surface; + } + + inline vec_t PointOnSegment(const vec_t& point, const vec_t& vertPos1, const vec_t& vertPos2) + { + vec_t c = point - vertPos1; + vec_t V; + + V.Normalize(vertPos2 - vertPos1); + float d = (vertPos2 - vertPos1).Length(); + float t = V.Dot3(c); + + if (t < 0.f) + { + return vertPos1; + } + + if (t > d) + { + return vertPos2; + } + + return vertPos1 + V * t; + } + + static float IntersectRayPlane(const vec_t& rOrigin, const vec_t& rVector, const vec_t& plan) + { + const float numer = plan.Dot3(rOrigin) - plan.w; + const float denom = plan.Dot3(rVector); + + if (fabsf(denom) < FLT_EPSILON) // normal is orthogonal to vector, cant intersect + { + return -1.0f; + } + + return -(numer / denom); + } + + static float DistanceToPlane(const vec_t& point, const vec_t& plan) + { + return plan.Dot3(point) + plan.w; + } + + static bool IsInContextRect(ImVec2 p) + { + return IsWithin(p.x, gContext.mX, gContext.mXMax) && IsWithin(p.y, gContext.mY, gContext.mYMax); + } + + static bool IsHoveringWindow() + { + ImGuiContext& g = *ImGui::GetCurrentContext(); + ImGuiWindow* window = ImGui::FindWindowByName(gContext.mDrawList->_OwnerName); + if (g.HoveredWindow == window) // Mouse hovering drawlist window + return true; + if (gContext.mAlternativeWindow != nullptr && g.HoveredWindow == gContext.mAlternativeWindow) + return true; + if (g.HoveredWindow != NULL) // Any other window is hovered + return false; + if (ImGui::IsMouseHoveringRect(window->InnerRect.Min, window->InnerRect.Max, false)) // Hovering drawlist window rect, while no other window is hovered (for _NoInputs windows) + return true; + return false; + } + + void SetRect(float x, float y, float width, float height) + { + gContext.mX = x; + gContext.mY = y; + gContext.mWidth = width; + gContext.mHeight = height; + gContext.mXMax = gContext.mX + gContext.mWidth; + gContext.mYMax = gContext.mY + gContext.mXMax; + gContext.mDisplayRatio = width / height; + } + + void SetOrthographic(bool isOrthographic) + { + gContext.mIsOrthographic = isOrthographic; + } + + void SetDrawlist(ImDrawList* drawlist) + { + gContext.mDrawList = drawlist ? drawlist : ImGui::GetWindowDrawList(); + } + + void SetImGuiContext(ImGuiContext* ctx) + { + ImGui::SetCurrentContext(ctx); + } + + void BeginFrame() + { + const ImU32 flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus; + +#ifdef IMGUI_HAS_VIEWPORT + ImGui::SetNextWindowSize(ImGui::GetMainViewport()->Size); + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->Pos); +#else + ImGuiIO& io = ImGui::GetIO(); + ImGui::SetNextWindowSize(io.DisplaySize); + ImGui::SetNextWindowPos(ImVec2(0, 0)); +#endif + + ImGui::PushStyleColor(ImGuiCol_WindowBg, 0); + ImGui::PushStyleColor(ImGuiCol_Border, 0); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + + ImGui::Begin("gizmo", NULL, flags); + gContext.mDrawList = ImGui::GetWindowDrawList(); + gContext.mbOverGizmoHotspot = false; + ImGui::End(); + ImGui::PopStyleVar(); + ImGui::PopStyleColor(2); + } + + bool IsUsing() + { + return (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID)) || gContext.mbUsingBounds; + } + + bool IsUsingViewManipulate() + { + return gContext.mbUsingViewManipulate; + } + + bool IsViewManipulateHovered() + { + return gContext.mIsViewManipulatorHovered; + } + + bool IsUsingAny() + { + return gContext.mbUsing || gContext.mbUsingBounds; + } + + bool IsOver() + { + return (Intersects(gContext.mOperation, TRANSLATE) && GetMoveType(gContext.mOperation, NULL) != MT_NONE) || + (Intersects(gContext.mOperation, ROTATE) && GetRotateType(gContext.mOperation) != MT_NONE) || + (Intersects(gContext.mOperation, SCALE) && GetScaleType(gContext.mOperation) != MT_NONE) || IsUsing(); + } + + bool IsOver(OPERATION op) + { + if(IsUsing()) + { + return true; + } + if(Intersects(op, SCALE) && GetScaleType(op) != MT_NONE) + { + return true; + } + if(Intersects(op, ROTATE) && GetRotateType(op) != MT_NONE) + { + return true; + } + if(Intersects(op, TRANSLATE) && GetMoveType(op, NULL) != MT_NONE) + { + return true; + } + return false; + } + + void Enable(bool enable) + { + gContext.mbEnable = enable; + if (!enable) + { + gContext.mbUsing = false; + gContext.mbUsingBounds = false; + } + } + + static void ComputeContext(const float* view, const float* projection, float* matrix, MODE mode) + { + gContext.mMode = mode; + gContext.mViewMat = *(matrix_t*)view; + gContext.mProjectionMat = *(matrix_t*)projection; + gContext.mbMouseOver = IsHoveringWindow(); + + gContext.mModelLocal = *(matrix_t*)matrix; + gContext.mModelLocal.OrthoNormalize(); + + if (mode == LOCAL) + { + gContext.mModel = gContext.mModelLocal; + } + else + { + gContext.mModel.Translation(((matrix_t*)matrix)->v.position); + } + gContext.mModelSource = *(matrix_t*)matrix; + gContext.mModelScaleOrigin.Set(gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length()); + + gContext.mModelInverse.Inverse(gContext.mModel); + gContext.mModelSourceInverse.Inverse(gContext.mModelSource); + gContext.mViewProjection = gContext.mViewMat * gContext.mProjectionMat; + gContext.mMVP = gContext.mModel * gContext.mViewProjection; + gContext.mMVPLocal = gContext.mModelLocal * gContext.mViewProjection; + + matrix_t viewInverse; + viewInverse.Inverse(gContext.mViewMat); + gContext.mCameraDir = viewInverse.v.dir; + gContext.mCameraEye = viewInverse.v.position; + gContext.mCameraRight = viewInverse.v.right; + gContext.mCameraUp = viewInverse.v.up; + + // projection reverse + vec_t nearPos, farPos; + nearPos.Transform(makeVect(0, 0, 1.f, 1.f), gContext.mProjectionMat); + farPos.Transform(makeVect(0, 0, 2.f, 1.f), gContext.mProjectionMat); + + gContext.mReversed = (nearPos.z/nearPos.w) > (farPos.z / farPos.w); + + // compute scale from the size of camera right vector projected on screen at the matrix position + vec_t pointRight = viewInverse.v.right; + pointRight.TransformPoint(gContext.mViewProjection); + + vec_t rightViewInverse = viewInverse.v.right; + rightViewInverse.TransformVector(gContext.mModelInverse); + float rightLength = GetSegmentLengthClipSpace(makeVect(0.f, 0.f), rightViewInverse); + gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / rightLength; + + ImVec2 centerSSpace = worldToPos(makeVect(0.f, 0.f), gContext.mMVP); + gContext.mScreenSquareCenter = centerSSpace; + gContext.mScreenSquareMin = ImVec2(centerSSpace.x - 10.f, centerSSpace.y - 10.f); + gContext.mScreenSquareMax = ImVec2(centerSSpace.x + 10.f, centerSSpace.y + 10.f); + + ComputeCameraRay(gContext.mRayOrigin, gContext.mRayVector); + } + + static void ComputeColors(ImU32* colors, int type, OPERATION operation) + { + if (gContext.mbEnable) + { + ImU32 selectionColor = GetColorU32(SELECTION); + + switch (operation) + { + case TRANSLATE: + colors[0] = (type == MT_MOVE_SCREEN) ? selectionColor : IM_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_MOVE_X + i)) ? selectionColor : GetColorU32(DIRECTION_X + i); + colors[i + 4] = (type == (int)(MT_MOVE_YZ + i)) ? selectionColor : GetColorU32(PLANE_X + i); + colors[i + 4] = (type == MT_MOVE_SCREEN) ? selectionColor : colors[i + 4]; + } + break; + case ROTATE: + colors[0] = (type == MT_ROTATE_SCREEN) ? selectionColor : IM_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_ROTATE_X + i)) ? selectionColor : GetColorU32(DIRECTION_X + i); + } + break; + case SCALEU: + case SCALE: + colors[0] = (type == MT_SCALE_XYZ) ? selectionColor : IM_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_SCALE_X + i)) ? selectionColor : GetColorU32(DIRECTION_X + i); + } + break; + // note: this internal function is only called with three possible values for operation + default: + break; + } + } + else + { + ImU32 inactiveColor = GetColorU32(INACTIVE); + for (int i = 0; i < 7; i++) + { + colors[i] = inactiveColor; + } + } + } + + static void ComputeTripodAxisAndVisibility(const int axisIndex, vec_t& dirAxis, vec_t& dirPlaneX, vec_t& dirPlaneY, bool& belowAxisLimit, bool& belowPlaneLimit, const bool localCoordinates = false) + { + dirAxis = directionUnary[axisIndex]; + dirPlaneX = directionUnary[(axisIndex + 1) % 3]; + dirPlaneY = directionUnary[(axisIndex + 2) % 3]; + + if (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID)) + { + // when using, use stored factors so the gizmo doesn't flip when we translate + + // Apply axis mask to axes and planes + belowAxisLimit = gContext.mBelowAxisLimit[axisIndex] && ((1< FLT_EPSILON) ? -1.f : 1.f; + float mulAxisX = (allowFlip && lenDirPlaneX < lenDirMinusPlaneX&& fabsf(lenDirPlaneX - lenDirMinusPlaneX) > FLT_EPSILON) ? -1.f : 1.f; + float mulAxisY = (allowFlip && lenDirPlaneY < lenDirMinusPlaneY&& fabsf(lenDirPlaneY - lenDirMinusPlaneY) > FLT_EPSILON) ? -1.f : 1.f; + dirAxis *= mulAxis; + dirPlaneX *= mulAxisX; + dirPlaneY *= mulAxisY; + + // for axis + float axisLengthInClipSpace = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirAxis * gContext.mScreenFactor, localCoordinates); + + float paraSurf = GetParallelogram(makeVect(0.f, 0.f, 0.f), dirPlaneX * gContext.mScreenFactor, dirPlaneY * gContext.mScreenFactor); + // Apply axis mask to axes and planes + belowPlaneLimit = (paraSurf > gContext.mAxisLimit) && (((1< gContext.mPlaneLimit) && !((1< (1.f - snapTension)) + { + *value = *value - modulo + snap * ((*value < 0.f) ? -1.f : 1.f); + } + } + static void ComputeSnap(vec_t& value, const float* snap) + { + for (int i = 0; i < 3; i++) + { + ComputeSnap(&value[i], snap[i]); + } + } + + static float ComputeAngleOnPlan() + { + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t localPos = Normalized(gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position); + + vec_t perpendicularVector; + perpendicularVector.Cross(gContext.mRotationVectorSource, gContext.mTranslationPlan); + perpendicularVector.Normalize(); + float acosAngle = Clamp(Dot(localPos, gContext.mRotationVectorSource), -1.f, 1.f); + float angle = acosf(acosAngle); + angle *= (Dot(localPos, perpendicularVector) < 0.f) ? 1.f : -1.f; + return angle; + } + + static void DrawRotationGizmo(OPERATION op, int type) + { + if(!Intersects(op, ROTATE)) + { + return; + } + ImDrawList* drawList = gContext.mDrawList; + + bool isMultipleAxesMasked = (gContext.mAxisMask & (gContext.mAxisMask - 1)) != 0; + bool isNoAxesMasked = !gContext.mAxisMask; + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, ROTATE); + + vec_t viewDirNormalized; + if (gContext.mIsOrthographic) + { + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)&gContext.mViewMat); + viewDirNormalized = -viewInverse.v.dir; + } + else + { + viewDirNormalized = Normalized(gContext.mCameraDir); + } + + viewDirNormalized.TransformVector(gContext.mModelInverse); + + gContext.mRadiusSquareCenter = screenRotateSize * gContext.mHeight; + + bool hasRSC = Intersects(op, ROTATE_SCREEN); + for (int axis = 0; axis < 3; axis++) + { + if(!Intersects(op, static_cast(ROTATE_Z >> axis))) + { + continue; + } + + bool isAxisMasked = ((1 << (2 - axis)) & gContext.mAxisMask) != 0; + + if ((!isAxisMasked || isMultipleAxesMasked) && !isNoAxesMasked) + { + continue; + } + const bool usingAxis = (gContext.mbUsing && type == MT_ROTATE_Z - axis); + const int circleMul = (hasRSC && !usingAxis) ? 1 : 2; + + ImVec2* circlePos = (ImVec2*)alloca(sizeof(ImVec2) * (circleMul * halfCircleSegmentCount + 1)); + + float angleStart = atan2f(viewDirNormalized[(4 - axis) % 3], viewDirNormalized[(3 - axis) % 3]) + (gContext.mIsOrthographic ? ZPI : -ZPI) * 0.5f; + + for (int i = 0; i < circleMul * halfCircleSegmentCount + 1; i++) + { + float ng = angleStart + (float)circleMul * ZPI * ((float)i / (float)(circleMul * halfCircleSegmentCount)); + vec_t axisPos = makeVect(cosf(ng), sinf(ng), 0.f); + vec_t pos = makeVect(axisPos[axis], axisPos[(axis + 1) % 3], axisPos[(axis + 2) % 3]) * gContext.mScreenFactor * rotationDisplayFactor; + circlePos[i] = worldToPos(pos, gContext.mMVP); + } + if (!gContext.mbUsing || usingAxis) + { + drawList->AddPolyline(circlePos, circleMul* halfCircleSegmentCount + 1, colors[3 - axis], false, gContext.mStyle.RotationLineThickness); + } + + float radiusAxis = sqrtf((ImLengthSqr(worldToPos(gContext.mModel.v.position, gContext.mViewProjection) - circlePos[0]))); + if (radiusAxis > gContext.mRadiusSquareCenter) + { + gContext.mRadiusSquareCenter = radiusAxis; + } + } + if(hasRSC && (!gContext.mbUsing || type == MT_ROTATE_SCREEN) && (!isMultipleAxesMasked && isNoAxesMasked)) + { + drawList->AddCircle(worldToPos(gContext.mModel.v.position, gContext.mViewProjection), gContext.mRadiusSquareCenter, colors[0], 64, gContext.mStyle.RotationOuterLineThickness); + } + + if (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID) && IsRotateType(type)) + { + ImVec2 circlePos[halfCircleSegmentCount + 1]; + + circlePos[0] = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + for (unsigned int i = 1; i < halfCircleSegmentCount + 1; i++) + { + float ng = gContext.mRotationAngle * ((float)(i - 1) / (float)(halfCircleSegmentCount - 1)); + matrix_t rotateVectorMatrix; + rotateVectorMatrix.RotationAxis(gContext.mTranslationPlan, ng); + vec_t pos; + pos.TransformPoint(gContext.mRotationVectorSource, rotateVectorMatrix); + pos *= gContext.mScreenFactor * rotationDisplayFactor; + circlePos[i] = worldToPos(pos + gContext.mModel.v.position, gContext.mViewProjection); + } + drawList->AddConvexPolyFilled(circlePos, halfCircleSegmentCount + 1, GetColorU32(ROTATION_USING_FILL)); + drawList->AddPolyline(circlePos, halfCircleSegmentCount + 1, GetColorU32(ROTATION_USING_BORDER), true, gContext.mStyle.RotationLineThickness); + + ImVec2 destinationPosOnScreen = circlePos[1]; + char tmps[512]; + ImFormatString(tmps, sizeof(tmps), rotationInfoMask[type - MT_ROTATE_X], (gContext.mRotationAngle / ZPI) * 180.f, gContext.mRotationAngle); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + } + + static void DrawHatchedAxis(const vec_t& axis) + { + if (gContext.mStyle.HatchedAxisLineThickness <= 0.0f) + { + return; + } + + for (int j = 1; j < 10; j++) + { + ImVec2 baseSSpace2 = worldToPos(axis * 0.05f * (float)(j * 2) * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace2 = worldToPos(axis * 0.05f * (float)(j * 2 + 1) * gContext.mScreenFactor, gContext.mMVP); + gContext.mDrawList->AddLine(baseSSpace2, worldDirSSpace2, GetColorU32(HATCHED_AXIS_LINES), gContext.mStyle.HatchedAxisLineThickness); + } + } + + static void DrawScaleGizmo(OPERATION op, int type) + { + ImDrawList* drawList = gContext.mDrawList; + + if(!Intersects(op, SCALE)) + { + return; + } + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, SCALE); + + // draw + vec_t scaleDisplay = { 1.f, 1.f, 1.f, 1.f }; + + if (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID)) + { + scaleDisplay = gContext.mScale; + } + + for (int i = 0; i < 3; i++) + { + if(!Intersects(op, static_cast(SCALE_X << i))) + { + continue; + } + const bool usingAxis = (gContext.mbUsing && type == MT_SCALE_X + i); + if (!gContext.mbUsing || usingAxis) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + + // draw axis + if (belowAxisLimit) + { + bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)); + float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; + ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale * scaleDisplay[i]) * gContext.mScreenFactor, gContext.mMVP); + + if (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID)) + { + ImU32 scaleLineColor = GetColorU32(SCALE_LINE); + drawList->AddLine(baseSSpace, worldDirSSpaceNoScale, scaleLineColor, gContext.mStyle.ScaleLineThickness); + drawList->AddCircleFilled(worldDirSSpaceNoScale, gContext.mStyle.ScaleLineCircleSize, scaleLineColor); + } + + if (!hasTranslateOnAxis || gContext.mbUsing) + { + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], gContext.mStyle.ScaleLineThickness); + } + drawList->AddCircleFilled(worldDirSSpace, gContext.mStyle.ScaleLineCircleSize, colors[i + 1]); + + if (gContext.mAxisFactor[i] < 0.f) + { + DrawHatchedAxis(dirAxis * scaleDisplay[i]); + } + } + } + } + + // draw screen cirle + drawList->AddCircleFilled(gContext.mScreenSquareCenter, gContext.mStyle.CenterCircleSize, colors[0], 32); + + if (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID) && IsScaleType(type)) + { + //ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + /*vec_t dif(destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y); + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + */ + char tmps[512]; + //vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_SCALE_X) * 3; + ImFormatString(tmps, sizeof(tmps), scaleInfoMask[type - MT_SCALE_X], scaleDisplay[translationInfoIndex[componentInfoIndex]]); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + } + + + static void DrawScaleUniveralGizmo(OPERATION op, int type) + { + ImDrawList* drawList = gContext.mDrawList; + + if (!Intersects(op, SCALEU)) + { + return; + } + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, SCALEU); + + // draw + vec_t scaleDisplay = { 1.f, 1.f, 1.f, 1.f }; + + if (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID)) + { + scaleDisplay = gContext.mScale; + } + + for (int i = 0; i < 3; i++) + { + if (!Intersects(op, static_cast(SCALE_XU << i))) + { + continue; + } + const bool usingAxis = (gContext.mbUsing && type == MT_SCALE_X + i); + if (!gContext.mbUsing || usingAxis) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + + // draw axis + if (belowAxisLimit) + { + bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)); + float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; + //ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVPLocal); + //ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale * scaleDisplay[i]) * gContext.mScreenFactor, gContext.mMVPLocal); + +#if 0 + if (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID)) + { + drawList->AddLine(baseSSpace, worldDirSSpaceNoScale, IM_COL32(0x40, 0x40, 0x40, 0xFF), 3.f); + drawList->AddCircleFilled(worldDirSSpaceNoScale, 6.f, IM_COL32(0x40, 0x40, 0x40, 0xFF)); + } + /* + if (!hasTranslateOnAxis || gContext.mbUsing) + { + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], 3.f); + } + */ +#endif + drawList->AddCircleFilled(worldDirSSpace, 12.f, colors[i + 1]); + } + } + } + + // draw screen cirle + drawList->AddCircle(gContext.mScreenSquareCenter, 20.f, colors[0], 32, gContext.mStyle.CenterCircleSize); + + if (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID) && IsScaleType(type)) + { + //ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + /*vec_t dif(destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y); + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + */ + char tmps[512]; + //vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_SCALE_X) * 3; + ImFormatString(tmps, sizeof(tmps), scaleInfoMask[type - MT_SCALE_X], scaleDisplay[translationInfoIndex[componentInfoIndex]]); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + } + + static void DrawTranslationGizmo(OPERATION op, int type) + { + ImDrawList* drawList = gContext.mDrawList; + if (!drawList) + { + return; + } + + if(!Intersects(op, TRANSLATE)) + { + return; + } + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, TRANSLATE); + + const ImVec2 origin = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + + // draw + bool belowAxisLimit = false; + bool belowPlaneLimit = false; + for (int i = 0; i < 3; ++i) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + + if (!gContext.mbUsing || (gContext.mbUsing && type == MT_MOVE_X + i)) + { + // draw axis + if (belowAxisLimit && Intersects(op, static_cast(TRANSLATE_X << i))) + { + ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos(dirAxis * gContext.mScreenFactor, gContext.mMVP); + + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], gContext.mStyle.TranslationLineThickness); + + // Arrow head begin + ImVec2 dir(origin - worldDirSSpace); + + float d = sqrtf(ImLengthSqr(dir)); + dir /= d; // Normalize + dir *= gContext.mStyle.TranslationLineArrowSize; + + ImVec2 ortogonalDir(dir.y, -dir.x); // Perpendicular vector + ImVec2 a(worldDirSSpace + dir); + drawList->AddTriangleFilled(worldDirSSpace - dir, a + ortogonalDir, a - ortogonalDir, colors[i + 1]); + // Arrow head end + + if (gContext.mAxisFactor[i] < 0.f) + { + DrawHatchedAxis(dirAxis); + } + } + } + // draw plane + if (!gContext.mbUsing || (gContext.mbUsing && type == MT_MOVE_YZ + i)) + { + if (belowPlaneLimit && Contains(op, TRANSLATE_PLANS[i])) + { + ImVec2 screenQuadPts[4]; + for (int j = 0; j < 4; ++j) + { + vec_t cornerWorldPos = (dirPlaneX * quadUV[j * 2] + dirPlaneY * quadUV[j * 2 + 1]) * gContext.mScreenFactor; + screenQuadPts[j] = worldToPos(cornerWorldPos, gContext.mMVP); + } + drawList->AddPolyline(screenQuadPts, 4, GetColorU32(DIRECTION_X + i), true, 1.0f); + drawList->AddConvexPolyFilled(screenQuadPts, 4, colors[i + 4]); + } + } + } + + drawList->AddCircleFilled(gContext.mScreenSquareCenter, gContext.mStyle.CenterCircleSize, colors[0], 32); + + if (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID) && IsTranslateType(type)) + { + ImU32 translationLineColor = GetColorU32(TRANSLATION_LINE); + + ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + vec_t dif = { destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y, 0.f, 0.f }; + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + + char tmps[512]; + vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_MOVE_X) * 3; + ImFormatString(tmps, sizeof(tmps), translationInfoMask[type - MT_MOVE_X], deltaInfo[translationInfoIndex[componentInfoIndex]], deltaInfo[translationInfoIndex[componentInfoIndex + 1]], deltaInfo[translationInfoIndex[componentInfoIndex + 2]]); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + } + + static bool CanActivate() + { + if (ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && !ImGui::IsAnyItemActive()) + { + return true; + } + return false; + } + + static bool HandleAndDrawLocalBounds(const float* bounds, matrix_t* matrix, const float* snapValues, OPERATION operation) + { + ImGuiIO& io = ImGui::GetIO(); + ImDrawList* drawList = gContext.mDrawList; + + bool manipulated = false; + + // compute best projection axis + vec_t axesWorldDirections[3]; + vec_t bestAxisWorldDirection = { 0.0f, 0.0f, 0.0f, 0.0f }; + int axes[3]; + unsigned int numAxes = 1; + axes[0] = gContext.mBoundsBestAxis; + int bestAxis = axes[0]; + if (!gContext.mbUsingBounds) + { + numAxes = 0; + float bestDot = 0.f; + for (int i = 0; i < 3; i++) + { + vec_t dirPlaneNormalWorld; + dirPlaneNormalWorld.TransformVector(directionUnary[i], gContext.mModelSource); + dirPlaneNormalWorld.Normalize(); + + float dt = fabsf(Dot(Normalized(gContext.mCameraEye - gContext.mModelSource.v.position), dirPlaneNormalWorld)); + if (dt >= bestDot) + { + bestDot = dt; + bestAxis = i; + bestAxisWorldDirection = dirPlaneNormalWorld; + } + + if (dt >= 0.1f) + { + axes[numAxes] = i; + axesWorldDirections[numAxes] = dirPlaneNormalWorld; + ++numAxes; + } + } + } + + if (numAxes == 0) + { + axes[0] = bestAxis; + axesWorldDirections[0] = bestAxisWorldDirection; + numAxes = 1; + } + + else if (bestAxis != axes[0]) + { + unsigned int bestIndex = 0; + for (unsigned int i = 0; i < numAxes; i++) + { + if (axes[i] == bestAxis) + { + bestIndex = i; + break; + } + } + int tempAxis = axes[0]; + axes[0] = axes[bestIndex]; + axes[bestIndex] = tempAxis; + vec_t tempDirection = axesWorldDirections[0]; + axesWorldDirections[0] = axesWorldDirections[bestIndex]; + axesWorldDirections[bestIndex] = tempDirection; + } + + for (unsigned int axisIndex = 0; axisIndex < numAxes; ++axisIndex) + { + bestAxis = axes[axisIndex]; + bestAxisWorldDirection = axesWorldDirections[axisIndex]; + + // corners + vec_t aabb[4]; + + int secondAxis = (bestAxis + 1) % 3; + int thirdAxis = (bestAxis + 2) % 3; + + for (int i = 0; i < 4; i++) + { + aabb[i][3] = aabb[i][bestAxis] = 0.f; + aabb[i][secondAxis] = bounds[secondAxis + 3 * (i >> 1)]; + aabb[i][thirdAxis] = bounds[thirdAxis + 3 * ((i >> 1) ^ (i & 1))]; + } + + // draw bounds + unsigned int anchorAlpha = gContext.mbEnable ? IM_COL32_BLACK : IM_COL32(0, 0, 0, 0x80); + + matrix_t boundsMVP = gContext.mModelSource * gContext.mViewProjection; + for (int i = 0; i < 4; i++) + { + ImVec2 worldBound1 = worldToPos(aabb[i], boundsMVP); + ImVec2 worldBound2 = worldToPos(aabb[(i + 1) % 4], boundsMVP); + if (!IsInContextRect(worldBound1) || !IsInContextRect(worldBound2)) + { + continue; + } + float boundDistance = sqrtf(ImLengthSqr(worldBound1 - worldBound2)); + int stepCount = (int)(boundDistance / 10.f); + stepCount = min(stepCount, 1000); + for (int j = 0; j < stepCount; j++) + { + float stepLength = 1.f / (float)stepCount; + float t1 = (float)j * stepLength; + float t2 = (float)j * stepLength + stepLength * 0.5f; + ImVec2 worldBoundSS1 = ImLerp(worldBound1, worldBound2, ImVec2(t1, t1)); + ImVec2 worldBoundSS2 = ImLerp(worldBound1, worldBound2, ImVec2(t2, t2)); + //drawList->AddLine(worldBoundSS1, worldBoundSS2, IM_COL32(0, 0, 0, 0) + anchorAlpha, 3.f); + drawList->AddLine(worldBoundSS1, worldBoundSS2, IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha, 2.f); + } + vec_t midPoint = (aabb[i] + aabb[(i + 1) % 4]) * 0.5f; + ImVec2 midBound = worldToPos(midPoint, boundsMVP); + static const float AnchorBigRadius = 8.f; + static const float AnchorSmallRadius = 6.f; + bool overBigAnchor = ImLengthSqr(worldBound1 - io.MousePos) <= (AnchorBigRadius * AnchorBigRadius); + bool overSmallAnchor = ImLengthSqr(midBound - io.MousePos) <= (AnchorBigRadius * AnchorBigRadius); + + int type = MT_NONE; + vec_t gizmoHitProportion; + + if(Intersects(operation, TRANSLATE)) + { + type = GetMoveType(operation, &gizmoHitProportion); + } + if(Intersects(operation, ROTATE) && type == MT_NONE) + { + type = GetRotateType(operation); + } + if(Intersects(operation, SCALE) && type == MT_NONE) + { + type = GetScaleType(operation); + } + + if (type != MT_NONE) + { + overBigAnchor = false; + overSmallAnchor = false; + } + + ImU32 selectionColor = GetColorU32(SELECTION); + + unsigned int bigAnchorColor = overBigAnchor ? selectionColor : (IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha); + unsigned int smallAnchorColor = overSmallAnchor ? selectionColor : (IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha); + + drawList->AddCircleFilled(worldBound1, AnchorBigRadius, IM_COL32_BLACK); + drawList->AddCircleFilled(worldBound1, AnchorBigRadius - 1.2f, bigAnchorColor); + + drawList->AddCircleFilled(midBound, AnchorSmallRadius, IM_COL32_BLACK); + drawList->AddCircleFilled(midBound, AnchorSmallRadius - 1.2f, smallAnchorColor); + int oppositeIndex = (i + 2) % 4; + // big anchor on corners + if (!gContext.mbUsingBounds && gContext.mbEnable && overBigAnchor && CanActivate()) + { + gContext.mBoundsPivot.TransformPoint(aabb[(i + 2) % 4], gContext.mModelSource); + gContext.mBoundsAnchor.TransformPoint(aabb[i], gContext.mModelSource); + gContext.mBoundsPlan = BuildPlan(gContext.mBoundsAnchor, bestAxisWorldDirection); + gContext.mBoundsBestAxis = bestAxis; + gContext.mBoundsAxis[0] = secondAxis; + gContext.mBoundsAxis[1] = thirdAxis; + + gContext.mBoundsLocalPivot.Set(0.f); + gContext.mBoundsLocalPivot[secondAxis] = aabb[oppositeIndex][secondAxis]; + gContext.mBoundsLocalPivot[thirdAxis] = aabb[oppositeIndex][thirdAxis]; + + gContext.mbUsingBounds = true; + gContext.mEditingID = gContext.GetCurrentID(); + gContext.mBoundsMatrix = gContext.mModelSource; + } + // small anchor on middle of segment + if (!gContext.mbUsingBounds && gContext.mbEnable && overSmallAnchor && CanActivate()) + { + vec_t midPointOpposite = (aabb[(i + 2) % 4] + aabb[(i + 3) % 4]) * 0.5f; + gContext.mBoundsPivot.TransformPoint(midPointOpposite, gContext.mModelSource); + gContext.mBoundsAnchor.TransformPoint(midPoint, gContext.mModelSource); + gContext.mBoundsPlan = BuildPlan(gContext.mBoundsAnchor, bestAxisWorldDirection); + gContext.mBoundsBestAxis = bestAxis; + int indices[] = { secondAxis , thirdAxis }; + gContext.mBoundsAxis[0] = indices[i % 2]; + gContext.mBoundsAxis[1] = -1; + + gContext.mBoundsLocalPivot.Set(0.f); + gContext.mBoundsLocalPivot[gContext.mBoundsAxis[0]] = aabb[oppositeIndex][indices[i % 2]];// bounds[gContext.mBoundsAxis[0]] * (((i + 1) & 2) ? 1.f : -1.f); + + gContext.mbUsingBounds = true; + gContext.mEditingID = gContext.GetCurrentID(); + gContext.mBoundsMatrix = gContext.mModelSource; + } + } + + if (gContext.mbUsingBounds && (gContext.GetCurrentID() == gContext.mEditingID)) + { + matrix_t scale; + scale.SetToIdentity(); + + // compute projected mouse position on plan + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mBoundsPlan); + vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + + // compute a reference and delta vectors base on mouse move + vec_t deltaVector = (newPos - gContext.mBoundsPivot).Abs(); + vec_t referenceVector = (gContext.mBoundsAnchor - gContext.mBoundsPivot).Abs(); + + // for 1 or 2 axes, compute a ratio that's used for scale and snap it based on resulting length + for (int i = 0; i < 2; i++) + { + int axisIndex1 = gContext.mBoundsAxis[i]; + if (axisIndex1 == -1) + { + continue; + } + + float ratioAxis = 1.f; + vec_t axisDir = gContext.mBoundsMatrix.component[axisIndex1].Abs(); + + float dtAxis = axisDir.Dot(referenceVector); + float boundSize = bounds[axisIndex1 + 3] - bounds[axisIndex1]; + if (dtAxis > FLT_EPSILON) + { + ratioAxis = axisDir.Dot(deltaVector) / dtAxis; + } + + if (snapValues) + { + float length = boundSize * ratioAxis; + ComputeSnap(&length, snapValues[axisIndex1]); + if (boundSize > FLT_EPSILON) + { + ratioAxis = length / boundSize; + } + } + scale.component[axisIndex1] *= ratioAxis; + + if (fabsf(ratioAxis - 1.0f) > FLT_EPSILON) { + manipulated = true; + } + } + + // transform matrix + matrix_t preScale, postScale; + preScale.Translation(-gContext.mBoundsLocalPivot); + postScale.Translation(gContext.mBoundsLocalPivot); + matrix_t res = preScale * scale * postScale * gContext.mBoundsMatrix; + *matrix = res; + + // info text + char tmps[512]; + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + ImFormatString(tmps, sizeof(tmps), "X: %.2f Y: %.2f Z: %.2f" + , (bounds[3] - bounds[0]) * gContext.mBoundsMatrix.component[0].Length() * scale.component[0].Length() + , (bounds[4] - bounds[1]) * gContext.mBoundsMatrix.component[1].Length() * scale.component[1].Length() + , (bounds[5] - bounds[2]) * gContext.mBoundsMatrix.component[2].Length() * scale.component[2].Length() + ); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + + if (!io.MouseDown[0]) { + gContext.mbUsingBounds = false; + gContext.mEditingID = -1; + } + if (gContext.mbUsingBounds) + { + break; + } + } + + return manipulated; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + + static int GetScaleType(OPERATION op) + { + if (gContext.mbUsing) + { + return MT_NONE; + } + ImGuiIO& io = ImGui::GetIO(); + int type = MT_NONE; + + // screen + if (io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x && + io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y && + Contains(op, SCALE)) + { + type = MT_SCALE_XYZ; + } + + // compute + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + if(!Intersects(op, static_cast(SCALE_X << i))) + { + continue; + } + bool isAxisMasked = ((1 << i) & gContext.mAxisMask) != 0; + + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + dirAxis.TransformVector(gContext.mModelLocal); + dirPlaneX.TransformVector(gContext.mModelLocal); + dirPlaneY.TransformVector(gContext.mModelLocal); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModelLocal.v.position, dirAxis)); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len; + + const float startOffset = Contains(op, static_cast(TRANSLATE_X << i)) ? 1.0f : 0.1f; + const float endOffset = Contains(op, static_cast(TRANSLATE_X << i)) ? 1.4f : 1.0f; + const ImVec2 posOnPlanScreen = worldToPos(posOnPlan, gContext.mViewProjection); + const ImVec2 axisStartOnScreen = worldToPos(gContext.mModelLocal.v.position + dirAxis * gContext.mScreenFactor * startOffset, gContext.mViewProjection); + const ImVec2 axisEndOnScreen = worldToPos(gContext.mModelLocal.v.position + dirAxis * gContext.mScreenFactor * endOffset, gContext.mViewProjection); + + vec_t closestPointOnAxis = PointOnSegment(makeVect(posOnPlanScreen), makeVect(axisStartOnScreen), makeVect(axisEndOnScreen)); + + if ((closestPointOnAxis - makeVect(posOnPlanScreen)).Length() < 12.f) // pixel size + { + if (!isAxisMasked) + type = MT_SCALE_X + i; + } + } + + // universal + + vec_t deltaScreen = { io.MousePos.x - gContext.mScreenSquareCenter.x, io.MousePos.y - gContext.mScreenSquareCenter.y, 0.f, 0.f }; + float dist = deltaScreen.Length(); + if (Contains(op, SCALEU) && dist >= 17.0f && dist < 23.0f) + { + type = MT_SCALE_XYZ; + } + + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + if (!Intersects(op, static_cast(SCALE_XU << i))) + { + continue; + } + + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + + // draw axis + if (belowAxisLimit) + { + bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)); + float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; + //ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVPLocal); + //ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale) * gContext.mScreenFactor, gContext.mMVPLocal); + + float distance = sqrtf(ImLengthSqr(worldDirSSpace - io.MousePos)); + if (distance < 12.f) + { + type = MT_SCALE_X + i; + } + } + } + return type; + } + + static int GetRotateType(OPERATION op) + { + if (gContext.mbUsing) + { + return MT_NONE; + } + + bool isNoAxesMasked = !gContext.mAxisMask; + bool isMultipleAxesMasked = (gContext.mAxisMask & (gContext.mAxisMask - 1)) != 0; + + ImGuiIO& io = ImGui::GetIO(); + int type = MT_NONE; + + vec_t deltaScreen = { io.MousePos.x - gContext.mScreenSquareCenter.x, io.MousePos.y - gContext.mScreenSquareCenter.y, 0.f, 0.f }; + float dist = deltaScreen.Length(); + if (Intersects(op, ROTATE_SCREEN) && dist >= (gContext.mRadiusSquareCenter - 4.0f) && dist < (gContext.mRadiusSquareCenter + 4.0f)) + { + if (!isNoAxesMasked) + return MT_NONE; + type = MT_ROTATE_SCREEN; + } + + const vec_t planNormals[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir }; + + vec_t modelViewPos; + modelViewPos.TransformPoint(gContext.mModel.v.position, gContext.mViewMat); + + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + if(!Intersects(op, static_cast(ROTATE_X << i))) + { + continue; + } + bool isAxisMasked = ((1 << i) & gContext.mAxisMask) != 0; + // pickup plan + vec_t pickupPlan = BuildPlan(gContext.mModel.v.position, planNormals[i]); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, pickupPlan); + const vec_t intersectWorldPos = gContext.mRayOrigin + gContext.mRayVector * len; + vec_t intersectViewPos; + intersectViewPos.TransformPoint(intersectWorldPos, gContext.mViewMat); + + if (ImAbs(modelViewPos.z) - ImAbs(intersectViewPos.z) < -FLT_EPSILON) + { + continue; + } + + const vec_t localPos = intersectWorldPos - gContext.mModel.v.position; + vec_t idealPosOnCircle = Normalized(localPos); + idealPosOnCircle.TransformVector(gContext.mModelInverse); + const ImVec2 idealPosOnCircleScreen = worldToPos(idealPosOnCircle * rotationDisplayFactor * gContext.mScreenFactor, gContext.mMVP); + + //gContext.mDrawList->AddCircle(idealPosOnCircleScreen, 5.f, IM_COL32_WHITE); + const ImVec2 distanceOnScreen = idealPosOnCircleScreen - io.MousePos; + + const float distance = makeVect(distanceOnScreen).Length(); + if (distance < 8.f) // pixel size + { + if ((!isAxisMasked || isMultipleAxesMasked) && !isNoAxesMasked) + break; + type = MT_ROTATE_X + i; + } + } + + return type; + } + + static int GetMoveType(OPERATION op, vec_t* gizmoHitProportion) + { + if(!Intersects(op, TRANSLATE) || gContext.mbUsing || !gContext.mbMouseOver) + { + return MT_NONE; + } + + bool isNoAxesMasked = !gContext.mAxisMask; + bool isMultipleAxesMasked = (gContext.mAxisMask & (gContext.mAxisMask - 1)) != 0; + + ImGuiIO& io = ImGui::GetIO(); + int type = MT_NONE; + + // screen + if (io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x && + io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y && + Contains(op, TRANSLATE)) + { + type = MT_MOVE_SCREEN; + } + + const vec_t screenCoord = makeVect(io.MousePos - ImVec2(gContext.mX, gContext.mY)); + + // compute + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + bool isAxisMasked = ((1 << i) & gContext.mAxisMask) != 0; + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + dirAxis.TransformVector(gContext.mModel); + dirPlaneX.TransformVector(gContext.mModel); + dirPlaneY.TransformVector(gContext.mModel); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModel.v.position, dirAxis)); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len; + + const ImVec2 axisStartOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor * 0.1f, gContext.mViewProjection) - ImVec2(gContext.mX, gContext.mY); + const ImVec2 axisEndOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor, gContext.mViewProjection) - ImVec2(gContext.mX, gContext.mY); + + vec_t closestPointOnAxis = PointOnSegment(screenCoord, makeVect(axisStartOnScreen), makeVect(axisEndOnScreen)); + if ((closestPointOnAxis - screenCoord).Length() < 12.f && Intersects(op, static_cast(TRANSLATE_X << i))) // pixel size + { + if (isAxisMasked) + break; + type = MT_MOVE_X + i; + } + + const float dx = dirPlaneX.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor)); + const float dy = dirPlaneY.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor)); + if (belowPlaneLimit && dx >= quadUV[0] && dx <= quadUV[4] && dy >= quadUV[1] && dy <= quadUV[3] && Contains(op, TRANSLATE_PLANS[i])) + { + if ((!isAxisMasked || isMultipleAxesMasked) && !isNoAxesMasked) + break; + type = MT_MOVE_YZ + i; + } + + if (gizmoHitProportion) + { + *gizmoHitProportion = makeVect(dx, dy, 0.f); + } + } + return type; + } + + static bool HandleTranslation(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) + { + if(!Intersects(op, TRANSLATE) || type != MT_NONE) + { + return false; + } + const ImGuiIO& io = ImGui::GetIO(); + const bool applyRotationLocaly = gContext.mMode == LOCAL || type == MT_MOVE_SCREEN; + bool modified = false; + + // move + if (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID) && IsTranslateType(gContext.mCurrentOperation)) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + const float signedLength = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + const float len = fabsf(signedLength); // near plan + const vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + + // compute delta + const vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor; + vec_t delta = newOrigin - gContext.mModel.v.position; + + // 1 axis constraint + if (gContext.mCurrentOperation >= MT_MOVE_X && gContext.mCurrentOperation <= MT_MOVE_Z) + { + const int axisIndex = gContext.mCurrentOperation - MT_MOVE_X; + const vec_t& axisValue = *(vec_t*)&gContext.mModel.m[axisIndex]; + const float lengthOnAxis = Dot(axisValue, delta); + delta = axisValue * lengthOnAxis; + } + + // snap + if (snap) + { + vec_t cumulativeDelta = gContext.mModel.v.position + delta - gContext.mMatrixOrigin; + if (applyRotationLocaly) + { + matrix_t modelSourceNormalized = gContext.mModelSource; + modelSourceNormalized.OrthoNormalize(); + matrix_t modelSourceNormalizedInverse; + modelSourceNormalizedInverse.Inverse(modelSourceNormalized); + cumulativeDelta.TransformVector(modelSourceNormalizedInverse); + ComputeSnap(cumulativeDelta, snap); + cumulativeDelta.TransformVector(modelSourceNormalized); + } + else + { + ComputeSnap(cumulativeDelta, snap); + } + delta = gContext.mMatrixOrigin + cumulativeDelta - gContext.mModel.v.position; + + } + + if (delta != gContext.mTranslationLastDelta) + { + modified = true; + } + gContext.mTranslationLastDelta = delta; + + // compute matrix & delta + matrix_t deltaMatrixTranslation; + deltaMatrixTranslation.Translation(delta); + if (deltaMatrix) + { + memcpy(deltaMatrix, deltaMatrixTranslation.m16, sizeof(float) * 16); + } + + const matrix_t res = gContext.mModelSource * deltaMatrixTranslation; + *(matrix_t*)matrix = res; + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + } + + type = gContext.mCurrentOperation; + } + else + { + // find new possible way to move + vec_t gizmoHitProportion; + type = gContext.mbOverGizmoHotspot ? MT_NONE : GetMoveType(op, &gizmoHitProportion); + gContext.mbOverGizmoHotspot |= type != MT_NONE; + if (type != MT_NONE) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + } + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.GetCurrentID(); + gContext.mCurrentOperation = type; + vec_t movePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, + gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, + -gContext.mCameraDir }; + + vec_t cameraToModelNormalized = Normalized(gContext.mModel.v.position - gContext.mCameraEye); + for (unsigned int i = 0; i < 3; i++) + { + vec_t orthoVector = Cross(movePlanNormal[i], cameraToModelNormalized); + movePlanNormal[i].Cross(orthoVector); + movePlanNormal[i].Normalize(); + } + // pickup plan + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - MT_MOVE_X]); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len; + gContext.mMatrixOrigin = gContext.mModel.v.position; + + gContext.mRelativeOrigin = (gContext.mTranslationPlanOrigin - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor); + } + } + return modified; + } + + static bool HandleScale(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) + { + if((!Intersects(op, SCALE) && !Intersects(op, SCALEU)) || type != MT_NONE || !gContext.mbMouseOver) + { + return false; + } + ImGuiIO& io = ImGui::GetIO(); + bool modified = false; + + if (!gContext.mbUsing) + { + // find new possible way to scale + type = gContext.mbOverGizmoHotspot ? MT_NONE : GetScaleType(op); + gContext.mbOverGizmoHotspot |= type != MT_NONE; + + if (type != MT_NONE) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + } + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.GetCurrentID(); + gContext.mCurrentOperation = type; + const vec_t movePlanNormal[] = { gContext.mModelLocal.v.up, gContext.mModelLocal.v.dir, gContext.mModelLocal.v.right, gContext.mModelLocal.v.dir, gContext.mModelLocal.v.up, gContext.mModelLocal.v.right, -gContext.mCameraDir }; + // pickup plan + + gContext.mTranslationPlan = BuildPlan(gContext.mModelLocal.v.position, movePlanNormal[type - MT_SCALE_X]); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len; + gContext.mMatrixOrigin = gContext.mModelLocal.v.position; + gContext.mScale.Set(1.f, 1.f, 1.f); + gContext.mRelativeOrigin = (gContext.mTranslationPlanOrigin - gContext.mModelLocal.v.position) * (1.f / gContext.mScreenFactor); + gContext.mScaleValueOrigin = makeVect(gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length()); + gContext.mSaveMousePosx = io.MousePos.x; + } + } + // scale + if (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID) && IsScaleType(gContext.mCurrentOperation)) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor; + vec_t delta = newOrigin - gContext.mModelLocal.v.position; + + // 1 axis constraint + if (gContext.mCurrentOperation >= MT_SCALE_X && gContext.mCurrentOperation <= MT_SCALE_Z) + { + int axisIndex = gContext.mCurrentOperation - MT_SCALE_X; + const vec_t& axisValue = *(vec_t*)&gContext.mModelLocal.m[axisIndex]; + float lengthOnAxis = Dot(axisValue, delta); + delta = axisValue * lengthOnAxis; + + vec_t baseVector = gContext.mTranslationPlanOrigin - gContext.mModelLocal.v.position; + float ratio = Dot(axisValue, baseVector + delta) / Dot(axisValue, baseVector); + + gContext.mScale[axisIndex] = max(ratio, 0.001f); + } + else + { + float scaleDelta = (io.MousePos.x - gContext.mSaveMousePosx) * 0.01f; + gContext.mScale.Set(max(1.f + scaleDelta, 0.001f)); + } + + // snap + if (snap) + { + float scaleSnap[] = { snap[0], snap[0], snap[0] }; + ComputeSnap(gContext.mScale, scaleSnap); + } + + // no 0 allowed + for (int i = 0; i < 3; i++) + gContext.mScale[i] = max(gContext.mScale[i], 0.001f); + + if (gContext.mScaleLast != gContext.mScale) + { + modified = true; + } + gContext.mScaleLast = gContext.mScale; + + // compute matrix & delta + matrix_t deltaMatrixScale; + deltaMatrixScale.Scale(gContext.mScale * gContext.mScaleValueOrigin); + + matrix_t res = deltaMatrixScale * gContext.mModelLocal; + *(matrix_t*)matrix = res; + + if (deltaMatrix) + { + vec_t deltaScale = gContext.mScale * gContext.mScaleValueOrigin; + + vec_t originalScaleDivider; + originalScaleDivider.x = 1 / gContext.mModelScaleOrigin.x; + originalScaleDivider.y = 1 / gContext.mModelScaleOrigin.y; + originalScaleDivider.z = 1 / gContext.mModelScaleOrigin.z; + + deltaScale = deltaScale * originalScaleDivider; + + deltaMatrixScale.Scale(deltaScale); + memcpy(deltaMatrix, deltaMatrixScale.m16, sizeof(float) * 16); + } + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + gContext.mScale.Set(1.f, 1.f, 1.f); + } + + type = gContext.mCurrentOperation; + } + return modified; + } + + static bool HandleRotation(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) + { + if(!Intersects(op, ROTATE) || type != MT_NONE || !gContext.mbMouseOver) + { + return false; + } + ImGuiIO& io = ImGui::GetIO(); + bool applyRotationLocaly = gContext.mMode == LOCAL; + bool modified = false; + + if (!gContext.mbUsing) + { + type = gContext.mbOverGizmoHotspot ? MT_NONE : GetRotateType(op); + gContext.mbOverGizmoHotspot |= type != MT_NONE; + + if (type != MT_NONE) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + } + + if (type == MT_ROTATE_SCREEN) + { + applyRotationLocaly = true; + } + + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.GetCurrentID(); + gContext.mCurrentOperation = type; + const vec_t rotatePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, -gContext.mCameraDir }; + // pickup plan + if (applyRotationLocaly) + { + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, rotatePlanNormal[type - MT_ROTATE_X]); + } + else + { + gContext.mTranslationPlan = BuildPlan(gContext.mModelSource.v.position, directionUnary[type - MT_ROTATE_X]); + } + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t localPos = gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position; + gContext.mRotationVectorSource = Normalized(localPos); + gContext.mRotationAngleOrigin = ComputeAngleOnPlan(); + } + } + + // rotation + if (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID) && IsRotateType(gContext.mCurrentOperation)) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + gContext.mRotationAngle = ComputeAngleOnPlan(); + if (snap) + { + float snapInRadian = snap[0] * DEG2RAD; + ComputeSnap(&gContext.mRotationAngle, snapInRadian); + } + vec_t rotationAxisLocalSpace; + + rotationAxisLocalSpace.TransformVector(makeVect(gContext.mTranslationPlan.x, gContext.mTranslationPlan.y, gContext.mTranslationPlan.z, 0.f), gContext.mModelInverse); + rotationAxisLocalSpace.Normalize(); + + matrix_t deltaRotation; + deltaRotation.RotationAxis(rotationAxisLocalSpace, gContext.mRotationAngle - gContext.mRotationAngleOrigin); + if (gContext.mRotationAngle != gContext.mRotationAngleOrigin) + { + modified = true; + } + gContext.mRotationAngleOrigin = gContext.mRotationAngle; + + matrix_t scaleOrigin; + scaleOrigin.Scale(gContext.mModelScaleOrigin); + + if (applyRotationLocaly) + { + *(matrix_t*)matrix = scaleOrigin * deltaRotation * gContext.mModelLocal; + } + else + { + matrix_t res = gContext.mModelSource; + res.v.position.Set(0.f); + + *(matrix_t*)matrix = res * deltaRotation; + ((matrix_t*)matrix)->v.position = gContext.mModelSource.v.position; + } + + if (deltaMatrix) + { + *(matrix_t*)deltaMatrix = gContext.mModelInverse * deltaRotation * gContext.mModel; + } + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + gContext.mEditingID = -1; + } + type = gContext.mCurrentOperation; + } + return modified; + } + + void DecomposeMatrixToComponents(const float* matrix, float* translation, float* rotation, float* scale) + { + matrix_t mat = *(matrix_t*)matrix; + + scale[0] = mat.v.right.Length(); + scale[1] = mat.v.up.Length(); + scale[2] = mat.v.dir.Length(); + + mat.OrthoNormalize(); + + rotation[0] = RAD2DEG * atan2f(mat.m[1][2], mat.m[2][2]); + rotation[1] = RAD2DEG * atan2f(-mat.m[0][2], sqrtf(mat.m[1][2] * mat.m[1][2] + mat.m[2][2] * mat.m[2][2])); + rotation[2] = RAD2DEG * atan2f(mat.m[0][1], mat.m[0][0]); + + translation[0] = mat.v.position.x; + translation[1] = mat.v.position.y; + translation[2] = mat.v.position.z; + } + + void RecomposeMatrixFromComponents(const float* translation, const float* rotation, const float* scale, float* matrix) + { + matrix_t& mat = *(matrix_t*)matrix; + + matrix_t rot[3]; + for (int i = 0; i < 3; i++) + { + rot[i].RotationAxis(directionUnary[i], rotation[i] * DEG2RAD); + } + + mat = rot[0] * rot[1] * rot[2]; + + float validScale[3]; + for (int i = 0; i < 3; i++) + { + if (fabsf(scale[i]) < FLT_EPSILON) + { + validScale[i] = 0.001f; + } + else + { + validScale[i] = scale[i]; + } + } + mat.v.right *= validScale[0]; + mat.v.up *= validScale[1]; + mat.v.dir *= validScale[2]; + mat.v.position.Set(translation[0], translation[1], translation[2], 1.f); + } + + void SetAlternativeWindow(ImGuiWindow* window) + { + gContext.mAlternativeWindow = window; + } + + void SetID(int id) + { + if (gContext.mIDStack.empty()) + { + gContext.mIDStack.push_back(-1); + } + gContext.mIDStack.back() = id; + } + + ImGuiID GetID(const char* str, const char* str_end) + { + ImGuiID seed = gContext.GetCurrentID(); + ImGuiID id = ImHashStr(str, str_end ? (str_end - str) : 0, seed); + return id; + } + + ImGuiID GetID(const char* str) + { + return GetID(str, nullptr); + } + + ImGuiID GetID(const void* ptr) + { + ImGuiID seed = gContext.GetCurrentID(); + ImGuiID id = ImHashData(&ptr, sizeof(void*), seed); + return id; + } + + ImGuiID GetID(int n) + { + ImGuiID seed = gContext.GetCurrentID(); + ImGuiID id = ImHashData(&n, sizeof(n), seed); + return id; + } + + void PushID(const char* str_id) + { + ImGuiID id = GetID(str_id); + gContext.mIDStack.push_back(id); + } + + void PushID(const char* str_id_begin, const char* str_id_end) + { + ImGuiID id = GetID(str_id_begin, str_id_end); + gContext.mIDStack.push_back(id); + } + + void PushID(const void* ptr_id) + { + ImGuiID id = GetID(ptr_id); + gContext.mIDStack.push_back(id); + } + + void PushID(int int_id) + { + ImGuiID id = GetID(int_id); + gContext.mIDStack.push_back(id); + } + + void PopID() + { + IM_ASSERT(gContext.mIDStack.Size > 1); // Too many PopID(), or could be popping in a wrong/different window? + gContext.mIDStack.pop_back(); + if (gContext.mIDStack.empty()) + { + gContext.mIDStack.clear(); + } + } + + void AllowAxisFlip(bool value) + { + gContext.mAllowAxisFlip = value; + } + + void SetAxisLimit(float value) + { + gContext.mAxisLimit=value; + } + + void SetAxisMask(bool x, bool y, bool z) + { + gContext.mAxisMask = (x ? 1 : 0) + (y ? 2 : 0) + (z ? 4 : 0); + } + + void SetPlaneLimit(float value) + { + gContext.mPlaneLimit = value; + } + + bool IsOver(float* position, float pixelRadius) + { + const ImGuiIO& io = ImGui::GetIO(); + + float radius = sqrtf((ImLengthSqr(worldToPos({ position[0], position[1], position[2], 0.0f }, gContext.mViewProjection) - io.MousePos))); + return radius < pixelRadius; + } + + bool Manipulate(const float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float* deltaMatrix, const float* snap, const float* localBounds, const float* boundsSnap) + { + gContext.mDrawList->PushClipRect (ImVec2 (gContext.mX, gContext.mY), ImVec2 (gContext.mX + gContext.mWidth, gContext.mY + gContext.mHeight), false); + + // Scale is always local or matrix will be skewed when applying world scale or oriented matrix + ComputeContext(view, projection, matrix, (operation & SCALE) ? LOCAL : mode); + + // set delta to identity + if (deltaMatrix) + { + ((matrix_t*)deltaMatrix)->SetToIdentity(); + } + + // behind camera + vec_t camSpacePosition; + camSpacePosition.TransformPoint(makeVect(0.f, 0.f, 0.f), gContext.mMVP); + if (!gContext.mIsOrthographic && camSpacePosition.z < 0.001f && !gContext.mbUsing) + { + return false; + } + + // -- + int type = MT_NONE; + bool manipulated = false; + if (gContext.mbEnable) + { + if (!gContext.mbUsingBounds) + { + manipulated = HandleTranslation(matrix, deltaMatrix, operation, type, snap) || + HandleScale(matrix, deltaMatrix, operation, type, snap) || + HandleRotation(matrix, deltaMatrix, operation, type, snap); + } + } + + if (localBounds && !gContext.mbUsing) + { + manipulated |= HandleAndDrawLocalBounds(localBounds, (matrix_t*)matrix, boundsSnap, operation); + } + + gContext.mOperation = operation; + if (!gContext.mbUsingBounds) + { + DrawRotationGizmo(operation, type); + DrawTranslationGizmo(operation, type); + DrawScaleGizmo(operation, type); + DrawScaleUniveralGizmo(operation, type); + } + + gContext.mDrawList->PopClipRect (); + return manipulated; + } + + void SetGizmoSizeClipSpace(float value) + { + gContext.mGizmoSizeClipSpace = value; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////// + void ComputeFrustumPlanes(vec_t* frustum, const float* clip) + { + frustum[0].x = clip[3] - clip[0]; + frustum[0].y = clip[7] - clip[4]; + frustum[0].z = clip[11] - clip[8]; + frustum[0].w = clip[15] - clip[12]; + + frustum[1].x = clip[3] + clip[0]; + frustum[1].y = clip[7] + clip[4]; + frustum[1].z = clip[11] + clip[8]; + frustum[1].w = clip[15] + clip[12]; + + frustum[2].x = clip[3] + clip[1]; + frustum[2].y = clip[7] + clip[5]; + frustum[2].z = clip[11] + clip[9]; + frustum[2].w = clip[15] + clip[13]; + + frustum[3].x = clip[3] - clip[1]; + frustum[3].y = clip[7] - clip[5]; + frustum[3].z = clip[11] - clip[9]; + frustum[3].w = clip[15] - clip[13]; + + frustum[4].x = clip[3] - clip[2]; + frustum[4].y = clip[7] - clip[6]; + frustum[4].z = clip[11] - clip[10]; + frustum[4].w = clip[15] - clip[14]; + + frustum[5].x = clip[3] + clip[2]; + frustum[5].y = clip[7] + clip[6]; + frustum[5].z = clip[11] + clip[10]; + frustum[5].w = clip[15] + clip[14]; + + for (int i = 0; i < 6; i++) + { + frustum[i].Normalize(); + } + } + + void DrawCubes(const float* view, const float* projection, const float* matrices, int matrixCount) + { + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)view); + + struct CubeFace + { + float z; + ImVec2 faceCoordsScreen[4]; + ImU32 color; + }; + CubeFace* faces = (CubeFace*)_malloca(sizeof(CubeFace) * matrixCount * 6); + + if (!faces) + { + return; + } + + vec_t frustum[6]; + matrix_t viewProjection = *(matrix_t*)view * *(matrix_t*)projection; + ComputeFrustumPlanes(frustum, viewProjection.m16); + + int cubeFaceCount = 0; + for (int cube = 0; cube < matrixCount; cube++) + { + const float* matrix = &matrices[cube * 16]; + + matrix_t res = *(matrix_t*)matrix * *(matrix_t*)view * *(matrix_t*)projection; + + for (int iFace = 0; iFace < 6; iFace++) + { + const int normalIndex = (iFace % 3); + const int perpXIndex = (normalIndex + 1) % 3; + const int perpYIndex = (normalIndex + 2) % 3; + const float invert = (iFace > 2) ? -1.f : 1.f; + + const vec_t faceCoords[4] = { directionUnary[normalIndex] + directionUnary[perpXIndex] + directionUnary[perpYIndex], + directionUnary[normalIndex] + directionUnary[perpXIndex] - directionUnary[perpYIndex], + directionUnary[normalIndex] - directionUnary[perpXIndex] - directionUnary[perpYIndex], + directionUnary[normalIndex] - directionUnary[perpXIndex] + directionUnary[perpYIndex], + }; + + // clipping + /* + bool skipFace = false; + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + vec_t camSpacePosition; + camSpacePosition.TransformPoint(faceCoords[iCoord] * 0.5f * invert, res); + if (camSpacePosition.z < 0.001f) + { + skipFace = true; + break; + } + } + if (skipFace) + { + continue; + } + */ + vec_t centerPosition, centerPositionVP; + centerPosition.TransformPoint(directionUnary[normalIndex] * 0.5f * invert, *(matrix_t*)matrix); + centerPositionVP.TransformPoint(directionUnary[normalIndex] * 0.5f * invert, res); + + bool inFrustum = true; + for (int iFrustum = 0; iFrustum < 6; iFrustum++) + { + float dist = DistanceToPlane(centerPosition, frustum[iFrustum]); + if (dist < 0.f) + { + inFrustum = false; + break; + } + } + + if (!inFrustum) + { + continue; + } + CubeFace& cubeFace = faces[cubeFaceCount]; + + // 3D->2D + //ImVec2 faceCoordsScreen[4]; + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + cubeFace.faceCoordsScreen[iCoord] = worldToPos(faceCoords[iCoord] * 0.5f * invert, res); + } + + ImU32 directionColor = GetColorU32(DIRECTION_X + normalIndex); + cubeFace.color = directionColor | IM_COL32(0x80, 0x80, 0x80, 0); + + cubeFace.z = centerPositionVP.z / centerPositionVP.w; + cubeFaceCount++; + } + } + qsort(faces, cubeFaceCount, sizeof(CubeFace), [](void const* _a, void const* _b) { + CubeFace* a = (CubeFace*)_a; + CubeFace* b = (CubeFace*)_b; + if (a->z < b->z) + { + return 1; + } + return -1; + }); + // draw face with lighter color + for (int iFace = 0; iFace < cubeFaceCount; iFace++) + { + const CubeFace& cubeFace = faces[iFace]; + gContext.mDrawList->AddConvexPolyFilled(cubeFace.faceCoordsScreen, 4, cubeFace.color); + } + + _freea(faces); + } + + void DrawGrid(const float* view, const float* projection, const float* matrix, const float gridSize) + { + matrix_t viewProjection = *(matrix_t*)view * *(matrix_t*)projection; + vec_t frustum[6]; + ComputeFrustumPlanes(frustum, viewProjection.m16); + matrix_t res = *(matrix_t*)matrix * viewProjection; + + for (float f = -gridSize; f <= gridSize; f += 1.f) + { + for (int dir = 0; dir < 2; dir++) + { + vec_t ptA = makeVect(dir ? -gridSize : f, 0.f, dir ? f : -gridSize); + vec_t ptB = makeVect(dir ? gridSize : f, 0.f, dir ? f : gridSize); + bool visible = true; + for (int i = 0; i < 6; i++) + { + float dA = DistanceToPlane(ptA, frustum[i]); + float dB = DistanceToPlane(ptB, frustum[i]); + if (dA < 0.f && dB < 0.f) + { + visible = false; + break; + } + if (dA > 0.f && dB > 0.f) + { + continue; + } + if (dA < 0.f) + { + float len = fabsf(dA - dB); + float t = fabsf(dA) / len; + ptA.Lerp(ptB, t); + } + if (dB < 0.f) + { + float len = fabsf(dB - dA); + float t = fabsf(dB) / len; + ptB.Lerp(ptA, t); + } + } + if (visible) + { + ImU32 col = IM_COL32(0x80, 0x80, 0x80, 0xFF); + col = (fmodf(fabsf(f), 10.f) < FLT_EPSILON) ? IM_COL32(0x90, 0x90, 0x90, 0xFF) : col; + col = (fabsf(f) < FLT_EPSILON) ? IM_COL32(0x40, 0x40, 0x40, 0xFF): col; + + float thickness = 1.f; + thickness = (fmodf(fabsf(f), 10.f) < FLT_EPSILON) ? 1.5f : thickness; + thickness = (fabsf(f) < FLT_EPSILON) ? 2.3f : thickness; + + gContext.mDrawList->AddLine(worldToPos(ptA, res), worldToPos(ptB, res), col, thickness); + } + } + } + } + + void ViewManipulate(float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor) + { + // Scale is always local or matrix will be skewed when applying world scale or oriented matrix + ComputeContext(view, projection, matrix, (operation & SCALE) ? LOCAL : mode); + ViewManipulate(view, length, position, size, backgroundColor); + } + + void ViewManipulate(float* view, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor) + { + static bool isDraging = false; + static bool isClicking = false; + static vec_t interpolationUp; + static vec_t interpolationDir; + static int interpolationFrames = 0; + const vec_t referenceUp = makeVect(0.f, 1.f, 0.f); + + matrix_t svgView, svgProjection; + svgView = gContext.mViewMat; + svgProjection = gContext.mProjectionMat; + + ImGuiIO& io = ImGui::GetIO(); + gContext.mDrawList->AddRectFilled(position, position + size, backgroundColor); + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)view); + + const vec_t camTarget = viewInverse.v.position - viewInverse.v.dir * length; + + // view/projection matrices + const float distance = 3.f; + matrix_t cubeProjection, cubeView; + float fov = acosf(distance / (sqrtf(distance * distance + 3.f))) * RAD2DEG; + Perspective(fov / sqrtf(2.f), size.x / size.y, 0.01f, 1000.f, cubeProjection.m16); + + vec_t dir = makeVect(viewInverse.m[2][0], viewInverse.m[2][1], viewInverse.m[2][2]); + vec_t up = makeVect(viewInverse.m[1][0], viewInverse.m[1][1], viewInverse.m[1][2]); + vec_t eye = dir * distance; + vec_t zero = makeVect(0.f, 0.f); + LookAt(&eye.x, &zero.x, &up.x, cubeView.m16); + + // set context + gContext.mViewMat = cubeView; + gContext.mProjectionMat = cubeProjection; + ComputeCameraRay(gContext.mRayOrigin, gContext.mRayVector, position, size); + + const matrix_t res = cubeView * cubeProjection; + + // panels + static const ImVec2 panelPosition[9] = { ImVec2(0.75f,0.75f), ImVec2(0.25f, 0.75f), ImVec2(0.f, 0.75f), + ImVec2(0.75f, 0.25f), ImVec2(0.25f, 0.25f), ImVec2(0.f, 0.25f), + ImVec2(0.75f, 0.f), ImVec2(0.25f, 0.f), ImVec2(0.f, 0.f) }; + + static const ImVec2 panelSize[9] = { ImVec2(0.25f,0.25f), ImVec2(0.5f, 0.25f), ImVec2(0.25f, 0.25f), + ImVec2(0.25f, 0.5f), ImVec2(0.5f, 0.5f), ImVec2(0.25f, 0.5f), + ImVec2(0.25f, 0.25f), ImVec2(0.5f, 0.25f), ImVec2(0.25f, 0.25f) }; + + // tag faces + bool boxes[27]{}; + static int overBox = -1; + for (int iPass = 0; iPass < 2; iPass++) + { + for (int iFace = 0; iFace < 6; iFace++) + { + const int normalIndex = (iFace % 3); + const int perpXIndex = (normalIndex + 1) % 3; + const int perpYIndex = (normalIndex + 2) % 3; + const float invert = (iFace > 2) ? -1.f : 1.f; + const vec_t indexVectorX = directionUnary[perpXIndex] * invert; + const vec_t indexVectorY = directionUnary[perpYIndex] * invert; + const vec_t boxOrigin = directionUnary[normalIndex] * -invert - indexVectorX - indexVectorY; + + // plan local space + const vec_t n = directionUnary[normalIndex] * invert; + vec_t viewSpaceNormal = n; + vec_t viewSpacePoint = n * 0.5f; + viewSpaceNormal.TransformVector(cubeView); + viewSpaceNormal.Normalize(); + viewSpacePoint.TransformPoint(cubeView); + const vec_t viewSpaceFacePlan = BuildPlan(viewSpacePoint, viewSpaceNormal); + + // back face culling + if (viewSpaceFacePlan.w > 0.f) + { + continue; + } + + const vec_t facePlan = BuildPlan(n * 0.5f, n); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, facePlan); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len - (n * 0.5f); + + float localx = Dot(directionUnary[perpXIndex], posOnPlan) * invert + 0.5f; + float localy = Dot(directionUnary[perpYIndex], posOnPlan) * invert + 0.5f; + + // panels + const vec_t dx = directionUnary[perpXIndex]; + const vec_t dy = directionUnary[perpYIndex]; + const vec_t origin = directionUnary[normalIndex] - dx - dy; + for (int iPanel = 0; iPanel < 9; iPanel++) + { + vec_t boxCoord = boxOrigin + indexVectorX * float(iPanel % 3) + indexVectorY * float(iPanel / 3) + makeVect(1.f, 1.f, 1.f); + const ImVec2 p = panelPosition[iPanel] * 2.f; + const ImVec2 s = panelSize[iPanel] * 2.f; + ImVec2 faceCoordsScreen[4]; + vec_t panelPos[4] = { dx * p.x + dy * p.y, + dx * p.x + dy * (p.y + s.y), + dx * (p.x + s.x) + dy * (p.y + s.y), + dx * (p.x + s.x) + dy * p.y }; + + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + faceCoordsScreen[iCoord] = worldToPos((panelPos[iCoord] + origin) * 0.5f * invert, res, position, size); + } + + const ImVec2 panelCorners[2] = { panelPosition[iPanel], panelPosition[iPanel] + panelSize[iPanel] }; + bool insidePanel = localx > panelCorners[0].x && localx < panelCorners[1].x && localy > panelCorners[0].y && localy < panelCorners[1].y; + int boxCoordInt = int(boxCoord.x * 9.f + boxCoord.y * 3.f + boxCoord.z); + IM_ASSERT(boxCoordInt < 27); + boxes[boxCoordInt] |= insidePanel && (!isDraging) && gContext.mbMouseOver; + + // draw face with lighter color + if (iPass) + { + ImU32 directionColor = GetColorU32(DIRECTION_X + normalIndex); + gContext.mDrawList->AddConvexPolyFilled(faceCoordsScreen, 4, (directionColor | IM_COL32(0x80, 0x80, 0x80, 0x80)) | (gContext.mIsViewManipulatorHovered ? IM_COL32(0x08, 0x08, 0x08, 0) : 0)); + if (boxes[boxCoordInt]) + { + ImU32 selectionColor = GetColorU32(SELECTION); + gContext.mDrawList->AddConvexPolyFilled(faceCoordsScreen, 4, selectionColor); + + if (io.MouseDown[0] && !isClicking && !isDraging && GImGui->ActiveId == 0) { + overBox = boxCoordInt; + isClicking = true; + isDraging = true; + } + } + } + } + } + } + if (interpolationFrames) + { + interpolationFrames--; + vec_t newDir = viewInverse.v.dir; + newDir.Lerp(interpolationDir, 0.2f); + newDir.Normalize(); + + vec_t newUp = viewInverse.v.up; + newUp.Lerp(interpolationUp, 0.3f); + newUp.Normalize(); + newUp = interpolationUp; + vec_t newEye = camTarget + newDir * length; + LookAt(&newEye.x, &camTarget.x, &newUp.x, view); + } + gContext.mIsViewManipulatorHovered = gContext.mbMouseOver && ImRect(position, position + size).Contains(io.MousePos); + + if (io.MouseDown[0] && (fabsf(io.MouseDelta[0]) || fabsf(io.MouseDelta[1])) && isClicking) + { + isClicking = false; + } + + if (!io.MouseDown[0]) + { + if (isClicking) + { + // apply new view direction + int cx = overBox / 9; + int cy = (overBox - cx * 9) / 3; + int cz = overBox % 3; + interpolationDir = makeVect(1.f - (float)cx, 1.f - (float)cy, 1.f - (float)cz); + interpolationDir.Normalize(); + + if (fabsf(Dot(interpolationDir, referenceUp)) > 1.0f - 0.01f) + { + vec_t right = viewInverse.v.right; + if (fabsf(right.x) > fabsf(right.z)) + { + right.z = 0.f; + } + else + { + right.x = 0.f; + } + right.Normalize(); + interpolationUp = Cross(interpolationDir, right); + interpolationUp.Normalize(); + } + else + { + interpolationUp = referenceUp; + } + interpolationFrames = 40; + + } + isClicking = false; + isDraging = false; + } + + + if (isDraging) + { + matrix_t rx, ry, roll; + + rx.RotationAxis(referenceUp, -io.MouseDelta.x * 0.01f); + ry.RotationAxis(viewInverse.v.right, -io.MouseDelta.y * 0.01f); + + roll = rx * ry; + + vec_t newDir = viewInverse.v.dir; + newDir.TransformVector(roll); + newDir.Normalize(); + + // clamp + vec_t planDir = Cross(viewInverse.v.right, referenceUp); + planDir.y = 0.f; + planDir.Normalize(); + float dt = Dot(planDir, newDir); + if (dt < 0.0f) + { + newDir += planDir * dt; + newDir.Normalize(); + } + + vec_t newEye = camTarget + newDir * length; + LookAt(&newEye.x, &camTarget.x, &referenceUp.x, view); + } + + gContext.mbUsingViewManipulate = (interpolationFrames != 0) || isDraging; + if (isClicking || gContext.mbUsingViewManipulate || gContext.mIsViewManipulatorHovered) { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + } + + // restore view/projection because it was used to compute ray + ComputeContext(svgView.m16, svgProjection.m16, gContext.mModelSource.m16, gContext.mMode); + } +}; diff --git a/src/ThirdParty/ImGuizmo/ImGuizmo.h b/src/ThirdParty/ImGuizmo/ImGuizmo.h new file mode 100644 index 00000000..4e1789d8 --- /dev/null +++ b/src/ThirdParty/ImGuizmo/ImGuizmo.h @@ -0,0 +1,239 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v1.92.5 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2016-2021 Cedric Guillemet +// +// 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. +// +// ------------------------------------------------------------------------------------------- +// History : +// 2019/11/03 View gizmo +// 2016/09/11 Behind camera culling. Scaling Delta matrix not multiplied by source matrix scales. local/world rotation and translation fixed. Display message is incorrect (X: ... Y:...) in local mode. +// 2016/09/09 Hatched negative axis. Snapping. Documentation update. +// 2016/09/04 Axis switch and translation plan autohiding. Scale transform stability improved +// 2016/09/01 Mogwai changed to Manipulate. Draw debug cube. Fixed inverted scale. Mixing scale and translation/rotation gives bad results. +// 2016/08/31 First version +// +// ------------------------------------------------------------------------------------------- +// Future (no order): +// +// - Multi view +// - display rotation/translation/scale infos in local/world space and not only local +// - finish local/world matrix application +// - OPERATION as bitmask +// +// ------------------------------------------------------------------------------------------- +// Example +#if 0 +void EditTransform(const Camera& camera, matrix_t& matrix) +{ + static ImGuizmo::OPERATION mCurrentGizmoOperation(ImGuizmo::ROTATE); + static ImGuizmo::MODE mCurrentGizmoMode(ImGuizmo::WORLD); + if (ImGui::IsKeyPressed(90)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + if (ImGui::IsKeyPressed(69)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + if (ImGui::IsKeyPressed(82)) // r Key + mCurrentGizmoOperation = ImGuizmo::SCALE; + if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE)) + mCurrentGizmoOperation = ImGuizmo::SCALE; + float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + ImGuizmo::DecomposeMatrixToComponents(matrix.m16, matrixTranslation, matrixRotation, matrixScale); + ImGui::InputFloat3("Tr", matrixTranslation, 3); + ImGui::InputFloat3("Rt", matrixRotation, 3); + ImGui::InputFloat3("Sc", matrixScale, 3); + ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix.m16); + + if (mCurrentGizmoOperation != ImGuizmo::SCALE) + { + if (ImGui::RadioButton("Local", mCurrentGizmoMode == ImGuizmo::LOCAL)) + mCurrentGizmoMode = ImGuizmo::LOCAL; + ImGui::SameLine(); + if (ImGui::RadioButton("World", mCurrentGizmoMode == ImGuizmo::WORLD)) + mCurrentGizmoMode = ImGuizmo::WORLD; + } + static bool useSnap(false); + if (ImGui::IsKeyPressed(83)) + useSnap = !useSnap; + ImGui::Checkbox("", &useSnap); + ImGui::SameLine(); + vec_t snap; + switch (mCurrentGizmoOperation) + { + case ImGuizmo::TRANSLATE: + snap = config.mSnapTranslation; + ImGui::InputFloat3("Snap", &snap.x); + break; + case ImGuizmo::ROTATE: + snap = config.mSnapRotation; + ImGui::InputFloat("Angle Snap", &snap.x); + break; + case ImGuizmo::SCALE: + snap = config.mSnapScale; + ImGui::InputFloat("Scale Snap", &snap.x); + break; + } + ImGuiIO& io = ImGui::GetIO(); + ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); + ImGuizmo::Manipulate(camera.mView.m16, camera.mProjection.m16, mCurrentGizmoOperation, mCurrentGizmoMode, matrix.m16, NULL, useSnap ? &snap.x : NULL); +} +#endif +#pragma once + +#ifdef USE_IMGUI_API +#include "imconfig.h" +#endif +#ifndef IMGUI_API +#define IMGUI_API +#endif + +#ifndef IMGUIZMO_NAMESPACE +#define IMGUIZMO_NAMESPACE ImGuizmo +#endif + +struct ImGuiWindow; + +namespace IMGUIZMO_NAMESPACE +{ + IMGUI_API void SetDrawlist(ImDrawList* drawlist = nullptr); + IMGUI_API void BeginFrame(); + IMGUI_API void SetImGuiContext(ImGuiContext* ctx); + IMGUI_API bool IsOver(); + IMGUI_API bool IsUsing(); + IMGUI_API bool IsUsingViewManipulate(); + IMGUI_API bool IsViewManipulateHovered(); + IMGUI_API bool IsUsingAny(); + IMGUI_API void Enable(bool enable); + IMGUI_API void DecomposeMatrixToComponents(const float* matrix, float* translation, float* rotation, float* scale); + IMGUI_API void RecomposeMatrixFromComponents(const float* translation, const float* rotation, const float* scale, float* matrix); + + IMGUI_API void SetRect(float x, float y, float width, float height); + IMGUI_API void SetOrthographic(bool isOrthographic); + + IMGUI_API void DrawCubes(const float* view, const float* projection, const float* matrices, int matrixCount); + IMGUI_API void DrawGrid(const float* view, const float* projection, const float* matrix, const float gridSize); + + enum OPERATION + { + TRANSLATE_X = (1u << 0), + TRANSLATE_Y = (1u << 1), + TRANSLATE_Z = (1u << 2), + ROTATE_X = (1u << 3), + ROTATE_Y = (1u << 4), + ROTATE_Z = (1u << 5), + ROTATE_SCREEN = (1u << 6), + SCALE_X = (1u << 7), + SCALE_Y = (1u << 8), + SCALE_Z = (1u << 9), + BOUNDS = (1u << 10), + SCALE_XU = (1u << 11), + SCALE_YU = (1u << 12), + SCALE_ZU = (1u << 13), + + TRANSLATE = TRANSLATE_X | TRANSLATE_Y | TRANSLATE_Z, + ROTATE = ROTATE_X | ROTATE_Y | ROTATE_Z | ROTATE_SCREEN, + SCALE = SCALE_X | SCALE_Y | SCALE_Z, + SCALEU = SCALE_XU | SCALE_YU | SCALE_ZU, + UNIVERSAL = TRANSLATE | ROTATE | SCALEU + }; + + inline OPERATION operator|(OPERATION lhs, OPERATION rhs) + { + return static_cast(static_cast(lhs) | static_cast(rhs)); + } + + enum MODE + { + LOCAL, + WORLD + }; + + IMGUI_API bool Manipulate(const float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float* deltaMatrix = NULL, const float* snap = NULL, const float* localBounds = NULL, const float* boundsSnap = NULL); + IMGUI_API void ViewManipulate(float* view, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor); + IMGUI_API void ViewManipulate(float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor); + + IMGUI_API void SetAlternativeWindow(ImGuiWindow* window); + + [[deprecated("Use PushID/PopID instead.")]] + IMGUI_API void SetID(int id); + + IMGUI_API void PushID(const char* str_id); + IMGUI_API void PushID(const char* str_id_begin, const char* str_id_end); + IMGUI_API void PushID(const void* ptr_id); + IMGUI_API void PushID(int int_id); + IMGUI_API void PopID(); + IMGUI_API ImGuiID GetID(const char* str_id); + IMGUI_API ImGuiID GetID(const char* str_id_begin, const char* str_id_end); + IMGUI_API ImGuiID GetID(const void* ptr_id); + + IMGUI_API bool IsOver(OPERATION op); + IMGUI_API void SetGizmoSizeClipSpace(float value); + + IMGUI_API void AllowAxisFlip(bool value); + IMGUI_API void SetAxisLimit(float value); + IMGUI_API void SetAxisMask(bool x, bool y, bool z); + IMGUI_API void SetPlaneLimit(float value); + IMGUI_API bool IsOver(float* position, float pixelRadius); + + enum COLOR + { + DIRECTION_X, + DIRECTION_Y, + DIRECTION_Z, + PLANE_X, + PLANE_Y, + PLANE_Z, + SELECTION, + INACTIVE, + TRANSLATION_LINE, + SCALE_LINE, + ROTATION_USING_BORDER, + ROTATION_USING_FILL, + HATCHED_AXIS_LINES, + TEXT, + TEXT_SHADOW, + COUNT + }; + + struct Style + { + IMGUI_API Style(); + + float TranslationLineThickness; + float TranslationLineArrowSize; + float RotationLineThickness; + float RotationOuterLineThickness; + float ScaleLineThickness; + float ScaleLineCircleSize; + float HatchedAxisLineThickness; + float CenterCircleSize; + + ImVec4 Colors[COLOR::COUNT]; + }; + + IMGUI_API Style& GetStyle(); +} diff --git a/src/cmake/SourceFiles.cmake b/src/cmake/SourceFiles.cmake index daa46ec8..b0efe76f 100644 --- a/src/cmake/SourceFiles.cmake +++ b/src/cmake/SourceFiles.cmake @@ -39,6 +39,8 @@ file(GLOB_RECURSE src_files_thirdparty "ThirdParty/miniaudio/*.h" "ThirdParty/lzav/*.h" "ThirdParty/tinybvh/*.h" + "ThirdParty/ImGuizmo/*.cpp" + "ThirdParty/ImGuizmo/*.h" "ThirdParty/ozz/*.h" ) From c11225e24a9ba184ad065491f401561fe65f3b4d Mon Sep 17 00:00:00 2001 From: gameKnife Date: Wed, 28 Jan 2026 00:05:20 +0800 Subject: [PATCH 05/53] docs: record gizmo notes Document gizmo integration lessons and clarify co-author attribution for the active model. Co-authored-by: gpt-5.2-codex --- AGENTS.md | 1 + AGENT_GUIDE/notes-gizmo.md | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 AGENT_GUIDE/notes-gizmo.md diff --git a/AGENTS.md b/AGENTS.md index 42ef5c73..46b46d60 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -174,3 +174,4 @@ Notes for Agents - Prefer scripted workflows (`build.sh`, `build.ps1`, `run.sh`) over manual CMake. - If you add a new dependency, update `vcpkg.json` accordingly. - If unsure about a preset name, use `cmake --list-presets=configure`. +- When creating commits, add an AI co-author line matching the model you used (e.g., `Co-authored-by: gpt-5.2-codex `). diff --git a/AGENT_GUIDE/notes-gizmo.md b/AGENT_GUIDE/notes-gizmo.md new file mode 100644 index 00000000..6f0fe92c --- /dev/null +++ b/AGENT_GUIDE/notes-gizmo.md @@ -0,0 +1,19 @@ +# Gizmo Integration Notes (gkNextEditor / gkNextRenderer) + +This note captures practical lessons from adding ImGuizmo-based transforms and selection edge highlighting. + +## Key Takeaways +- Prefer a shared gizmo controller for Editor/Renderer so input gating and transform updates stay consistent. +- Use ImGuizmo with engine UBO matrices; for Vulkan + ImGui, flip projection Y (`projection[1][1] *= -1`) to avoid inverted gizmo. +- `ImGuizmo::SetDrawlist(ImGui::GetForegroundDrawList())` avoids creating the `Debug##Default` window. +- Input isolation: when gizmo is over/using, skip camera controls (mouse/key/scroll) to prevent viewport drift. +- Update CPU BVH after gizmo edits; a lightweight option is to call `UpdateBVH` when the drag ends (transition from using->not using). +- For tool UI, anchor the toolbar inside the active viewport and disable docking to prevent overlap. + +## Useful Integration Points +- Selection: set `Scene::SetSelectedId()` on hit and toggle `ShowEdge` for visual feedback. +- Gizmo draw call should run during UI render with the correct viewport rect (swapchain output for renderer; central dock viewport for editor). + +## Gotchas +- `ImGuizmo::BeginFrame()` is fine, but without an explicit draw list it can spawn an empty ImGui window. +- Avoid updating BVH every frame while dragging unless necessary; it is expensive. From 1a817c0f0250eae4f9549c880a5136f5d4a2234a Mon Sep 17 00:00:00 2001 From: gameKnife Date: Wed, 28 Jan 2026 12:32:03 +0800 Subject: [PATCH 06/53] feat: add focus and orbit camera controls - Implement smooth camera focus on selected nodes - Add Alt+RightClick orbit controls using selected node bounds - Refactor node bounds calculation into Assets::Scene for reuse - Optimize rotation inversion in Orbit using transpose --- .../gkNextRenderer/gkNextRenderer.cpp | 25 ++++ src/Assets/Scene.cpp | 41 ++++++ src/Assets/Scene.hpp | 1 + src/Editor/EditorMain.cpp | 44 ++++-- src/Runtime/ModelViewController.cpp | 136 ++++++++++++++++-- src/Runtime/ModelViewController.hpp | 19 +++ 6 files changed, 245 insertions(+), 21 deletions(-) diff --git a/src/Application/gkNextRenderer/gkNextRenderer.cpp b/src/Application/gkNextRenderer/gkNextRenderer.cpp index 5ef3dcac..23eb1bf2 100644 --- a/src/Application/gkNextRenderer/gkNextRenderer.cpp +++ b/src/Application/gkNextRenderer/gkNextRenderer.cpp @@ -258,6 +258,16 @@ bool NextRendererGameInstance::OnKey(SDL_Event& event) GetEngine().GetScene().SetSelectedId(-1); GetEngine().GetShowFlags().ShowEdge = false; return true; + case SDLK_F: + { + glm::vec3 focusCenter; + float radius; + if (GetEngine().GetScene().GetSelectedNodeBounds(focusCenter, radius)) + { + modelViewController_.Focus(focusCenter, radius); + } + } + break; case SDLK_F1: GetEngine().GetUserSettings().ShowSettings = !GetEngine().GetUserSettings().ShowSettings; return true; break; case SDLK_F2: GetEngine().GetUserSettings().ShowOverlay = !GetEngine().GetUserSettings().ShowOverlay; return true; @@ -282,6 +292,21 @@ bool NextRendererGameInstance::OnKey(SDL_Event& event) bool NextRendererGameInstance::OnCursorPosition(double xpos, double ypos) { + // Update Controller Context + bool alt = (SDL_GetModState() & SDL_KMOD_ALT) != 0; + modelViewController_.SetAltPressed(alt); + + glm::vec3 center; + float radius; + if (GetEngine().GetScene().GetSelectedNodeBounds(center, radius)) + { + modelViewController_.SetOrbitTarget(center); + } + else + { + modelViewController_.SetOrbitTarget(std::nullopt); + } + if (!gizmoController_.IsInteracting()) { modelViewController_.OnCursorPosition(xpos, ypos); diff --git a/src/Assets/Scene.cpp b/src/Assets/Scene.cpp index c91c895f..68073782 100644 --- a/src/Assets/Scene.cpp +++ b/src/Assets/Scene.cpp @@ -762,6 +762,47 @@ namespace Assets return nullptr; } + bool Scene::GetSelectedNodeBounds(glm::vec3& center, float& radius) const + { + if (selectedId_ == static_cast(-1)) + { + return false; + } + + const Node* foundNode = nullptr; + for (const auto& node : nodes_) + { + if (node->GetInstanceId() == selectedId_) + { + foundNode = node.get(); + break; + } + } + + if (!foundNode) return false; + + center = glm::vec3(foundNode->WorldTransform()[3]); + + auto renderComp = foundNode->GetComponent(); + if (renderComp) + { + const auto* model = GetModel(renderComp->GetModelId()); + if (model) + { + glm::vec3 localCenter = (model->GetLocalAABBMax() + model->GetLocalAABBMin()) * 0.5f; + center = glm::vec3(foundNode->WorldTransform() * glm::vec4(localCenter, 1.0f)); + + glm::vec3 extent = (model->GetLocalAABBMax() - model->GetLocalAABBMin()) * foundNode->WorldScale(); + radius = glm::length(extent) * 0.5f; + return true; + } + } + + // Fallback for non-render nodes (default small radius) + radius = 1.0f; + return true; + } + const Model* Scene::GetModel(uint32_t id) const { if (id < models_.size()) diff --git a/src/Assets/Scene.hpp b/src/Assets/Scene.hpp index b5093e99..4179e7cd 100644 --- a/src/Assets/Scene.hpp +++ b/src/Assets/Scene.hpp @@ -84,6 +84,7 @@ namespace Assets uint32_t GetSelectedId() const { return selectedId_; } void SetSelectedId( uint32_t id ) const { selectedId_ = id; } + bool GetSelectedNodeBounds(glm::vec3& center, float& radius) const; void Tick(float DeltaSeconds); void UpdateAllMaterials(); diff --git a/src/Editor/EditorMain.cpp b/src/Editor/EditorMain.cpp index 104874ca..d057ee5b 100644 --- a/src/Editor/EditorMain.cpp +++ b/src/Editor/EditorMain.cpp @@ -5,6 +5,8 @@ #include "EditorInterface.hpp" #include "Runtime/Engine.hpp" #include "Runtime/NextEngineHelper.h" +#include "Assets/Node.h" +#include "Runtime/Components/RenderComponent.h" #include "Editor/EditorCommand.hpp" #include "Editor/EditorInterface.hpp" @@ -108,16 +110,40 @@ bool EditorGameInstance::OnKey(SDL_Event& event) { case SDLK_ESCAPE: GetEngine().GetScene().SetSelectedId(-1); break; - default: break; + case SDLK_F: + { + glm::vec3 focusCenter; + float radius; + if (GetEngine().GetScene().GetSelectedNodeBounds(focusCenter, radius)) + { + modelViewController_.Focus(focusCenter, radius); + } + } + break; + default: break; + } + } + return true; } - } - return true; -} - -bool EditorGameInstance::OnCursorPosition(double xpos, double ypos) -{ - if (!gizmoController_.IsInteracting()) - { + + bool EditorGameInstance::OnCursorPosition(double xpos, double ypos) + { + // Update Controller Context + bool alt = (SDL_GetModState() & SDL_KMOD_ALT) != 0; + modelViewController_.SetAltPressed(alt); + + glm::vec3 center; + float radius; + if (GetEngine().GetScene().GetSelectedNodeBounds(center, radius)) + { + modelViewController_.SetOrbitTarget(center); + } + else + { + modelViewController_.SetOrbitTarget(std::nullopt); + } + + if (!gizmoController_.IsInteracting()) { modelViewController_.OnCursorPosition(xpos, ypos); } return true; diff --git a/src/Runtime/ModelViewController.cpp b/src/Runtime/ModelViewController.cpp index c5de43d8..34e659ee 100644 --- a/src/Runtime/ModelViewController.cpp +++ b/src/Runtime/ModelViewController.cpp @@ -46,6 +46,9 @@ glm::mat4 ModelViewController::ModelView() const bool ModelViewController::OnKey(SDL_Event& event) { + // Any manual input cancels focus animation + if (isFocusing_) isFocusing_ = false; + switch (event.key.key) { case SDLK_S: cameraMovingBackward_ = event.key.type != SDL_EVENT_KEY_UP; cameraMovingSpeed_ = {1.0f, 1.0f}; @@ -75,6 +78,14 @@ bool ModelViewController::OnGamepadInput(const int16_t leftStickX, const int16_t const double stickThreshold = 0.7; // 摇杆阈值 bool inputDetected = false; + // Any manual input cancels focus animation + if (std::abs(leftStickX) > deadZone || std::abs(leftStickY) > deadZone || + std::abs(rightStickX) > deadZone || std::abs(rightStickY) > deadZone || + leftTrigger > deadZone || rightTrigger > deadZone) + { + isFocusing_ = false; + } + // 左摇杆控制前后左右移动 if (std::abs(leftStickX) > deadZone) { cameraMovingRightJoystick_ = leftStickX > 0; @@ -141,18 +152,27 @@ bool ModelViewController::OnCursorPosition(const double xpos, const double ypos) const auto deltaX = static_cast(xpos - mousePosX_) * mouseSensitive_; const auto deltaY = static_cast(ypos - mousePosY_) * mouseSensitive_; - if (mouseLeftPressed_) - { - cameraRotXAbs_ += deltaX; - cameraRotYAbs_ += deltaY; - } - + // Mouse Right Button Handling if (mouseRightPressed_) { - rawModelRotX_ += deltaX * 500.0; - rawModelRotY_ += deltaY * 500.0; + // Cancel focus on manual rotation + isFocusing_ = false; + + if (altPressed_ && orbitTarget_.has_value()) + { + // Orbit Mode + Orbit(deltaX, deltaY); + } + else + { + // Free Look Mode (was on Left Button) + cameraRotXAbs_ += deltaX; + cameraRotYAbs_ += deltaY; + } } + // Mouse Left Button is now ignored for camera control. + mousePosX_ = xpos; mousePosY_ = ypos; @@ -164,10 +184,7 @@ bool ModelViewController::OnMouseButton(SDL_Event& event) if (event.button.button == SDL_BUTTON_LEFT) { mouseLeftPressed_ = event.button.type == SDL_EVENT_MOUSE_BUTTON_DOWN; - if (mouseLeftPressed_) - { - resetMousePos_ = true; - } + // Left button does not trigger camera reset or movement anymore } if (event.button.button == SDL_BUTTON_RIGHT) @@ -181,6 +198,79 @@ bool ModelViewController::OnMouseButton(SDL_Event& event) return true; } +void ModelViewController::Focus(const glm::vec3& focusPoint, float radius) +{ + // Move camera to look at the object from a fixed distance based on radius + + // Calculate distance to fit the object + // half_size = distance * tan(fov/2) + // distance = half_size / tan(fov/2) + + float fovRad = glm::radians(fieldOfView_); + float dist = radius / glm::sin(fovRad * 0.5f); + + // Add a little margin + dist *= 1.1f; + + // Preserve current viewing angle: + // Move the camera to a position such that it looks at 'focusPoint' + // with the SAME orientation it currently has. + // This means moving along the backward vector of the camera. + + glm::vec3 fwd = GetForward(); + glm::vec3 currentPos = glm::vec3(position_); + + // Target position is simply back from the focus point along the forward vector + glm::vec3 targetPos = focusPoint - fwd * dist; + + // Setup interpolation + isFocusing_ = true; + focusTimer_ = 0.0f; + + focusStartPos_ = currentPos; + focusTargetPos_ = targetPos; + + focusStartRot_ = glm::quat_cast(orientation_); + focusTargetRot_ = focusStartRot_; // Keep rotation constant +} + +void ModelViewController::Orbit(float deltaX, float deltaY) +{ + if (!orbitTarget_.has_value()) return; + + glm::vec3 target = orbitTarget_.value(); + glm::vec3 camPos = glm::vec3(position_); + + // Calculate Rotation Matrices + // Yaw (World Y) + // Drag Right (deltaX > 0) -> Rotate Camera Left (CW around Y) -> Negative Angle + glm::mat4 yaw = glm::rotate(glm::mat4(1.0f), -deltaX * 5.0f, glm::vec3(0, 1, 0)); + + // Pitch (Camera Right) + // Drag Down (deltaY > 0) -> Rotate Camera Up (CW around Right?) + // Right(1,0,0). Z(0,0,1). Y(0,1,0). + // +90 around X: Z -> -Y (Down). + // -90 around X: Z -> Y (Up). + // So Negative Angle for Drag Down. + glm::mat4 pitch = glm::rotate(glm::mat4(1.0f), -deltaY * 5.0f, glm::vec3(GetRight())); + + glm::mat4 R = yaw * pitch; + + // Update Position + // R rotates the Arm vector in World Space + glm::vec3 arm = glm::vec3(position_) - target; + position_ = glm::vec4(target + glm::vec3(R * glm::vec4(arm, 0.0f)), 1.0f); + + // Update Orientation + // The Camera Frame must also rotate by R in World Space to maintain relative alignment. + // C2W_new = R * C2W_old + // W2C_new = inv(C2W_new) = inv(R * C2W_old) = W2C_old * inv(R) + // Since R is a rotation matrix, inv(R) == transpose(R) + orientation_ = orientation_ * glm::transpose(R); + + UpdateVectors(); +} + bool ModelViewController::OnTouch(bool down, double xpos, double ypos) { mouseRightPressed_ = down; @@ -200,6 +290,28 @@ void ModelViewController::OnScroll(double xoffset, double yoffset) bool ModelViewController::UpdateCamera(const double speed, const double timeDelta) { + if (isFocusing_) + { + focusTimer_ += static_cast(timeDelta); + float t = glm::clamp(focusTimer_ / focusDuration_, 0.0f, 1.0f); + + // Smooth step + t = t * t * (3.0f - 2.0f * t); + + position_ = glm::vec4(glm::mix(focusStartPos_, focusTargetPos_, t), 1.0f); + glm::quat currentRot = glm::slerp(focusStartRot_, focusTargetRot_, t); + orientation_ = glm::mat4(glm::mat3(currentRot)); + + UpdateVectors(); + + if (t >= 1.0f) + { + isFocusing_ = false; + } + + return true; + } + const auto d = static_cast(speed * timeDelta); if (cameraMovingLeft_ || cameraMovingLeftJoystick_) MoveRight(-d * cameraMovingSpeed_.x); diff --git a/src/Runtime/ModelViewController.hpp b/src/Runtime/ModelViewController.hpp index 33dd0b8c..ad8e5d3b 100644 --- a/src/Runtime/ModelViewController.hpp +++ b/src/Runtime/ModelViewController.hpp @@ -1,6 +1,7 @@ #pragma once #include "Utilities/Glm.hpp" +#include namespace Assets { @@ -36,6 +37,10 @@ class ModelViewController final modelRotY_ = y; } + void SetOrbitTarget(std::optional target) { orbitTarget_ = target; } + void SetAltPressed(bool pressed) { altPressed_ = pressed; } + void Focus(const glm::vec3& focusPoint, float radius = 0.5f); + glm::vec3 GetRight(); glm::vec3 GetUp(); glm::vec3 GetForward(); @@ -47,6 +52,7 @@ class ModelViewController final void MoveRight(float d); void MoveUp(float d); void Rotate(float y, float x); + void Orbit(float x, float y); void UpdateVectors(); // Matrices and vectors. @@ -57,7 +63,20 @@ class ModelViewController final glm::vec4 up_{ 0, 1, 0, 0 }; glm::vec4 forward_{ 0, 0, -1, 0 }; + // Orbit control + std::optional orbitTarget_; + bool altPressed_{}; + // Control states. + bool isFocusing_{}; + float focusTimer_{}; + const float focusDuration_ = 0.5f; + + glm::vec3 focusStartPos_{}; + glm::vec3 focusTargetPos_{}; + glm::quat focusStartRot_{}; + glm::quat focusTargetRot_{}; + bool cameraMovingLeft_{}; bool cameraMovingRight_{}; bool cameraMovingBackward_{}; From ae434932bbef264b048f1a750a4dd16c8c0e5a56 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Wed, 28 Jan 2026 22:35:35 +0800 Subject: [PATCH 07/53] feat: add scene saving to GLTF/GLB format Implement scene export functionality to save edited scenes as GLTF/GLB files. Core Features: - Add FSceneSaver class for scene serialization - Serialize Node hierarchy with TRS transforms - Export Mesh geometry (vertices, normals, UVs, tangents, indices) - Export Material PBR parameters (base color, metallic, roughness) - Export Camera data and node associations - Texture export placeholder (GPU readback to be implemented) Editor Integration: - Add "Save Scene As..." menu item with Ctrl+Shift+S shortcut - Add KeepCPUMeshData option for editor mode to retain mesh data Additional Improvements: - Add EmissiveTexture loading support in FSceneLoader - Exclude FSceneLoader/FSceneSaver from Unity Build to avoid STB conflicts - Add const overload for Scene::Materials() Co-Authored-By: Claude Sonnet 4.5 --- src/Assets/FSceneLoader.cpp | 14 +- src/Assets/FSceneSaver.cpp | 541 +++++++++++++++++++++++++++++++++++ src/Assets/FSceneSaver.h | 153 ++++++++++ src/Assets/Scene.cpp | 36 ++- src/Assets/Scene.hpp | 9 +- src/CMakeLists.txt | 6 +- src/Editor/EditorMain.cpp | 1 + src/Editor/EditorMenubar.cpp | 19 ++ src/Options.hpp | 1 + src/Runtime/Engine.cpp | 2 +- 10 files changed, 774 insertions(+), 8 deletions(-) create mode 100644 src/Assets/FSceneSaver.cpp create mode 100644 src/Assets/FSceneSaver.h diff --git a/src/Assets/FSceneLoader.cpp b/src/Assets/FSceneLoader.cpp index a5270198..3e09de3d 100644 --- a/src/Assets/FSceneLoader.cpp +++ b/src/Assets/FSceneLoader.cpp @@ -291,7 +291,7 @@ namespace Assets mikktspaceContext.m_pUserData = m; genTangSpaceDefault(&mikktspaceContext); } - + DISABLE_OPTIMIZATION bool FSceneLoader::LoadGLTFScene(const std::string& filename, Assets::EnvironmentSetting& cameraInit, std::vector< std::shared_ptr >& nodes, std::vector& models, std::vector& materials, std::vector& lights, std::vector& tracks, std::vector& skeletons) @@ -531,10 +531,12 @@ namespace Assets m.DiffuseTextureId = lambdaGetTexture( mat.pbrMetallicRoughness.baseColorTexture.index ); m.MRATextureId = lambdaGetTexture(mat.pbrMetallicRoughness.metallicRoughnessTexture.index); // metallic in B, roughness in G - + m.NormalTextureId = lambdaGetTexture(mat.normalTexture.index); m.NormalTextureScale = static_cast(mat.normalTexture.scale); + m.EmissiveTextureId = lambdaGetTexture(mat.emissiveTexture.index); + if (mat.occlusionTexture.index != -1 && mat.occlusionTexture.index != mat.pbrMetallicRoughness.metallicRoughnessTexture.index) { SPDLOG_WARN("Material '{}': Separate Occlusion texture not supported. Pack it into the R channel of Metallic-Roughness texture.", mat.name); @@ -617,7 +619,11 @@ namespace Assets m.EmissiveTextureId = lambdaGetTexture(mat.emissiveTexture.index); } - materials.push_back( { m, mat.name } ); + FMaterial fmat; + fmat.gpuMaterial_ = m; + fmat.name_ = mat.name; + + materials.push_back(fmat); } // export whole scene into a big buffer, with vertice indices materials @@ -1007,7 +1013,7 @@ namespace Assets //printf("model.cameras: %d\n", i); return true; } - + ENABLE_OPTIMIZATION Camera FSceneLoader::AutoFocusCamera(Assets::EnvironmentSetting& cameraInit, std::vector>& nodes, std::vector& models) { //auto center camera by scene bounds diff --git a/src/Assets/FSceneSaver.cpp b/src/Assets/FSceneSaver.cpp new file mode 100644 index 00000000..9e19556a --- /dev/null +++ b/src/Assets/FSceneSaver.cpp @@ -0,0 +1,541 @@ +#include "Common/CoreMinimal.hpp" +#include "FSceneSaver.h" + +#include +#include +#include + +#include "Scene.hpp" +#include "Node.h" +#include "Model.hpp" +#include "Material.hpp" +#include "Runtime/Components/RenderComponent.h" + +// Dummy image loader for TinyGLTF (we don't load images when saving) +// This must be in global namespace for TinyGLTF to find it +namespace tinygltf +{ + bool LoadImageData(Image* image, const int imageIdx, std::string* err, + std::string* warn, int reqWidth, int reqHeight, + const unsigned char* bytes, int size, void* userData) + { + image->as_is = true; + return true; + } +} + +namespace Assets +{ + +bool FSceneSaver::SaveGLBScene(const std::string& filename, const Scene& scene) +{ + try + { + // 验证Scene数据 + if (scene.Nodes().empty()) + { + SPDLOG_ERROR("Cannot save empty scene"); + return false; + } + + tinygltf::Model model; + if (!SerializeScene(model, scene)) + { + SPDLOG_ERROR("Failed to serialize scene"); + return false; + } + + tinygltf::TinyGLTF gltfWriter; + std::string err, warn; + + bool success = gltfWriter.WriteGltfSceneToFile(&model, filename, + true, // embedImages + true, // embedBuffers + false, // prettyPrint + true); // writeBinary + + if (!success) + { + SPDLOG_ERROR("Failed to write GLB file: {}", err); + return false; + } + + if (!warn.empty()) + { + SPDLOG_WARN("GLB write warnings: {}", warn); + } + + SPDLOG_INFO("Successfully saved scene to: {}", filename); + return true; + } + catch (const std::exception& e) + { + SPDLOG_ERROR("Exception during scene save: {}", e.what()); + return false; + } +} + +bool FSceneSaver::SaveGLTFScene(const std::string& filename, const Scene& scene) +{ + try + { + // 验证Scene数据 + if (scene.Nodes().empty()) + { + SPDLOG_ERROR("Cannot save empty scene"); + return false; + } + + tinygltf::Model model; + if (!SerializeScene(model, scene)) + { + SPDLOG_ERROR("Failed to serialize scene"); + return false; + } + + tinygltf::TinyGLTF gltfWriter; + std::string err, warn; + + bool success = gltfWriter.WriteGltfSceneToFile(&model, filename, + false, // embedImages + false, // embedBuffers + true, // prettyPrint + false); // writeBinary + + if (!success) + { + SPDLOG_ERROR("Failed to write GLTF file: {}", err); + return false; + } + + if (!warn.empty()) + { + SPDLOG_WARN("GLTF write warnings: {}", warn); + } + + SPDLOG_INFO("Successfully saved scene to: {}", filename); + return true; + } + catch (const std::exception& e) + { + SPDLOG_ERROR("Exception during scene save: {}", e.what()); + return false; + } +} + +bool FSceneSaver::SerializeScene(tinygltf::Model& outModel, const Scene& scene) +{ + // 设置GLTF元数据 + outModel.asset.version = "2.0"; + outModel.asset.generator = "gkNextRenderer Scene Saver"; + + // 序列化各个部分 + SerializeMeshes(outModel, scene); + SerializeMaterials(outModel, scene); + SerializeCameras(outModel, scene); + SerializeNodes(outModel, scene); + + return true; +} + +void FSceneSaver::SerializeNodes(tinygltf::Model& model, const Scene& scene) +{ + const auto& nodes = scene.Nodes(); + const auto& cameras = scene.GetCameras(); + + // 建立Node名称到Camera索引的映射 + // Camera的name格式是" " + std::map nodeToCameraIdx; + for (size_t camIdx = 0; camIdx < cameras.size(); ++camIdx) + { + const std::string& camName = cameras[camIdx].name; + // 从Camera名称中提取Node名称(跳过前面的索引部分) + size_t spacePos = camName.find(' '); + if (spacePos != std::string::npos && spacePos + 1 < camName.size()) + { + std::string nodeName = camName.substr(spacePos + 1); + nodeToCameraIdx[nodeName] = static_cast(camIdx); + } + } + + // 建立Node指针到索引的映射 + std::map nodeIndexMap; + + // 第一遍:创建所有tinygltf::Node + for (size_t i = 0; i < nodes.size(); ++i) + { + const auto& node = nodes[i]; + tinygltf::Node gltfNode; + gltfNode.name = node->GetName(); + + // 使用TRS形式(优先于矩阵) + glm::vec3 t = node->Translation(); + glm::quat r = node->Rotation(); + glm::vec3 s = node->Scale(); + + gltfNode.translation = {t.x, t.y, t.z}; + // GLTF四元数顺序是[x,y,z,w],GLM是[w,x,y,z] + gltfNode.rotation = {r.x, r.y, r.z, r.w}; + gltfNode.scale = {s.x, s.y, s.z}; + + // 关联Camera(如果有) + auto it = nodeToCameraIdx.find(node->GetName()); + if (it != nodeToCameraIdx.end()) + { + gltfNode.camera = it->second; + } + + // 关联Mesh(如果有RenderComponent) + auto renderComp = node->GetComponent(); + if (renderComp && renderComp->IsDrawable()) + { + gltfNode.mesh = renderComp->GetModelId(); + } + + model.nodes.push_back(gltfNode); + nodeIndexMap[node.get()] = static_cast(i); + } + + // 第二遍:建立父子关系 + for (size_t i = 0; i < nodes.size(); ++i) + { + for (const auto& child : nodes[i]->Children()) + { + int childIdx = nodeIndexMap[child.get()]; + model.nodes[i].children.push_back(childIdx); + } + } + + // 创建默认Scene,添加根节点 + tinygltf::Scene gltfScene; + gltfScene.name = "Scene"; + for (size_t i = 0; i < nodes.size(); ++i) + { + if (nodes[i]->GetParent() == nullptr) + { + gltfScene.nodes.push_back(static_cast(i)); + } + } + model.scenes.push_back(gltfScene); + model.defaultScene = 0; +} + +void FSceneSaver::SerializeMeshes(tinygltf::Model& model, const Scene& scene) +{ + // 创建全局Buffer + tinygltf::Buffer buffer; + buffer.name = "SceneBuffer"; + + const auto& models = scene.Models(); + const auto& nodes = scene.Nodes(); + + // 为每个Model找到第一个使用它的Node,以获取材质信息 + std::map modelToRenderComp; + for (const auto& node : nodes) + { + auto renderComp = node->GetComponent(); + if (renderComp && renderComp->IsDrawable()) + { + uint32_t modelId = renderComp->GetModelId(); + if (modelToRenderComp.find(modelId) == modelToRenderComp.end()) + { + modelToRenderComp[modelId] = renderComp.get(); + } + } + } + + for (size_t modelIdx = 0; modelIdx < models.size(); ++modelIdx) + { + const auto& assetModel = models[modelIdx]; + tinygltf::Mesh mesh; + mesh.name = assetModel.Name(); + + // 第一版简化处理:一个Model = 一个Primitive + tinygltf::Primitive primitive; + primitive.mode = TINYGLTF_MODE_TRIANGLES; + + const auto& vertices = assetModel.CPUVertices(); + if (vertices.empty()) + { + SPDLOG_WARN("Model {} has no vertices, skipping", assetModel.Name()); + continue; + } + + // === 写入Position数据 === + std::vector positions; + positions.reserve(vertices.size() * 3); + + glm::vec3 minPos(FLT_MAX), maxPos(-FLT_MAX); + for (const auto& v : vertices) + { + positions.push_back(v.Position.x); + positions.push_back(v.Position.y); + positions.push_back(v.Position.z); + minPos = glm::min(minPos, v.Position); + maxPos = glm::max(maxPos, v.Position); + } + + size_t posOffset = buffer.data.size(); + size_t posSize = positions.size() * sizeof(float); + buffer.data.insert(buffer.data.end(), + reinterpret_cast(positions.data()), + reinterpret_cast(positions.data()) + posSize); + + int posBufferView = CreateBufferView(model, 0, posOffset, posSize, TINYGLTF_TARGET_ARRAY_BUFFER); + int posAccessor = CreateAccessor(model, posBufferView, + TINYGLTF_COMPONENT_TYPE_FLOAT, static_cast(vertices.size()), "VEC3", + {minPos.x, minPos.y, minPos.z}, {maxPos.x, maxPos.y, maxPos.z}); + primitive.attributes["POSITION"] = posAccessor; + + // === 写入Normal数据 === + std::vector normals; + normals.reserve(vertices.size() * 3); + for (const auto& v : vertices) + { + normals.push_back(v.Normal.x); + normals.push_back(v.Normal.y); + normals.push_back(v.Normal.z); + } + + size_t normalOffset = buffer.data.size(); + size_t normalSize = normals.size() * sizeof(float); + buffer.data.insert(buffer.data.end(), + reinterpret_cast(normals.data()), + reinterpret_cast(normals.data()) + normalSize); + + int normalBufferView = CreateBufferView(model, 0, normalOffset, normalSize, TINYGLTF_TARGET_ARRAY_BUFFER); + int normalAccessor = CreateAccessor(model, normalBufferView, + TINYGLTF_COMPONENT_TYPE_FLOAT, static_cast(vertices.size()), "VEC3"); + primitive.attributes["NORMAL"] = normalAccessor; + + // === 写入TexCoord数据 === + std::vector texCoords; + texCoords.reserve(vertices.size() * 2); + for (const auto& v : vertices) + { + texCoords.push_back(v.TexCoord.x); + texCoords.push_back(v.TexCoord.y); + } + + size_t texCoordOffset = buffer.data.size(); + size_t texCoordSize = texCoords.size() * sizeof(float); + buffer.data.insert(buffer.data.end(), + reinterpret_cast(texCoords.data()), + reinterpret_cast(texCoords.data()) + texCoordSize); + + int texCoordBufferView = CreateBufferView(model, 0, texCoordOffset, texCoordSize, TINYGLTF_TARGET_ARRAY_BUFFER); + int texCoordAccessor = CreateAccessor(model, texCoordBufferView, + TINYGLTF_COMPONENT_TYPE_FLOAT, static_cast(vertices.size()), "VEC2"); + primitive.attributes["TEXCOORD_0"] = texCoordAccessor; + + // === 写入Tangent数据 === + std::vector tangents; + tangents.reserve(vertices.size() * 4); + for (const auto& v : vertices) + { + tangents.push_back(v.Tangent.x); + tangents.push_back(v.Tangent.y); + tangents.push_back(v.Tangent.z); + tangents.push_back(v.Tangent.w); + } + + size_t tangentOffset = buffer.data.size(); + size_t tangentSize = tangents.size() * sizeof(float); + buffer.data.insert(buffer.data.end(), + reinterpret_cast(tangents.data()), + reinterpret_cast(tangents.data()) + tangentSize); + + int tangentBufferView = CreateBufferView(model, 0, tangentOffset, tangentSize, TINYGLTF_TARGET_ARRAY_BUFFER); + int tangentAccessor = CreateAccessor(model, tangentBufferView, + TINYGLTF_COMPONENT_TYPE_FLOAT, static_cast(vertices.size()), "VEC4"); + primitive.attributes["TANGENT"] = tangentAccessor; + + // === 写入索引数据 === + const auto& indices = assetModel.CPUIndices(); + if (!indices.empty()) + { + size_t indicesOffset = buffer.data.size(); + size_t indicesSize = indices.size() * sizeof(uint32_t); + buffer.data.insert(buffer.data.end(), + reinterpret_cast(indices.data()), + reinterpret_cast(indices.data()) + indicesSize); + + int indicesBufferView = CreateBufferView(model, 0, indicesOffset, indicesSize, + TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER); + int indicesAccessor = CreateAccessor(model, indicesBufferView, + TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT, static_cast(indices.size()), "SCALAR"); + primitive.indices = indicesAccessor; + } + + // === 关联材质 === + // 从RenderComponent获取材质信息 + if (modelToRenderComp.find(modelIdx) != modelToRenderComp.end()) + { + auto renderComp = modelToRenderComp[modelIdx]; + const auto& materials = renderComp->Materials(); + // 使用第一个材质索引(如果Mesh有多个Section,这是简化处理) + primitive.material = static_cast(materials[0]); + } + else + { + // 如果找不到RenderComponent,使用默认材质 + primitive.material = 0; + } + + mesh.primitives.push_back(primitive); + model.meshes.push_back(mesh); + } + + model.buffers.push_back(buffer); +} + +void FSceneSaver::SerializeMaterials(tinygltf::Model& model, const Scene& scene) +{ + const auto& materials = scene.Materials(); + + for (const auto& mat : materials) + { + tinygltf::Material gltfMat; + gltfMat.name = mat.name_; + + const auto& gpuMat = mat.gpuMaterial_; + + // 基础颜色(注意:加载时做了sqrt进行gamma转线性,保存时平方回去) + gltfMat.pbrMetallicRoughness.baseColorFactor = { + gpuMat.Diffuse.r * gpuMat.Diffuse.r, + gpuMat.Diffuse.g * gpuMat.Diffuse.g, + gpuMat.Diffuse.b * gpuMat.Diffuse.b, + gpuMat.Diffuse.a + }; + + // 金属度和粗糙度 + gltfMat.pbrMetallicRoughness.metallicFactor = gpuMat.Metalness; + gltfMat.pbrMetallicRoughness.roughnessFactor = gpuMat.Fuzziness; + + // TODO: 纹理将通过GPU回读方式添加 + + // 发光强度 + if (gpuMat.MaterialModel == Material::Enum::DiffuseLight) + { + gltfMat.emissiveFactor = { + gpuMat.Diffuse.r, gpuMat.Diffuse.g, gpuMat.Diffuse.b + }; + } + + // 折射率扩展 + if (std::abs(gpuMat.RefractionIndex - 1.46f) > 0.01f) + { + tinygltf::Value iorExt(tinygltf::Value::Object{ + {"ior", tinygltf::Value(static_cast(gpuMat.RefractionIndex))} + }); + gltfMat.extensions["KHR_materials_ior"] = iorExt; + } + + // 透射扩展 + if (gpuMat.MaterialModel == Material::Enum::Dielectric) + { + tinygltf::Value transExt(tinygltf::Value::Object{ + {"transmissionFactor", tinygltf::Value(1.0)} + }); + gltfMat.extensions["KHR_materials_transmission"] = transExt; + } + + model.materials.push_back(gltfMat); + } +} + +void FSceneSaver::SerializeCameras(tinygltf::Model& model, const Scene& scene) +{ + const auto& cameras = scene.GetCameras(); + + for (const auto& cam : cameras) + { + tinygltf::Camera gltfCam; + gltfCam.name = cam.name; + gltfCam.type = "perspective"; + + // 设置透视相机参数 + gltfCam.perspective.aspectRatio = 0; // 0表示使用视口宽高比 + gltfCam.perspective.yfov = glm::radians(cam.FieldOfView); // 转换为弧度 + gltfCam.perspective.znear = cam.NearPlane; + gltfCam.perspective.zfar = cam.FarPlane; + + model.cameras.push_back(gltfCam); + } +} + +void FSceneSaver::CopyTextures(tinygltf::Model& outModel, const tinygltf::Model& sourceModel) +{ + // TODO: 实现通过GPU回读纹理数据的方式来保存纹理 + // 而不是从sourceModel复制,避免引发问题 + SPDLOG_WARN("Texture saving not implemented yet - will be added via GPU readback"); +} + +int FSceneSaver::AppendToBuffer(tinygltf::Buffer& buffer, const void* data, size_t size) +{ + int offset = static_cast(buffer.data.size()); + const unsigned char* byteData = static_cast(data); + buffer.data.insert(buffer.data.end(), byteData, byteData + size); + return offset; +} + +int FSceneSaver::CreateBufferView(tinygltf::Model& model, int bufferIdx, + size_t offset, size_t length, int target) +{ + tinygltf::BufferView view; + view.buffer = bufferIdx; + view.byteOffset = offset; + view.byteLength = length; + if (target != 0) + { + view.target = target; + } + model.bufferViews.push_back(view); + return static_cast(model.bufferViews.size() - 1); +} + +int FSceneSaver::CreateAccessor(tinygltf::Model& model, int bufferViewIdx, + int componentType, int count, const std::string& type, + const std::vector& min, const std::vector& max) +{ + tinygltf::Accessor accessor; + accessor.bufferView = bufferViewIdx; + accessor.componentType = componentType; + accessor.count = count; + + // Convert type string to GLTF type constant + if (type == "SCALAR") + { + accessor.type = TINYGLTF_TYPE_SCALAR; + } + else if (type == "VEC2") + { + accessor.type = TINYGLTF_TYPE_VEC2; + } + else if (type == "VEC3") + { + accessor.type = TINYGLTF_TYPE_VEC3; + } + else if (type == "VEC4") + { + accessor.type = TINYGLTF_TYPE_VEC4; + } + else + { + SPDLOG_ERROR("Unknown accessor type: {}", type); + accessor.type = TINYGLTF_TYPE_SCALAR; + } + + if (!min.empty()) + { + accessor.minValues = min; + } + if (!max.empty()) + { + accessor.maxValues = max; + } + model.accessors.push_back(accessor); + return static_cast(model.accessors.size() - 1); +} + +} diff --git a/src/Assets/FSceneSaver.h b/src/Assets/FSceneSaver.h new file mode 100644 index 00000000..c7bd6e66 --- /dev/null +++ b/src/Assets/FSceneSaver.h @@ -0,0 +1,153 @@ +#pragma once + +#include +#include + +namespace tinygltf +{ + class Model; + struct Buffer; +} + +namespace Assets +{ + class Scene; + + /** + * @brief 场景保存器 - 将运行时Scene导出为GLTF/GLB文件 + * + * 支持保存: + * - Node树的层级关系和Transform(位置、旋转、缩放) + * - Mesh几何数据(顶点、法线、纹理坐标、切线、索引) + * - Material PBR参数和纹理引用 + * - 原始纹理数据(从sourceModel复制) + */ + class FSceneSaver + { + public: + /** + * @brief 保存场景为GLB格式(二进制,推荐) + * @param filename 输出文件路径(.glb扩展名) + * @param scene 要保存的场景对象 + * @return 成功返回true,失败返回false + */ + static bool SaveGLBScene( + const std::string& filename, + const Scene& scene); + + /** + * @brief 保存场景为GLTF格式(JSON + 外部文件) + * @param filename 输出文件路径(.gltf扩展名) + * @param scene 要保存的场景对象 + * @return 成功返回true,失败返回false + */ + static bool SaveGLTFScene( + const std::string& filename, + const Scene& scene); + + private: + /** + * @brief 核心序列化方法 - 将Scene数据填充到tinygltf::Model + * @param outModel 输出的GLTF模型对象 + * @param scene 输入的场景对象 + * @return 成功返回true,失败返回false + */ + static bool SerializeScene( + tinygltf::Model& outModel, + const Scene& scene); + + /** + * @brief 序列化Node树和Transform + * @param model 输出的GLTF模型对象 + * @param scene 输入的场景对象 + */ + static void SerializeNodes( + tinygltf::Model& model, + const Scene& scene); + + /** + * @brief 序列化Mesh几何数据 + * @param model 输出的GLTF模型对象 + * @param scene 输入的场景对象 + */ + static void SerializeMeshes( + tinygltf::Model& model, + const Scene& scene); + + /** + * @brief 序列化Material参数 + * @param model 输出的GLTF模型对象 + * @param scene 输入的场景对象 + */ + static void SerializeMaterials( + tinygltf::Model& model, + const Scene& scene); + + /** + * @brief 序列化Camera数据 + * @param model 输出的GLTF模型对象 + * @param scene 输入的场景对象 + */ + static void SerializeCameras( + tinygltf::Model& model, + const Scene& scene); + + /** + * @brief 复制纹理数据(从sourceModel) + * @param outModel 输出的GLTF模型对象 + * @param sourceModel 原始的GLTF模型对象(包含纹理数据) + */ + static void CopyTextures( + tinygltf::Model& outModel, + const tinygltf::Model& sourceModel); + + /** + * @brief 将数据追加到Buffer + * @param buffer 目标Buffer + * @param data 数据指针 + * @param size 数据大小(字节) + * @return 数据在Buffer中的起始偏移量 + */ + static int AppendToBuffer( + tinygltf::Buffer& buffer, + const void* data, + size_t size); + + /** + * @brief 创建BufferView + * @param model GLTF模型对象 + * @param bufferIdx Buffer索引 + * @param offset 在Buffer中的偏移量 + * @param length 数据长度 + * @param target 目标类型(TINYGLTF_TARGET_ARRAY_BUFFER或TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER) + * @return 创建的BufferView索引 + */ + static int CreateBufferView( + tinygltf::Model& model, + int bufferIdx, + size_t offset, + size_t length, + int target); + + /** + * @brief 创建Accessor + * @param model GLTF模型对象 + * @param bufferViewIdx BufferView索引 + * @param componentType 组件类型(TINYGLTF_COMPONENT_TYPE_FLOAT等) + * @param count 元素数量 + * @param type 类型字符串("VEC3"、"SCALAR"等) + * @param min 最小值(可选,用于优化) + * @param max 最大值(可选,用于优化) + * @return 创建的Accessor索引 + */ + static int CreateAccessor( + tinygltf::Model& model, + int bufferViewIdx, + int componentType, + int count, + const std::string& type, + const std::vector& min = {}, + const std::vector& max = {}); + }; + +} diff --git a/src/Assets/Scene.cpp b/src/Assets/Scene.cpp index 68073782..3ca883e7 100644 --- a/src/Assets/Scene.cpp +++ b/src/Assets/Scene.cpp @@ -4,6 +4,8 @@ #include "Options.hpp" #include "Vulkan/BufferUtil.hpp" #include "Assets/TextureImage.hpp" +#include "FSceneSaver.h" +#include #include #include #include @@ -352,7 +354,11 @@ namespace Assets model.SetSectionCount(processSection); - model.FreeMemory(); + // 在编辑器模式下保留CPU网格数据用于场景保存功能 + if (!GOption->KeepCPUMeshData) + { + model.FreeMemory(); + } } int flags = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; @@ -850,4 +856,32 @@ namespace Assets skinnedVerticesSimpleAddr_ = skinnedVerticesSimple; jointMatricesAddr_ = jointMatrices; } + + bool Scene::Save(const std::string& filename) const + { + // 根据文件扩展名选择保存格式 + if (filename.ends_with(".glb") || filename.ends_with(".GLB")) + { + return SaveAsGLB(filename); + } + else if (filename.ends_with(".gltf") || filename.ends_with(".GLTF")) + { + return SaveAsGLTF(filename); + } + else + { + SPDLOG_ERROR("Unsupported file extension. Use .glb or .gltf"); + return false; + } + } + + bool Scene::SaveAsGLB(const std::string& filename) const + { + return FSceneSaver::SaveGLBScene(filename, *this); + } + + bool Scene::SaveAsGLTF(const std::string& filename) const + { + return FSceneSaver::SaveGLTFScene(filename, *this); + } } diff --git a/src/Assets/Scene.hpp b/src/Assets/Scene.hpp index 4179e7cd..9a043bb9 100644 --- a/src/Assets/Scene.hpp +++ b/src/Assets/Scene.hpp @@ -60,6 +60,7 @@ namespace Assets const std::vector>& Nodes() const { return nodes_; } const std::vector& Models() const { return models_; } std::vector& Materials() { return materials_; } + const std::vector& Materials() const { return materials_; } const std::vector& Offsets() const { return offsets_; } const std::vector& Lights() const { return lights_; } const Vulkan::Buffer& VertexBuffer() const { return *vertexBuffer_; } @@ -135,7 +136,12 @@ namespace Assets TextureImage& ShadowMap() const { return *cpuShadowMap_; } FCPUAccelerationStructure& GetCPUAccelerationStructure() { return cpuAccelerationStructure_; } - + + // Scene saving功能 + bool Save(const std::string& filename) const; + bool SaveAsGLB(const std::string& filename) const; + bool SaveAsGLTF(const std::string& filename) const; + private: std::vector materials_; std::vector gpuMaterials_; @@ -230,5 +236,6 @@ namespace Assets VkDeviceAddress skinnedVerticesAddr_ = 0; VkDeviceAddress skinnedVerticesSimpleAddr_ = 0; VkDeviceAddress jointMatricesAddr_ = 0; + }; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5daf5f81..fe7ae7f5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -186,10 +186,14 @@ foreach(target IN LISTS AllTargets) set_target_properties(${target} PROPERTIES UNITY_BUILD_BATCH_SIZE 12) set_source_files_properties(${src_files_assets} PROPERTIES UNITY_GROUP "assets") - set_source_files_properties(${src_files_utilities} PROPERTIES UNITY_GROUP "utilities") + set_source_files_properties(${src_files_utilities} PROPERTIES UNITY_GROUP "utilities") set_source_files_properties(${src_files_engine} PROPERTIES UNITY_GROUP "engine") set_source_files_properties(${src_files_vulkan} PROPERTIES UNITY_GROUP "vulkan") set_source_files_properties(${src_files_thirdparty} PROPERTIES UNITY_GROUP "thirdparty") + + # Exclude files with STB implementation from Unity Build to avoid redefinition conflicts + set_source_files_properties(Assets/FSceneLoader.cpp PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) + set_source_files_properties(Assets/FSceneSaver.cpp PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) endif() find_package(WebP CONFIG REQUIRED) target_link_libraries(${target} PRIVATE SDL3::SDL3 xxHash::xxhash meshoptimizer::meshoptimizer fmt::fmt CURL::libcurl glm::glm imgui::imgui draco::draco WebP::webp ${extra_libs}) diff --git a/src/Editor/EditorMain.cpp b/src/Editor/EditorMain.cpp index d057ee5b..692015f2 100644 --- a/src/Editor/EditorMain.cpp +++ b/src/Editor/EditorMain.cpp @@ -36,6 +36,7 @@ EditorGameInstance::EditorGameInstance(Vulkan::WindowConfig& config, Options& op options.ForceSDR = true; options.NoDenoiser = true; options.SuperResolution = 2; + options.KeepCPUMeshData = true; // 编辑器模式保留CPU网格数据用于场景保存 } void EditorGameInstance::OnInit() diff --git a/src/Editor/EditorMenubar.cpp b/src/Editor/EditorMenubar.cpp index 659c6979..e5a35939 100644 --- a/src/Editor/EditorMenubar.cpp +++ b/src/Editor/EditorMenubar.cpp @@ -3,6 +3,8 @@ #include "EditorGUI.h" #include "Runtime/Engine.hpp" #include "Utilities/ImGui.hpp" +#include "Assets/Scene.hpp" +#include void Editor::GUI::ShowMenubar() { @@ -31,6 +33,23 @@ void Editor::GUI::ShowMenubar() /// menu-file if (ImGui::BeginMenu("File")) { + if (ImGui::MenuItem("Save Scene As...", "Ctrl+Shift+S")) + { + // TODO: Add file dialog for save path selection + std::string filename = "saved_scene.glb"; + bool success = NextEngine::GetInstance()->GetScene().Save(filename); + if (success) + { + SPDLOG_INFO("Scene saved successfully: {}", filename); + } + else + { + SPDLOG_ERROR("Failed to save scene: {}", filename); + } + } + + ImGui::Separator(); + if (ImGui::MenuItem("Exit")) { state = false; diff --git a/src/Options.hpp b/src/Options.hpp index 6275b872..58579cfe 100644 --- a/src/Options.hpp +++ b/src/Options.hpp @@ -35,6 +35,7 @@ class Options final bool Validation{}; bool FastExit{true}; bool AgentValidation{}; + bool KeepCPUMeshData{}; // 保留CPU网格数据(编辑器模式需要) std::string locale{}; // Renderer options. diff --git a/src/Runtime/Engine.cpp b/src/Runtime/Engine.cpp index 838e0cc8..571f05e3 100644 --- a/src/Runtime/Engine.cpp +++ b/src/Runtime/Engine.cpp @@ -1021,7 +1021,7 @@ void NextEngine::LoadScene(std::string sceneFileName) { SceneTaskContext taskContext {}; const auto timer = std::chrono::high_resolution_clock::now(); - + taskContext.success = SceneList::LoadScene( sceneFileName, *cameraState, *nodes, *models, *materials, *lights, *tracks, *skeletons); taskContext.elapsed = std::chrono::duration(std::chrono::high_resolution_clock::now() - timer).count(); From 68f5daf14a1ef3d1f000db95ac23ecd0596dcad6 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Wed, 28 Jan 2026 23:51:00 +0800 Subject: [PATCH 08/53] feat: add undoable node duplicate/delete commands Introduce runtime command stack and gizmo/delete hooks so apps can undo or redo node transforms, duplication, and deletion. Co-authored-by: gpt-5.2-codex --- src/Assets/Node.cpp | 12 +- src/Assets/Node.h | 1 + src/Assets/Scene.cpp | 121 ++++++++++++++++++- src/Assets/Scene.hpp | 11 ++ src/Runtime/Command/CommandSystem.cpp | 89 ++++++++++++++ src/Runtime/Command/CommandSystem.hpp | 39 ++++++ src/Runtime/Command/DeleteNodeCommand.cpp | 63 ++++++++++ src/Runtime/Command/DeleteNodeCommand.hpp | 27 +++++ src/Runtime/Command/DuplicateNodeCommand.cpp | 115 ++++++++++++++++++ src/Runtime/Command/DuplicateNodeCommand.hpp | 33 +++++ src/Runtime/Command/TransformNodeCommand.cpp | 62 ++++++++++ src/Runtime/Command/TransformNodeCommand.hpp | 40 ++++++ src/Runtime/Engine.cpp | 73 +++++++++++ src/Runtime/Engine.hpp | 12 ++ src/Runtime/GizmoController.cpp | 55 +++++++-- src/Runtime/GizmoController.hpp | 6 + 16 files changed, 746 insertions(+), 13 deletions(-) create mode 100644 src/Runtime/Command/CommandSystem.cpp create mode 100644 src/Runtime/Command/CommandSystem.hpp create mode 100644 src/Runtime/Command/DeleteNodeCommand.cpp create mode 100644 src/Runtime/Command/DeleteNodeCommand.hpp create mode 100644 src/Runtime/Command/DuplicateNodeCommand.cpp create mode 100644 src/Runtime/Command/DuplicateNodeCommand.hpp create mode 100644 src/Runtime/Command/TransformNodeCommand.cpp create mode 100644 src/Runtime/Command/TransformNodeCommand.hpp diff --git a/src/Assets/Node.cpp b/src/Assets/Node.cpp index 46ef14dc..3b1f5a25 100644 --- a/src/Assets/Node.cpp +++ b/src/Assets/Node.cpp @@ -131,6 +131,16 @@ namespace Assets RecalcTransform(); } + void Node::ClearParent() + { + if (parent_) + { + parent_->RemoveChild(shared_from_this()); + parent_.reset(); + RecalcTransform(); + } + } + void Node::AddChild(std::shared_ptr child) { children_.insert(child); @@ -182,4 +192,4 @@ namespace Assets RecalcTransform(); prevTransform_ = transform_; } -} \ No newline at end of file +} diff --git a/src/Assets/Node.h b/src/Assets/Node.h index ce036e6c..d90204c3 100644 --- a/src/Assets/Node.h +++ b/src/Assets/Node.h @@ -44,6 +44,7 @@ namespace Assets bool TickVelocity(glm::mat4& combinedTS); void SetParent(std::shared_ptr parent); + void ClearParent(); Node* GetParent() { return parent_.get(); } void AddChild(std::shared_ptr child); diff --git a/src/Assets/Scene.cpp b/src/Assets/Scene.cpp index 3ca883e7..9974d03d 100644 --- a/src/Assets/Scene.cpp +++ b/src/Assets/Scene.cpp @@ -20,6 +20,7 @@ #include "Runtime/Components/SkinnedMeshComponent.h" #include +#include namespace Assets { @@ -399,10 +400,10 @@ namespace Assets void Scene::AddNode(std::shared_ptr node) { nodes_.push_back(node); - if ( NextPhysics* physicsEngine = NextEngine::GetInstance()->GetPhysicsEngine() ) + if (NextPhysics* physicsEngine = NextEngine::GetInstance()->GetPhysicsEngine()) { auto render = node->GetComponent(); - // bind the mesh shape to the node + // bind the mesh shape to the node if (render && render->GetModelId() < cachedMeshShapes_.size() && cachedMeshShapes_[render->GetModelId()]) { auto phys = node->GetComponent(); @@ -418,10 +419,10 @@ namespace Assets if (cachedMeshShapes_[render->GetModelId()].GetPtr() && cachedMeshShapes_[render->GetModelId()]->mIndexedTriangles.size() > 0) validShape = true; #endif - if ( validShape ) + if (validShape) { NextBodyID id = physicsEngine->CreateMeshBody(cachedMeshShapes_[render->GetModelId()], node->WorldTranslation(), node->WorldRotation(), node->WorldScale(), motionType, layer); - + if (!phys) { phys = std::make_shared(); @@ -437,6 +438,118 @@ namespace Assets } } + std::shared_ptr Scene::RemoveNodeByInstanceId(uint32_t id) + { + for (auto it = nodes_.begin(); it != nodes_.end(); ++it) + { + if ((*it)->GetInstanceId() == id) + { + auto node = *it; + node->ClearParent(); + nodes_.erase(it); + return node; + } + } + return nullptr; + } + + std::shared_ptr Scene::GetNodeSharedByInstanceId(uint32_t id) const + { + for (const auto& node : nodes_) + { + if (node->GetInstanceId() == id) + { + return node; + } + } + return nullptr; + } + + uint32_t Scene::GenerateInstanceId() const + { + uint32_t maxId = 0; + for (const auto& node : nodes_) + { + maxId = std::max(maxId, node->GetInstanceId()); + } + return nodes_.empty() ? 0 : maxId + 1; + } + + namespace + { + void CollectNodeHierarchy(const std::shared_ptr& node, std::vector>& outNodes) + { + if (!node) + { + return; + } + outNodes.push_back(node); + for (const auto& child : node->Children()) + { + CollectNodeHierarchy(child, outNodes); + } + } + } + + std::vector Scene::RemoveNodeHierarchy(uint32_t id, std::shared_ptr& outParent) + { + std::vector removedEntries; + auto root = GetNodeSharedByInstanceId(id); + if (!root) + { + outParent = nullptr; + return removedEntries; + } + + outParent = nullptr; + if (Node* parent = root->GetParent()) + { + outParent = GetNodeSharedByInstanceId(parent->GetInstanceId()); + root->ClearParent(); + } + + std::vector> nodesToRemove; + CollectNodeHierarchy(root, nodesToRemove); + + std::unordered_set removeIds; + removeIds.reserve(nodesToRemove.size()); + for (const auto& node : nodesToRemove) + { + removeIds.insert(node->GetInstanceId()); + } + + removedEntries.reserve(nodesToRemove.size()); + for (size_t index = 0; index < nodes_.size(); ++index) + { + const auto& node = nodes_[index]; + if (removeIds.find(node->GetInstanceId()) != removeIds.end()) + { + removedEntries.push_back({node, index}); + } + } + + nodes_.erase(std::remove_if(nodes_.begin(), nodes_.end(), [&removeIds](const std::shared_ptr& node) + { + return removeIds.find(node->GetInstanceId()) != removeIds.end(); + }), nodes_.end()); + + return removedEntries; + } + + void Scene::RestoreNodes(const std::vector& entries, const std::shared_ptr& parent, const std::shared_ptr& root) + { + for (auto it = entries.rbegin(); it != entries.rend(); ++it) + { + const size_t index = std::min(it->index, nodes_.size()); + nodes_.insert(nodes_.begin() + index, it->node); + } + + if (parent && root) + { + root->SetParent(parent); + } + } + const Assets::GPUScene& Scene::FetchGPUScene(const uint32_t imageIndex) const { // all gpu device address diff --git a/src/Assets/Scene.hpp b/src/Assets/Scene.hpp index 9a043bb9..9ca1dc4d 100644 --- a/src/Assets/Scene.hpp +++ b/src/Assets/Scene.hpp @@ -119,6 +119,17 @@ namespace Assets void MarkEnvDirty(); void AddNode(std::shared_ptr node); + std::shared_ptr RemoveNodeByInstanceId(uint32_t id); + std::shared_ptr GetNodeSharedByInstanceId(uint32_t id) const; + uint32_t GenerateInstanceId() const; + + struct RemovedNodeEntry + { + std::shared_ptr node; + size_t index; + }; + std::vector RemoveNodeHierarchy(uint32_t id, std::shared_ptr& outParent); + void RestoreNodes(const std::vector& entries, const std::shared_ptr& parent, const std::shared_ptr& root); void SetSkinningBuffers(VkDeviceAddress skinnedVertices, VkDeviceAddress skinnedVerticesSimple, VkDeviceAddress jointMatrices); diff --git a/src/Runtime/Command/CommandSystem.cpp b/src/Runtime/Command/CommandSystem.cpp new file mode 100644 index 00000000..58ff8c2c --- /dev/null +++ b/src/Runtime/Command/CommandSystem.cpp @@ -0,0 +1,89 @@ +#include "Runtime/Command/CommandSystem.hpp" + +CommandSystem::CommandSystem(size_t historyLimit) + : historyLimit_(historyLimit) +{ +} + +bool CommandSystem::ExecuteCommand(std::unique_ptr command) +{ + if (!command) + { + return false; + } + + if (!command->Execute()) + { + return false; + } + + redoStack_.clear(); + undoStack_.push_back(std::move(command)); + TrimHistory(); + return true; +} + +bool CommandSystem::Undo() +{ + if (undoStack_.empty()) + { + return false; + } + + auto command = std::move(undoStack_.back()); + undoStack_.pop_back(); + command->Undo(); + redoStack_.push_back(std::move(command)); + return true; +} + +bool CommandSystem::Redo() +{ + if (redoStack_.empty()) + { + return false; + } + + auto command = std::move(redoStack_.back()); + redoStack_.pop_back(); + command->Redo(); + undoStack_.push_back(std::move(command)); + TrimHistory(); + return true; +} + +bool CommandSystem::CanUndo() const +{ + return !undoStack_.empty(); +} + +bool CommandSystem::CanRedo() const +{ + return !redoStack_.empty(); +} + +void CommandSystem::Clear() +{ + undoStack_.clear(); + redoStack_.clear(); +} + +void CommandSystem::SetHistoryLimit(size_t limit) +{ + historyLimit_ = limit; + TrimHistory(); +} + +void CommandSystem::TrimHistory() +{ + if (historyLimit_ == 0) + { + Clear(); + return; + } + + while (undoStack_.size() > historyLimit_) + { + undoStack_.erase(undoStack_.begin()); + } +} diff --git a/src/Runtime/Command/CommandSystem.hpp b/src/Runtime/Command/CommandSystem.hpp new file mode 100644 index 00000000..abf1a2fa --- /dev/null +++ b/src/Runtime/Command/CommandSystem.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include "Common/CoreMinimal.hpp" + +#include +#include + +class ICommand +{ +public: + virtual ~ICommand() = default; + virtual bool Execute() = 0; + virtual void Undo() = 0; + virtual void Redo() = 0; + virtual const char* Name() const { return "Command"; } +}; + +class CommandSystem +{ +public: + explicit CommandSystem(size_t historyLimit = 100); + + bool ExecuteCommand(std::unique_ptr command); + bool Undo(); + bool Redo(); + bool CanUndo() const; + bool CanRedo() const; + void Clear(); + + size_t HistoryLimit() const { return historyLimit_; } + void SetHistoryLimit(size_t limit); + +private: + void TrimHistory(); + + std::vector> undoStack_; + std::vector> redoStack_; + size_t historyLimit_ = 100; +}; diff --git a/src/Runtime/Command/DeleteNodeCommand.cpp b/src/Runtime/Command/DeleteNodeCommand.cpp new file mode 100644 index 00000000..f40856d0 --- /dev/null +++ b/src/Runtime/Command/DeleteNodeCommand.cpp @@ -0,0 +1,63 @@ +#include "Runtime/Command/DeleteNodeCommand.hpp" + +#include "Assets/Node.h" +#include "Assets/Scene.hpp" + +DeleteNodeCommand::DeleteNodeCommand(Assets::Scene& scene, uint32_t instanceId) + : scene_(&scene) + , instanceId_(instanceId) +{ +} + +bool DeleteNodeCommand::Execute() +{ + if (!scene_) + { + return false; + } + + root_ = scene_->GetNodeSharedByInstanceId(instanceId_); + if (!root_) + { + return false; + } + + previousSelectedId_ = scene_->GetSelectedId(); + removedEntries_ = scene_->RemoveNodeHierarchy(instanceId_, parent_); + if (removedEntries_.empty()) + { + return false; + } + + scene_->SetSelectedId(static_cast(-1)); + scene_->MarkDirty(); + scene_->GetCPUAccelerationStructure().UpdateBVH(*scene_); + return true; +} + +void DeleteNodeCommand::Undo() +{ + if (!scene_ || removedEntries_.empty()) + { + return; + } + + scene_->RestoreNodes(removedEntries_, parent_, root_); + scene_->SetSelectedId(previousSelectedId_); + scene_->MarkDirty(); + scene_->GetCPUAccelerationStructure().UpdateBVH(*scene_); +} + +void DeleteNodeCommand::Redo() +{ + if (!scene_ || !root_) + { + return; + } + + removedEntries_.clear(); + removedEntries_ = scene_->RemoveNodeHierarchy(instanceId_, parent_); + scene_->SetSelectedId(static_cast(-1)); + scene_->MarkDirty(); + scene_->GetCPUAccelerationStructure().UpdateBVH(*scene_); +} diff --git a/src/Runtime/Command/DeleteNodeCommand.hpp b/src/Runtime/Command/DeleteNodeCommand.hpp new file mode 100644 index 00000000..49c6f17c --- /dev/null +++ b/src/Runtime/Command/DeleteNodeCommand.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "Common/CoreMinimal.hpp" +#include "Runtime/Command/CommandSystem.hpp" +#include "Assets/Scene.hpp" + +#include +#include + +class DeleteNodeCommand final : public ICommand +{ +public: + DeleteNodeCommand(Assets::Scene& scene, uint32_t instanceId); + + bool Execute() override; + void Undo() override; + void Redo() override; + const char* Name() const override { return "DeleteNode"; } + +private: + Assets::Scene* scene_ = nullptr; + uint32_t instanceId_ = 0; + uint32_t previousSelectedId_ = static_cast(-1); + std::shared_ptr parent_{}; + std::shared_ptr root_{}; + std::vector removedEntries_{}; +}; diff --git a/src/Runtime/Command/DuplicateNodeCommand.cpp b/src/Runtime/Command/DuplicateNodeCommand.cpp new file mode 100644 index 00000000..7efd2a91 --- /dev/null +++ b/src/Runtime/Command/DuplicateNodeCommand.cpp @@ -0,0 +1,115 @@ +#include "Runtime/Command/DuplicateNodeCommand.hpp" + +#include "Assets/Node.h" +#include "Assets/Scene.hpp" +#include "Runtime/Components/PhysicsComponent.h" +#include "Runtime/Components/RenderComponent.h" + +namespace +{ + std::shared_ptr CloneNode(const Assets::Node& source, uint32_t newInstanceId) + { + auto clone = Assets::Node::CreateNode( + source.GetName() + "_copy", + source.Translation(), + source.Rotation(), + source.Scale(), + newInstanceId); + + if (auto render = source.GetComponent()) + { + auto newRender = std::make_shared(); + newRender->SetModelId(render->GetModelId()); + newRender->SetMaterial(render->Materials()); + newRender->SetVisible(render->IsVisible()); + newRender->SetRayCastVisible(render->IsRayCastVisible()); + newRender->SetSkinIndex(render->GetSkinIndex()); + clone->AddComponent(newRender); + } + + if (auto phys = source.GetComponent()) + { + auto newPhys = std::make_shared(); + newPhys->SetMobility(phys->GetMobility()); + newPhys->SetPhysicsOffset(phys->GetPhysicsOffset()); + clone->AddComponent(newPhys); + } + + return clone; + } +} + +DuplicateNodeCommand::DuplicateNodeCommand(Assets::Scene& scene, uint32_t sourceInstanceId) + : scene_(&scene) + , sourceInstanceId_(sourceInstanceId) +{ +} + +bool DuplicateNodeCommand::Execute() +{ + if (!scene_) + { + return false; + } + + auto sourceNode = scene_->GetNodeSharedByInstanceId(sourceInstanceId_); + if (!sourceNode) + { + return false; + } + + previousSelectedId_ = scene_->GetSelectedId(); + + if (!newNode_) + { + newInstanceId_ = scene_->GenerateInstanceId(); + newNode_ = CloneNode(*sourceNode, newInstanceId_); + + if (Assets::Node* parent = sourceNode->GetParent()) + { + parent_ = scene_->GetNodeSharedByInstanceId(parent->GetInstanceId()); + } + } + + if (parent_) + { + newNode_->SetParent(parent_); + } + + scene_->AddNode(newNode_); + scene_->SetSelectedId(newInstanceId_); + scene_->MarkDirty(); + scene_->GetCPUAccelerationStructure().UpdateBVH(*scene_); + return true; +} + +void DuplicateNodeCommand::Undo() +{ + if (!scene_ || !newNode_) + { + return; + } + + scene_->RemoveNodeByInstanceId(newInstanceId_); + scene_->SetSelectedId(previousSelectedId_); + scene_->MarkDirty(); + scene_->GetCPUAccelerationStructure().UpdateBVH(*scene_); +} + +void DuplicateNodeCommand::Redo() +{ + if (!scene_ || !newNode_) + { + return; + } + + if (parent_) + { + newNode_->SetParent(parent_); + } + + scene_->AddNode(newNode_); + scene_->SetSelectedId(newInstanceId_); + scene_->MarkDirty(); + scene_->GetCPUAccelerationStructure().UpdateBVH(*scene_); +} diff --git a/src/Runtime/Command/DuplicateNodeCommand.hpp b/src/Runtime/Command/DuplicateNodeCommand.hpp new file mode 100644 index 00000000..f1b18b55 --- /dev/null +++ b/src/Runtime/Command/DuplicateNodeCommand.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "Common/CoreMinimal.hpp" +#include "Runtime/Command/CommandSystem.hpp" + +#include + +namespace Assets +{ + class Scene; + class Node; +} + +class DuplicateNodeCommand final : public ICommand +{ +public: + DuplicateNodeCommand(Assets::Scene& scene, uint32_t sourceInstanceId); + + bool Execute() override; + void Undo() override; + void Redo() override; + const char* Name() const override { return "DuplicateNode"; } + + uint32_t GetNewInstanceId() const { return newInstanceId_; } + +private: + Assets::Scene* scene_ = nullptr; + uint32_t sourceInstanceId_ = 0; + uint32_t newInstanceId_ = 0; + uint32_t previousSelectedId_ = static_cast(-1); + std::shared_ptr parent_{}; + std::shared_ptr newNode_{}; +}; diff --git a/src/Runtime/Command/TransformNodeCommand.cpp b/src/Runtime/Command/TransformNodeCommand.cpp new file mode 100644 index 00000000..ec9975d4 --- /dev/null +++ b/src/Runtime/Command/TransformNodeCommand.cpp @@ -0,0 +1,62 @@ +#include "Runtime/Command/TransformNodeCommand.hpp" + +#include "Assets/Node.h" +#include "Assets/Scene.hpp" + +#include + +TransformNodeCommand::TransformNodeCommand(Assets::Scene& scene, uint32_t instanceId, const TransformSnapshot& before, const TransformSnapshot& after) + : scene_(&scene) + , instanceId_(instanceId) + , before_(before) + , after_(after) +{ +} + +bool TransformNodeCommand::Execute() +{ + Apply(after_); + return true; +} + +void TransformNodeCommand::Undo() +{ + Apply(before_); +} + +void TransformNodeCommand::Redo() +{ + Apply(after_); +} + +bool TransformNodeCommand::IsDifferent(const TransformSnapshot& before, const TransformSnapshot& after) +{ + constexpr float kEpsilon = 1e-4f; + const float translationDelta = glm::length(before.translation - after.translation); + const float scaleDelta = glm::length(before.scale - after.scale); + const float rotationDot = std::abs(glm::dot(before.rotation, after.rotation)); + const float rotationDelta = 1.0f - rotationDot; + + return translationDelta > kEpsilon || scaleDelta > kEpsilon || rotationDelta > kEpsilon; +} + +void TransformNodeCommand::Apply(const TransformSnapshot& snapshot) +{ + if (scene_ == nullptr) + { + return; + } + + Assets::Node* node = scene_->GetNodeByInstanceId(instanceId_); + if (node == nullptr) + { + return; + } + + node->SetTranslation(snapshot.translation); + node->SetRotation(snapshot.rotation); + node->SetScale(snapshot.scale); + node->RecalcTransform(true); + scene_->MarkDirty(); + scene_->GetCPUAccelerationStructure().UpdateBVH(*scene_); +} diff --git a/src/Runtime/Command/TransformNodeCommand.hpp b/src/Runtime/Command/TransformNodeCommand.hpp new file mode 100644 index 00000000..337ffb8b --- /dev/null +++ b/src/Runtime/Command/TransformNodeCommand.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "Common/CoreMinimal.hpp" +#include "Runtime/Command/CommandSystem.hpp" + +#include +#include + +namespace Assets +{ + class Scene; +} + +struct TransformSnapshot +{ + glm::vec3 translation{}; + glm::quat rotation{}; + glm::vec3 scale{1.0f, 1.0f, 1.0f}; +}; + +class TransformNodeCommand final : public ICommand +{ +public: + TransformNodeCommand(Assets::Scene& scene, uint32_t instanceId, const TransformSnapshot& before, const TransformSnapshot& after); + + bool Execute() override; + void Undo() override; + void Redo() override; + const char* Name() const override { return "TransformNode"; } + + static bool IsDifferent(const TransformSnapshot& before, const TransformSnapshot& after); + +private: + void Apply(const TransformSnapshot& snapshot); + + Assets::Scene* scene_ = nullptr; + uint32_t instanceId_ = 0; + TransformSnapshot before_{}; + TransformSnapshot after_{}; +}; diff --git a/src/Runtime/Engine.cpp b/src/Runtime/Engine.cpp index 571f05e3..85ad8566 100644 --- a/src/Runtime/Engine.cpp +++ b/src/Runtime/Engine.cpp @@ -11,6 +11,7 @@ #include "Vulkan/Instance.hpp" #include "ScreenShot.hpp" #include "QuickJSEngine.hpp" +#include "Runtime/Command/DeleteNodeCommand.hpp" #include #include @@ -876,12 +877,84 @@ void NextEngine::OnKey(SDL_Event& event) return; } + if (event.type == SDL_EVENT_KEY_DOWN) + { + const SDL_Keymod modifiers = SDL_GetModState(); + const bool hasCtrlOrCmd = (modifiers & (SDL_KMOD_CTRL | SDL_KMOD_GUI)) != 0; + if (hasCtrlOrCmd) + { + const bool hasShift = (modifiers & SDL_KMOD_SHIFT) != 0; + if (event.key.key == SDLK_Z) + { + if (hasShift) + { + if (commandSystem_.Redo()) + { + return; + } + } + else + { + if (commandSystem_.Undo()) + { + return; + } + } + } + else if (event.key.key == SDLK_Y) + { + if (commandSystem_.Redo()) + { + return; + } + } + } + + if (event.key.key == SDLK_DELETE || event.key.key == SDLK_BACKSPACE) + { + const uint32_t selectedId = GetScene().GetSelectedId(); + if (selectedId != static_cast(-1)) + { + auto command = std::make_unique(GetScene(), selectedId); + if (ExecuteCommand(std::move(command))) + { + return; + } + } + } + } + if( gameInstance_->OnKey(event) ) { return; } } +bool NextEngine::ExecuteCommand(std::unique_ptr command) +{ + return commandSystem_.ExecuteCommand(std::move(command)); +} + +bool NextEngine::UndoCommand() +{ + return commandSystem_.Undo(); +} + +bool NextEngine::RedoCommand() +{ + return commandSystem_.Redo(); +} + +bool NextEngine::CanUndo() const +{ + return commandSystem_.CanUndo(); +} + +bool NextEngine::CanRedo() const +{ + return commandSystem_.CanRedo(); +} + void NextEngine::OnTouch(bool down, double xpos, double ypos) { //OnMouseButton(GLFW_MOUSE_BUTTON_RIGHT, down ? GLFW_PRESS : GLFW_RELEASE, 0); diff --git a/src/Runtime/Engine.hpp b/src/Runtime/Engine.hpp index b9f1edf5..991eb2f9 100644 --- a/src/Runtime/Engine.hpp +++ b/src/Runtime/Engine.hpp @@ -11,6 +11,7 @@ #include "Rendering/VulkanBaseRenderer.hpp" #include "Options.hpp" #include "Utilities/FileHelper.hpp" +#include "Runtime/Command/CommandSystem.hpp" class NextPhysics; class QuickJSEngine; @@ -167,6 +168,15 @@ class NextEngine final // scene loading void RequestLoadScene(std::string sceneFileName); + // command system + bool ExecuteCommand(std::unique_ptr command); + bool UndoCommand(); + bool RedoCommand(); + bool CanUndo() const; + bool CanRedo() const; + CommandSystem& GetCommandSystem() { return commandSystem_; } + const CommandSystem& GetCommandSystem() const { return commandSystem_; } + Vulkan::Window& GetWindow() const {return *window_;} class UserInterface* GetUserInterface() {return userInterface_.get();} @@ -256,6 +266,8 @@ class NextEngine final std::unique_ptr packageFileSystem_; std::unique_ptr quickJSEngine_; + + CommandSystem commandSystem_{}; // engine status NextRenderer::EApplicationStatus status_{}; diff --git a/src/Runtime/GizmoController.cpp b/src/Runtime/GizmoController.cpp index 5d8c35fe..21efc2c7 100644 --- a/src/Runtime/GizmoController.cpp +++ b/src/Runtime/GizmoController.cpp @@ -3,9 +3,12 @@ #include "Assets/Node.h" #include "Assets/Scene.hpp" #include "Runtime/Engine.hpp" +#include "Runtime/Command/TransformNodeCommand.hpp" +#include "Runtime/Command/DuplicateNodeCommand.hpp" #include "ThirdParty/ImGuizmo/ImGuizmo.h" #include +#include #include #include @@ -53,6 +56,7 @@ void GizmoController::ResetState() isOver_ = false; isShowing_ = false; wasUsing_ = false; + dragActive_ = false; } void GizmoController::DrawToolbar() @@ -89,7 +93,7 @@ void GizmoController::DrawToolbar() void GizmoController::Draw(NextEngine& engine, const glm::vec2& viewportPos, const glm::vec2& viewportSize) { Assets::Scene& scene = engine.GetScene(); - const uint32_t selectedId = scene.GetSelectedId(); + uint32_t selectedId = scene.GetSelectedId(); if (selectedId == static_cast(-1)) { ResetState(); @@ -126,6 +130,7 @@ void GizmoController::Draw(NextEngine& engine, const glm::vec2& viewportPos, con projection[1][1] *= -1.0f; glm::mat4 worldMatrix = node->WorldTransform(); + Assets::Node* activeNode = node; ImGuizmo::SetOrthographic(false); ImGuizmo::BeginFrame(); @@ -142,12 +147,36 @@ void GizmoController::Draw(NextEngine& engine, const glm::vec2& viewportPos, con isUsing_ = ImGuizmo::IsUsing(); isOver_ = ImGuizmo::IsOver(); + if (isUsing_ && !wasUsing_) + { + if (io.KeyShift) + { + auto duplicateCommand = std::make_unique(scene, selectedId); + auto* duplicateCommandPtr = duplicateCommand.get(); + if (engine.ExecuteCommand(std::move(duplicateCommand))) + { + const uint32_t newId = duplicateCommandPtr->GetNewInstanceId(); + Assets::Node* newNode = scene.GetNodeByInstanceId(newId); + if (newNode) + { + selectedId = newId; + scene.SetSelectedId(newId); + activeNode = newNode; + } + } + } + dragActive_ = true; + dragInstanceId_ = selectedId; + dragStartTranslation_ = activeNode->Translation(); + dragStartRotation_ = activeNode->Rotation(); + dragStartScale_ = activeNode->Scale(); + } if (isUsing_) { glm::mat4 parentWorld(1.0f); - if (node->GetParent() != nullptr) + if (activeNode->GetParent() != nullptr) { - parentWorld = node->GetParent()->WorldTransform(); + parentWorld = activeNode->GetParent()->WorldTransform(); } glm::mat4 localMatrix = glm::inverse(parentWorld) * worldMatrix; @@ -159,16 +188,26 @@ void GizmoController::Draw(NextEngine& engine, const glm::vec2& viewportPos, con if (glm::decompose(localMatrix, scale, rotation, translation, skew, perspective)) { - node->SetTranslation(translation); - node->SetRotation(rotation); - node->SetScale(scale); - node->RecalcTransform(true); + activeNode->SetTranslation(translation); + activeNode->SetRotation(rotation); + activeNode->SetScale(scale); + activeNode->RecalcTransform(true); scene.MarkDirty(); } } else if (wasUsing_) { - scene.GetCPUAccelerationStructure().UpdateBVH(scene); + if (dragActive_) + { + TransformSnapshot beforeSnapshot{dragStartTranslation_, dragStartRotation_, dragStartScale_}; + TransformSnapshot afterSnapshot{activeNode->Translation(), activeNode->Rotation(), activeNode->Scale()}; + if (TransformNodeCommand::IsDifferent(beforeSnapshot, afterSnapshot)) + { + auto command = std::make_unique(scene, dragInstanceId_, beforeSnapshot, afterSnapshot); + engine.ExecuteCommand(std::move(command)); + } + } + dragActive_ = false; } wasUsing_ = isUsing_; diff --git a/src/Runtime/GizmoController.hpp b/src/Runtime/GizmoController.hpp index 2f567237..f21669eb 100644 --- a/src/Runtime/GizmoController.hpp +++ b/src/Runtime/GizmoController.hpp @@ -3,6 +3,7 @@ #include "Common/CoreMinimal.hpp" #include +#include class NextEngine; struct ImGuiIO; @@ -27,4 +28,9 @@ class GizmoController bool isOver_ = false; bool isShowing_ = false; bool wasUsing_ = false; + bool dragActive_ = false; + uint32_t dragInstanceId_ = 0; + glm::vec3 dragStartTranslation_ {}; + glm::quat dragStartRotation_ {}; + glm::vec3 dragStartScale_ {1.0f, 1.0f, 1.0f}; }; From 42f710e0dc036aa9de96af712c44beae2ce4e95a Mon Sep 17 00:00:00 2001 From: gameKnife Date: Thu, 29 Jan 2026 00:34:29 +0800 Subject: [PATCH 09/53] feat: implement additive scene loading Allow users to add glTF/GLB models to the current scene without unloading existing content. Core Features: - Implement Scene::Append to merge new scene data (nodes, materials, models, lights, tracks) into the active scene. - Ensure unique root node naming for appended scenes (e.g., SceneName_1). - Remap resource IDs (ModelId, MaterialId) to avoid conflicts. - Refactor NextEngine scene loading logic using LaunchLoadSceneTask to reduce duplication between LoadScene and LoadSceneAdd. Editor Integration: - Add Add To Scene context menu option for GLB files in Content Browser. - Register ECmdIO_LoadSceneAdd editor command. --- src/Assets/Node.h | 1 + src/Assets/Scene.cpp | 77 +++++++++++++++++ src/Assets/Scene.hpp | 6 ++ src/Editor/EditorCommand.hpp | 1 + src/Editor/EditorContentBrowser.cpp | 25 +++++- src/Editor/EditorGUI.h | 2 +- src/Editor/EditorMain.cpp | 5 ++ src/Runtime/Engine.cpp | 126 +++++++++++++++++++--------- src/Runtime/Engine.hpp | 15 ++++ 9 files changed, 216 insertions(+), 42 deletions(-) diff --git a/src/Assets/Node.h b/src/Assets/Node.h index d90204c3..3b1951bf 100644 --- a/src/Assets/Node.h +++ b/src/Assets/Node.h @@ -41,6 +41,7 @@ namespace Assets const std::string& GetName() const {return name_; } uint32_t GetInstanceId() const { return instanceId_; } + void SetInstanceId(uint32_t id) { instanceId_ = id; } bool TickVelocity(glm::mat4& combinedTS); void SetParent(std::shared_ptr parent); diff --git a/src/Assets/Scene.cpp b/src/Assets/Scene.cpp index 9974d03d..353eca7f 100644 --- a/src/Assets/Scene.cpp +++ b/src/Assets/Scene.cpp @@ -120,6 +120,83 @@ namespace Assets tracks_ = std::move(tracks); } + void Scene::Append(const std::string& sceneName, std::vector>& nodes, std::vector& models, + std::vector& materials, std::vector& lights, std::vector& tracks, + const std::vector& skeletons) + { + uint32_t modelOffset = static_cast(models_.size()); + uint32_t materialOffset = static_cast(materials_.size()); + + // Ensure unique root node name + std::string uniqueName = sceneName; + int counter = 1; + while (GetNode(uniqueName) != nullptr) + { + uniqueName = fmt::format("{}_{}", sceneName, counter++); + } + + // Create a root node for the appended scene + uint32_t currentMaxId = GenerateInstanceId(); + auto rootNode = Node::CreateNode(uniqueName, glm::vec3(0), glm::quat(1,0,0,0), glm::vec3(1), currentMaxId++); + + // Update IDs for all new nodes (assuming nodes is a flat list of all new nodes) + for (auto& node : nodes) + { + node->SetInstanceId(currentMaxId++); + + // Update RenderComponent + auto render = node->GetComponent(); + if (render) + { + if (render->GetModelId() != -1) + { + render->SetModelId(render->GetModelId() + modelOffset); + } + auto& mats = render->Materials(); + for (auto& matId : mats) + { + matId += materialOffset; + } + + // Handle Skeletons + if (render->GetSkinIndex() != -1 && render->GetSkinIndex() < skeletons.size()) + { + auto comp = std::make_shared(skeletons[render->GetSkinIndex()]); + comp->AddAnimations(tracks); + comp->PlayAnimation("Default"); + node->AddComponent(comp); + } + } + + // Reparent roots (nodes that don't have a parent in the new scene hierarchy) + if (node->GetParent() == nullptr) + { + node->SetParent(rootNode); + } + } + + // Merge vectors + // models_.insert(models_.end(), models.begin(), models.end()); + models_.reserve(models_.size() + models.size()); + for (auto& model : models) + { + models_.push_back(std::move(model)); + } + + materials_.insert(materials_.end(), materials.begin(), materials.end()); + lights_.insert(lights_.end(), lights.begin(), lights.end()); + tracks_.insert(tracks_.end(), tracks.begin(), tracks.end()); + + // Add new nodes to scene nodes + nodes_.insert(nodes_.end(), nodes.begin(), nodes.end()); + + // Add root node to scene + nodes_.push_back(rootNode); + + // Mark dirty + MarkDirty(); + } + void Scene::RebuildMeshBuffer(Vulkan::CommandPool& commandPool, bool supportRayTracing) { // Rebuild the cpu bvh diff --git a/src/Assets/Scene.hpp b/src/Assets/Scene.hpp index 9ca1dc4d..91c0b8ec 100644 --- a/src/Assets/Scene.hpp +++ b/src/Assets/Scene.hpp @@ -50,6 +50,12 @@ namespace Assets std::vector& materials, std::vector& lights, std::vector& tracks); + void Append(const std::string& sceneName, std::vector>& nodes, + std::vector& models, + std::vector& materials, + std::vector& lights, + std::vector& tracks, + const std::vector& skeletons); void RebuildMeshBuffer(Vulkan::CommandPool& commandPool, bool supportRayTracing); void CleanUp(); diff --git a/src/Editor/EditorCommand.hpp b/src/Editor/EditorCommand.hpp index 6f0251b4..66377b69 100644 --- a/src/Editor/EditorCommand.hpp +++ b/src/Editor/EditorCommand.hpp @@ -10,6 +10,7 @@ enum class EEditorCommand { ECmdSystem_RequestMaximum, ECmdIO_LoadScene, + ECmdIO_LoadSceneAdd, ECmdIO_LoadHDRI, }; diff --git a/src/Editor/EditorContentBrowser.cpp b/src/Editor/EditorContentBrowser.cpp index 73bd6ad1..6bdb43a0 100644 --- a/src/Editor/EditorContentBrowser.cpp +++ b/src/Editor/EditorContentBrowser.cpp @@ -10,7 +10,7 @@ const int iconSize = 96; const int iconPadding = 20; -void Editor::GUI::DrawGeneralContentBrowser(bool iconOrTex, uint32_t globalId, const std::string& name, const char* icon, ImU32 color, std::function doubleclickAction) +void Editor::GUI::DrawGeneralContentBrowser(bool iconOrTex, uint32_t globalId, const std::string& name, const char* icon, ImU32 color, std::function doubleclickAction, std::function contextMenuAction) { ImGui::BeginGroup(); ImGui::PushFont(bigIcon_); // use the font awesome font @@ -57,6 +57,15 @@ void Editor::GUI::DrawGeneralContentBrowser(bool iconOrTex, uint32_t globalId, c ImGui::SetCursorPosY(ImGui::GetCursorPosY() + iconPadding); ImGui::EndGroup(); + + if (contextMenuAction) + { + if (ImGui::BeginPopupContextItem((name + "_context").c_str())) + { + contextMenuAction(); + ImGui::EndPopup(); + } + } } void Editor::GUI::ShowMeshBrowser() @@ -249,6 +258,20 @@ void Editor::GUI::ShowContentBrowser() EditorCommand::ExecuteCommand(EEditorCommand::ECmdIO_LoadHDRI, abspath); } } + }, + [&]() + { + if (ext == ".glb") + { + if (ImGui::MenuItem("Open Scene")) + { + EditorCommand::ExecuteCommand(EEditorCommand::ECmdIO_LoadScene, abspath); + } + if (ImGui::MenuItem("Add To Scene")) + { + EditorCommand::ExecuteCommand(EEditorCommand::ECmdIO_LoadSceneAdd, abspath); + } + } }); if ((elementIdx + 1) % itemsPerRow != 0) diff --git a/src/Editor/EditorGUI.h b/src/Editor/EditorGUI.h index 260468e2..4b91baaf 100644 --- a/src/Editor/EditorGUI.h +++ b/src/Editor/EditorGUI.h @@ -66,7 +66,7 @@ namespace Editor bool meshBrowser = true; void ShowMeshBrowser(); - void DrawGeneralContentBrowser(bool iconOrTex, uint32_t globalId, const std::string& name, const char* icon, ImU32 color, std::function doubleclick_action); + void DrawGeneralContentBrowser(bool iconOrTex, uint32_t globalId, const std::string& name, const char* icon, ImU32 color, std::function doubleclick_action, std::function contextMenuAction = nullptr); bool ed_material = false; // Material Editor Assets::FMaterial* selected_material = nullptr; // Material Selected diff --git a/src/Editor/EditorMain.cpp b/src/Editor/EditorMain.cpp index 692015f2..c5a2bd61 100644 --- a/src/Editor/EditorMain.cpp +++ b/src/Editor/EditorMain.cpp @@ -62,6 +62,11 @@ void EditorGameInstance::OnInit() GetEngine().RequestLoadScene(args); return true; }); + EditorCommand::RegisterEdtiorCommand(EEditorCommand::ECmdIO_LoadSceneAdd, [this](std::string& args)-> bool + { + GetEngine().RequestLoadSceneAdd(args); + return true; + }); EditorCommand::RegisterEdtiorCommand(EEditorCommand::ECmdIO_LoadHDRI, [this](std::string& args)-> bool { //Assets::GlobalTexturePool::UpdateHDRTexture(0, args.c_str(), Vulkan::SamplerConfig()); diff --git a/src/Runtime/Engine.cpp b/src/Runtime/Engine.cpp index 85ad8566..5db8612f 100644 --- a/src/Runtime/Engine.cpp +++ b/src/Runtime/Engine.cpp @@ -1068,34 +1068,44 @@ void NextEngine::RequestLoadScene(std::string sceneFileName) }); } -void NextEngine::LoadScene(std::string sceneFileName) +void NextEngine::RequestLoadSceneAdd(std::string sceneFileName) +{ + AddTickedTask([this, sceneFileName](double deltaSeconds)->bool + { + if ( status_ != NextRenderer::EApplicationStatus::Running ) + { + return false; + } + + LoadSceneAdd(sceneFileName); + return true; + }); +} + +void NextEngine::LaunchLoadSceneTask(std::string sceneFileName, std::function onGpuLoad) { // wait all task finish TaskCoordinator::GetInstance()->CancelAllParralledTasks(); TaskCoordinator::GetInstance()->WaitForAllParralledTask(); - scene_->CleanUp(); - status_ = NextRenderer::EApplicationStatus::Loading; - std::shared_ptr< std::vector > models = std::make_shared< std::vector >(); - std::shared_ptr< std::vector< std::shared_ptr > > nodes = std::make_shared< std::vector< std::shared_ptr > >(); - std::shared_ptr< std::vector > materials = std::make_shared< std::vector >(); - std::shared_ptr< std::vector > lights = std::make_shared< std::vector >(); - std::shared_ptr< std::vector > tracks = std::make_shared< std::vector >(); - std::shared_ptr< std::vector > skeletons = std::make_shared< std::vector >(); - std::shared_ptr< Assets::EnvironmentSetting > cameraState = std::make_shared< Assets::EnvironmentSetting >(); + SceneLoadContext ctx; + ctx.models = std::make_shared< std::vector >(); + ctx.nodes = std::make_shared< std::vector< std::shared_ptr > >(); + ctx.materials = std::make_shared< std::vector >(); + ctx.lights = std::make_shared< std::vector >(); + ctx.tracks = std::make_shared< std::vector >(); + ctx.skeletons = std::make_shared< std::vector >(); + ctx.cameraState = std::make_shared< Assets::EnvironmentSetting >(); - physicsEngine_->OnSceneDestroyed(); - Assets::GlobalTexturePool::GetInstance()->FreeNonSystemTextures(); - // dispatch in thread task and reset in main thread - TaskCoordinator::GetInstance()->AddTask( [cameraState, sceneFileName, models, nodes, materials, lights, tracks, skeletons](ResTask& task) + TaskCoordinator::GetInstance()->AddTask( [ctx, sceneFileName](ResTask& task) { SceneTaskContext taskContext {}; const auto timer = std::chrono::high_resolution_clock::now(); - taskContext.success = SceneList::LoadScene( sceneFileName, *cameraState, *nodes, *models, *materials, *lights, *tracks, *skeletons); + taskContext.success = SceneList::LoadScene( sceneFileName, *ctx.cameraState, *ctx.nodes, *ctx.models, *ctx.materials, *ctx.lights, *ctx.tracks, *ctx.skeletons); taskContext.elapsed = std::chrono::duration(std::chrono::high_resolution_clock::now() - timer).count(); @@ -1103,43 +1113,23 @@ void NextEngine::LoadScene(std::string sceneFileName) std::copy(info.begin(), info.end(), taskContext.outputInfo.data()); task.SetContext( taskContext ); }, - [this, cameraState, sceneFileName, models, nodes, materials, lights, tracks, skeletons](ResTask& task) + [this, ctx, sceneFileName, onGpuLoad](ResTask& task) mutable { SceneTaskContext taskContext {}; task.GetContext( taskContext ); if (taskContext.success ) { SPDLOG_INFO("{}", taskContext.outputInfo.data()); - const auto timer = std::chrono::high_resolution_clock::now(); - scene_->GetEnvSettings().Reset(); - scene_->SetEnvSettings(*cameraState); - - gameInstance_->OnSceneUnloaded(); - physicsEngine_->OnSceneStarted(); - + renderer_->Device().WaitIdle(); renderer_->DeleteSwapChain(); - renderer_->OnPreLoadScene(); - - gameInstance_->BeforeSceneRebuild(*nodes, *models, *materials, *lights, *tracks); - scene_->Reload(*nodes, *models, *materials, *lights, *tracks); - scene_->PostLoad(*skeletons); - scene_->RebuildMeshBuffer(renderer_->CommandPool(), renderer_->supportRayTracing_); - renderer_->SetScene(scene_); - - userSettings_.CameraIdx = 0; - assert(!scene_->GetEnvSettings().cameras.empty()); - scene_->SetRenderCamera(scene_->GetEnvSettings().cameras[0]); + + // Execute the specific GPU load logic + onGpuLoad(ctx); totalFrames_ = 0; - renderer_->OnPostLoadScene(); renderer_->CreateSwapChain(); - - gameInstance_->OnSceneLoaded(); - - float elapsed = std::chrono::duration(std::chrono::high_resolution_clock::now() - timer).count(); - SPDLOG_INFO("uploaded scene [{}] to gpu in {:.2f}ms", std::filesystem::path(sceneFileName).filename().string(), elapsed * 1000.f); } else { @@ -1151,6 +1141,62 @@ void NextEngine::LoadScene(std::string sceneFileName) 1); } +void NextEngine::LoadScene(std::string sceneFileName) +{ + scene_->CleanUp(); + physicsEngine_->OnSceneDestroyed(); + Assets::GlobalTexturePool::GetInstance()->FreeNonSystemTextures(); + + LaunchLoadSceneTask(sceneFileName, [this, sceneFileName](SceneLoadContext& ctx) + { + const auto timer = std::chrono::high_resolution_clock::now(); + scene_->GetEnvSettings().Reset(); + scene_->SetEnvSettings(*ctx.cameraState); + + gameInstance_->OnSceneUnloaded(); + physicsEngine_->OnSceneStarted(); + + renderer_->OnPreLoadScene(); + + gameInstance_->BeforeSceneRebuild(*ctx.nodes, *ctx.models, *ctx.materials, *ctx.lights, *ctx.tracks); + scene_->Reload(*ctx.nodes, *ctx.models, *ctx.materials, *ctx.lights, *ctx.tracks); + scene_->PostLoad(*ctx.skeletons); + scene_->RebuildMeshBuffer(renderer_->CommandPool(), renderer_->supportRayTracing_); + renderer_->SetScene(scene_); + + userSettings_.CameraIdx = 0; + assert(!scene_->GetEnvSettings().cameras.empty()); + scene_->SetRenderCamera(scene_->GetEnvSettings().cameras[0]); + + gameInstance_->OnSceneLoaded(); + + float elapsed = std::chrono::duration(std::chrono::high_resolution_clock::now() - timer).count(); + SPDLOG_INFO("uploaded scene [{}] to gpu in {:.2f}ms", std::filesystem::path(sceneFileName).filename().string(), elapsed * 1000.f); + }); +} + +void NextEngine::LoadSceneAdd(std::string sceneFileName) +{ + LaunchLoadSceneTask(sceneFileName, [this, sceneFileName](SceneLoadContext& ctx) + { + const auto timer = std::chrono::high_resolution_clock::now(); + + renderer_->OnPreLoadScene(); + + gameInstance_->BeforeSceneRebuild(*ctx.nodes, *ctx.models, *ctx.materials, *ctx.lights, *ctx.tracks); + + std::string name = std::filesystem::path(sceneFileName).stem().string(); + scene_->Append(name, *ctx.nodes, *ctx.models, *ctx.materials, *ctx.lights, *ctx.tracks, *ctx.skeletons); + scene_->RebuildMeshBuffer(renderer_->CommandPool(), renderer_->supportRayTracing_); + renderer_->SetScene(scene_); + + // gameInstance_->OnSceneLoaded(); // Maybe trigger this too? + + float elapsed = std::chrono::duration(std::chrono::high_resolution_clock::now() - timer).count(); + SPDLOG_INFO("uploaded scene [{}] to gpu in {:.2f}ms", std::filesystem::path(sceneFileName).filename().string(), elapsed * 1000.f); + }); +} + void NextEngine::InitPhysics() { diff --git a/src/Runtime/Engine.hpp b/src/Runtime/Engine.hpp index 991eb2f9..a87cebc8 100644 --- a/src/Runtime/Engine.hpp +++ b/src/Runtime/Engine.hpp @@ -167,6 +167,7 @@ class NextEngine final // scene loading void RequestLoadScene(std::string sceneFileName); + void RequestLoadSceneAdd(std::string sceneFileName); // command system bool ExecuteCommand(std::unique_ptr command); @@ -217,7 +218,21 @@ class NextEngine final void TickGamepadInput(); private: + struct SceneLoadContext + { + std::shared_ptr< std::vector > models; + std::shared_ptr< std::vector< std::shared_ptr > > nodes; + std::shared_ptr< std::vector > materials; + std::shared_ptr< std::vector > lights; + std::shared_ptr< std::vector > tracks; + std::shared_ptr< std::vector > skeletons; + std::shared_ptr< Assets::EnvironmentSetting > cameraState; + }; + + void LaunchLoadSceneTask(std::string sceneFileName, std::function onGpuLoad); + void LoadScene(std::string sceneFileName); + void LoadSceneAdd(std::string sceneFileName); void InitPhysics(); From 080aac639686f62e99e925218b722e46821578c5 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Thu, 29 Jan 2026 00:40:08 +0800 Subject: [PATCH 10/53] delete wrong method --- GEMINI.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/GEMINI.md b/GEMINI.md index 6bc45a80..57168406 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -37,16 +37,4 @@ * **代码风格:** 强制执行 `.clang-format`。详见 `AGENT_GUIDE/coding-standards.md`。 * **测试规范:** 测试代码位于 `src/Tests`。**关键限制:** 运行单元测试时,工作目录必须设置为二进制文件所在的同级目录。 -## 快速开始 - -```bash -# 安装依赖 -./vcpkg.bat # Windows -./vcpkg.sh # macOS/Linux - -# 构建项目 (自动检测平台) -./build.bat # Windows -./build.sh # macOS/Linux -``` - 关于 Android、特定预设 (Presets) 或运行测试的详细命令,请参考 `AGENT_GUIDE/quick-commands.md`。 \ No newline at end of file From f48e78f0b1a00c53aee0afb3241a3af4ba684d86 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Thu, 29 Jan 2026 12:34:29 +0800 Subject: [PATCH 11/53] Refactor editor UI panels and actions Co-authored-by: gpt-5-codex --- src/Editor/Core/EditorUiState.hpp | 64 ++ src/Editor/EditorActionDispatcher.hpp | 46 ++ src/Editor/EditorCommand.hpp | 52 -- src/Editor/EditorContentBrowser.cpp | 286 ------- src/Editor/EditorContext.hpp | 20 + src/Editor/EditorGUI.h | 91 +- src/Editor/EditorInterface.cpp | 565 +++++++------ src/Editor/EditorInterface.hpp | 62 +- src/Editor/EditorMain.cpp | 206 ++--- src/Editor/EditorMain.h | 10 +- src/Editor/EditorMaterialEd.cpp | 159 ---- src/Editor/EditorMenubar.cpp | 187 ----- src/Editor/EditorOutliner.cpp | 98 --- src/Editor/EditorUi.hpp | 31 + src/Editor/EditorViewport.cpp | 85 -- src/Editor/Nodes/NodeSetInt.cpp | 44 +- src/Editor/Overlays/TitleBarOverlay.cpp | 221 +++++ src/Editor/Panels/ContentBrowserPanel.cpp | 342 ++++++++ src/Editor/Panels/MaterialEditorPanel.cpp | 180 ++++ src/Editor/Panels/OutlinerPanel.cpp | 106 +++ .../PropertiesPanel.cpp} | 111 +-- src/Editor/Panels/ViewportOverlay.cpp | 107 +++ src/Runtime/UserInterface.cpp | 781 +++++++++--------- 23 files changed, 2029 insertions(+), 1825 deletions(-) create mode 100644 src/Editor/Core/EditorUiState.hpp create mode 100644 src/Editor/EditorActionDispatcher.hpp delete mode 100644 src/Editor/EditorCommand.hpp delete mode 100644 src/Editor/EditorContentBrowser.cpp create mode 100644 src/Editor/EditorContext.hpp delete mode 100644 src/Editor/EditorMaterialEd.cpp delete mode 100644 src/Editor/EditorMenubar.cpp delete mode 100644 src/Editor/EditorOutliner.cpp create mode 100644 src/Editor/EditorUi.hpp delete mode 100644 src/Editor/EditorViewport.cpp create mode 100644 src/Editor/Overlays/TitleBarOverlay.cpp create mode 100644 src/Editor/Panels/ContentBrowserPanel.cpp create mode 100644 src/Editor/Panels/MaterialEditorPanel.cpp create mode 100644 src/Editor/Panels/OutlinerPanel.cpp rename src/Editor/{EditorProperties.cpp => Panels/PropertiesPanel.cpp} (50%) create mode 100644 src/Editor/Panels/ViewportOverlay.cpp diff --git a/src/Editor/Core/EditorUiState.hpp b/src/Editor/Core/EditorUiState.hpp new file mode 100644 index 00000000..735f9c5c --- /dev/null +++ b/src/Editor/Core/EditorUiState.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include "Common/CoreMinimal.hpp" + +#include + +#include +#include + +namespace Assets +{ + struct FMaterial; +} + +namespace Editor +{ + inline uint32_t ActiveColor = IM_COL32(64, 128, 255, 255); + constexpr uint32_t InvalidId = std::numeric_limits::max(); + + struct EditorUiState + { + bool state = true; + + // Panels + bool sidebar = true; + bool properties = true; + bool viewport = true; + bool contentBrowser = true; + bool materialBrowser = true; + bool textureBrowser = true; + bool meshBrowser = true; + + // Selection + uint32_t selected_obj_id = InvalidId; + + // Cross-panel asset selections (avoid mixing ids from different sources) + uint32_t selectedMaterialId = InvalidId; + uint32_t selectedTextureId = InvalidId; + uint32_t selectedContentItemId = InvalidId; + + // Viewport content rect (screen space) + ImVec2 viewportContentPos{0.0f, 0.0f}; + ImVec2 viewportContentSize{0.0f, 0.0f}; + bool viewportOnMainViewport = true; + + // Material editor + bool ed_material = false; + Assets::FMaterial* selected_material = nullptr; + + // Tools/children + bool child_style = false; + bool child_demo = false; + bool child_metrics = false; + bool child_color = false; + bool child_stack = false; + bool child_resources = false; + bool child_about = false; + bool child_mat_editor = false; + + // Fonts + ImFont* fontIcon = nullptr; + ImFont* bigIcon = nullptr; + }; +} // namespace Editor diff --git a/src/Editor/EditorActionDispatcher.hpp b/src/Editor/EditorActionDispatcher.hpp new file mode 100644 index 00000000..9cb7ce4c --- /dev/null +++ b/src/Editor/EditorActionDispatcher.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "Common/CoreMinimal.hpp" + +#include "Editor/EditorContext.hpp" + +#include +#include +#include + +enum class EEditorAction +{ + System_RequestExit, + System_RequestMinimize, + System_ToggleMaximize, + + IO_LoadScene, + IO_LoadSceneAdd, + IO_LoadHDRI, +}; + +class EditorActionDispatcher final +{ +public: + using ActionFn = std::function; + + void RegisterAction(EEditorAction action, ActionFn fn) { actions_[action] = std::move(fn); } + + bool Dispatch(EditorContext& ctx, EEditorAction action, std::string_view args = {}) const + { + auto it = actions_.find(action); + if (it == actions_.end() || !it->second) + { + return false; + } + return it->second(ctx, args); + } + +private: + struct EnumHash + { + size_t operator()(EEditorAction v) const noexcept { return static_cast(v); } + }; + + std::unordered_map actions_; +}; diff --git a/src/Editor/EditorCommand.hpp b/src/Editor/EditorCommand.hpp deleted file mode 100644 index 66377b69..00000000 --- a/src/Editor/EditorCommand.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once -#include -#include -#include -#include "Utilities/Exception.hpp" - -enum class EEditorCommand { - ECmdSystem_RequestExit, - ECmdSystem_RequestMinimize, - ECmdSystem_RequestMaximum, - - ECmdIO_LoadScene, - ECmdIO_LoadSceneAdd, - ECmdIO_LoadHDRI, -}; - -struct EditorCommandArgs { - uint8_t context_1k[1024]; -}; - -typedef std::function CommandFunc; - -class EditorCommand { -public: - static void RegisterEdtiorCommand( EEditorCommand cmd, CommandFunc lambda ) { - if( commandMaps_.find(cmd) == commandMaps_.end() ) { - commandMaps_[cmd] = lambda; - } - else { - Throw(std::runtime_error(std::string("Duplicated Command"))); - } - } - - static bool ExecuteCommand( EEditorCommand cmd, std::string& args) { - if( commandMaps_.find(cmd) != commandMaps_.end() ) { - return commandMaps_[cmd](args); - } - else { - Throw(std::runtime_error(std::string("Call Unregisterd Command"))); - } - return false; - } - - static bool ExecuteCommand( EEditorCommand cmd) { - std::string args; - return ExecuteCommand(cmd, args); - } -private: - static std::unordered_map commandMaps_; -}; - -inline std::unordered_map EditorCommand::commandMaps_; \ No newline at end of file diff --git a/src/Editor/EditorContentBrowser.cpp b/src/Editor/EditorContentBrowser.cpp deleted file mode 100644 index 6bdb43a0..00000000 --- a/src/Editor/EditorContentBrowser.cpp +++ /dev/null @@ -1,286 +0,0 @@ -#include - -#include "EditorCommand.hpp" -#include "ThirdParty/fontawesome/IconsFontAwesome6.h" -#include "EditorGUI.h" -#include "Utilities/FileHelper.hpp" -#include "EditorInterface.hpp" -#include "Assets/Scene.hpp" - -const int iconSize = 96; -const int iconPadding = 20; - -void Editor::GUI::DrawGeneralContentBrowser(bool iconOrTex, uint32_t globalId, const std::string& name, const char* icon, ImU32 color, std::function doubleclickAction, std::function contextMenuAction) -{ - ImGui::BeginGroup(); - ImGui::PushFont(bigIcon_); // use the font awesome font - ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(32, 32, 32, 255)); - ImGui::PushID(name.c_str()); - - if ( iconOrTex || (VK_NULL_HANDLE == GUserInterface->RequestImTextureId(globalId))) - { - ImGui::Button(icon, ImVec2(iconSize, iconSize)); - } - else - { - ImGui::Image((ImTextureID)(intptr_t)GUserInterface->RequestImTextureId(globalId), ImVec2(iconSize, iconSize)); - } - - ImGui::PopID(); - ImGui::PopStyleColor(); - ImGui::PopFont(); - - if( ImGui::IsItemHovered(ImGuiHoveredFlags_None) ) - { - if( ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) - { - selectedItemId = -1; - doubleclickAction(); - } - if( ImGui::IsMouseClicked(ImGuiMouseButton_Left)) - { - selectedItemId = globalId; - } - } - - auto cursorPos = ImGui::GetCursorPos() + ImGui::GetWindowPos() - ImVec2(0, 4 + ImGui::GetScrollY()); - bool selected = selectedItemId == globalId; - ImGui::GetWindowDrawList()->AddRectFilled(cursorPos, cursorPos + ImVec2(iconSize, iconSize / 5 * 3),selected ? Editor::ActiveColor : IM_COL32(64, 64, 64, 255), 4); - ImGui::GetWindowDrawList()->AddLine(cursorPos, cursorPos + ImVec2(iconSize, 0), color, 2); - - ImGui::PushItemWidth(iconSize); - ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + iconSize); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5); - ImGui::Text("%s", name.c_str()); - ImGui::PopTextWrapPos(); - ImGui::PopItemWidth(); - - ImGui::SetCursorPosY(ImGui::GetCursorPosY() + iconPadding); - ImGui::EndGroup(); - - if (contextMenuAction) - { - if (ImGui::BeginPopupContextItem((name + "_context").c_str())) - { - contextMenuAction(); - ImGui::EndPopup(); - } - } -} - -void Editor::GUI::ShowMeshBrowser() -{ - ImGui::Begin("Mesh Browser", NULL); - { - if (current_scene) - { - auto& allModels = current_scene->Models(); - - float windowWidth = ImGui::GetContentRegionAvail().x; - int itemsPerRow = std::max(1, (int)(windowWidth / (iconSize + ImGui::GetStyle().ItemSpacing.x))); - - for ( uint32_t i = 0; i < allModels.size(); ++i) - { - auto& model = allModels[i]; - std::string name = fmt::format("{}_#{}", model.Name(), i); - DrawGeneralContentBrowser(true, i, name, ICON_FA_BOXES_PACKING, IM_COL32(132, 182, 255, 255), [this, i]() - { - - }); - - if ((i + 1) % itemsPerRow != 0) ImGui::SameLine(); - } - } - } - ImGui::End(); -} - -void Editor::GUI::ShowMaterialBrowser() -{ - ImGui::Begin("Material Browser", NULL); - { - if (current_scene) - { - auto& allMaterials = current_scene->Materials(); - - float windowWidth = ImGui::GetContentRegionAvail().x; - int itemsPerRow = std::max(1, (int)(windowWidth / (iconSize + ImGui::GetStyle().ItemSpacing.x))); - - for ( uint32_t i = 0; i < allMaterials.size(); ++i) - { - auto& mat = allMaterials[i]; - DrawGeneralContentBrowser(true, i, mat.name_, ICON_FA_BOWLING_BALL, IM_COL32(132, 255, 132, 255), [this, i]() - { - selected_material = &(current_scene->Materials()[i]); - ed_material = true; - OpenMaterialEditor(); - }); - - if ((i + 1) % itemsPerRow != 0) ImGui::SameLine(); - } - } - - - } - ImGui::End(); -} - -void Editor::GUI::ShowTextureBrowser() -{ - ImGui::Begin("Texture Browser", NULL ); - { - if (current_scene) - { - auto& totalTextureMap = Assets::GlobalTexturePool::GetInstance()->TotalTextureMap(); - - float windowWidth = ImGui::GetContentRegionAvail().x; - int itemsPerRow = std::max(1, (int)(windowWidth / (iconSize + ImGui::GetStyle().ItemSpacing.x))); - - int itemIndex = 0; - for ( auto& textureGroup : totalTextureMap ) - { - DrawGeneralContentBrowser(false, textureGroup.second.GlobalIdx_, textureGroup.first, ICON_FA_LINK_SLASH, IM_COL32(255, 72, 72, 255), [this]() - { - - }); - - if ((itemIndex++ + 1) % itemsPerRow != 0) ImGui::SameLine(); - } - } - } - ImGui::End(); -} - -void Editor::GUI::ShowContentBrowser() -{ - ImGui::Begin("Content Browser", NULL); - { - static auto modelpath = Utilities::FileHelper::GetPlatformFilePath("assets"); - std::filesystem::path path = modelpath; - - std::vector paths; - std::string pathstr = path.string(); - std::string delimiter = std::string(1, std::filesystem::path::preferred_separator); - size_t pos = 0; - std::string token; - while ((pos = pathstr.find(delimiter)) != std::string::npos) { - token = pathstr.substr(0, pos); - paths.push_back(token); - pathstr.erase(0, pos + delimiter.length()); - } - paths.push_back(pathstr); - - ImGui::PushFont(fontIcon_); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8,4)); - if (ImGui::Button(ICON_FA_HOUSE)) modelpath = Utilities::FileHelper::GetPlatformFilePath("assets"); - for (int i = 1; i < paths.size(); i++) - { - ImGui::SameLine(); - ImGui::TextUnformatted(">"); - ImGui::SameLine(); - auto upperPath = paths[i]; - if (!upperPath.empty()) upperPath[0] = std::toupper(upperPath[0]); - if (ImGui::Button(upperPath.c_str())) - { - auto newpath = std::filesystem::path(paths[0]); - for (int j = 1; j <= i; j++) - { - newpath = newpath.append(paths[j]); - } - modelpath = newpath.string(); - } - } - ImGui::PopStyleVar(); - ImGui::PopFont(); - - auto cursorPos = ImGui::GetWindowPos() + ImVec2(0, ImGui::GetCursorPos().y + 2); - ImGui::NewLine(); - ImGui::GetWindowDrawList()->AddLine(cursorPos + ImVec2(0,1), cursorPos + ImVec2(ImGui::GetWindowSize().x,1), IM_COL32(20,20,20,128), 1); - ImGui::GetWindowDrawList()->AddLine(cursorPos, cursorPos + ImVec2(ImGui::GetWindowSize().x,0), IM_COL32(20,20,20,255), 1); - - ImGui::BeginChild("Content Items"); - - static std::string contextMenuFile; - // if (ImGui::BeginPopupContextItem(path.c_str())) - // { - // ImGui::SeparatorText("Context Menu"); - // menu_actions(); - // ImGui::EndPopup(); - // } - // ImGui::OpenPopupOnItemClick(path.c_str(), ImGuiPopupFlags_MouseButtonRight); - - std::filesystem::directory_iterator it(path); - float windowWidth = ImGui::GetContentRegionAvail().x; - int itemsPerRow = std::max(1, (int)(windowWidth / (iconSize + ImGui::GetStyle().ItemSpacing.x))); - - uint32_t elementIdx = 0; - for (auto& entry : it) - { - std::string abspath = absolute(entry.path()).string(); - std::string relpath = relative(entry.path()).string(); - std::string name = entry.path().filename().string(); - std::string ext = entry.path().extension().string(); - - const char* icon = ICON_FA_FOLDER; - ImU32 color = IM_COL32(0, 172, 255, 255); - if (entry.is_regular_file()) - { - if (ext == ".glb") - { - icon = ICON_FA_CUBE; - color = IM_COL32(255, 172, 0, 255); - } - else if (ext == ".hdr") - { - icon = ICON_FA_FILE_IMAGE; - color = IM_COL32(200, 64, 64, 255); - } - else - { - continue; - } - } - - DrawGeneralContentBrowser(true, elementIdx, name, icon, color, [&]() - { - if (entry.is_directory()) - { - modelpath = relpath; - } - else - { - if (ext == ".glb") - { - EditorCommand::ExecuteCommand(EEditorCommand::ECmdIO_LoadScene, abspath); - } - else if (ext == ".hdr") - { - EditorCommand::ExecuteCommand(EEditorCommand::ECmdIO_LoadHDRI, abspath); - } - } - }, - [&]() - { - if (ext == ".glb") - { - if (ImGui::MenuItem("Open Scene")) - { - EditorCommand::ExecuteCommand(EEditorCommand::ECmdIO_LoadScene, abspath); - } - if (ImGui::MenuItem("Add To Scene")) - { - EditorCommand::ExecuteCommand(EEditorCommand::ECmdIO_LoadSceneAdd, abspath); - } - } - }); - - if ((elementIdx + 1) % itemsPerRow != 0) - { - ImGui::SameLine(); - } - elementIdx++; - } - ImGui::EndChild(); - } - ImGui::End(); -} diff --git a/src/Editor/EditorContext.hpp b/src/Editor/EditorContext.hpp new file mode 100644 index 00000000..8b4069ad --- /dev/null +++ b/src/Editor/EditorContext.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "Common/CoreMinimal.hpp" + +class NextEngine; +class UserInterface; +class EditorActionDispatcher; + +namespace Assets +{ + class Scene; +} + +struct EditorContext final +{ + NextEngine& engine; + Assets::Scene& scene; + UserInterface& ui; + EditorActionDispatcher& actions; +}; diff --git a/src/Editor/EditorGUI.h b/src/Editor/EditorGUI.h index 4b91baaf..7741b8d1 100644 --- a/src/Editor/EditorGUI.h +++ b/src/Editor/EditorGUI.h @@ -1,92 +1,11 @@ -#if WIN32 && !defined(__MINGW32__) -#pragma warning( disable : 4005) -#endif - #pragma once -#include -#include -#include -#include "imgui.h" -#include "imgui_stdlib.h" -#include "EditorUtils.h" - -class NextEngine; - -namespace Assets -{ - struct Material; - struct FMaterial; -} - -struct Statistics; +// Compatibility shim: old code used Editor::GUI. +// New code should prefer Editor::EditorUiState and free Draw* functions in Editor/EditorUi.hpp. -namespace Assets -{ - class Node; -} - -namespace Assets -{ - class Scene; -} +#include "Editor/Core/EditorUiState.hpp" namespace Editor { - inline uint32_t ActiveColor = IM_COL32(64, 128, 255, 255); - - struct GUI - { - bool state = true; // Alive - - bool menubar = true; // Menubar State - void ShowMenubar(); - - bool sidebar = true; // Sidebar State - void ShowSidebar(Assets::Scene* scene); - - bool properties = true; // Properties State - void ShowProperties(); - - bool viewport = true; // Viewport State - uint32_t selected_obj_id = -1; // Viewport Selected - Assets::Scene* current_scene = nullptr; - NextEngine* engine = nullptr; // Engine - void ShowViewport (ImGuiID id); - - bool contentBrowser = true; // Workspace "Output" - void ShowContentBrowser(); - - bool materialBrowser = true; // Workspace "Output" - void ShowMaterialBrowser(); - - bool textureBrowser = true; // Workspace "Output" - void ShowTextureBrowser(); - - bool meshBrowser = true; - void ShowMeshBrowser(); - - void DrawGeneralContentBrowser(bool iconOrTex, uint32_t globalId, const std::string& name, const char* icon, ImU32 color, std::function doubleclick_action, std::function contextMenuAction = nullptr); - - bool ed_material = false; // Material Editor - Assets::FMaterial* selected_material = nullptr; // Material Selected - void ShowMaterialEditor(); - void OpenMaterialEditor(); - void ApplyMaterial(); - - bool child_style = false; // Show Style Editor - bool child_demo = false; // Show Demo Window - bool child_metrics = false; // Show Metrics Window - bool child_color = false; // Show Color Export - bool child_stack = false; // Show Stack Tool - bool child_resources = false; // Show Help Resources - bool child_about = false; // Show About Window - bool child_mat_editor = false; // Show About Window - - ImFont* fontIcon_ = nullptr; - ImFont* bigIcon_ = nullptr; - - uint32_t selectedItemId = -1; // Selected Item in Content Browser - }; - -} \ No newline at end of file + using GUI = EditorUiState; +} diff --git a/src/Editor/EditorInterface.cpp b/src/Editor/EditorInterface.cpp index 17c577ba..8c42a176 100644 --- a/src/Editor/EditorInterface.cpp +++ b/src/Editor/EditorInterface.cpp @@ -1,16 +1,6 @@ #include "EditorInterface.hpp" -#include "Runtime/SceneList.hpp" -#include "Runtime/UserSettings.hpp" #include "Utilities/Exception.hpp" -#include "Vulkan/DescriptorPool.hpp" -#include "Vulkan/Device.hpp" -#include "Vulkan/Instance.hpp" -#include "Vulkan/RenderPass.hpp" -#include "Vulkan/SingleTimeCommands.hpp" -#include "Vulkan/Surface.hpp" -#include "Vulkan/SwapChain.hpp" -#include "Vulkan/Window.hpp" #include #include @@ -19,301 +9,338 @@ #include #include -#include -#include #include +#include +#include "Editor/EditorUi.hpp" -#include "Options.hpp" #include "Assets/Scene.hpp" #include "Assets/TextureImage.hpp" +#include "Common/CoreMinimal.hpp" +#include "Editor/EditorActionDispatcher.hpp" +#include "Editor/EditorContext.hpp" #include "Editor/EditorMain.h" +#include "Editor/EditorUtils.h" +#include "Options.hpp" +#include "Rendering/VulkanBaseRenderer.hpp" +#include "ThirdParty/fontawesome/IconsFontAwesome6.h" #include "Utilities/FileHelper.hpp" #include "Utilities/Localization.hpp" #include "Utilities/Math.hpp" -#include "Vulkan/ImageView.hpp" -#include "Vulkan/RenderImage.hpp" -#include "Rendering/VulkanBaseRenderer.hpp" -#include "ThirdParty/fontawesome/IconsFontAwesome6.h" -#include "Common/CoreMinimal.hpp" extern std::unique_ptr GApplication; namespace { - void CheckVulkanResultCallback(const VkResult err) - { - if (err != VK_SUCCESS) - { - Throw(std::runtime_error(std::string("ImGui Vulkan error (") + Vulkan::ToString(err) + ")")); - } - } - - const ImWchar* GetGlyphRangesFontAwesome() - { - static const ImWchar ranges[] = - { - ICON_MIN_FA, ICON_MAX_FA, // Basic Latin + Latin Supplement - 0, - }; - return &ranges[0]; - } -} - -EditorInterface::EditorInterface(class EditorGameInstance* editor) - : editor_(editor) -{ - -} - -EditorInterface::~EditorInterface() -{ - editorGUI_.reset(); -} + void CheckVulkanResultCallback(const VkResult err) + { + if (err != VK_SUCCESS) + { + Throw(std::runtime_error(std::string("ImGui Vulkan error (") + Vulkan::ToString(err) + ")")); + } + } + + const ImWchar* GetGlyphRangesFontAwesome() + { + static const ImWchar ranges[] = { + ICON_MIN_FA, + ICON_MAX_FA, // Basic Latin + Latin Supplement + 0, + }; + return &ranges[0]; + } +} // namespace + +EditorInterface::EditorInterface(class EditorGameInstance* editor) : editor_(editor) {} + +EditorInterface::~EditorInterface() = default; void EditorInterface::Config() { - auto& io = ImGui::GetIO(); + auto& io = ImGui::GetIO(); - io.IniFilename = "editor.ini"; - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls - io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls - io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking - io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; + io.IniFilename = "editor.ini"; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking + io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; } void EditorInterface::Init() { - editorGUI_.reset(new Editor::GUI()); - auto& io = ImGui::GetIO(); - - // Window scaling and style. - const auto scaleFactor = 1.0; - //ImGui::GetStyle().ScaleAllSizes(scaleFactor); - - io.Fonts->FontBuilderIO = ImGuiFreeType::GetBuilderForFreeType(); - io.Fonts->FontBuilderFlags = ImGuiFreeTypeBuilderFlags_NoHinting; - const ImWchar* glyphRange = GOption->locale == "RU" ? io.Fonts->GetGlyphRangesCyrillic() : GOption->locale == "zhCN" ? io.Fonts->GetGlyphRangesChineseFull() : io.Fonts->GetGlyphRangesDefault(); - - const ImWchar* iconRange = GetGlyphRangesFontAwesome(); - ImFontConfig config; - config.MergeMode = true; - config.GlyphMinAdvanceX = 14.0f; - config.GlyphOffset = ImVec2(0, 0); - if (!io.Fonts->AddFontFromFileTTF(Utilities::FileHelper::GetPlatformFilePath("assets/fonts/fa-solid-900.ttf").c_str(), 14 * scaleFactor, &config, iconRange )) - { - - } - - fontIcon_ = io.Fonts->AddFontFromFileTTF(Utilities::FileHelper::GetPlatformFilePath("assets/fonts/Roboto-BoldCondensed.ttf").c_str(), 18 * scaleFactor, nullptr, glyphRange ); - - config.GlyphMinAdvanceX = 20.0f; - config.GlyphOffset = ImVec2(0, 0); - io.Fonts->AddFontFromFileTTF(Utilities::FileHelper::GetPlatformFilePath("assets/fonts/fa-solid-900.ttf").c_str(), 18 * scaleFactor, &config, iconRange ); - - fontBigIcon_ = io.Fonts->AddFontFromFileTTF(Utilities::FileHelper::GetPlatformFilePath("assets/fonts/fa-solid-900.ttf").c_str(), 32 * scaleFactor, nullptr, iconRange ); - - firstRun = true; - - editorGUI_->fontIcon_ = fontIcon_; - editorGUI_->bigIcon_ = fontBigIcon_; + auto& io = ImGui::GetIO(); + + // Window scaling and style. + const auto scaleFactor = 1.0; + // ImGui::GetStyle().ScaleAllSizes(scaleFactor); + + io.Fonts->FontBuilderIO = ImGuiFreeType::GetBuilderForFreeType(); + io.Fonts->FontBuilderFlags = ImGuiFreeTypeBuilderFlags_NoHinting; + const ImWchar* glyphRange = GOption->locale == "RU" ? io.Fonts->GetGlyphRangesCyrillic() + : GOption->locale == "zhCN" ? io.Fonts->GetGlyphRangesChineseFull() + : io.Fonts->GetGlyphRangesDefault(); + + const ImWchar* iconRange = GetGlyphRangesFontAwesome(); + ImFontConfig config; + config.MergeMode = true; + config.GlyphMinAdvanceX = 14.0f; + config.GlyphOffset = ImVec2(0, 0); + if (!io.Fonts->AddFontFromFileTTF( + Utilities::FileHelper::GetPlatformFilePath("assets/fonts/fa-solid-900.ttf").c_str(), 14 * scaleFactor, + &config, iconRange)) + { + } + + ImFont* fontIcon = io.Fonts->AddFontFromFileTTF( + Utilities::FileHelper::GetPlatformFilePath("assets/fonts/Roboto-BoldCondensed.ttf").c_str(), 18 * scaleFactor, + nullptr, glyphRange); + + config.GlyphMinAdvanceX = 20.0f; + config.GlyphOffset = ImVec2(0, 0); + io.Fonts->AddFontFromFileTTF(Utilities::FileHelper::GetPlatformFilePath("assets/fonts/fa-solid-900.ttf").c_str(), + 18 * scaleFactor, &config, iconRange); + + ImFont* fontBigIcon = io.Fonts->AddFontFromFileTTF( + Utilities::FileHelper::GetPlatformFilePath("assets/fonts/fa-solid-900.ttf").c_str(), 32 * scaleFactor, nullptr, + iconRange); + + uiState_.fontIcon = fontIcon; + uiState_.bigIcon = fontBigIcon; + firstRun_ = true; } -const float toolbarSize = 50; -const float toolbarIconWidth = 32; -const float toolbarIconHeight = 32; -const float titleBarHeight = 55; -const float footBarHeight = 40; -float MenuBarHeight = 0; +namespace +{ + constexpr float kToolbarSize = 50.0f; + constexpr float kToolbarIconWidth = 32.0f; + constexpr float kToolbarIconHeight = 32.0f; + constexpr float kTitleBarHeight = 55.0f; + constexpr float kFootBarHeight = 40.0f; + float gMenuBarHeight = 0.0f; +} // namespace ImGuiID EditorInterface::DockSpaceUI() { - ImGuiViewport* viewport = ImGui::GetMainViewport(); - ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + toolbarSize + titleBarHeight - MenuBarHeight)); - ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, viewport->Size.y - toolbarSize - titleBarHeight + MenuBarHeight - footBarHeight)); - ImGui::SetNextWindowViewport(viewport->ID); - ImGui::SetNextWindowBgAlpha(0); - ImGuiWindowFlags windowFlags = 0 - | ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking - | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse - | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove - | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; - - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); - ImGui::Begin("Master DockSpace", NULL, windowFlags); - ImGuiID dockMain = ImGui::GetID("MyDockspace"); - - // Save off menu bar height for later. - MenuBarHeight = ImGui::GetCurrentWindow()->MenuBarHeight; - - ImGui::DockSpace(dockMain, ImVec2(0,0), ImGuiDockNodeFlags_NoDockingInCentralNode | ImGuiDockNodeFlags_PassthruCentralNode); - ImGui::End(); - ImGui::PopStyleVar(3); - - return dockMain; + ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos( + ImVec2(viewport->Pos.x, viewport->Pos.y + kToolbarSize + kTitleBarHeight - gMenuBarHeight)); + ImGui::SetNextWindowSize( + ImVec2(viewport->Size.x, viewport->Size.y - kToolbarSize - kTitleBarHeight + gMenuBarHeight - kFootBarHeight)); + ImGui::SetNextWindowViewport(viewport->ID); + ImGui::SetNextWindowBgAlpha(0); + ImGuiWindowFlags windowFlags = 0 | ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking | + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::Begin("Master DockSpace", NULL, windowFlags); + ImGuiID dockMain = ImGui::GetID("MyDockspace"); + + // Save off menu bar height for later. + gMenuBarHeight = ImGui::GetCurrentWindow()->MenuBarHeight; + + ImGui::DockSpace(dockMain, ImVec2(0, 0), + ImGuiDockNodeFlags_NoDockingInCentralNode | ImGuiDockNodeFlags_PassthruCentralNode); + ImGui::End(); + ImGui::PopStyleVar(3); + + return dockMain; } void EditorInterface::ToolbarUI() { - ImGuiViewport* viewport = ImGui::GetMainViewport(); - ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + titleBarHeight)); - ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, toolbarSize)); - ImGui::SetNextWindowViewport(viewport->ID); - - ImGuiWindowFlags windowFlags = 0 - | ImGuiWindowFlags_NoDocking - | ImGuiWindowFlags_NoTitleBar - | ImGuiWindowFlags_NoResize - | ImGuiWindowFlags_NoMove - | ImGuiWindowFlags_NoScrollbar - | ImGuiWindowFlags_NoSavedSettings - ; - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - - ImGui::Begin("TOOLBAR", NULL, windowFlags); - ImGui::PopStyleVar(); - ImGui::PopStyleVar(); - - ImGui::BeginGroup(); - ImGui::PushFont(fontIcon_); - ImGui::Button(ICON_FA_FLOPPY_DISK, ImVec2(toolbarIconWidth, toolbarIconHeight));ImGui::SameLine(); - ImGui::Button(ICON_FA_FOLDER, ImVec2(toolbarIconWidth, toolbarIconHeight));ImGui::SameLine(); - ImGui::PopFont(); - ImGui::EndGroup();ImGui::SameLine(); - - ImGui::BeginGroup();ImGui::SameLine(50); - ImGui::PushFont(fontIcon_); - ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(80,210,0,255)); - if( ImGui::Button(ICON_FA_PLAY, ImVec2(toolbarIconWidth, toolbarIconHeight)) ) - { - std::filesystem::path currentPath = std::filesystem::current_path(); - std::string cmdline = (currentPath / "gkNextRenderer").string() + (GOption->ForceSDR ? " --forcesdr" : ""); - std::system(cmdline.c_str()); - } - ImGui::SameLine(); - - ImGui::PopStyleColor(); - ImGui::PopFont(); - static int item = 3; - static float color[4] = { 0.4f, 0.7f, 0.0f, 0.5f }; - ImGui::SetNextItemWidth(120); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(7,7)); - ImGui::Combo("##Render", &item, "RTPipe\0ModernDeferred\0LegacyDeferred\0RayQuery\0HybirdRender\0\0");ImGui::SameLine(); - ImGui::PopStyleVar(); - ImGui::EndGroup();ImGui::SameLine(); - - - ImGui::BeginGroup();ImGui::SameLine(50); - ImGui::PushFont(fontIcon_); - ImGui::Button(ICON_FA_FILE_IMPORT, ImVec2(toolbarIconWidth, toolbarIconHeight));ImGui::SameLine(); - ImGui::PopFont(); - ImGui::EndGroup(); - - ImGui::End(); + ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + kTitleBarHeight)); + ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, kToolbarSize)); + ImGui::SetNextWindowViewport(viewport->ID); + + ImGuiWindowFlags windowFlags = 0 | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoSavedSettings; + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + + ImGui::Begin("TOOLBAR", NULL, windowFlags); + ImGui::PopStyleVar(); + ImGui::PopStyleVar(); + + ImGui::BeginGroup(); + if (uiState_.fontIcon) + { + ImGui::PushFont(uiState_.fontIcon); + } + ImGui::Button(ICON_FA_FLOPPY_DISK, ImVec2(kToolbarIconWidth, kToolbarIconHeight)); + ImGui::SameLine(); + ImGui::Button(ICON_FA_FOLDER, ImVec2(kToolbarIconWidth, kToolbarIconHeight)); + ImGui::SameLine(); + if (uiState_.fontIcon) + { + ImGui::PopFont(); + } + ImGui::EndGroup(); + ImGui::SameLine(); + + ImGui::BeginGroup(); + ImGui::SameLine(50); + if (uiState_.fontIcon) + { + ImGui::PushFont(uiState_.fontIcon); + } + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(80, 210, 0, 255)); + if (ImGui::Button(ICON_FA_PLAY, ImVec2(kToolbarIconWidth, kToolbarIconHeight))) + { + std::filesystem::path currentPath = std::filesystem::current_path(); + std::string cmdline = (currentPath / "gkNextRenderer").string() + (GOption->ForceSDR ? " --forcesdr" : ""); + std::system(cmdline.c_str()); + } + ImGui::SameLine(); + + ImGui::PopStyleColor(); + if (uiState_.fontIcon) + { + ImGui::PopFont(); + } + static int item = 3; + ImGui::SetNextItemWidth(120); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(7, 7)); + ImGui::Combo("##Render", &item, "RTPipe\0ModernDeferred\0LegacyDeferred\0RayQuery\0HybirdRender\0\0"); + ImGui::SameLine(); + ImGui::PopStyleVar(); + ImGui::EndGroup(); + ImGui::SameLine(); + + + ImGui::BeginGroup(); + ImGui::SameLine(50); + if (uiState_.fontIcon) + { + ImGui::PushFont(uiState_.fontIcon); + } + ImGui::Button(ICON_FA_FILE_IMPORT, ImVec2(kToolbarIconWidth, kToolbarIconHeight)); + ImGui::SameLine(); + if (uiState_.fontIcon) + { + ImGui::PopFont(); + } + ImGui::EndGroup(); + + ImGui::End(); } void EditorInterface::Render() { - GUserInterface = editor_->GetEngine().GetUserInterface(); - - editorGUI_->selected_obj_id = editor_->GetEngine().GetScene().GetSelectedId(); - - ImGuiID id = DockSpaceUI(); - ToolbarUI(); - - MainWindowGUI(*editorGUI_, editor_->GetEngine().GetScene(), id, firstRun); - - ImGuiDockNode* node = ImGui::DockBuilderGetCentralNode(id); - ImGuiViewport* viewport = ImGui::GetMainViewport(); - editor_->GetEngine().GetRenderer().SwapChain().UpdateOutputViewport(Utilities::Math::floorToInt(node->Pos.x - viewport->Pos.x), Utilities::Math::floorToInt(node->Pos.y - viewport->Pos.y), Utilities::Math::ceilToInt(node->Size.x), Utilities::Math::ceilToInt(node->Size.y)); - //editor_->GetEngine().GetRenderer().SwapChain().UpdateRenderViewport(0, 0, Utilities::Math::ceilToInt(node->Size.x), Utilities::Math::ceilToInt(node->Size.y)); - - editor_->DrawGizmo(glm::vec2(node->Pos.x, node->Pos.y), glm::vec2(node->Size.x, node->Size.y)); - - firstRun = false; - GUserInterface = nullptr; + UserInterface* ui = editor_->GetEngine().GetUserInterface(); + if (ui == nullptr) + { + return; + } + + EditorContext ctx{editor_->GetEngine(), editor_->GetEngine().GetScene(), *ui, editor_->Actions()}; + void* previousUserData = ImGui::GetIO().UserData; + ImGui::GetIO().UserData = &ctx; + + uiState_.selected_obj_id = ctx.scene.GetSelectedId(); + + ImGuiID id = DockSpaceUI(); + ToolbarUI(); + + if (firstRun_) + { + ImGuiID dock1 = ImGui::DockBuilderSplitNode(id, ImGuiDir_Left, 0.1f, nullptr, &id); + ImGuiID dock2 = ImGui::DockBuilderSplitNode(id, ImGuiDir_Right, 0.2f, nullptr, &id); + ImGuiID dock3 = ImGui::DockBuilderSplitNode(id, ImGuiDir_Down, 0.25f, nullptr, &id); + + ImGui::DockBuilderDockWindow("Outliner", dock1); + ImGui::DockBuilderDockWindow("Properties", dock2); + ImGui::DockBuilderDockWindow("Content Browser", dock3); + ImGui::DockBuilderDockWindow("Material Browser", dock3); + ImGui::DockBuilderDockWindow("Texture Browser", dock3); + ImGui::DockBuilderDockWindow("Mesh Browser", dock3); + ImGui::DockBuilderFinish(id); + } + + Editor::DrawTitleBarOverlay(ctx, uiState_); + + if (uiState_.sidebar) + Editor::DrawOutlinerPanel(ctx, uiState_); + if (uiState_.properties) + Editor::DrawPropertiesPanel(ctx, uiState_); + if (uiState_.contentBrowser) + Editor::DrawContentBrowserPanel(ctx, uiState_); + if (uiState_.materialBrowser) + Editor::DrawMaterialBrowserPanel(ctx, uiState_); + if (uiState_.textureBrowser) + Editor::DrawTextureBrowserPanel(ctx, uiState_); + if (uiState_.meshBrowser) + Editor::DrawMeshBrowserPanel(ctx, uiState_); + if (uiState_.viewport) + Editor::DrawViewportOverlay(ctx, uiState_); + + if (uiState_.child_style) + utils::ShowStyleEditorWindow(&uiState_.child_style); + if (uiState_.child_demo) + ImGui::ShowDemoWindow(&uiState_.child_demo); + if (uiState_.child_metrics) + ImGui::ShowMetricsWindow(&uiState_.child_metrics); + if (uiState_.child_stack) + ImGui::ShowStackToolWindow(&uiState_.child_stack); + if (uiState_.child_color) + utils::ShowColorExportWindow(&uiState_.child_color); + if (uiState_.child_resources) + utils::ShowResourcesWindow(&uiState_.child_resources); + if (uiState_.child_about) + utils::ShowAboutWindow(&uiState_.child_about); + + if (uiState_.ed_material) + Editor::DrawMaterialEditorPanel(ctx, uiState_); + + // The renderer output rect is the dockspace central node. + uiState_.viewportOnMainViewport = true; + uiState_.viewportContentPos = ImVec2(0.0f, 0.0f); + uiState_.viewportContentSize = ImVec2(0.0f, 0.0f); + + if (ImGuiDockNode* node = ImGui::DockBuilderGetCentralNode(id)) + { + uiState_.viewportContentPos = node->Pos; + uiState_.viewportContentSize = node->Size; + + ImGuiViewport* mainViewport = ImGui::GetMainViewport(); + if (node->HostWindow && node->HostWindow->Viewport && node->HostWindow->Viewport != mainViewport) + { + uiState_.viewportOnMainViewport = false; + } + + if (uiState_.viewportOnMainViewport && node->Size.x >= 1.0f && node->Size.y >= 1.0f) + { + editor_->GetEngine().GetRenderer().SwapChain().UpdateOutputViewport( + Utilities::Math::floorToInt(node->Pos.x - mainViewport->Pos.x), + Utilities::Math::floorToInt(node->Pos.y - mainViewport->Pos.y), + Utilities::Math::ceilToInt(node->Size.x), Utilities::Math::ceilToInt(node->Size.y)); + + editor_->DrawGizmo(glm::vec2(node->Pos.x, node->Pos.y), glm::vec2(node->Size.x, node->Size.y)); + } + } + + firstRun_ = false; + ImGui::GetIO().UserData = previousUserData; } void EditorInterface::DrawIndicator(uint32_t frameCount) { - ImGui::OpenPopup("Loading"); - // Always center this window when appearing - ImVec2 center = ImGui::GetMainViewport()->GetCenter(); - ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); - ImGui::SetNextWindowSize(ImVec2(100,40)); - - if (ImGui::BeginPopupModal("Loading", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar)) - { - ImGui::Text("Loading%s ", frameCount % 4 == 0 ? "" : frameCount % 4 == 1 ? "." : frameCount % 4 == 2 ? ".." : "..."); - ImGui::EndPopup(); - } -} - - -void EditorInterface::MainWindowGUI(Editor::GUI & guiR, Assets::Scene& scene, ImGuiID id, bool firstRun) -{ - ////////////////////////////////// - Editor::GUI &gui = guiR; - - gui.current_scene = &scene; - gui.engine = &(editor_->GetEngine()); - - // Only run DockBuilder functions on the first frame of the app: - if (firstRun) { - ImGuiID dock1 = ImGui::DockBuilderSplitNode(id, ImGuiDir_Left, 0.1f, nullptr, &id); - ImGuiID dock2 = ImGui::DockBuilderSplitNode(id, ImGuiDir_Right, 0.2f, nullptr, &id); - ImGuiID dock3 = ImGui::DockBuilderSplitNode(id, ImGuiDir_Down, 0.25f, nullptr, &id); - - ImGui::DockBuilderDockWindow("Outliner", dock1); - ImGui::DockBuilderDockWindow("Properties", dock2); - ImGui::DockBuilderDockWindow("Content Browser", dock3); - ImGui::DockBuilderDockWindow("Material Browser", dock3); - ImGui::DockBuilderDockWindow("Texture Browser", dock3); - ImGui::DockBuilderDockWindow("Mesh Browser", dock3); - - ImGui::DockBuilderFinish(id); - } - - { - gui.ShowMenubar(); - - // create-sidebar - if (gui.sidebar) gui.ShowSidebar(&scene); - - // create-properties - if (gui.properties) gui.ShowProperties(); - - // content browser - if (gui.contentBrowser) gui.ShowContentBrowser(); - - // material browser - if (gui.materialBrowser) gui.ShowMaterialBrowser(); - - // texture browser - if (gui.textureBrowser) gui.ShowTextureBrowser(); - - // mesh browser - if (gui.meshBrowser) gui.ShowMeshBrowser(); - - // create-viewport - if (gui.viewport) gui.ShowViewport(id); - } - - { // create-children - if (gui.child_style) utils::ShowStyleEditorWindow(&gui.child_style); - if (gui.child_demo) ImGui::ShowDemoWindow(&gui.child_demo); - if (gui.child_metrics) ImGui::ShowMetricsWindow(&gui.child_metrics); - if (gui.child_stack) ImGui::ShowStackToolWindow(&gui.child_stack); - if (gui.child_color) utils::ShowColorExportWindow(&gui.child_color); - if (gui.child_resources) utils::ShowResourcesWindow(&gui.child_resources); - if (gui.child_about) utils::ShowAboutWindow(&gui.child_about); - //if (gui.child_mat_editor) utils::ShowDemoMaterialEditor(&gui.child_mat_editor); - } - - { - if(gui.ed_material) gui.ShowMaterialEditor(); - } - + ImGui::OpenPopup("Loading"); + // Always center this window when appearing + ImVec2 center = ImGui::GetMainViewport()->GetCenter(); + ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + ImGui::SetNextWindowSize(ImVec2(100, 40)); + + if (ImGui::BeginPopupModal("Loading", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar)) + { + ImGui::Text("Loading%s ", + frameCount % 4 == 0 ? "" + : frameCount % 4 == 1 ? "." + : frameCount % 4 == 2 ? ".." + : "..."); + ImGui::EndPopup(); + } } diff --git a/src/Editor/EditorInterface.hpp b/src/Editor/EditorInterface.hpp index 13698bb8..8b860789 100644 --- a/src/Editor/EditorInterface.hpp +++ b/src/Editor/EditorInterface.hpp @@ -1,66 +1,30 @@ #pragma once #include -#include +#include "Editor/Core/EditorUiState.hpp" #include "Vulkan/Vulkan.hpp" -#include "Vulkan/FrameBuffer.hpp" -#include "Runtime/UserInterface.hpp" -#include #include -#include -#include -#include - - -namespace Assets -{ - class Scene; -} -namespace Editor -{ - struct GUI; -} - -namespace Vulkan -{ - class Window; - class CommandPool; - class DepthBuffer; - class DescriptorPool; - class FrameBuffer; - class RenderPass; - class SwapChain; - class VulkanGpuTimer; - class RenderImage; -} class EditorInterface final { public: + VULKAN_NON_COPIABLE(EditorInterface) - VULKAN_NON_COPIABLE(EditorInterface) - - EditorInterface( class EditorGameInstance* editor ); - ~EditorInterface(); + EditorInterface(class EditorGameInstance* editor); + ~EditorInterface(); - void Config(); - void Init(); - void Render(); + void Config(); + void Init(); + void Render(); private: - ImGuiID DockSpaceUI(); - void ToolbarUI(); - void MainWindowGUI(Editor::GUI & gui, Assets::Scene& scene, ImGuiID id, bool firstRun); + ImGuiID DockSpaceUI(); + void ToolbarUI(); + void DrawIndicator(uint32_t frameCount); - void DrawIndicator(uint32_t frameCount); + EditorGameInstance* editor_; + Editor::EditorUiState uiState_{}; - EditorGameInstance* editor_; - std::unique_ptr editorGUI_; - - ImFont* fontBigIcon_; - ImFont* fontIcon_; - bool firstRun; + bool firstRun_ = true; }; - -inline UserInterface* GUserInterface = nullptr; diff --git a/src/Editor/EditorMain.cpp b/src/Editor/EditorMain.cpp index c5a2bd61..7f391733 100644 --- a/src/Editor/EditorMain.cpp +++ b/src/Editor/EditorMain.cpp @@ -1,22 +1,22 @@ #include "EditorMain.h" -#include #include - +#include "Assets/Node.h" #include "EditorInterface.hpp" +#include "Runtime/Components/RenderComponent.h" #include "Runtime/Engine.hpp" #include "Runtime/NextEngineHelper.h" -#include "Assets/Node.h" -#include "Runtime/Components/RenderComponent.h" -#include "Editor/EditorCommand.hpp" -#include "Editor/EditorInterface.hpp" +#include "Editor/EditorActionDispatcher.hpp" +#include "Editor/EditorContext.hpp" -std::unique_ptr CreateGameInstance(Vulkan::WindowConfig& config, Options& options, NextEngine* engine) +std::unique_ptr CreateGameInstance(Vulkan::WindowConfig& config, Options& options, + NextEngine* engine) { return std::make_unique(config, options, engine); } -EditorGameInstance::EditorGameInstance(Vulkan::WindowConfig& config, Options& options, NextEngine* engine): NextGameInstanceBase(config, options, engine), engine_(engine) +EditorGameInstance::EditorGameInstance(Vulkan::WindowConfig& config, Options& options, NextEngine* engine) : + NextGameInstanceBase(config, options, engine), engine_(engine) { editorUserInterface_ = std::make_unique(this); @@ -36,43 +36,51 @@ EditorGameInstance::EditorGameInstance(Vulkan::WindowConfig& config, Options& op options.ForceSDR = true; options.NoDenoiser = true; options.SuperResolution = 2; - options.KeepCPUMeshData = true; // 编辑器模式保留CPU网格数据用于场景保存 + options.KeepCPUMeshData = true; // 编辑器模式保留CPU网格数据用于场景保存 } void EditorGameInstance::OnInit() { - // EditorCommand, need Refactoring - EditorCommand::RegisterEdtiorCommand(EEditorCommand::ECmdSystem_RequestExit, [this](std::string& args)-> bool - { - GetEngine().GetWindow().Close(); - return true; - }); - EditorCommand::RegisterEdtiorCommand(EEditorCommand::ECmdSystem_RequestMaximum, [this](std::string& args)-> bool - { - GetEngine().GetWindow().Maximum(); - return true; - }); - EditorCommand::RegisterEdtiorCommand(EEditorCommand::ECmdSystem_RequestMinimize, [this](std::string& args)-> bool - { - GetEngine().GetWindow().Minimize(); - return true; - }); - EditorCommand::RegisterEdtiorCommand(EEditorCommand::ECmdIO_LoadScene, [this](std::string& args)-> bool - { - GetEngine().RequestLoadScene(args); - return true; - }); - EditorCommand::RegisterEdtiorCommand(EEditorCommand::ECmdIO_LoadSceneAdd, [this](std::string& args)-> bool - { - GetEngine().RequestLoadSceneAdd(args); - return true; - }); - EditorCommand::RegisterEdtiorCommand(EEditorCommand::ECmdIO_LoadHDRI, [this](std::string& args)-> bool - { - //Assets::GlobalTexturePool::UpdateHDRTexture(0, args.c_str(), Vulkan::SamplerConfig()); - //GetEngine().GetUserSettings().SkyIdx = 0; - return true; - }); + actions_.RegisterAction(EEditorAction::System_RequestExit, + [](EditorContext& ctx, std::string_view /*args*/) -> bool + { + ctx.engine.RequestClose(); + return true; + }); + actions_.RegisterAction(EEditorAction::System_ToggleMaximize, + [](EditorContext& ctx, std::string_view /*args*/) -> bool + { + ctx.engine.ToggleMaximize(); + return true; + }); + actions_.RegisterAction(EEditorAction::System_RequestMinimize, + [](EditorContext& ctx, std::string_view /*args*/) -> bool + { + ctx.engine.RequestMinimize(); + return true; + }); + + // Scene switching invalidates undo/redo history. + actions_.RegisterAction(EEditorAction::IO_LoadScene, + [](EditorContext& ctx, std::string_view args) -> bool + { + ctx.engine.GetCommandSystem().Clear(); + ctx.engine.RequestLoadScene(std::string(args)); + return true; + }); + actions_.RegisterAction(EEditorAction::IO_LoadSceneAdd, + [](EditorContext& ctx, std::string_view args) -> bool + { + ctx.engine.GetCommandSystem().Clear(); + ctx.engine.RequestLoadSceneAdd(std::string(args)); + return true; + }); + actions_.RegisterAction(EEditorAction::IO_LoadHDRI, + [](EditorContext& /*ctx*/, std::string_view /*args*/) -> bool + { + // TODO: integrate HDRI changes with scene/env settings. + return true; + }); GetEngine().GetShowFlags().ShowEdge = true; } @@ -83,15 +91,9 @@ void EditorGameInstance::OnTick(double deltaSeconds) GetEngine().SetProgressiveRendering(!moving, false); } -void EditorGameInstance::OnSceneLoaded() -{ - modelViewController_.Reset(GetEngine().GetScene().GetRenderCamera()); -} +void EditorGameInstance::OnSceneLoaded() { modelViewController_.Reset(GetEngine().GetScene().GetRenderCamera()); } -void EditorGameInstance::OnPreConfigUI() -{ - editorUserInterface_->Config(); -} +void EditorGameInstance::OnPreConfigUI() { editorUserInterface_->Config(); } bool EditorGameInstance::OnRenderUI() { @@ -99,10 +101,7 @@ bool EditorGameInstance::OnRenderUI() return true; } -void EditorGameInstance::OnInitUI() -{ - editorUserInterface_->Init(); -} +void EditorGameInstance::OnInitUI() { editorUserInterface_->Init(); } bool EditorGameInstance::OnKey(SDL_Event& event) { @@ -114,42 +113,45 @@ bool EditorGameInstance::OnKey(SDL_Event& event) { switch (event.key.key) { - case SDLK_ESCAPE: GetEngine().GetScene().SetSelectedId(-1); + case SDLK_ESCAPE: + GetEngine().GetScene().SetSelectedId(-1); break; - case SDLK_F: - { - glm::vec3 focusCenter; - float radius; - if (GetEngine().GetScene().GetSelectedNodeBounds(focusCenter, radius)) - { - modelViewController_.Focus(focusCenter, radius); - } - } - break; - default: break; + case SDLK_F: + { + glm::vec3 focusCenter; + float radius; + if (GetEngine().GetScene().GetSelectedNodeBounds(focusCenter, radius)) + { + modelViewController_.Focus(focusCenter, radius); } } - return true; + break; + default: + break; } - - bool EditorGameInstance::OnCursorPosition(double xpos, double ypos) - { - // Update Controller Context - bool alt = (SDL_GetModState() & SDL_KMOD_ALT) != 0; - modelViewController_.SetAltPressed(alt); - - glm::vec3 center; - float radius; - if (GetEngine().GetScene().GetSelectedNodeBounds(center, radius)) - { - modelViewController_.SetOrbitTarget(center); - } - else - { - modelViewController_.SetOrbitTarget(std::nullopt); - } - - if (!gizmoController_.IsInteracting()) { + } + return true; +} + +bool EditorGameInstance::OnCursorPosition(double xpos, double ypos) +{ + // Update Controller Context + bool alt = (SDL_GetModState() & SDL_KMOD_ALT) != 0; + modelViewController_.SetAltPressed(alt); + + glm::vec3 center; + float radius; + if (GetEngine().GetScene().GetSelectedNodeBounds(center, radius)) + { + modelViewController_.SetOrbitTarget(center); + } + else + { + modelViewController_.SetOrbitTarget(std::nullopt); + } + + if (!gizmoController_.IsInteracting()) + { modelViewController_.OnCursorPosition(xpos, ypos); } return true; @@ -171,22 +173,24 @@ bool EditorGameInstance::OnMouseButton(SDL_Event& event) glm::vec3 org; glm::vec3 dir; NextEngineHelper::GetScreenToWorldRay(mousePos, org, dir); - GetEngine().RayCastGPU(org, dir, [this](Assets::RayCastResult result) - { - if (result.Hitted) - { - GetEngine().GetScene().GetRenderCamera().FocalDistance = result.T; - NextEngineHelper::DrawAuxPoint(result.HitPoint, glm::vec4(0.2, 1, 0.2, 1), 2, 30); - // selection - GetEngine().GetScene().SetSelectedId(result.InstanceId); - } - else - { - GetEngine().GetScene().SetSelectedId(-1); - } - - return true; - }); + GetEngine().RayCastGPU(org, dir, + [this](Assets::RayCastResult result) + { + if (result.Hitted) + { + GetEngine().GetScene().GetRenderCamera().FocalDistance = result.T; + NextEngineHelper::DrawAuxPoint(result.HitPoint, glm::vec4(0.2, 1, 0.2, 1), 2, + 30); + // selection + GetEngine().GetScene().SetSelectedId(result.InstanceId); + } + else + { + GetEngine().GetScene().SetSelectedId(-1); + } + + return true; + }); return true; } return true; diff --git a/src/Editor/EditorMain.h b/src/Editor/EditorMain.h index 445a704c..faf234c0 100644 --- a/src/Editor/EditorMain.h +++ b/src/Editor/EditorMain.h @@ -1,6 +1,6 @@ #pragma once -#include "EditorGUI.h" +#include "Editor/EditorActionDispatcher.hpp" #include "Runtime/Engine.hpp" #include "Runtime/GizmoController.hpp" #include "Runtime/ModelViewController.hpp" @@ -9,7 +9,7 @@ class EditorInterface; namespace Assets { - class Scene; + class Scene; } class EditorGameInstance : public NextGameInstanceBase @@ -35,14 +35,18 @@ class EditorGameInstance : public NextGameInstanceBase bool OnScroll(double xoffset, double yoffset) override; bool OverrideRenderCamera(Assets::Camera& OutRenderCamera) const override; - + // quick access engine NextEngine& GetEngine() { return *engine_; } + EditorActionDispatcher& Actions() { return actions_; } + const EditorActionDispatcher& Actions() const { return actions_; } void DrawGizmo(const glm::vec2& viewportPos, const glm::vec2& viewportSize); private: NextEngine* engine_; + EditorActionDispatcher actions_{}; + std::unique_ptr editorUserInterface_; ModelViewController modelViewController_; GizmoController gizmoController_; diff --git a/src/Editor/EditorMaterialEd.cpp b/src/Editor/EditorMaterialEd.cpp deleted file mode 100644 index efbc7c36..00000000 --- a/src/Editor/EditorMaterialEd.cpp +++ /dev/null @@ -1,159 +0,0 @@ -#include "EditorGUI.h" -#include "ImNodeFlow.h" -#include "Editor/Nodes/NodeSetInt.hpp" -#include "Editor/Nodes/NodeSetFloat.hpp" -#include "Nodes/NodeMaterial.hpp" -#include "Assets/Material.hpp" -#include "Assets/Scene.hpp" - -static std::unique_ptr MyNode; -static std::weak_ptr MatNode; -static bool InitNodes = true; - -void Editor::GUI::OpenMaterialEditor() -{ - MyNode.reset( new ImFlow::ImNodeFlow() ); - InitNodes = true; - - if(selected_material != nullptr) - { - float baseY = 20.0f; - float seprateY = 100.0f; - float seprateX = 600.0f; - // from material to node - auto nodeIOR = MyNode->placeNodeAt(ImVec2(30,baseY), "IOR", selected_material->gpuMaterial_.RefractionIndex); - auto nodeShadingMode = MyNode->placeNodeAt(ImVec2(30,baseY += seprateY), "ShadingMode", (int)selected_material->gpuMaterial_.MaterialModel); - - auto nodeAlbedo = MyNode->placeNodeAt(ImVec2(30, baseY += seprateY), "Albedo", glm::vec3(selected_material->gpuMaterial_.Diffuse)); - auto nodeRoughness = MyNode->placeNodeAt(ImVec2(30, baseY += seprateY), "Roughness", selected_material->gpuMaterial_.Fuzziness); - auto nodeMetalness = MyNode->placeNodeAt(ImVec2(30, baseY += seprateY), "Metalness", selected_material->gpuMaterial_.Metalness); - - - - auto nodeMat = MyNode->placeNodeAt(ImVec2(seprateX, baseY * 0.5f - seprateY)); - MatNode = nodeMat; - - nodeIOR->outPin("Out")->createLink(nodeMat->inPin("IOR")); - nodeShadingMode->outPin("Out")->createLink(nodeMat->inPin("ShadingMode")); - - nodeAlbedo->outPin("Out")->createLink(nodeMat->inPin("Albedo")); - nodeRoughness->outPin("Out")->createLink(nodeMat->inPin("Roughness")); - nodeMetalness->outPin("Out")->createLink(nodeMat->inPin("Metalness")); - - seprateY = 200.0f; - if(selected_material->gpuMaterial_.DiffuseTextureId != -1) - { - auto nodeTexture = MyNode->placeNodeAt(ImVec2(30,baseY += seprateY), "AlbedoTexture", selected_material->gpuMaterial_.DiffuseTextureId); - nodeTexture->outPin("Out")->createLink(nodeMat->inPin("AlbedoTexture")); - } - - if(selected_material->gpuMaterial_.NormalTextureId != -1) - { - auto nodeTexture = MyNode->placeNodeAt(ImVec2(30,baseY += seprateY), "NormalTexture", selected_material->gpuMaterial_.NormalTextureId); - nodeTexture->outPin("Out")->createLink(nodeMat->inPin("NormalTexture")); - } - - if(selected_material->gpuMaterial_.MRATextureId != -1) - { - auto nodeTexture = MyNode->placeNodeAt(ImVec2(30,baseY += seprateY), "MRATexture", selected_material->gpuMaterial_.MRATextureId); - nodeTexture->outPin("Out")->createLink(nodeMat->inPin("MRATexture")); - } - } -} - -void Editor::GUI::ApplyMaterial() -{ - if(std::shared_ptr mat = MatNode.lock()) - { - const glm::vec3& color = mat->getInVal("Albedo"); - - selected_material->gpuMaterial_.Diffuse = glm::vec4(color,1.0); - selected_material->gpuMaterial_.Fuzziness = mat->getInVal("Roughness"); - selected_material->gpuMaterial_.Metalness = mat->getInVal("Metalness"); - selected_material->gpuMaterial_.RefractionIndex = mat->getInVal("IOR"); - - current_scene->UpdateAllMaterials(); - } -} - -void Editor::GUI::ShowMaterialEditor() -{ - if(InitNodes) - { - ImGui::SetNextWindowSize(ImVec2(1280,800)); - ImGui::SetWindowFocus("Material Editor"); - - ImGuiWindowClass windowClass1; - windowClass1.ClassId = ImGui::GetID("Material Editor"); - windowClass1.ViewportFlagsOverrideSet = ImGuiViewportFlags_TopMost | ImGuiViewportFlags_NoAutoMerge; - - ImGui::SetNextWindowClass(&windowClass1); - InitNodes = false; - } - - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8,16)); - ImGui::Begin("Material Editor", &ed_material, ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoCollapse); - ImGui::PopStyleVar(); - - MyNode->rightClickPopUpContent([](ImFlow::BaseNode *node) - { - if (node != nullptr) - { - ImGui::Text("%lu", node->getUID()); - ImGui::Text("%s", node->getName().c_str()); - if (ImGui::Button("Ok")) - { - ImGui::CloseCurrentPopup(); - } - if (ImGui::Button("Delete Node")) - { - node->destroy(); - ImGui::CloseCurrentPopup(); - } - } - else - { - if (ImGui::Button("Add Node")) - { - ImVec2 pos = ImGui::GetMousePos(); - MyNode->placeNodeAt(pos, "Test", 1.0f); - ImGui::CloseCurrentPopup(); - } - } }); - - MyNode->droppedLinkPopUpContent([](ImFlow::Pin *dragged) - { dragged->deleteLink(); }); - - std::vector> myLinks = MyNode->getLinks(); - - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8,8)); - if ( ImGui::Button("Apply Material") ) - { - ApplyMaterial(); - } - ImGui::PopStyleVar(); - - for (auto wp : myLinks) - { - auto p = wp.lock(); - if (p == nullptr) - continue; - if (p->isHovered()) - { - // ImGui::Text("Hovered"); - } - if (p->isSelected() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) - { - // ImGui::Text("Selected and Right Mouse click"); - ImFlow::Pin *right = p->right(); - ImFlow::Pin *left = p->left(); - right->deleteLink(); - left->deleteLink(); - } - } - - MyNode->update(); - ImGui::End(); -} - - diff --git a/src/Editor/EditorMenubar.cpp b/src/Editor/EditorMenubar.cpp deleted file mode 100644 index e5a35939..00000000 --- a/src/Editor/EditorMenubar.cpp +++ /dev/null @@ -1,187 +0,0 @@ -#include "EditorCommand.hpp" -#include "ThirdParty/fontawesome/IconsFontAwesome6.h" -#include "EditorGUI.h" -#include "Runtime/Engine.hpp" -#include "Utilities/ImGui.hpp" -#include "Assets/Scene.hpp" -#include - -void Editor::GUI::ShowMenubar() -{ - ImGuiViewport* viewport = ImGui::GetMainViewport(); - - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); - - // MENU - ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x + 55, viewport->Pos.y)); - ImGui::SetNextWindowSize(ImVec2(viewport->Size.x - 255, 55)); - ImGui::SetNextWindowViewport(viewport->ID); - ImGui::SetNextWindowBgAlpha(0); - - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); - ImGui::Begin("Menubar", NULL, - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoDocking); - - - ImGui::GetWindowDrawList()->AddRectFilled(viewport->Pos, viewport->Pos + ImVec2(viewport->Size.x, 55), ImGui::GetColorU32(ImGuiCol_MenuBarBg)); - ImGui::PopStyleVar(); - - if (ImGui::BeginMenuBar()) - { - /// menu-file - if (ImGui::BeginMenu("File")) - { - if (ImGui::MenuItem("Save Scene As...", "Ctrl+Shift+S")) - { - // TODO: Add file dialog for save path selection - std::string filename = "saved_scene.glb"; - bool success = NextEngine::GetInstance()->GetScene().Save(filename); - if (success) - { - SPDLOG_INFO("Scene saved successfully: {}", filename); - } - else - { - SPDLOG_ERROR("Failed to save scene: {}", filename); - } - } - - ImGui::Separator(); - - if (ImGui::MenuItem("Exit")) - { - state = false; - }; - ImGui::EndMenu(); - } - - /// menu-edit - if (ImGui::BeginMenu("Edit")) - { - if (ImGui::BeginMenu("Layout")) - { - ImGui::MenuItem("Reset"); - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Behavior")) - { - ImGui::MenuItem("Static Mode", NULL); - ImGui::SameLine(); - utils::HelpMarker("Toggle between static/linear layout and fixed/manual layout"); - - ImGui::EndMenu(); - } - if (ImGui::MenuItem("Reset")) - { - - } - - ImGui::EndMenu(); - } - - /// menu-tools - if (ImGui::BeginMenu("Tools")) - { - ImGui::MenuItem("Style Editor", NULL, &child_style); - ImGui::MenuItem("Demo Window", NULL, &child_demo); - ImGui::MenuItem("Metrics", NULL, &child_metrics); - ImGui::MenuItem("Stack Tool", NULL, &child_stack); - ImGui::MenuItem("Color Export", NULL, &child_color); - ImGui::MenuItem("Material Editor", NULL, &child_mat_editor); - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("Help")) - { - if (ImGui::MenuItem("Resources")) child_resources = true; - if (ImGui::MenuItem("About ImStudio")) child_about = true; - ImGui::EndMenu(); - } - - ImGui::EndMenuBar(); - } - - ImGui::End(); - - // LOGO - ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y)); - ImGui::SetNextWindowSize(ImVec2(55, 55)); - ImGui::SetNextWindowViewport(viewport->ID); - ImGui::SetNextWindowBgAlpha(0); - - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); - - ImGui::Begin("Logo", NULL, - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoDocking); - - ImGui::GetWindowDrawList()->AddRectFilled(viewport->Pos, viewport->Pos + ImVec2(55, 55), ImGui::GetColorU32(ImGuiCol_MenuBarBg)); - ImGui::PushFont(bigIcon_); - ImGui::GetWindowDrawList()->AddText( viewport->Pos + ImVec2(10,7), IM_COL32(240,180,60,255), ICON_FA_SHEKEL_SIGN); - ImGui::PopFont(); - ImGui::End(); - - // XMARK - ImGui::SetNextWindowPos(viewport->Pos + ImVec2(viewport->Size.x - 200, 0)); - ImGui::SetNextWindowSize(ImVec2(200, 55)); - ImGui::SetNextWindowViewport(viewport->ID); - ImGui::SetNextWindowBgAlpha(0); - - ImGui::Begin("XMark", NULL, - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoDocking); - - ImGui::GetWindowDrawList()->AddRectFilled(viewport->Pos + ImVec2(viewport->Size.x - 200, 0), viewport->Pos + ImVec2(viewport->Size.x, 55), ImGui::GetColorU32(ImGuiCol_MenuBarBg)); - ImGui::SetCursorPos( ImVec2(50,5) ); - ImGui::PushStyleColor( ImGuiCol_Button, IM_COL32(0, 0, 0, 0)); - if(ImGui::Button(ICON_FA_WINDOW_MINIMIZE, ImVec2(40, 40))) { - EditorCommand::ExecuteCommand(EEditorCommand::ECmdSystem_RequestMinimize); - };ImGui::SameLine(); - if(ImGui::Button(ICON_FA_WINDOW_MAXIMIZE, ImVec2(40, 40))) { - EditorCommand::ExecuteCommand(EEditorCommand::ECmdSystem_RequestMaximum); - };ImGui::SameLine(); - if(ImGui::Button(ICON_FA_XMARK, ImVec2(40, 40))) { - EditorCommand::ExecuteCommand(EEditorCommand::ECmdSystem_RequestExit); - };ImGui::SameLine(); - ImGui::PopStyleColor(); - ImGui::End(); - - ImGui::PopStyleVar(); - ImGui::PopStyleVar(); - ImGui::PopStyleVar(); - - // FOOTER - ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + viewport->Size.y - 40)); - ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, 40)); - ImGui::SetNextWindowViewport(viewport->ID); - ImGui::SetNextWindowBgAlpha(100); - - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6.0f, 6.0f)); - - ImGui::Begin("Footer", NULL, - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoDocking); - - ImGui::GetWindowDrawList()->AddLine(ImVec2(viewport->Pos.x, viewport->Pos.y + viewport->Size.y - 40), ImVec2(viewport->Pos.x + viewport->Size.x, viewport->Pos.y + viewport->Size.y - 40), IM_COL32(20,20,20,255), 2); - - if(ImGui::Button(ICON_FA_HOUSE, ImVec2(60, 30))) { - - };ImGui::SameLine(); - if(ImGui::Button(ICON_FA_PEN, ImVec2(60, 30))) { - - };ImGui::SameLine(); - char cvar[255] = ""; - ImGui::SetNextItemWidth(200); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6,6)); - ImGui::InputTextWithHint("##CVar", "Execute CVar...", cvar, 255); - ImGui::PopStyleVar(); - ImGui::End(); - - ImGui::PopStyleVar(); - ImGui::PopStyleVar(); - ImGui::PopStyleVar(); -} \ No newline at end of file diff --git a/src/Editor/EditorOutliner.cpp b/src/Editor/EditorOutliner.cpp deleted file mode 100644 index 1ae6446e..00000000 --- a/src/Editor/EditorOutliner.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include "ThirdParty/fontawesome/IconsFontAwesome6.h" -#include "EditorGUI.h" -#include "Assets/Model.hpp" -#include "Assets/Node.h" -#include "Runtime/Components/RenderComponent.h" -#include "Assets/Scene.hpp" - - -void DrawNode(Assets::Scene* scene, Assets::Node* node) -{ - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - - bool selected = scene->GetSelectedId() == node->GetInstanceId(); - ImGuiTreeNodeFlags flag = ImGuiTreeNodeFlags_FramePadding | - (selected ? ImGuiTreeNodeFlags_Selected : 0) | - (node->Children().empty() ? ImGuiTreeNodeFlags_Leaf : 0); - - - ImGui::PushStyleColor(ImGuiCol_Text, selected ? Editor::ActiveColor : ImGui::GetColorU32(ImGuiCol_Text)); - auto render = node->GetComponent(); - int modelId = render ? render->GetModelId() : -1; - if (ImGui::TreeNodeEx(((modelId == -1 ? ICON_FA_CIRCLE_NOTCH : ICON_FA_CUBE) + std::string(" ") + node->GetName()).c_str(), flag)) - { - ImGui::PopStyleColor(); - if (ImGui::IsItemClicked()) - { - scene->SetSelectedId(node->GetInstanceId()); - } - auto& childrenNodes = node->Children(); - for (auto& child : childrenNodes) - { - DrawNode(scene, child.get()); - } - ImGui::TreePop(); - } - else - { - ImGui::PopStyleColor(); - } -} - - -void Editor::GUI::ShowSidebar(Assets::Scene* scene) -{ - ImGui::Begin("Outliner", NULL); - - { - ImGui::TextDisabled("NOTE"); - ImGui::SameLine(); - utils::HelpMarker - ("ALL SCENE NODES\n" - "limited to 1000 nodes\n" - "select and view node properties\n"); - ImGui::Separator(); - - ImGui::Text("Nodes"); - ImGui::Separator(); - - ImGui::BeginChild("ListBox", ImVec2(0, -50)); - - if (ImGui::BeginTable("NodesList", 1, ImGuiTableFlags_NoBordersInBodyUntilResize | ImGuiTableFlags_RowBg)) - { - ImGui::TableSetupColumn("NodeName"); - auto& allnodes = scene->Nodes(); - uint32_t limit = 1000; - for (auto& node : allnodes) - { - if (node->GetParent() != nullptr) - { - continue; - } - - DrawNode(scene, node.get()); - - if (limit-- <= 0) - { - break; - } - } - ImGui::EndTable(); - } - - ImGui::EndChild(); - - ImGui::Spacing(); - - ImGui::Text("%d Nodes", scene->Nodes().size()); - - ImGui::Spacing(); - - if ((ImGui::GetIO().KeyAlt) && (ImGui::IsKeyPressed(ImGuiKey_F4))) - { - state = false; - } - } - ImGui::End(); -} diff --git a/src/Editor/EditorUi.hpp b/src/Editor/EditorUi.hpp new file mode 100644 index 00000000..097ca710 --- /dev/null +++ b/src/Editor/EditorUi.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "Common/CoreMinimal.hpp" + +#include "Editor/Core/EditorUiState.hpp" +#include "Editor/EditorContext.hpp" + +#include + +namespace Editor +{ + // Overlays (custom titlebar/toolbar/footer) + void DrawTitleBarOverlay(EditorContext& ctx, EditorUiState& ui); + + // Docked panels + void DrawOutlinerPanel(EditorContext& ctx, EditorUiState& ui); + void DrawPropertiesPanel(EditorContext& ctx, EditorUiState& ui); + void DrawContentBrowserPanel(EditorContext& ctx, EditorUiState& ui); + void DrawMaterialBrowserPanel(EditorContext& ctx, EditorUiState& ui); + void DrawTextureBrowserPanel(EditorContext& ctx, EditorUiState& ui); + void DrawMeshBrowserPanel(EditorContext& ctx, EditorUiState& ui); + + // Viewport overlay widgets (stats/tools) + void DrawViewportOverlay(EditorContext& ctx, EditorUiState& ui); + + // Floating panels + void DrawMaterialEditorPanel(EditorContext& ctx, EditorUiState& ui); + + // Cross-panel helpers + void OpenMaterialEditor(EditorContext& ctx, EditorUiState& ui); +} // namespace Editor diff --git a/src/Editor/EditorViewport.cpp b/src/Editor/EditorViewport.cpp deleted file mode 100644 index 34980188..00000000 --- a/src/Editor/EditorViewport.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "EditorGUI.h" -#include "ThirdParty/fontawesome/IconsFontAwesome6.h" -#include "Runtime/UserInterface.hpp" -#include "Assets/Model.hpp" -#include "Runtime/Engine.hpp" -#include "Utilities/ImGui.hpp" -#include "Utilities/Localization.hpp" -#include "Utilities/Math.hpp" -#include - -const float toolIconWidth = 32.0f; - -void Editor::GUI::ShowViewport(ImGuiID id) -{ - ImGuiViewport* viewport = ImGui::GetMainViewport(); - ImGuiDockNode* node = ImGui::DockBuilderGetCentralNode(id); - - ImGui::SetNextWindowPos(node->Pos + ImVec2(5,5)); - ImGui::SetNextWindowSize(ImVec2(160, 140)); - ImGui::SetNextWindowViewport(viewport->ID); - - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); - ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.5f)); - - ImGuiWindowFlags windowFlags = 0 - | ImGuiWindowFlags_NoDocking - | ImGuiWindowFlags_NoTitleBar - | ImGuiWindowFlags_NoResize - | ImGuiWindowFlags_NoMove - | ImGuiWindowFlags_NoScrollbar - | ImGuiWindowFlags_NoSavedSettings - ; - - ImGui::Begin("ViewportStat", nullptr, windowFlags); - - ImGui::Text("Reatime Statstics: "); - ImGui::Text("Frame rate: %.0f fps", 1.0f / engine->GetSmoothDeltaSeconds()); - ImGui::Text("Progressive: %d", engine->IsProgressiveRendering()); - - auto& gpuDrivenStat = current_scene->GetGpuDrivenStat(); - uint32_t instanceCount = gpuDrivenStat.ProcessedCount - gpuDrivenStat.CulledCount; - uint32_t triangleCount = gpuDrivenStat.TriangleCount - gpuDrivenStat.CulledTriangleCount; - ImGui::Text("Tris: %s/%s", Utilities::metricFormatter(static_cast(triangleCount), "").c_str(), Utilities::metricFormatter(static_cast(gpuDrivenStat.TriangleCount), "").c_str()); - ImGui::Text("Draw: %s/%s", Utilities::metricFormatter(static_cast(instanceCount), "").c_str(), Utilities::metricFormatter(static_cast(gpuDrivenStat.ProcessedCount), "").c_str()); - - ImGui::End(); - - ImGui::SetNextWindowPos(node->Pos + ImVec2(170,0)); - ImGui::SetNextWindowSize(ImVec2(node->Size.x - 170, toolIconWidth + 8)); - ImGui::SetNextWindowViewport(viewport->ID); - ImGui::SetNextWindowBgAlpha(0); - - ImGui::Begin("ViewportTool", nullptr, windowFlags); - - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.5f)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); - - ImGui::Dummy(ImVec2(node->Size.x - 170 - (toolIconWidth + 16) * 3, 0)); - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_EYE, ImVec2(toolIconWidth, toolIconWidth))) - { - ImGui::OpenPopup("ViewportShowFlags"); - } - BUTTON_TOOLTIP(LOCTEXT("Show Flags")) - - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12, 12)); - if (ImGui::BeginPopup("ViewportShowFlags")) - { - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8, 10)); - auto& showFlags = engine->GetShowFlags(); - Utilities::UI::DrawShowFlagsCommon(showFlags); - - ImGui::PopStyleVar(); - ImGui::EndPopup(); - } - ImGui::PopStyleVar(); - - ImGui::PopStyleColor(); - ImGui::PopStyleVar(); - - ImGui::PopStyleColor(); - ImGui::PopStyleVar(); - - ImGui::End(); -} diff --git a/src/Editor/Nodes/NodeSetInt.cpp b/src/Editor/Nodes/NodeSetInt.cpp index bb8aa882..f7de2fc0 100644 --- a/src/Editor/Nodes/NodeSetInt.cpp +++ b/src/Editor/Nodes/NodeSetInt.cpp @@ -1,13 +1,9 @@ #include "NodeSetInt.hpp" -#include #include +#include "Editor/EditorContext.hpp" #include "Runtime/UserInterface.hpp" -#include "Assets/Texture.hpp" -#include "Assets/TextureImage.hpp" -#include "Editor/EditorInterface.hpp" -#include "Vulkan/ImageView.hpp" namespace Nodes { @@ -16,7 +12,7 @@ namespace Nodes { if (name == "") { - setTitle("Set float"); + setTitle("Set int"); } else { @@ -26,9 +22,7 @@ namespace Nodes value = initValue; setStyle(ImFlow::NodeStyle::green()); - addOUT("Out") - ->behaviour([this]() - { return value; }); + addOUT("Out")->behaviour([this]() { return value; }); } void NodeSetInt::draw() @@ -41,7 +35,7 @@ namespace Nodes { if (name == "") { - setTitle("Set float"); + setTitle("Set texture"); } else { @@ -49,25 +43,33 @@ namespace Nodes } textureId = initTextureId; - + // setStyle(ImFlow::NodeStyle::green()); - addOUT("Out") - ->behaviour([this]() - { return textureId; }); + addOUT("Out")->behaviour([this]() { return textureId; }); } - NodeSetTexture::~NodeSetTexture() - { - } + NodeSetTexture::~NodeSetTexture() {} void NodeSetTexture::draw() { - //ImGui::SetNextItemWidth(100.f); - if(textureId != -1 && GUserInterface) + // ImGui::SetNextItemWidth(100.f); + if (textureId == -1) + { + return; + } + + EditorContext* ctx = static_cast(ImGui::GetIO().UserData); + if (ctx == nullptr) + { + return; + } + + VkDescriptorSet tex = ctx->ui.RequestImTextureId(static_cast(textureId)); + if (tex != VK_NULL_HANDLE) { - ImGui::Image( (ImTextureID)(intptr_t)GUserInterface->RequestImTextureId(textureId), ImVec2(128, 128)); + ImGui::Image((ImTextureID)(intptr_t)tex, ImVec2(128, 128)); } } -} +} // namespace Nodes diff --git a/src/Editor/Overlays/TitleBarOverlay.cpp b/src/Editor/Overlays/TitleBarOverlay.cpp new file mode 100644 index 00000000..ca2b8fb8 --- /dev/null +++ b/src/Editor/Overlays/TitleBarOverlay.cpp @@ -0,0 +1,221 @@ +#include "Editor/EditorUi.hpp" + +#include "Assets/Scene.hpp" +#include "Editor/EditorActionDispatcher.hpp" +#include "ThirdParty/fontawesome/IconsFontAwesome6.h" + +#include "Editor/EditorUtils.h" + +#include + +namespace Editor +{ + namespace + { + constexpr float kTitleBarHeight = 55.0f; + constexpr float kFooterHeight = 40.0f; + } // namespace + + void DrawTitleBarOverlay(EditorContext& ctx, EditorUiState& ui) + { + ImGuiViewport* viewport = ImGui::GetMainViewport(); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + + // MENU + ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x + kTitleBarHeight, viewport->Pos.y)); + ImGui::SetNextWindowSize(ImVec2(viewport->Size.x - 255.0f, kTitleBarHeight)); + ImGui::SetNextWindowViewport(viewport->ID); + ImGui::SetNextWindowBgAlpha(0); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + ImGui::Begin("Menubar", nullptr, + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | + ImGuiWindowFlags_NoDocking); + + ImGui::GetWindowDrawList()->AddRectFilled(viewport->Pos, + viewport->Pos + ImVec2(viewport->Size.x, kTitleBarHeight), + ImGui::GetColorU32(ImGuiCol_MenuBarBg)); + ImGui::PopStyleVar(); + + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + if (ImGui::MenuItem("Save Scene As...", "Ctrl+Shift+S")) + { + // TODO: Add file dialog for save path selection + const std::string filename = "saved_scene.glb"; + const bool success = ctx.scene.Save(filename); + if (success) + { + SPDLOG_INFO("Scene saved successfully: {}", filename); + } + else + { + SPDLOG_ERROR("Failed to save scene: {}", filename); + } + } + + ImGui::Separator(); + + if (ImGui::MenuItem("Exit")) + { + ctx.actions.Dispatch(ctx, EEditorAction::System_RequestExit); + } + + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Edit")) + { + if (ImGui::BeginMenu("Layout")) + { + ImGui::MenuItem("Reset"); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Behavior")) + { + ImGui::MenuItem("Static Mode", nullptr); + ImGui::SameLine(); + utils::HelpMarker("Toggle between static/linear layout and fixed/manual layout"); + ImGui::EndMenu(); + } + if (ImGui::MenuItem("Reset")) + { + } + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Tools")) + { + ImGui::MenuItem("Style Editor", nullptr, &ui.child_style); + ImGui::MenuItem("Demo Window", nullptr, &ui.child_demo); + ImGui::MenuItem("Metrics", nullptr, &ui.child_metrics); + ImGui::MenuItem("Stack Tool", nullptr, &ui.child_stack); + ImGui::MenuItem("Color Export", nullptr, &ui.child_color); + ImGui::MenuItem("Material Editor", nullptr, &ui.child_mat_editor); + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Help")) + { + if (ImGui::MenuItem("Resources")) + ui.child_resources = true; + if (ImGui::MenuItem("About ImStudio")) + ui.child_about = true; + ImGui::EndMenu(); + } + + ImGui::EndMenuBar(); + } + ImGui::End(); + + // LOGO + ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y)); + ImGui::SetNextWindowSize(ImVec2(kTitleBarHeight, kTitleBarHeight)); + ImGui::SetNextWindowViewport(viewport->ID); + ImGui::SetNextWindowBgAlpha(0); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + ImGui::Begin("Logo", nullptr, + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | + ImGuiWindowFlags_NoDocking); + + ImGui::GetWindowDrawList()->AddRectFilled(viewport->Pos, + viewport->Pos + ImVec2(kTitleBarHeight, kTitleBarHeight), + ImGui::GetColorU32(ImGuiCol_MenuBarBg)); + if (ui.bigIcon) + { + ImGui::PushFont(ui.bigIcon); + } + ImGui::GetWindowDrawList()->AddText(viewport->Pos + ImVec2(10, 7), IM_COL32(240, 180, 60, 255), + ICON_FA_SHEKEL_SIGN); + if (ui.bigIcon) + { + ImGui::PopFont(); + } + ImGui::End(); + + // XMARK + ImGui::SetNextWindowPos(viewport->Pos + ImVec2(viewport->Size.x - 200.0f, 0.0f)); + ImGui::SetNextWindowSize(ImVec2(200.0f, kTitleBarHeight)); + ImGui::SetNextWindowViewport(viewport->ID); + ImGui::SetNextWindowBgAlpha(0); + + ImGui::Begin("XMark", nullptr, + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | + ImGuiWindowFlags_NoDocking); + + ImGui::GetWindowDrawList()->AddRectFilled(viewport->Pos + ImVec2(viewport->Size.x - 200.0f, 0.0f), + viewport->Pos + ImVec2(viewport->Size.x, kTitleBarHeight), + ImGui::GetColorU32(ImGuiCol_MenuBarBg)); + ImGui::SetCursorPos(ImVec2(50, 5)); + ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 0, 0, 0)); + if (ImGui::Button(ICON_FA_WINDOW_MINIMIZE, ImVec2(40, 40))) + { + ctx.actions.Dispatch(ctx, EEditorAction::System_RequestMinimize); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_WINDOW_MAXIMIZE, ImVec2(40, 40))) + { + ctx.actions.Dispatch(ctx, EEditorAction::System_ToggleMaximize); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_XMARK, ImVec2(40, 40))) + { + ctx.actions.Dispatch(ctx, EEditorAction::System_RequestExit); + } + ImGui::SameLine(); + ImGui::PopStyleColor(); + ImGui::End(); + + ImGui::PopStyleVar(); + ImGui::PopStyleVar(); + ImGui::PopStyleVar(); + + // FOOTER + ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + viewport->Size.y - kFooterHeight)); + ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, kFooterHeight)); + ImGui::SetNextWindowViewport(viewport->ID); + ImGui::SetNextWindowBgAlpha(1.0f); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6.0f, 6.0f)); + + ImGui::Begin("Footer", nullptr, + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | + ImGuiWindowFlags_NoDocking); + + ImGui::GetWindowDrawList()->AddLine( + ImVec2(viewport->Pos.x, viewport->Pos.y + viewport->Size.y - kFooterHeight), + ImVec2(viewport->Pos.x + viewport->Size.x, viewport->Pos.y + viewport->Size.y - kFooterHeight), + IM_COL32(20, 20, 20, 255), 2); + + if (ImGui::Button(ICON_FA_HOUSE, ImVec2(60, 30))) + { + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_PEN, ImVec2(60, 30))) + { + } + ImGui::SameLine(); + + static char cvar[255] = ""; + ImGui::SetNextItemWidth(200); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6, 6)); + ImGui::InputTextWithHint("##CVar", "Execute CVar...", cvar, 255); + ImGui::PopStyleVar(); + ImGui::End(); + + ImGui::PopStyleVar(); + ImGui::PopStyleVar(); + ImGui::PopStyleVar(); + } +} // namespace Editor diff --git a/src/Editor/Panels/ContentBrowserPanel.cpp b/src/Editor/Panels/ContentBrowserPanel.cpp new file mode 100644 index 00000000..43ca5a50 --- /dev/null +++ b/src/Editor/Panels/ContentBrowserPanel.cpp @@ -0,0 +1,342 @@ +#include "Editor/EditorUi.hpp" + +#include "Assets/Scene.hpp" +#include "Assets/TextureImage.hpp" +#include "Editor/EditorActionDispatcher.hpp" +#include "Runtime/UserInterface.hpp" +#include "ThirdParty/fontawesome/IconsFontAwesome6.h" +#include "Utilities/FileHelper.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace Editor +{ + namespace + { + constexpr int kIconSize = 96; + constexpr int kIconPadding = 20; + + uint32_t Fnv1a32(std::string_view s) + { + uint32_t hash = 2166136261u; + for (unsigned char c : s) + { + hash ^= static_cast(c); + hash *= 16777619u; + } + return hash; + } + + void DrawGeneralContentBrowser(EditorContext& ctx, EditorUiState& ui, uint32_t& selectionId, bool iconOrTex, + uint32_t globalId, const std::string& name, const char* icon, ImU32 color, + const std::function& doubleclickAction, + const std::function& contextMenuAction) + { + ImGui::BeginGroup(); + if (ui.bigIcon) + { + ImGui::PushFont(ui.bigIcon); + } + ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(32, 32, 32, 255)); + ImGui::PushID(static_cast(globalId)); + + VkDescriptorSet textureId = ctx.ui.RequestImTextureId(globalId); + if (iconOrTex || (VK_NULL_HANDLE == textureId)) + { + ImGui::Button(icon, ImVec2(kIconSize, kIconSize)); + } + else + { + ImGui::Image((ImTextureID)(intptr_t)textureId, ImVec2(kIconSize, kIconSize)); + } + + ImGui::PopID(); + ImGui::PopStyleColor(); + if (ui.bigIcon) + { + ImGui::PopFont(); + } + + if (ImGui::IsItemHovered(ImGuiHoveredFlags_None)) + { + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + { + selectionId = InvalidId; + doubleclickAction(); + } + if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) + { + selectionId = globalId; + } + } + + auto cursorPos = ImGui::GetCursorPos() + ImGui::GetWindowPos() - ImVec2(0, 4 + ImGui::GetScrollY()); + const bool selected = selectionId == globalId; + ImGui::GetWindowDrawList()->AddRectFilled(cursorPos, cursorPos + ImVec2(kIconSize, kIconSize / 5.0f * 3.0f), + selected ? ActiveColor : IM_COL32(64, 64, 64, 255), 4); + ImGui::GetWindowDrawList()->AddLine(cursorPos, cursorPos + ImVec2(kIconSize, 0), color, 2); + + ImGui::PushItemWidth(kIconSize); + ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + kIconSize); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5); + ImGui::Text("%s", name.c_str()); + ImGui::PopTextWrapPos(); + ImGui::PopItemWidth(); + + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + kIconPadding); + ImGui::EndGroup(); + + if (contextMenuAction) + { + if (ImGui::BeginPopupContextItem("Context")) + { + contextMenuAction(); + ImGui::EndPopup(); + } + } + } + } // namespace + + void DrawMeshBrowserPanel(EditorContext& ctx, EditorUiState& ui) + { + ImGui::Begin("Mesh Browser", nullptr); + { + auto& allModels = ctx.scene.Models(); + + const float windowWidth = ImGui::GetContentRegionAvail().x; + const int itemsPerRow = + std::max(1, static_cast(windowWidth / (kIconSize + ImGui::GetStyle().ItemSpacing.x))); + + for (uint32_t i = 0; i < allModels.size(); ++i) + { + auto& model = allModels[i]; + const std::string name = fmt::format("{}_#{}", model.Name(), i); + uint32_t dummySelection = InvalidId; + DrawGeneralContentBrowser( + ctx, ui, dummySelection, true, i, name, ICON_FA_BOXES_PACKING, IM_COL32(132, 182, 255, 255), + []() {}, nullptr); + if ((i + 1) % itemsPerRow != 0) + { + ImGui::SameLine(); + } + } + } + ImGui::End(); + } + + void DrawMaterialBrowserPanel(EditorContext& ctx, EditorUiState& ui) + { + ImGui::Begin("Material Browser", nullptr); + { + auto& allMaterials = ctx.scene.Materials(); + + const float windowWidth = ImGui::GetContentRegionAvail().x; + const int itemsPerRow = + std::max(1, static_cast(windowWidth / (kIconSize + ImGui::GetStyle().ItemSpacing.x))); + + for (uint32_t i = 0; i < allMaterials.size(); ++i) + { + auto& mat = allMaterials[i]; + DrawGeneralContentBrowser( + ctx, ui, ui.selectedMaterialId, true, i, mat.name_, ICON_FA_BOWLING_BALL, + IM_COL32(132, 255, 132, 255), + [&]() + { + ui.selected_material = &(ctx.scene.Materials()[i]); + ui.ed_material = true; + OpenMaterialEditor(ctx, ui); + }, + nullptr); + + if ((i + 1) % itemsPerRow != 0) + { + ImGui::SameLine(); + } + } + } + ImGui::End(); + } + + void DrawTextureBrowserPanel(EditorContext& ctx, EditorUiState& ui) + { + ImGui::Begin("Texture Browser", nullptr); + { + auto& totalTextureMap = Assets::GlobalTexturePool::GetInstance()->TotalTextureMap(); + + const float windowWidth = ImGui::GetContentRegionAvail().x; + const int itemsPerRow = + std::max(1, static_cast(windowWidth / (kIconSize + ImGui::GetStyle().ItemSpacing.x))); + + int itemIndex = 0; + for (auto& textureGroup : totalTextureMap) + { + DrawGeneralContentBrowser( + ctx, ui, ui.selectedTextureId, false, textureGroup.second.GlobalIdx_, textureGroup.first, + ICON_FA_LINK_SLASH, IM_COL32(255, 72, 72, 255), []() {}, nullptr); + + if ((itemIndex++ + 1) % itemsPerRow != 0) + { + ImGui::SameLine(); + } + } + } + ImGui::End(); + } + + void DrawContentBrowserPanel(EditorContext& ctx, EditorUiState& ui) + { + ImGui::Begin("Content Browser", nullptr); + { + static const std::filesystem::path rootPath = + std::filesystem::path(Utilities::FileHelper::GetPlatformFilePath("assets")); + static std::filesystem::path currentPath = rootPath; + + // Safety: keep browsing rooted under assets. + const std::string rootStr = rootPath.string(); + const std::string curStr = currentPath.string(); + if (curStr.rfind(rootStr, 0) != 0) + { + currentPath = rootPath; + } + + if (ui.fontIcon) + { + ImGui::PushFont(ui.fontIcon); + } + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 4)); + if (ImGui::Button(ICON_FA_HOUSE)) + currentPath = rootPath; + + const std::filesystem::path rel = currentPath.lexically_relative(rootPath); + std::vector parts; + if (!rel.empty() && rel != ".") + { + for (const auto& p : rel) + { + parts.push_back(p); + } + } + + for (int i = 0; i < static_cast(parts.size()); ++i) + { + ImGui::SameLine(); + ImGui::TextUnformatted(">"); + ImGui::SameLine(); + + std::string label = parts[i].string(); + if (!label.empty()) + { + label[0] = static_cast(std::toupper(static_cast(label[0]))); + } + + if (ImGui::Button(label.c_str())) + { + std::filesystem::path newPath = rootPath; + for (int j = 0; j <= i; ++j) + { + newPath /= parts[j]; + } + currentPath = newPath; + } + } + ImGui::PopStyleVar(); + if (ui.fontIcon) + { + ImGui::PopFont(); + } + + auto cursorPos = ImGui::GetWindowPos() + ImVec2(0, ImGui::GetCursorPos().y + 2); + ImGui::NewLine(); + ImGui::GetWindowDrawList()->AddLine(cursorPos + ImVec2(0, 1), + cursorPos + ImVec2(ImGui::GetWindowSize().x, 1), + IM_COL32(20, 20, 20, 128), 1); + ImGui::GetWindowDrawList()->AddLine(cursorPos, cursorPos + ImVec2(ImGui::GetWindowSize().x, 0), + IM_COL32(20, 20, 20, 255), 1); + + ImGui::BeginChild("Content Items"); + + std::filesystem::directory_iterator it(currentPath); + const float windowWidth = ImGui::GetContentRegionAvail().x; + const int itemsPerRow = + std::max(1, static_cast(windowWidth / (kIconSize + ImGui::GetStyle().ItemSpacing.x))); + + uint32_t elementIdx = 0; + for (auto& entry : it) + { + const std::string abspath = absolute(entry.path()).string(); + const std::string name = entry.path().filename().string(); + const std::string ext = entry.path().extension().string(); + + const char* icon = ICON_FA_FOLDER; + ImU32 color = IM_COL32(0, 172, 255, 255); + if (entry.is_regular_file()) + { + if (ext == ".glb") + { + icon = ICON_FA_CUBE; + color = IM_COL32(255, 172, 0, 255); + } + else if (ext == ".hdr") + { + icon = ICON_FA_FILE_IMAGE; + color = IM_COL32(200, 64, 64, 255); + } + else + { + continue; + } + } + + const uint32_t stableId = Fnv1a32(abspath); + DrawGeneralContentBrowser( + ctx, ui, ui.selectedContentItemId, true, stableId, name, icon, color, + [&]() + { + if (entry.is_directory()) + { + currentPath = entry.path(); + } + else + { + if (ext == ".glb") + { + ctx.actions.Dispatch(ctx, EEditorAction::IO_LoadScene, abspath); + } + else if (ext == ".hdr") + { + ctx.actions.Dispatch(ctx, EEditorAction::IO_LoadHDRI, abspath); + } + } + }, + [&]() + { + if (ext == ".glb") + { + if (ImGui::MenuItem("Open Scene")) + { + ctx.actions.Dispatch(ctx, EEditorAction::IO_LoadScene, abspath); + } + if (ImGui::MenuItem("Add To Scene")) + { + ctx.actions.Dispatch(ctx, EEditorAction::IO_LoadSceneAdd, abspath); + } + } + }); + + if ((elementIdx + 1) % itemsPerRow != 0) + { + ImGui::SameLine(); + } + elementIdx++; + } + ImGui::EndChild(); + } + ImGui::End(); + } +} // namespace Editor diff --git a/src/Editor/Panels/MaterialEditorPanel.cpp b/src/Editor/Panels/MaterialEditorPanel.cpp new file mode 100644 index 00000000..eaa83ad6 --- /dev/null +++ b/src/Editor/Panels/MaterialEditorPanel.cpp @@ -0,0 +1,180 @@ +#include "Editor/EditorUi.hpp" + +#include "Assets/Material.hpp" +#include "Assets/Scene.hpp" +#include "Editor/Nodes/NodeMaterial.hpp" +#include "Editor/Nodes/NodeSetFloat.hpp" +#include "Editor/Nodes/NodeSetInt.hpp" +#include "ImNodeFlow.h" + +#include +#include + +namespace Editor +{ + namespace + { + std::unique_ptr gNodeFlow; + std::weak_ptr gMatNode; + bool gInitNodes = true; + + void ApplyMaterial(EditorContext& ctx, EditorUiState& ui) + { + if (ui.selected_material == nullptr) + { + return; + } + + if (std::shared_ptr mat = gMatNode.lock()) + { + const glm::vec3& color = mat->getInVal("Albedo"); + ui.selected_material->gpuMaterial_.Diffuse = glm::vec4(color, 1.0f); + ui.selected_material->gpuMaterial_.Fuzziness = mat->getInVal("Roughness"); + ui.selected_material->gpuMaterial_.Metalness = mat->getInVal("Metalness"); + ui.selected_material->gpuMaterial_.RefractionIndex = mat->getInVal("IOR"); + ctx.scene.UpdateAllMaterials(); + } + } + } // namespace + + void OpenMaterialEditor(EditorContext& /*ctx*/, EditorUiState& ui) + { + gNodeFlow.reset(new ImFlow::ImNodeFlow()); + gInitNodes = true; + + if (ui.selected_material == nullptr) + { + return; + } + + float baseY = 20.0f; + float seprateY = 100.0f; + const float seprateX = 600.0f; + + auto nodeIOR = gNodeFlow->placeNodeAt(ImVec2(30, baseY), "IOR", + ui.selected_material->gpuMaterial_.RefractionIndex); + auto nodeShadingMode = gNodeFlow->placeNodeAt( + ImVec2(30, baseY += seprateY), "ShadingMode", + static_cast(ui.selected_material->gpuMaterial_.MaterialModel)); + + auto nodeAlbedo = gNodeFlow->placeNodeAt( + ImVec2(30, baseY += seprateY), "Albedo", glm::vec3(ui.selected_material->gpuMaterial_.Diffuse)); + auto nodeRoughness = gNodeFlow->placeNodeAt(ImVec2(30, baseY += seprateY), "Roughness", + ui.selected_material->gpuMaterial_.Fuzziness); + auto nodeMetalness = gNodeFlow->placeNodeAt(ImVec2(30, baseY += seprateY), "Metalness", + ui.selected_material->gpuMaterial_.Metalness); + + auto nodeMat = gNodeFlow->placeNodeAt(ImVec2(seprateX, baseY * 0.5f - seprateY)); + gMatNode = nodeMat; + + nodeIOR->outPin("Out")->createLink(nodeMat->inPin("IOR")); + nodeShadingMode->outPin("Out")->createLink(nodeMat->inPin("ShadingMode")); + + nodeAlbedo->outPin("Out")->createLink(nodeMat->inPin("Albedo")); + nodeRoughness->outPin("Out")->createLink(nodeMat->inPin("Roughness")); + nodeMetalness->outPin("Out")->createLink(nodeMat->inPin("Metalness")); + + seprateY = 200.0f; + if (ui.selected_material->gpuMaterial_.DiffuseTextureId != -1) + { + auto nodeTexture = gNodeFlow->placeNodeAt( + ImVec2(30, baseY += seprateY), "AlbedoTexture", ui.selected_material->gpuMaterial_.DiffuseTextureId); + nodeTexture->outPin("Out")->createLink(nodeMat->inPin("AlbedoTexture")); + } + + if (ui.selected_material->gpuMaterial_.NormalTextureId != -1) + { + auto nodeTexture = gNodeFlow->placeNodeAt( + ImVec2(30, baseY += seprateY), "NormalTexture", ui.selected_material->gpuMaterial_.NormalTextureId); + nodeTexture->outPin("Out")->createLink(nodeMat->inPin("NormalTexture")); + } + + if (ui.selected_material->gpuMaterial_.MRATextureId != -1) + { + auto nodeTexture = gNodeFlow->placeNodeAt( + ImVec2(30, baseY += seprateY), "MRATexture", ui.selected_material->gpuMaterial_.MRATextureId); + nodeTexture->outPin("Out")->createLink(nodeMat->inPin("MRATexture")); + } + } + + void DrawMaterialEditorPanel(EditorContext& ctx, EditorUiState& ui) + { + if (!gNodeFlow) + { + OpenMaterialEditor(ctx, ui); + } + + if (gInitNodes) + { + ImGui::SetNextWindowSize(ImVec2(1280, 800)); + ImGui::SetWindowFocus("Material Editor"); + + ImGuiWindowClass windowClass1; + windowClass1.ClassId = ImGui::GetID("Material Editor"); + windowClass1.ViewportFlagsOverrideSet = ImGuiViewportFlags_TopMost | ImGuiViewportFlags_NoAutoMerge; + ImGui::SetNextWindowClass(&windowClass1); + gInitNodes = false; + } + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 16)); + ImGui::Begin("Material Editor", &ui.ed_material, ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoCollapse); + ImGui::PopStyleVar(); + + gNodeFlow->rightClickPopUpContent( + [](ImFlow::BaseNode* node) + { + if (node != nullptr) + { + ImGui::Text("%lu", node->getUID()); + ImGui::Text("%s", node->getName().c_str()); + if (ImGui::Button("Ok")) + { + ImGui::CloseCurrentPopup(); + } + if (ImGui::Button("Delete Node")) + { + node->destroy(); + ImGui::CloseCurrentPopup(); + } + } + else + { + if (ImGui::Button("Add Node")) + { + ImVec2 pos = ImGui::GetMousePos(); + gNodeFlow->placeNodeAt(pos, "Test", 1.0f); + ImGui::CloseCurrentPopup(); + } + } + }); + + gNodeFlow->droppedLinkPopUpContent([](ImFlow::Pin* dragged) { dragged->deleteLink(); }); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 8)); + if (ImGui::Button("Apply Material")) + { + ApplyMaterial(ctx, ui); + } + ImGui::PopStyleVar(); + + const std::vector> links = gNodeFlow->getLinks(); + for (auto wp : links) + { + auto p = wp.lock(); + if (!p) + { + continue; + } + if (p->isSelected() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) + { + ImFlow::Pin* right = p->right(); + ImFlow::Pin* left = p->left(); + right->deleteLink(); + left->deleteLink(); + } + } + + gNodeFlow->update(); + ImGui::End(); + } +} // namespace Editor diff --git a/src/Editor/Panels/OutlinerPanel.cpp b/src/Editor/Panels/OutlinerPanel.cpp new file mode 100644 index 00000000..fb1f345a --- /dev/null +++ b/src/Editor/Panels/OutlinerPanel.cpp @@ -0,0 +1,106 @@ +#include "Editor/EditorUi.hpp" + +#include "Editor/EditorActionDispatcher.hpp" +#include "Editor/EditorUtils.h" + +#include "Assets/Node.h" +#include "Assets/Scene.hpp" +#include "Runtime/Components/RenderComponent.h" + +#include "ThirdParty/fontawesome/IconsFontAwesome6.h" + +namespace Editor +{ + namespace + { + void DrawNode(Assets::Scene& scene, Assets::Node& node) + { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + + const bool selected = scene.GetSelectedId() == node.GetInstanceId(); + ImGuiTreeNodeFlags flag = ImGuiTreeNodeFlags_FramePadding | (selected ? ImGuiTreeNodeFlags_Selected : 0) | + (node.Children().empty() ? ImGuiTreeNodeFlags_Leaf : 0); + + ImGui::PushID(static_cast(node.GetInstanceId())); + ImGui::PushStyleColor(ImGuiCol_Text, selected ? ActiveColor : ImGui::GetColorU32(ImGuiCol_Text)); + auto render = node.GetComponent(); + const int modelId = render ? render->GetModelId() : -1; + + const std::string label = + (modelId == -1 ? ICON_FA_CIRCLE_NOTCH : ICON_FA_CUBE) + std::string(" ") + node.GetName(); + const bool opened = ImGui::TreeNodeEx(label.c_str(), flag); + + ImGui::PopStyleColor(); + + if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) + { + scene.SetSelectedId(node.GetInstanceId()); + } + + if (opened) + { + for (auto& child : node.Children()) + { + DrawNode(scene, *child); + } + ImGui::TreePop(); + } + + ImGui::PopID(); + } + } // namespace + + void DrawOutlinerPanel(EditorContext& ctx, EditorUiState& ui) + { + ImGui::Begin("Outliner", nullptr); + { + ImGui::TextDisabled("NOTE"); + ImGui::SameLine(); + utils::HelpMarker("ALL SCENE NODES\n" + "limited to 1000 nodes\n" + "select and view node properties\n"); + ImGui::Separator(); + + ImGui::Text("Nodes"); + ImGui::Separator(); + + ImGui::BeginChild("ListBox", ImVec2(0, -50)); + + if (ImGui::BeginTable("NodesList", 1, ImGuiTableFlags_NoBordersInBodyUntilResize | ImGuiTableFlags_RowBg)) + { + ImGui::TableSetupColumn("NodeName"); + auto& allnodes = ctx.scene.Nodes(); + uint32_t limit = 1000; + for (auto& node : allnodes) + { + if (node->GetParent() != nullptr) + { + continue; + } + + DrawNode(ctx.scene, *node); + + if (limit-- <= 0) + { + break; + } + } + ImGui::EndTable(); + } + + ImGui::EndChild(); + + ImGui::Spacing(); + ImGui::Text("%d Nodes", static_cast(ctx.scene.Nodes().size())); + ImGui::Spacing(); + + if ((ImGui::GetIO().KeyAlt) && (ImGui::IsKeyPressed(ImGuiKey_F4))) + { + ctx.actions.Dispatch(ctx, EEditorAction::System_RequestExit); + ui.state = false; + } + } + ImGui::End(); + } +} // namespace Editor diff --git a/src/Editor/EditorProperties.cpp b/src/Editor/Panels/PropertiesPanel.cpp similarity index 50% rename from src/Editor/EditorProperties.cpp rename to src/Editor/Panels/PropertiesPanel.cpp index 1d5fccff..69d2faed 100644 --- a/src/Editor/EditorProperties.cpp +++ b/src/Editor/Panels/PropertiesPanel.cpp @@ -1,68 +1,85 @@ -#include "EditorGUI.h" +#include "Editor/EditorUi.hpp" + #include "Assets/Node.h" -#include "Runtime/Components/RenderComponent.h" #include "Assets/Scene.hpp" +#include "Runtime/Components/RenderComponent.h" #include "ThirdParty/fontawesome/IconsFontAwesome6.h" +#include + +#include -void Editor::GUI::ShowProperties() +namespace Editor { - //ImGui::SetNextWindowPos(pt_P); - //ImGui::SetNextWindowSize(pt_S); - ImGui::Begin("Properties", NULL); + void DrawPropertiesPanel(EditorContext& ctx, EditorUiState& ui) { - if (selected_obj_id != -1) + ImGui::Begin("Properties", nullptr); { - Assets::Node* selectedObj = current_scene->GetNodeByInstanceId(selected_obj_id); + if (ui.selected_obj_id == InvalidId) + { + ImGui::End(); + return; + } + + Assets::Node* selectedObj = ctx.scene.GetNodeByInstanceId(ui.selected_obj_id); if (selectedObj == nullptr) { ImGui::End(); return; } - - ImGui::PushFont(fontIcon_); + + if (ui.fontIcon) + { + ImGui::PushFont(ui.fontIcon); + } ImGui::TextUnformatted(selectedObj->GetName().c_str()); - ImGui::PopFont(); + if (ui.fontIcon) + { + ImGui::PopFont(); + } + ImGui::Separator(); ImGui::NewLine(); ImGui::Text(ICON_FA_LOCATION_ARROW " Transform"); ImGui::Separator(); - auto mat4 = selectedObj->WorldTransform(); - + ImGui::BeginGroup(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.2f, 0.2f, 1.0f)); - ImGui::Button("L"); ImGui::SameLine(); + ImGui::Button("L"); + ImGui::SameLine(); ImGui::PopStyleColor(); - if ( ImGui::DragFloat3("##Location", &selectedObj->Translation().x, 0.1f) ) + if (ImGui::DragFloat3("##Location", &selectedObj->Translation().x, 0.1f)) { selectedObj->RecalcTransform(true); - current_scene->MarkDirty(); + ctx.scene.MarkDirty(); } ImGui::EndGroup(); ImGui::BeginGroup(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.8f, 0.2f, 1.0f)); - ImGui::Button("R"); ImGui::SameLine(); + ImGui::Button("R"); + ImGui::SameLine(); ImGui::PopStyleColor(); - static glm::vec3 eular = glm::eulerAngles(selectedObj->Rotation()); - if ( ImGui::DragFloat3("##Rotation", &eular.x, 0.1f) ) + glm::vec3 eular = glm::eulerAngles(selectedObj->Rotation()); + if (ImGui::DragFloat3("##Rotation", &eular.x, 0.1f)) { - selectedObj->SetRotation( glm::quat(eular)); + selectedObj->SetRotation(glm::quat(eular)); selectedObj->RecalcTransform(true); - current_scene->MarkDirty(); + ctx.scene.MarkDirty(); } ImGui::EndGroup(); ImGui::BeginGroup(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.2f, 0.8f, 1.0f)); - ImGui::Button("S"); ImGui::SameLine(); + ImGui::Button("S"); + ImGui::SameLine(); ImGui::PopStyleColor(); - if ( ImGui::DragFloat3("##Scale", &selectedObj->Scale().x, 0.1f) ) + if (ImGui::DragFloat3("##Scale", &selectedObj->Scale().x, 0.1f)) { selectedObj->RecalcTransform(true); - current_scene->MarkDirty(); + ctx.scene.MarkDirty(); } ImGui::EndGroup(); @@ -74,45 +91,47 @@ void Editor::GUI::ShowProperties() ImGui::InputInt("##ModelId", &modelId, 1, 1, ImGuiInputTextFlags_ReadOnly); ImGui::NewLine(); - ImGui::Text( ICON_FA_CIRCLE_HALF_STROKE " Material"); + ImGui::Text(ICON_FA_CIRCLE_HALF_STROKE " Material"); ImGui::Separator(); - - if(current_scene != nullptr && modelId != -1 && render) + + if (modelId != -1 && render) { - auto& model = current_scene->Models()[modelId]; auto& mats = render->Materials(); - for ( auto& mat : mats) + for (auto& mat : mats) { - int matIdx = mat; - if (matIdx == 0) continue; - auto& refMat = current_scene->Materials()[matIdx]; + const int matIdx = mat; + if (matIdx == 0) + { + continue; + } + + auto& refMat = ctx.scene.Materials()[matIdx]; ImGui::PushID(matIdx); ImGui::InputText("##MatName", &refMat.name_, ImGuiInputTextFlags_ReadOnly); ImGui::PopID(); - + ImGui::SameLine(); - - if( ImGui::Button(ICON_FA_CIRCLE_LEFT) ) + + if (ImGui::Button(ICON_FA_CIRCLE_LEFT)) { - if (selectedItemId != -1) + if (ui.selectedMaterialId != InvalidId) { - mat = selectedItemId; + mat = static_cast(ui.selectedMaterialId); } } - + ImGui::SameLine(); - if( ImGui::Button(ICON_FA_PEN_TO_SQUARE) ) + if (ImGui::Button(ICON_FA_PEN_TO_SQUARE)) { - selected_material = &(current_scene->Materials()[matIdx]); - ed_material = true; - OpenMaterialEditor(); + ui.selected_material = &(ctx.scene.Materials()[matIdx]); + ui.ed_material = true; + OpenMaterialEditor(ctx, ui); } - } } } - } - ImGui::End(); -} + ImGui::End(); + } +} // namespace Editor diff --git a/src/Editor/Panels/ViewportOverlay.cpp b/src/Editor/Panels/ViewportOverlay.cpp new file mode 100644 index 00000000..c99e6442 --- /dev/null +++ b/src/Editor/Panels/ViewportOverlay.cpp @@ -0,0 +1,107 @@ +#include "Editor/EditorUi.hpp" + +#include "Assets/Scene.hpp" +#include "Runtime/Engine.hpp" +#include "ThirdParty/fontawesome/IconsFontAwesome6.h" +#include "Utilities/ImGui.hpp" +#include "Utilities/Math.hpp" + +namespace Editor +{ + namespace + { + constexpr float kToolIconWidth = 32.0f; + } + + void DrawViewportOverlay(EditorContext& ctx, EditorUiState& ui) + { + if (!ui.viewportOnMainViewport) + { + return; + } + + ImGuiViewport* viewport = ImGui::GetMainViewport(); + const ImVec2 pos = ui.viewportContentPos; + const ImVec2 size = ui.viewportContentSize; + if (size.x <= 0.0f || size.y <= 0.0f) + { + return; + } + + constexpr float padding = 5.0f; + const float statW = std::max(60.0f, std::min(160.0f, size.x - padding * 2.0f)); + const float statH = std::max(60.0f, std::min(140.0f, size.y - padding * 2.0f)); + + ImGui::SetNextWindowPos(pos + ImVec2(padding, padding)); + ImGui::SetNextWindowSize(ImVec2(statW, statH)); + ImGui::SetNextWindowViewport(viewport->ID); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.5f)); + + ImGuiWindowFlags windowFlags = 0 | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoSavedSettings; + + ImGui::Begin("ViewportStat", nullptr, windowFlags); + + const double smoothDelta = ctx.engine.GetSmoothDeltaSeconds(); + const double frameRate = smoothDelta > 0.0 ? (1.0 / smoothDelta) : 0.0; + ImGui::Text("Realtime Statistics:"); + ImGui::Text("Frame rate: %.0f fps", frameRate); + ImGui::Text("Progressive: %d", ctx.engine.IsProgressiveRendering()); + + auto& gpuDrivenStat = ctx.scene.GetGpuDrivenStat(); + const uint32_t instanceCount = gpuDrivenStat.ProcessedCount - gpuDrivenStat.CulledCount; + const uint32_t triangleCount = gpuDrivenStat.TriangleCount - gpuDrivenStat.CulledTriangleCount; + ImGui::Text("Tris: %s/%s", Utilities::metricFormatter(static_cast(triangleCount), "").c_str(), + Utilities::metricFormatter(static_cast(gpuDrivenStat.TriangleCount), "").c_str()); + ImGui::Text("Draw: %s/%s", Utilities::metricFormatter(static_cast(instanceCount), "").c_str(), + Utilities::metricFormatter(static_cast(gpuDrivenStat.ProcessedCount), "").c_str()); + + ImGui::End(); + + const float toolH = kToolIconWidth + 8.0f; + float toolW = kToolIconWidth + 16.0f; + toolW = std::max(60.0f, std::min(toolW, size.x - padding * 2.0f)); + + ImGui::SetNextWindowPos(pos + ImVec2(std::max(padding, size.x - toolW - padding), padding)); + ImGui::SetNextWindowSize(ImVec2(toolW, toolH)); + ImGui::SetNextWindowViewport(viewport->ID); + ImGui::SetNextWindowBgAlpha(0); + + ImGui::Begin("ViewportTool", nullptr, windowFlags); + + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.5f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); + + const float startX = ImGui::GetCursorPosX(); + const float availW = ImGui::GetContentRegionAvail().x; + ImGui::SetCursorPosX(startX + std::max(0.0f, availW - kToolIconWidth)); + if (ImGui::Button(ICON_FA_EYE, ImVec2(kToolIconWidth, kToolIconWidth))) + { + ImGui::OpenPopup("ViewportShowFlags"); + } + BUTTON_TOOLTIP("Show Flags") + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12, 12)); + if (ImGui::BeginPopup("ViewportShowFlags")) + { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8, 10)); + auto& showFlags = ctx.engine.GetShowFlags(); + Utilities::UI::DrawShowFlagsCommon(showFlags); + + ImGui::PopStyleVar(); + ImGui::EndPopup(); + } + ImGui::PopStyleVar(); + + ImGui::PopStyleColor(); + ImGui::PopStyleVar(); + + ImGui::PopStyleColor(); + ImGui::PopStyleVar(); + + ImGui::End(); + } +} // namespace Editor diff --git a/src/Runtime/UserInterface.cpp b/src/Runtime/UserInterface.cpp index 8bc448d2..e2373aa8 100644 --- a/src/Runtime/UserInterface.cpp +++ b/src/Runtime/UserInterface.cpp @@ -1,5 +1,6 @@ #include "UserInterface.hpp" +#include "Engine.hpp" #include "SceneList.hpp" #include "UserSettings.hpp" #include "Utilities/Exception.hpp" @@ -11,7 +12,6 @@ #include "Vulkan/Surface.hpp" #include "Vulkan/SwapChain.hpp" #include "Vulkan/Window.hpp" -#include "Engine.hpp" #include #include @@ -24,121 +24,117 @@ #endif - #include #include -#include -#include #include +#include -#include "ThirdParty/fontawesome/IconsFontAwesome6.h" +#include "Assets/TextureImage.hpp" #include "Options.hpp" +#include "Rendering/VulkanBaseRenderer.hpp" #include "TaskCoordinator.hpp" -#include "Assets/TextureImage.hpp" +#include "ThirdParty/fontawesome/IconsFontAwesome6.h" #include "Utilities/FileHelper.hpp" -#include "Utilities/Math.hpp" -#include "Rendering/VulkanBaseRenderer.hpp" #include "Utilities/ImGui.hpp" +#include "Utilities/Math.hpp" #include "Vulkan/ImageView.hpp" extern float GAndroidMagicScale; extern std::unique_ptr GApplication; -UserInterface::UserInterface( - NextEngine* engine, - Vulkan::CommandPool& commandPool, - const Vulkan::SwapChain& swapChain, - const Vulkan::DepthBuffer& depthBuffer, - UserSettings& userSettings, std::function funcPreConfig, std::function funcInit) : - userSettings_(userSettings), - engine_(engine) +UserInterface::UserInterface(NextEngine* engine, Vulkan::CommandPool& commandPool, const Vulkan::SwapChain& swapChain, + const Vulkan::DepthBuffer& depthBuffer, UserSettings& userSettings, + std::function funcPreConfig, std::function funcInit) : + userSettings_(userSettings), engine_(engine) { - const auto& device = swapChain.Device(); - const auto& window = device.Surface().Instance().Window(); - - // Initialise descriptor pool and render pass for ImGui. - const std::vector descriptorBindings = - { - {0, 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0}, - }; - descriptorPool_.reset(new Vulkan::DescriptorPool(device, descriptorBindings, swapChain.MinImageCount() + 2048)); - renderPass_.reset(new Vulkan::RenderPass(swapChain, depthBuffer, VK_ATTACHMENT_LOAD_OP_LOAD)); - renderPass_->SetDebugName("ImGui Render Pass"); - - // Initialise ImGui - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - - auto& io = ImGui::GetIO(); - // No ini file. - io.IniFilename = "imgui.ini"; - io.WantCaptureMouse = false; - io.WantCaptureKeyboard = false; - - funcPreConfig(); - - // Initialise ImGui GLFW adapter - if (!ImGui_ImplSDL3_InitForVulkan(window.Handle())) - { - Throw(std::runtime_error("failed to initialise ImGui GLFW adapter")); - } - - // Initialise ImGui Vulkan adapter - ImGui_ImplVulkan_InitInfo vulkanInit = {}; - vulkanInit.Instance = device.Surface().Instance().Handle(); - vulkanInit.PhysicalDevice = device.PhysicalDevice(); - vulkanInit.Device = device.Handle(); - vulkanInit.QueueFamily = device.GraphicsFamilyIndex(); - vulkanInit.Queue = device.GraphicsQueue(); - vulkanInit.PipelineCache = nullptr; - vulkanInit.DescriptorPool = descriptorPool_->Handle(); - vulkanInit.MinImageCount = swapChain.MinImageCount(); - vulkanInit.ImageCount = static_cast(swapChain.Images().size()); - vulkanInit.Allocator = nullptr; - vulkanInit.RenderPass = renderPass_->Handle(); - - if (!ImGui_ImplVulkan_Init(&vulkanInit)) - { - Throw(std::runtime_error("failed to initialise ImGui vulkan adapter")); - } - - // Window scaling and style. + const auto& device = swapChain.Device(); + const auto& window = device.Surface().Instance().Window(); + + // Initialise descriptor pool and render pass for ImGui. + const std::vector descriptorBindings = { + {0, 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0}, + }; + descriptorPool_.reset(new Vulkan::DescriptorPool(device, descriptorBindings, swapChain.MinImageCount() + 2048)); + renderPass_.reset(new Vulkan::RenderPass(swapChain, depthBuffer, VK_ATTACHMENT_LOAD_OP_LOAD)); + renderPass_->SetDebugName("ImGui Render Pass"); + + // Initialise ImGui + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + + auto& io = ImGui::GetIO(); + // No ini file. + io.IniFilename = "imgui.ini"; + io.WantCaptureMouse = false; + io.WantCaptureKeyboard = false; + + funcPreConfig(); + + // Initialise ImGui GLFW adapter + if (!ImGui_ImplSDL3_InitForVulkan(window.Handle())) + { + Throw(std::runtime_error("failed to initialise ImGui GLFW adapter")); + } + + // Initialise ImGui Vulkan adapter + ImGui_ImplVulkan_InitInfo vulkanInit = {}; + vulkanInit.Instance = device.Surface().Instance().Handle(); + vulkanInit.PhysicalDevice = device.PhysicalDevice(); + vulkanInit.Device = device.Handle(); + vulkanInit.QueueFamily = device.GraphicsFamilyIndex(); + vulkanInit.Queue = device.GraphicsQueue(); + vulkanInit.PipelineCache = nullptr; + vulkanInit.DescriptorPool = descriptorPool_->Handle(); + vulkanInit.MinImageCount = swapChain.MinImageCount(); + vulkanInit.ImageCount = static_cast(swapChain.Images().size()); + vulkanInit.Allocator = nullptr; + vulkanInit.RenderPass = renderPass_->Handle(); + + if (!ImGui_ImplVulkan_Init(&vulkanInit)) + { + Throw(std::runtime_error("failed to initialise ImGui vulkan adapter")); + } + + // Window scaling and style. #if ANDROID const auto scaleFactor = 0.75 / GAndroidMagicScale; #else const auto scaleFactor = 1.0; #endif - const auto fontSize = 16; - - UserInterface::SetStyle(); - ImGui::GetStyle().ScaleAllSizes(scaleFactor); - - // Upload ImGui fonts (use ImGuiFreeType for better font rendering, see https://github.com/ocornut/imgui/tree/master/misc/freetype). - io.Fonts->FontBuilderIO = ImGuiFreeType::GetBuilderForFreeType(); - io.Fonts->FontBuilderFlags = ImGuiFreeTypeBuilderFlags_NoHinting; - const ImWchar* glyphRange = GOption->locale == "RU" ? io.Fonts->GetGlyphRangesCyrillic() : GOption->locale == "zhCN" ? io.Fonts->GetGlyphRangesChineseFull() : io.Fonts->GetGlyphRangesDefault(); - + const auto fontSize = 16; + + UserInterface::SetStyle(); + ImGui::GetStyle().ScaleAllSizes(scaleFactor); + + // Upload ImGui fonts (use ImGuiFreeType for better font rendering, see + // https://github.com/ocornut/imgui/tree/master/misc/freetype). + io.Fonts->FontBuilderIO = ImGuiFreeType::GetBuilderForFreeType(); + io.Fonts->FontBuilderFlags = ImGuiFreeTypeBuilderFlags_NoHinting; + const ImWchar* glyphRange = GOption->locale == "RU" ? io.Fonts->GetGlyphRangesCyrillic() + : GOption->locale == "zhCN" ? io.Fonts->GetGlyphRangesChineseFull() + : io.Fonts->GetGlyphRangesDefault(); + std::vector tmpData; if (Utilities::Package::FPackageFileSystem::GetInstance().LoadFile("assets/fonts/Roboto-Regular.ttf", tmpData)) { void* dataSrc = IM_ALLOC(tmpData.size()); std::memcpy(dataSrc, tmpData.data(), tmpData.size()); - io.Fonts->AddFontFromMemoryTTF(dataSrc, int(tmpData.size()) , fontSize * scaleFactor, nullptr, glyphRange); + io.Fonts->AddFontFromMemoryTTF(dataSrc, int(tmpData.size()), fontSize * scaleFactor, nullptr, glyphRange); } - else - { - Throw(std::runtime_error("failed to load basic ImGui Text font")); - } - - static const ImWchar iconRange[] = - { - ICON_MIN_FA, ICON_MAX_FA, // Basic Latin + Latin Supplement - 0, - }; - ImFontConfig config; - config.MergeMode = true; - config.GlyphMinAdvanceX = fontSize; - config.GlyphOffset = ImVec2(0, 0); + else + { + Throw(std::runtime_error("failed to load basic ImGui Text font")); + } + + static const ImWchar iconRange[] = { + ICON_MIN_FA, + ICON_MAX_FA, // Basic Latin + Latin Supplement + 0, + }; + ImFontConfig config; + config.MergeMode = true; + config.GlyphMinAdvanceX = fontSize; + config.GlyphOffset = ImVec2(0, 0); if (Utilities::Package::FPackageFileSystem::GetInstance().LoadFile("assets/fonts/fa-regular-400.ttf", tmpData)) { @@ -158,363 +154,382 @@ UserInterface::UserInterface( std::memcpy(dataSrc, tmpData.data(), tmpData.size()); io.Fonts->AddFontFromMemoryTTF(dataSrc, int(tmpData.size()), fontSize * scaleFactor, &config, iconRange); } - - ImFontConfig configLocale; - configLocale.MergeMode = true; + + ImFontConfig configLocale; + configLocale.MergeMode = true; if (Utilities::Package::FPackageFileSystem::GetInstance().LoadFile("assets/fonts/DroidSansFallback.ttf", tmpData)) { void* dataSrc = IM_ALLOC(tmpData.size()); std::memcpy(dataSrc, tmpData.data(), tmpData.size()); - io.Fonts->AddFontFromMemoryTTF(dataSrc, int(tmpData.size()), (fontSize + 2) * scaleFactor, &configLocale, glyphRange); + io.Fonts->AddFontFromMemoryTTF(dataSrc, int(tmpData.size()), (fontSize + 2) * scaleFactor, &configLocale, + glyphRange); } - if(funcInit != nullptr) - { - funcInit(); - } - - Vulkan::SingleTimeCommands::Submit(commandPool, [] (VkCommandBuffer commandBuffer) - { - if (!ImGui_ImplVulkan_CreateFontsTexture()) - { - Throw(std::runtime_error("failed to create ImGui font textures")); - } - }); + if (funcInit != nullptr) + { + funcInit(); + } + + Vulkan::SingleTimeCommands::Submit(commandPool, + [](VkCommandBuffer commandBuffer) + { + if (!ImGui_ImplVulkan_CreateFontsTexture()) + { + Throw(std::runtime_error("failed to create ImGui font textures")); + } + }); } UserInterface::~UserInterface() { - uiFrameBuffers_.clear(); - - ImGui_ImplVulkan_Shutdown(); - ImGui_ImplSDL3_Shutdown(); - ImGui::DestroyContext(); + uiFrameBuffers_.clear(); + + ImGui_ImplVulkan_Shutdown(); + ImGui_ImplSDL3_Shutdown(); + ImGui::DestroyContext(); } void UserInterface::OnCreateSurface(const Vulkan::SwapChain& swapChain, const Vulkan::DepthBuffer& depthBuffer) { - renderPass_.reset(new Vulkan::RenderPass(swapChain, depthBuffer, VK_ATTACHMENT_LOAD_OP_LOAD)); - renderPass_->SetDebugName("ImGui Render Pass"); - - for (const auto& imageView : swapChain.ImageViews()) - { - uiFrameBuffers_.emplace_back(swapChain.Extent(), *imageView, *renderPass_, false); - } + renderPass_.reset(new Vulkan::RenderPass(swapChain, depthBuffer, VK_ATTACHMENT_LOAD_OP_LOAD)); + renderPass_->SetDebugName("ImGui Render Pass"); + + for (const auto& imageView : swapChain.ImageViews()) + { + uiFrameBuffers_.emplace_back(swapChain.Extent(), *imageView, *renderPass_, false); + } } void UserInterface::OnDestroySurface() { - renderPass_.reset(); - uiFrameBuffers_.clear(); - for ( auto image : imTextureIdMap_) - { - ImGui_ImplVulkan_RemoveTexture(image.second); - } - imTextureIdMap_.clear(); + renderPass_.reset(); + uiFrameBuffers_.clear(); + for (auto image : imTextureIdMap_) + { + ImGui_ImplVulkan_RemoveTexture(image.second); + } + imTextureIdMap_.clear(); } VkDescriptorSet UserInterface::RequestImTextureId(uint32_t globalTextureId) { - auto texture = Assets::GlobalTexturePool::GetTextureImage(globalTextureId); - if (texture == nullptr) return VK_NULL_HANDLE; - - if( imTextureIdMap_.find(globalTextureId) == imTextureIdMap_.end() ) - { - imTextureIdMap_[globalTextureId] = ImGui_ImplVulkan_AddTexture(texture->Sampler().Handle(), texture->ImageView().Handle(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - } - - return imTextureIdMap_[globalTextureId]; + auto texture = Assets::GlobalTexturePool::GetTextureImage(globalTextureId); + if (texture == nullptr) + return VK_NULL_HANDLE; + + if (imTextureIdMap_.find(globalTextureId) == imTextureIdMap_.end()) + { + imTextureIdMap_[globalTextureId] = ImGui_ImplVulkan_AddTexture( + texture->Sampler().Handle(), texture->ImageView().Handle(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + } + + return imTextureIdMap_[globalTextureId]; } VkDescriptorSet UserInterface::RequestImTextureByName(const std::string& name) { - uint32_t id = Assets::GlobalTexturePool::GetTextureIndexByName(name); - if( id == -1) - { - return VK_NULL_HANDLE; - } - return RequestImTextureId(id); + uint32_t id = Assets::GlobalTexturePool::GetTextureIndexByName(name); + if (id == -1) + { + return VK_NULL_HANDLE; + } + return RequestImTextureId(id); } void UserInterface::SetStyle() { - ImGuiIO &io = ImGui::GetIO(); + ImGuiIO& io = ImGui::GetIO(); - io.IniFilename = NULL; + // NOTE: Do not override io.IniFilename here. + // The app/editor is responsible for choosing its ini file in the PreConfig hook. - ImVec4 activeColor = true ? ImVec4(0.42f, 0.45f, 0.5f, 1.00f) : ImVec4(0.28f, 0.45f, 0.70f, 1.00f); + ImVec4 activeColor = true ? ImVec4(0.42f, 0.45f, 0.5f, 1.00f) : ImVec4(0.28f, 0.45f, 0.70f, 1.00f); ImGuiStyle* style = &ImGui::GetStyle(); ImVec4* colors = style->Colors; ImGui::StyleColorsDark(style); - colors[ImGuiCol_Text] = ImVec4(0.84f, 0.84f, 0.84f, 1.00f); - colors[ImGuiCol_WindowBg] = ImVec4(0.22f, 0.22f, 0.22f, 1.00f); - colors[ImGuiCol_ChildBg] = ImVec4(0.19f, 0.19f, 0.19f, 1.00f); - colors[ImGuiCol_PopupBg] = ImVec4(0.19f, 0.19f, 0.19f, 1.00f); - colors[ImGuiCol_Border] = ImVec4(0.17f, 0.17f, 0.17f, 1.00f); - colors[ImGuiCol_BorderShadow] = ImVec4(0.10f, 0.10f, 0.10f, 0.00f); - colors[ImGuiCol_FrameBg] = ImVec4(0.16f, 0.16f, 0.16f, 1.00f); - colors[ImGuiCol_FrameBgHovered] = ImVec4(0.16f, 0.16f, 0.16f, 1.00f); - colors[ImGuiCol_FrameBgActive] = ImVec4(0.16f, 0.16f, 0.16f, 1.00f); - colors[ImGuiCol_TitleBg] = ImVec4(0.11f, 0.11f, 0.11f, 1.00f); - colors[ImGuiCol_TitleBgActive] = ImVec4(0.0f, 0.0f, 0.0f, 1.00f); // TrueBlack - colors[ImGuiCol_MenuBarBg] = ImVec4(0.11f, 0.11f, 0.11f, 1.00f); - colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.33f, 0.33f, 0.33f, 1.00f); - colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.33f, 0.33f, 0.33f, 1.00f); - colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.35f, 0.35f, 0.35f, 1.00f); - colors[ImGuiCol_CheckMark] = activeColor; - colors[ImGuiCol_SliderGrab] = activeColor; - colors[ImGuiCol_SliderGrabActive] = activeColor; - colors[ImGuiCol_Button] = ImVec4(0.33f, 0.33f, 0.33f, 1.00f); - colors[ImGuiCol_ButtonHovered] = ImVec4(0.40f, 0.40f, 0.40f, 1.00f); - colors[ImGuiCol_ButtonActive] = activeColor; - colors[ImGuiCol_Header] = ImVec4(0.15f, 0.15f, 0.15f, 1.00f); - colors[ImGuiCol_HeaderHovered] = activeColor; - colors[ImGuiCol_HeaderActive] = ImVec4(0.15f, 0.15f, 0.15f, 1.00f); - colors[ImGuiCol_Separator] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f); - colors[ImGuiCol_SeparatorHovered] = activeColor; - colors[ImGuiCol_SeparatorActive] = activeColor; - colors[ImGuiCol_ResizeGrip] = ImVec4(0.54f, 0.54f, 0.54f, 1.00f); - colors[ImGuiCol_ResizeGripHovered] = activeColor; - colors[ImGuiCol_ResizeGripActive] = ImVec4(0.19f, 0.39f, 0.69f, 1.00f); - colors[ImGuiCol_Tab] = ImVec4(0.11f, 0.11f, 0.11f, 1.00f); - colors[ImGuiCol_TabHovered] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); - colors[ImGuiCol_TabActive] = ImVec4(0.19f, 0.19f, 0.19f, 1.00f); - colors[ImGuiCol_PlotHistogram] = activeColor; - colors[ImGuiCol_PlotHistogramHovered] = ImVec4(0.20f, 0.39f, 0.69f, 1.00f); - colors[ImGuiCol_TextSelectedBg] = activeColor; - colors[ImGuiCol_NavHighlight] = activeColor; - colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.5f); - - style->WindowPadding = ImVec2(12.00f, 8.00f); - style->ItemSpacing = ImVec2(6.00f, 6.00f); - style->GrabMinSize = 20.00f; - style->WindowRounding = 8.00f; - style->FrameBorderSize = 0.00f; - style->FrameRounding = 4.00f; - style->GrabRounding = 12.00f; - style->SeparatorTextBorderSize = 1.0f; + colors[ImGuiCol_Text] = ImVec4(0.84f, 0.84f, 0.84f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.22f, 0.22f, 0.22f, 1.00f); + colors[ImGuiCol_ChildBg] = ImVec4(0.19f, 0.19f, 0.19f, 1.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.19f, 0.19f, 0.19f, 1.00f); + colors[ImGuiCol_Border] = ImVec4(0.17f, 0.17f, 0.17f, 1.00f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.10f, 0.10f, 0.10f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.16f, 0.16f, 0.16f, 1.00f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.16f, 0.16f, 0.16f, 1.00f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.16f, 0.16f, 0.16f, 1.00f); + colors[ImGuiCol_TitleBg] = ImVec4(0.11f, 0.11f, 0.11f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.0f, 0.0f, 0.0f, 1.00f); // TrueBlack + colors[ImGuiCol_MenuBarBg] = ImVec4(0.11f, 0.11f, 0.11f, 1.00f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.33f, 0.33f, 0.33f, 1.00f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.33f, 0.33f, 0.33f, 1.00f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.35f, 0.35f, 0.35f, 1.00f); + colors[ImGuiCol_CheckMark] = activeColor; + colors[ImGuiCol_SliderGrab] = activeColor; + colors[ImGuiCol_SliderGrabActive] = activeColor; + colors[ImGuiCol_Button] = ImVec4(0.33f, 0.33f, 0.33f, 1.00f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.40f, 0.40f, 0.40f, 1.00f); + colors[ImGuiCol_ButtonActive] = activeColor; + colors[ImGuiCol_Header] = ImVec4(0.15f, 0.15f, 0.15f, 1.00f); + colors[ImGuiCol_HeaderHovered] = activeColor; + colors[ImGuiCol_HeaderActive] = ImVec4(0.15f, 0.15f, 0.15f, 1.00f); + colors[ImGuiCol_Separator] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f); + colors[ImGuiCol_SeparatorHovered] = activeColor; + colors[ImGuiCol_SeparatorActive] = activeColor; + colors[ImGuiCol_ResizeGrip] = ImVec4(0.54f, 0.54f, 0.54f, 1.00f); + colors[ImGuiCol_ResizeGripHovered] = activeColor; + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.19f, 0.39f, 0.69f, 1.00f); + colors[ImGuiCol_Tab] = ImVec4(0.11f, 0.11f, 0.11f, 1.00f); + colors[ImGuiCol_TabHovered] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); + colors[ImGuiCol_TabActive] = ImVec4(0.19f, 0.19f, 0.19f, 1.00f); + colors[ImGuiCol_PlotHistogram] = activeColor; + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(0.20f, 0.39f, 0.69f, 1.00f); + colors[ImGuiCol_TextSelectedBg] = activeColor; + colors[ImGuiCol_NavHighlight] = activeColor; + colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.5f); + + style->WindowPadding = ImVec2(12.00f, 8.00f); + style->ItemSpacing = ImVec2(6.00f, 6.00f); + style->GrabMinSize = 20.00f; + style->WindowRounding = 8.00f; + style->FrameBorderSize = 0.00f; + style->FrameRounding = 4.00f; + style->GrabRounding = 12.00f; + style->SeparatorTextBorderSize = 1.0f; } void UserInterface::DrawPoint(float x, float y, float size, glm::vec4 color) { - // in viewport mode, the start from the display - auxDrawRequest_.push_back( [=]() { - ImVec2 startPos = ImGui::GetMainViewport()->Pos; - ImGui::GetBackgroundDrawList()->AddRectFilled(startPos + ImVec2{x - size, y - size}, startPos + ImVec2{x + size, y + size}, Utilities::UI::Vec4ToImU32(color)); - }); + // in viewport mode, the start from the display + auxDrawRequest_.push_back( + [=]() + { + ImVec2 startPos = ImGui::GetMainViewport()->Pos; + ImGui::GetBackgroundDrawList()->AddRectFilled(startPos + ImVec2{x - size, y - size}, + startPos + ImVec2{x + size, y + size}, + Utilities::UI::Vec4ToImU32(color)); + }); } void UserInterface::DrawLine(float fromx, float fromy, float tox, float toy, float size, glm::vec4 color) { - auxDrawRequest_.push_back( [=]() { - ImVec2 startPos = ImGui::GetMainViewport()->Pos; - ImGui::GetBackgroundDrawList()->AddLine( startPos + ImVec2(fromx, fromy), startPos + ImVec2(tox, toy), Utilities::UI::Vec4ToImU32(color), size); - }); + auxDrawRequest_.push_back( + [=]() + { + ImVec2 startPos = ImGui::GetMainViewport()->Pos; + ImGui::GetBackgroundDrawList()->AddLine(startPos + ImVec2(fromx, fromy), startPos + ImVec2(tox, toy), + Utilities::UI::Vec4ToImU32(color), size); + }); } void UserInterface::PreRender() { - ImGui_ImplVulkan_NewFrame(); - ImGui_ImplSDL3_NewFrame(); - ImGui::NewFrame(); - - // update texture to ui - uint32_t maxId = Assets::GlobalTexturePool::GetInstance()->TotalTextures(); - for ( uint32_t idx = 0; idx < maxId; ++idx) - { - RequestImTextureId(idx); - } + ImGui_ImplVulkan_NewFrame(); + ImGui_ImplSDL3_NewFrame(); + ImGui::NewFrame(); + + // update texture to ui + uint32_t maxId = Assets::GlobalTexturePool::GetInstance()->TotalTextures(); + for (uint32_t idx = 0; idx < maxId; ++idx) + { + RequestImTextureId(idx); + } } void UserInterface::Render(const Statistics& statistics, Vulkan::VulkanGpuTimer* gpuTimer, Assets::Scene* scene) { - DrawOverlay(statistics, gpuTimer); + DrawOverlay(statistics, gpuTimer); } void UserInterface::PostRender(VkCommandBuffer commandBuffer, const Vulkan::SwapChain& swapChain, uint32_t imageIdx) { - if( GetEngine().GetEngineStatus() == NextRenderer::EApplicationStatus::Loading ) DrawIndicator(GetEngine().GetTotalFrames()); - - // aux - for( auto& req : auxDrawRequest_) { - req(); - } - auxDrawRequest_.clear(); - - ImGui::Render(); - - VkRenderPassBeginInfo renderPassInfo = {}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; - renderPassInfo.renderPass = renderPass_->Handle(); - renderPassInfo.framebuffer = uiFrameBuffers_[imageIdx].Handle(); - renderPassInfo.renderArea.offset = { 0, 0 }; - renderPassInfo.renderArea.extent = renderPass_->SwapChain().Extent(); - renderPassInfo.clearValueCount = 0; - renderPassInfo.pClearValues = nullptr; - - vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), commandBuffer); - vkCmdEndRenderPass(commandBuffer); - - auto& io = ImGui::GetIO(); - if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) - { - ImGui::UpdatePlatformWindows(); - ImGui::RenderPlatformWindowsDefault(); - } -} + if (GetEngine().GetEngineStatus() == NextRenderer::EApplicationStatus::Loading) + DrawIndicator(GetEngine().GetTotalFrames()); -void UserInterface::HandleEvent(const SDL_Event* event) -{ - ImGui_ImplSDL3_ProcessEvent(event); -} + // aux + for (auto& req : auxDrawRequest_) + { + req(); + } + auxDrawRequest_.clear(); -bool UserInterface::WantsToCaptureKeyboard() const -{ - return ImGui::GetIO().WantCaptureKeyboard; -} + ImGui::Render(); -bool UserInterface::WantsToCaptureMouse() const -{ - return ImGui::GetIO().WantCaptureMouse; + VkRenderPassBeginInfo renderPassInfo = {}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass_->Handle(); + renderPassInfo.framebuffer = uiFrameBuffers_[imageIdx].Handle(); + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = renderPass_->SwapChain().Extent(); + renderPassInfo.clearValueCount = 0; + renderPassInfo.pClearValues = nullptr; + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), commandBuffer); + vkCmdEndRenderPass(commandBuffer); + + auto& io = ImGui::GetIO(); + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + ImGui::UpdatePlatformWindows(); + ImGui::RenderPlatformWindowsDefault(); + } } +void UserInterface::HandleEvent(const SDL_Event* event) { ImGui_ImplSDL3_ProcessEvent(event); } + +bool UserInterface::WantsToCaptureKeyboard() const { return ImGui::GetIO().WantCaptureKeyboard; } + +bool UserInterface::WantsToCaptureMouse() const { return ImGui::GetIO().WantCaptureMouse; } + void UserInterface::DrawOverlay(const Statistics& statistics, Vulkan::VulkanGpuTimer* gpuTimer) { - if (!Settings().ShowOverlay) - { - return; - } - - const auto& io = ImGui::GetIO(); - const float distance = 10.0f; - const ImVec2 pos = ImVec2(io.DisplaySize.x - distance, distance + 40); - const ImVec2 posPivot = ImVec2(1.0f, 0.0f); - - ImGui::SetNextWindowPos(pos, ImGuiCond_Always, posPivot); - ImGui::SetNextWindowBgAlpha(0.3f); // Transparent background - - const auto flags = - ImGuiWindowFlags_AlwaysAutoResize | - ImGuiWindowFlags_NoDecoration | - ImGuiWindowFlags_NoFocusOnAppearing | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoNav | - ImGuiWindowFlags_NoSavedSettings; - - if (ImGui::Begin("Statistics", &Settings().ShowOverlay, flags)) - { - // Colors - const ImVec4 colHeader = ImVec4(0.8f, 0.8f, 1.0f, 1.0f); - const ImVec4 colLabel = ImVec4(0.7f, 0.7f, 0.7f, 1.0f); - const ImVec4 colVal = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); - const ImVec4 colGood = ImVec4(0.4f, 1.0f, 0.4f, 1.0f); - const ImVec4 colWarn = ImVec4(1.0f, 0.8f, 0.2f, 1.0f); - const ImVec4 colBad = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); - - // Helper - auto LabelVal = [&](const char* label, const char* fmt, auto... args) { - ImGui::TextColored(colLabel, "%s", label); - ImGui::SameLine(); - ImGui::TextColored(colVal, fmt, args...); - }; - - ImGui::TextColored(colHeader, "Resolution: %dx%d -> %dx%d", statistics.RenderSize.width, statistics.RenderSize.height, statistics.FramebufferSize.width, statistics.FramebufferSize.height); - ImGui::TextColored(colLabel, "%s", statistics.Stats["gpu"].c_str()); - - ImGui::Separator(); - - // FPS - ImVec4 fpsColor = statistics.FrameRate > 55.0f ? colGood : (statistics.FrameRate > 30.0f ? colWarn : colBad); - ImGui::TextColored(colLabel, "Frame rate:"); ImGui::SameLine(); - ImGui::TextColored(fpsColor, "%.0f fps", statistics.FrameRate); - - LabelVal("Frame time:", "%.2f ms", statistics.FrameTime); - - ImGui::Separator(); - - // Scene - LabelVal("Node:", "%s", Utilities::metricFormatter(static_cast(statistics.NodeCount), "").c_str()); - LabelVal("Instance:", "%s", Utilities::metricFormatter(static_cast(statistics.InstanceCount), "").c_str()); - LabelVal("Texture:", "%d", statistics.TextureCount); - - ImGui::Separator(); - - // GPU Stats - auto& gpuDrivenStat = NextEngine::GetInstance()->GetScene().GetGpuDrivenStat(); - uint32_t instanceCount = gpuDrivenStat.ProcessedCount - gpuDrivenStat.CulledCount; - uint32_t triangleCount = gpuDrivenStat.TriangleCount - gpuDrivenStat.CulledTriangleCount; - - ImGui::TextColored(colHeader, "GPU Stats (Visible/Total):"); - LabelVal("Draws:", "%s / %s", Utilities::metricFormatter(static_cast(instanceCount), "").c_str(), Utilities::metricFormatter(static_cast(gpuDrivenStat.ProcessedCount), "").c_str()); - LabelVal("Tris:", "%s / %s", Utilities::metricFormatter(static_cast(triangleCount), "").c_str(), Utilities::metricFormatter(static_cast(gpuDrivenStat.TriangleCount), "").c_str()); - - uint32_t mainTasks = TaskCoordinator::GetInstance()->GetMainTaskCount(); - uint32_t lowTasks = TaskCoordinator::GetInstance()->GetParralledTaskCount(); - uint32_t completeTasks = TaskCoordinator::GetInstance()->GetComleteTaskQueueCount(); - LabelVal("Tasks:", "%d / %d / %d", mainTasks, lowTasks, completeTasks); - - ImGui::Separator(); - - // Timers - auto times = gpuTimer->FetchAllTimes(4); - float totalGpuTime = 0; - for(auto& time : times) { - if (std::get<2>(time) == 0) totalGpuTime += std::get<1>(time); - } - - ImGui::TextColored(colHeader, "GPU Time (%.2fms):", totalGpuTime); - for(auto& time : times) - { - float ms = std::get<1>(time); - int depth = std::get<2>(time); - std::string name = std::get<0>(time); - - ImGui::Indent(depth * 10.0f); - ImGui::TextColored(depth == 0 ? colVal : colLabel, "%s: %.2fms", name.c_str(), ms); - - if (totalGpuTime > 0) { - ImGui::SameLine(200); - - ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.2f, 0.7f, 0.2f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0,0,0,0)); - ImGui::PushStyleColor(ImGuiCol_Border, colLabel); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); - - ImGui::ProgressBar(ms / (totalGpuTime > 0.001f ? totalGpuTime : 1.0f), ImVec2(50, ImGui::GetTextLineHeight()), ""); - - ImGui::PopStyleVar(); - ImGui::PopStyleColor(3); - } - ImGui::Unindent(depth * 10.0f); - } - - ImGui::TextColored(colHeader, "CPU Time (%.2fms):", gpuTimer->GetCpuTime("draw-frame")); - // ImGui::Text("drawframe: %.2fms", gpuTimer->GetCpuTime("draw-frame")); - LabelVal(" - hwquery:", "%.2fms", gpuTimer->GetCpuTime("hwquery")); - LabelVal(" - fence:", "%.2fms", gpuTimer->GetCpuTime("fence")); - LabelVal(" - present:", "%.2fms", gpuTimer->GetCpuTime("present")); - - ImGui::Separator(); - LabelVal("Frame:", "%d", statistics.TotalFrames); - LabelVal("Time:", "%s", fmt::format("{:%H:%M:%S}", std::chrono::seconds(static_cast(statistics.RenderTime))).c_str()); - } - ImGui::End(); + if (!Settings().ShowOverlay) + { + return; + } + + const auto& io = ImGui::GetIO(); + const float distance = 10.0f; + const ImVec2 pos = ImVec2(io.DisplaySize.x - distance, distance + 40); + const ImVec2 posPivot = ImVec2(1.0f, 0.0f); + + ImGui::SetNextWindowPos(pos, ImGuiCond_Always, posPivot); + ImGui::SetNextWindowBgAlpha(0.3f); // Transparent background + + const auto flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav | + ImGuiWindowFlags_NoSavedSettings; + + if (ImGui::Begin("Statistics", &Settings().ShowOverlay, flags)) + { + // Colors + const ImVec4 colHeader = ImVec4(0.8f, 0.8f, 1.0f, 1.0f); + const ImVec4 colLabel = ImVec4(0.7f, 0.7f, 0.7f, 1.0f); + const ImVec4 colVal = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + const ImVec4 colGood = ImVec4(0.4f, 1.0f, 0.4f, 1.0f); + const ImVec4 colWarn = ImVec4(1.0f, 0.8f, 0.2f, 1.0f); + const ImVec4 colBad = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); + + // Helper + auto LabelVal = [&](const char* label, const char* fmt, auto... args) + { + ImGui::TextColored(colLabel, "%s", label); + ImGui::SameLine(); + ImGui::TextColored(colVal, fmt, args...); + }; + + ImGui::TextColored(colHeader, "Resolution: %dx%d -> %dx%d", statistics.RenderSize.width, + statistics.RenderSize.height, statistics.FramebufferSize.width, + statistics.FramebufferSize.height); + ImGui::TextColored(colLabel, "%s", statistics.Stats["gpu"].c_str()); + + ImGui::Separator(); + + // FPS + ImVec4 fpsColor = statistics.FrameRate > 55.0f ? colGood : (statistics.FrameRate > 30.0f ? colWarn : colBad); + ImGui::TextColored(colLabel, "Frame rate:"); + ImGui::SameLine(); + ImGui::TextColored(fpsColor, "%.0f fps", statistics.FrameRate); + + LabelVal("Frame time:", "%.2f ms", statistics.FrameTime); + + ImGui::Separator(); + + // Scene + LabelVal("Node:", "%s", Utilities::metricFormatter(static_cast(statistics.NodeCount), "").c_str()); + LabelVal("Instance:", "%s", + Utilities::metricFormatter(static_cast(statistics.InstanceCount), "").c_str()); + LabelVal("Texture:", "%d", statistics.TextureCount); + + ImGui::Separator(); + + // GPU Stats + auto& gpuDrivenStat = NextEngine::GetInstance()->GetScene().GetGpuDrivenStat(); + uint32_t instanceCount = gpuDrivenStat.ProcessedCount - gpuDrivenStat.CulledCount; + uint32_t triangleCount = gpuDrivenStat.TriangleCount - gpuDrivenStat.CulledTriangleCount; + + ImGui::TextColored(colHeader, "GPU Stats (Visible/Total):"); + LabelVal("Draws:", "%s / %s", Utilities::metricFormatter(static_cast(instanceCount), "").c_str(), + Utilities::metricFormatter(static_cast(gpuDrivenStat.ProcessedCount), "").c_str()); + LabelVal("Tris:", "%s / %s", Utilities::metricFormatter(static_cast(triangleCount), "").c_str(), + Utilities::metricFormatter(static_cast(gpuDrivenStat.TriangleCount), "").c_str()); + + uint32_t mainTasks = TaskCoordinator::GetInstance()->GetMainTaskCount(); + uint32_t lowTasks = TaskCoordinator::GetInstance()->GetParralledTaskCount(); + uint32_t completeTasks = TaskCoordinator::GetInstance()->GetComleteTaskQueueCount(); + LabelVal("Tasks:", "%d / %d / %d", mainTasks, lowTasks, completeTasks); + + ImGui::Separator(); + + // Timers + auto times = gpuTimer->FetchAllTimes(4); + float totalGpuTime = 0; + for (auto& time : times) + { + if (std::get<2>(time) == 0) + totalGpuTime += std::get<1>(time); + } + + ImGui::TextColored(colHeader, "GPU Time (%.2fms):", totalGpuTime); + for (auto& time : times) + { + float ms = std::get<1>(time); + int depth = std::get<2>(time); + std::string name = std::get<0>(time); + + ImGui::Indent(depth * 10.0f); + ImGui::TextColored(depth == 0 ? colVal : colLabel, "%s: %.2fms", name.c_str(), ms); + + if (totalGpuTime > 0) + { + ImGui::SameLine(200); + + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.2f, 0.7f, 0.2f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleColor(ImGuiCol_Border, colLabel); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); + + ImGui::ProgressBar(ms / (totalGpuTime > 0.001f ? totalGpuTime : 1.0f), + ImVec2(50, ImGui::GetTextLineHeight()), ""); + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(3); + } + ImGui::Unindent(depth * 10.0f); + } + + ImGui::TextColored(colHeader, "CPU Time (%.2fms):", gpuTimer->GetCpuTime("draw-frame")); + // ImGui::Text("drawframe: %.2fms", gpuTimer->GetCpuTime("draw-frame")); + LabelVal(" - hwquery:", "%.2fms", gpuTimer->GetCpuTime("hwquery")); + LabelVal(" - fence:", "%.2fms", gpuTimer->GetCpuTime("fence")); + LabelVal(" - present:", "%.2fms", gpuTimer->GetCpuTime("present")); + + ImGui::Separator(); + LabelVal("Frame:", "%d", statistics.TotalFrames); + LabelVal( + "Time:", "%s", + fmt::format("{:%H:%M:%S}", std::chrono::seconds(static_cast(statistics.RenderTime))).c_str()); + } + ImGui::End(); } void UserInterface::DrawIndicator(uint32_t frameCount) { - frameCount /= 60; - ImGui::OpenPopup("Loading"); - // Always center this window when appearing - ImVec2 center = ImGui::GetMainViewport()->GetCenter(); - ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); - ImGui::SetNextWindowSize(ImVec2(200,40)); - - if (ImGui::BeginPopupModal("Loading", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoResize)) - { - ImGui::Text("Loading%s", frameCount % 4 == 0 ? "" : frameCount % 4 == 1 ? "." : frameCount % 4 == 2 ? ".." : "..."); - ImGui::EndPopup(); - } + frameCount /= 60; + ImGui::OpenPopup("Loading"); + // Always center this window when appearing + ImVec2 center = ImGui::GetMainViewport()->GetCenter(); + ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + ImGui::SetNextWindowSize(ImVec2(200, 40)); + + if (ImGui::BeginPopupModal("Loading", NULL, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoResize)) + { + ImGui::Text("Loading%s", + frameCount % 4 == 0 ? "" + : frameCount % 4 == 1 ? "." + : frameCount % 4 == 2 ? ".." + : "..."); + ImGui::EndPopup(); + } } From 82710176b91311958cf5c3544b98444889c1d700 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Thu, 29 Jan 2026 12:38:06 +0800 Subject: [PATCH 12/53] fix compile --- src/Editor/EditorInterface.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Editor/EditorInterface.cpp b/src/Editor/EditorInterface.cpp index 8c42a176..fbe448b7 100644 --- a/src/Editor/EditorInterface.cpp +++ b/src/Editor/EditorInterface.cpp @@ -26,6 +26,7 @@ #include "Utilities/FileHelper.hpp" #include "Utilities/Localization.hpp" #include "Utilities/Math.hpp" +#include "Vulkan/SwapChain.hpp" extern std::unique_ptr GApplication; From da2f81eda6d18f48a36e9b21ff7e2380d96fb299 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Thu, 29 Jan 2026 12:57:28 +0800 Subject: [PATCH 13/53] fix build.sh on macos --- build.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/build.sh b/build.sh index c0c23bb2..ce310c26 100755 --- a/build.sh +++ b/build.sh @@ -142,12 +142,14 @@ if [ "$RECONFIGURE" -eq 1 ] || [ "$CLEAN" -eq 1 ] || [ ! -f "$CACHE_FILE" ]; the fi # Check if any -D arguments are passed (requires reconfigure) -for arg in "${CMAKE_ARGS[@]}"; do +if [ ${#CMAKE_ARGS[@]} -gt 0 ]; then + for arg in "${CMAKE_ARGS[@]}"; do if [[ "$arg" == -D* ]]; then NEEDS_CONFIGURE=1 break fi -done + done +fi config_time=0 if [ "$NEEDS_CONFIGURE" -eq 1 ]; then From e2f82845f050e957edcd7e27d906b666fa2d56ce Mon Sep 17 00:00:00 2001 From: gameKnife Date: Thu, 29 Jan 2026 19:31:04 +0800 Subject: [PATCH 14/53] magicalego md --- AGENT_GUIDE/MagicaLego.md | 115 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 AGENT_GUIDE/MagicaLego.md diff --git a/AGENT_GUIDE/MagicaLego.md b/AGENT_GUIDE/MagicaLego.md new file mode 100644 index 00000000..4558e2aa --- /dev/null +++ b/AGENT_GUIDE/MagicaLego.md @@ -0,0 +1,115 @@ +# MagicaLego 小游戏代码梳理 + +本文档梳理 `src/Application/MagicaLego` 目录下乐高搭建小游戏的结构与关键流程,便于后续扩展与维护。 + +## 功能概览 +- 玩法:基于网格的乐高方块搭建,支持放置/挖掘/选择、朝向旋转、基座尺寸切换。 +- 视觉:基于引擎渲染与 GPU Raycast,提供预览框、缩略图刷子与场景重建。 +- 辅助:存档读写、时间轴回放、截图与录屏、背景音乐。 + +## 目录与入口 +- `src/Application/MagicaLego/MagicaLegoGameInstance.hpp` + - `MagicaLegoGameInstance`:核心运行逻辑。 + - 枚举:`ELegoMode`、`ECamMode`、`EBasePlane`、`EOrientation`。 + - 数据结构:`FBasicBlock`、`FPlacedBlock`、`FMagicaLegoSave`。 +- `src/Application/MagicaLego/MagicaLegoGameInstance.cpp` + - 主要运行流程与核心行为实现。 + - `CreateGameInstance` 为游戏入口工厂。 +- `src/Application/MagicaLego/MagicaLegoUserInterface.hpp/.cpp` + - `MagicaLegoUserInterface`:ImGui UI 逻辑。 + +## 核心数据结构 +- `FBasicBlock` + - `brushId_`:刷子索引。 + - `modelId_`:引擎模型 ID。 + - `matType` / `color`:材质与颜色。 + - `name` / `type`:显示用名称与类型。 +- `FPlacedBlock` + - `location`:格子坐标(int16)。 + - `orientation`:朝向。 + - `modelId_`:刷子索引(< 0 表示删除)。 +- `FMagicaLegoSave` + - `Save` / `Load`:写入/读取 `.mls` 存档。 + - 保存内容为 `brushs` + `records`。 + +## 运行流程 +- `OnInit` + - 加载 BGM 列表并播放。 + - 请求加载场景 `assets/models/legobricks.glb`。 +- `OnSceneLoaded` + - 隐藏基础板节点,复制基座并生成 21x21 网格。 + - 扫描并注册基础方块类型(`AddBlockGroup`)。 + - 初始化预览块 `previewNode_`。 + - 初始化基座显示与清理记录。 +- `OnTick` + - 触发 CPU Raycast 与指示器更新。 + - 相机中心与指示框缓动。 + - 预览块朝向与位置更新。 +- `OnRenderUI` / `OnInitUI` + - 委托给 `MagicaLegoUserInterface`。 + +## 放置与交互逻辑 +- Raycast + - `CPURaycast` 通过 `ProjectScreenToWorld` 产生射线,再由引擎 `RayCastGPU` 返回结果。 + - 命中后走 `OnRayHitResponse`。 +- 放置/挖掘/选择 + - `ELM_Place`:根据 `currentBlockIdx_` 和 `currentOrientation_` 放置。 + - `ELM_Dig`:将目标位置写入 `modelId_ = -1`,等价删除。 + - `ELM_Select`:更新选中节点,并在 AutoFocus 下对焦。 +- 动态块管理 + - `BlocksDynamics`:位置 hash -> `FPlacedBlock`。 + - `BlockRecords`:操作记录序列(用于回放与存档)。 + - `RebuildScene`:清理动态实例并重建所有放置块。 + +## 相机与操作 +- 相机模式 + - `ECM_Orbit`:围绕中心旋转。 + - `ECM_Pan`:平移相机中心。 + - `ECM_AutoFocus`:选择/放置后自动对焦。 +- 鼠标/键盘 + - 键盘:`Q/W/E` 模式,`A/S/D` 相机,`1/2/3` 基座,`R` 旋转。 + - 鼠标:左键放置/选择,右键旋转视角,滚轮控制 FOV。 + +## UI 模块概览 +- 标题栏 `DrawTitleBar` + - 窗口控制、截图/录屏、BGM、质量切换、帮助入口。 +- 左侧栏 `DrawLeftBar` + - 模式、相机、基座、光照参数、存档管理。 +- 右侧栏 `DrawRightBar` + - 类型选择 + 方块缩略图刷子。 +- 时间轴 `DrawTimeline` + - 回放步数、播放/暂停、回滚到某一步。 +- 引导与提示 + - `DrawOpening` / `DrawHelp` / `DrawNotify`。 + +## 资源与文件路径 +- 场景模型:`assets/models/legobricks.glb` +- pak:`assets/paks/lego.pak`、`assets/paks/thumbs.pak` +- 缩略图:`assets/textures/thumb/thumb__.jpg` +- 存档:`assets/legos/*.mls` +- 截图:`screenshots/` +- 录屏:`captures/` + `temps/`,通过 ffmpeg 拼接。 + +## 常见扩展点 +- 新增方块类型 + - 在场景中添加对应命名的节点;`AddBlockGroup` 会自动收集。 + - 增加 `BasicNodeIndicatorMap` 尺寸配置。 + - 提供对应缩略图资源。 +- 存档版本升级 + - `MAGICALEGO_SAVE_VERSION` 与 `FMagicaLegoSave::Load`。 + - 需要兼容旧版 `brushId_` 映射逻辑。 +- 交互扩展 + - 在 `OnRayHitResponse` 中新增模式或额外处理。 + - 在 `MagicaLegoUserInterface` 添加工具/按钮入口。 + +## 已知注意点 +- `RebuildScene` 会清空动态实例并重建,放置频繁时可能有性能压力。 +- `BlocksDynamics` 以 hash 作为 key,需保证 `GetHashFromBlockLocation` 不冲突。 +- 缩略图在非 Apple 平台按文件名动态加载。 +- 录屏依赖系统 `ffmpeg`,缺失时会失败。 + +## 关联文件速查 +- `src/Application/MagicaLego/MagicaLegoGameInstance.hpp` +- `src/Application/MagicaLego/MagicaLegoGameInstance.cpp` +- `src/Application/MagicaLego/MagicaLegoUserInterface.hpp` +- `src/Application/MagicaLego/MagicaLegoUserInterface.cpp` From 132c0f6b40ed06be260177aaa4a0020f86bf3aa5 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Thu, 29 Jan 2026 21:21:56 +0800 Subject: [PATCH 15/53] drag with assets & editor refactor Co-authored-by: gpt-5-codex --- src/Assets/Scene.cpp | 371 +++++++----- src/Assets/Scene.hpp | 437 +++++++-------- src/Editor/EditorDragDrop.hpp | 23 + src/Editor/Panels/ContentBrowserPanel.cpp | 391 ++++++++----- src/Editor/Panels/ViewportOverlay.cpp | 102 ++++ src/Runtime/Engine.cpp | 652 +++++++++++----------- src/Runtime/Engine.hpp | 429 +++++++------- 7 files changed, 1353 insertions(+), 1052 deletions(-) create mode 100644 src/Editor/EditorDragDrop.hpp diff --git a/src/Assets/Scene.cpp b/src/Assets/Scene.cpp index 353eca7f..d95d4517 100644 --- a/src/Assets/Scene.cpp +++ b/src/Assets/Scene.cpp @@ -1,52 +1,66 @@ #include "Scene.hpp" +#include +#include +#include +#include +#include +#include "Assets/TextureImage.hpp" #include "Common/CoreMinimal.hpp" +#include "FSceneSaver.h" #include "Model.hpp" #include "Options.hpp" -#include "Vulkan/BufferUtil.hpp" -#include "Assets/TextureImage.hpp" -#include "FSceneSaver.h" -#include -#include -#include -#include -#include #include "Runtime/NextPhysics.h" +#include "Scene.hpp" +#include "Vulkan/BufferUtil.hpp" #include "Node.h" -#include "Runtime/Components/RenderComponent.h" #include "Runtime/Components/PhysicsComponent.h" +#include "Runtime/Components/RenderComponent.h" +#include "Runtime/Components/SkinnedMeshComponent.h" #include "Runtime/Engine.hpp" #include "Runtime/NextEngineHelper.h" -#include "Runtime/Components/SkinnedMeshComponent.h" -#include #include +#include namespace Assets { - Scene::Scene(Vulkan::CommandPool& commandPool, - bool supportRayTracing) + Scene::Scene(Vulkan::CommandPool& commandPool, bool supportRayTracing) { - int flags = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + int flags = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; - Vulkan::BufferUtil::CreateDeviceBufferLocal(commandPool, "VoxelDatas", flags, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,Assets::CUBE_SIZE_XY * Assets::CUBE_SIZE_XY * Assets::CUBE_SIZE_Z * sizeof(Assets::VoxelData), farAmbientCubeBuffer_, - farAmbientCubeBufferMemory_); - Vulkan::BufferUtil::CreateDeviceBufferLocal(commandPool, "PageIndex", flags,VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, ACGI_PAGE_COUNT * ACGI_PAGE_COUNT * sizeof(Assets::PageIndex), pageIndexBuffer_, - pageIndexBufferMemory_); + Vulkan::BufferUtil::CreateDeviceBufferLocal( + commandPool, "VoxelDatas", flags, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + Assets::CUBE_SIZE_XY * Assets::CUBE_SIZE_XY * Assets::CUBE_SIZE_Z * sizeof(Assets::VoxelData), + farAmbientCubeBuffer_, farAmbientCubeBufferMemory_); + Vulkan::BufferUtil::CreateDeviceBufferLocal( + commandPool, "PageIndex", flags, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + ACGI_PAGE_COUNT * ACGI_PAGE_COUNT * sizeof(Assets::PageIndex), pageIndexBuffer_, pageIndexBufferMemory_); - Vulkan::BufferUtil::CreateDeviceBufferLocal( commandPool, "GPUDrivenStats", flags, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, sizeof(Assets::GPUDrivenStat), gpuDrivenStatsBuffer_, gpuDrivenStatsBuffer_Memory_ ); + Vulkan::BufferUtil::CreateDeviceBufferLocal( + commandPool, "GPUDrivenStats", flags, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, sizeof(Assets::GPUDrivenStat), + gpuDrivenStatsBuffer_, gpuDrivenStatsBuffer_Memory_); + + Vulkan::BufferUtil::CreateDeviceBufferLocal( + commandPool, "HDRSH", flags, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + sizeof(SphericalHarmonics) * 100, hdrSHBuffer_, hdrSHBufferMemory_); - Vulkan::BufferUtil::CreateDeviceBufferLocal( commandPool, "HDRSH", flags, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, sizeof(SphericalHarmonics) * 100, hdrSHBuffer_, hdrSHBufferMemory_ ); - // gpu local buffers - Vulkan::BufferUtil::CreateDeviceBufferLocal(commandPool, "IndirectDraws", flags | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, sizeof(VkDrawIndexedIndirectCommand) * 65535, indirectDrawBuffer_, - indirectDrawBufferMemory_); // support 65535 nodes - Vulkan::BufferUtil::CreateDeviceBufferLocal(commandPool, "AmbientCubes", flags, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,Assets::CUBE_SIZE_XY * Assets::CUBE_SIZE_XY * Assets::CUBE_SIZE_Z * sizeof(Assets::AmbientCube), ambientCubeBuffer_, - ambientCubeBufferMemory_); + Vulkan::BufferUtil::CreateDeviceBufferLocal( + commandPool, "IndirectDraws", flags | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, sizeof(VkDrawIndexedIndirectCommand) * 65535, indirectDrawBuffer_, + indirectDrawBufferMemory_); // support 65535 nodes + Vulkan::BufferUtil::CreateDeviceBufferLocal( + commandPool, "AmbientCubes", flags, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + Assets::CUBE_SIZE_XY * Assets::CUBE_SIZE_XY * Assets::CUBE_SIZE_Z * sizeof(Assets::AmbientCube), + ambientCubeBuffer_, ambientCubeBufferMemory_); // shadow maps - cpuShadowMap_.reset(new TextureImage(commandPool, SHADOWMAP_SIZE, SHADOWMAP_SIZE, 1, VK_FORMAT_R32_SFLOAT, nullptr, 0)); - cpuShadowMap_->Image().TransitionImageLayout( commandPool, VK_IMAGE_LAYOUT_GENERAL); + cpuShadowMap_.reset( + new TextureImage(commandPool, SHADOWMAP_SIZE, SHADOWMAP_SIZE, 1, VK_FORMAT_R32_SFLOAT, nullptr, 0)); + cpuShadowMap_->Image().TransitionImageLayout(commandPool, VK_IMAGE_LAYOUT_GENERAL); cpuShadowMap_->SetDebugName("Shadowmap"); RebuildMeshBuffer(commandPool, supportRayTracing); @@ -110,7 +124,8 @@ namespace Assets } } - void Scene::Reload(std::vector>& nodes, std::vector& models, std::vector& materials, std::vector& lights, + void Scene::Reload(std::vector>& nodes, std::vector& models, + std::vector& materials, std::vector& lights, std::vector& tracks) { nodes_ = std::move(nodes); @@ -120,13 +135,14 @@ namespace Assets tracks_ = std::move(tracks); } - void Scene::Append(const std::string& sceneName, std::vector>& nodes, std::vector& models, - std::vector& materials, std::vector& lights, std::vector& tracks, - const std::vector& skeletons) + std::shared_ptr Scene::Append(const std::string& sceneName, std::vector>& nodes, + std::vector& models, std::vector& materials, + std::vector& lights, std::vector& tracks, + const std::vector& skeletons) { uint32_t modelOffset = static_cast(models_.size()); uint32_t materialOffset = static_cast(materials_.size()); - + // Ensure unique root node name std::string uniqueName = sceneName; int counter = 1; @@ -137,13 +153,13 @@ namespace Assets // Create a root node for the appended scene uint32_t currentMaxId = GenerateInstanceId(); - auto rootNode = Node::CreateNode(uniqueName, glm::vec3(0), glm::quat(1,0,0,0), glm::vec3(1), currentMaxId++); + auto rootNode = Node::CreateNode(uniqueName, glm::vec3(0), glm::quat(1, 0, 0, 0), glm::vec3(1), currentMaxId++); // Update IDs for all new nodes (assuming nodes is a flat list of all new nodes) for (auto& node : nodes) { node->SetInstanceId(currentMaxId++); - + // Update RenderComponent auto render = node->GetComponent(); if (render) @@ -155,9 +171,9 @@ namespace Assets auto& mats = render->Materials(); for (auto& matId : mats) { - matId += materialOffset; + matId += materialOffset; } - + // Handle Skeletons if (render->GetSkinIndex() != -1 && render->GetSkinIndex() < skeletons.size()) { @@ -167,7 +183,7 @@ namespace Assets node->AddComponent(comp); } } - + // Reparent roots (nodes that don't have a parent in the new scene hierarchy) if (node->GetParent() == nullptr) { @@ -186,15 +202,17 @@ namespace Assets materials_.insert(materials_.end(), materials.begin(), materials.end()); lights_.insert(lights_.end(), lights.begin(), lights.end()); tracks_.insert(tracks_.end(), tracks.begin(), tracks.end()); - + // Add new nodes to scene nodes nodes_.insert(nodes_.end(), nodes.begin(), nodes.end()); - + // Add root node to scene nodes_.push_back(rootNode); // Mark dirty MarkDirty(); + + return rootNode; } void Scene::RebuildMeshBuffer(Vulkan::CommandPool& commandPool, bool supportRayTracing) @@ -205,7 +223,8 @@ namespace Assets // force static flag std::function SetKinematicRecursive = [&](Node* node) { - if (node == nullptr) return; + if (node == nullptr) + return; if (auto phys = node->GetComponent()) { phys->SetMobility(Node::ENodeMobility::Kinematic); @@ -222,15 +241,15 @@ namespace Assets } }; - for ( auto& track : tracks_ ) + for (auto& track : tracks_) { Node* node = GetNode(track.NodeName_); SetKinematicRecursive(node); } - + // calculate the scene aabb - sceneAABBMin_ = { FLT_MAX, FLT_MAX, FLT_MAX }; - sceneAABBMax_ = { -FLT_MAX, -FLT_MAX, -FLT_MAX }; + sceneAABBMin_ = {FLT_MAX, FLT_MAX, FLT_MAX}; + sceneAABBMax_ = {-FLT_MAX, -FLT_MAX, -FLT_MAX}; for (auto& node : nodes_) { auto render = node->GetComponent(); @@ -265,20 +284,21 @@ namespace Assets sceneAABBMax_ = glm::max(sceneAABBMax_, worldAABBMax); } } - + // static mesh to jolt mesh shape - if ( NextPhysics* physicsEngine = NextEngine::GetInstance()->GetPhysicsEngine() ) + if (NextPhysics* physicsEngine = NextEngine::GetInstance()->GetPhysicsEngine()) { cachedMeshShapes_.clear(); for (auto& model : models_) { if (model.NumberOfIndices() < 65535 * 3 && model.NumberOfIndices() > 0) { - cachedMeshShapes_.push_back( NextRefConst(physicsEngine->CreateMeshShape(model)) ); + cachedMeshShapes_.push_back( + NextRefConst(physicsEngine->CreateMeshShape(model))); } else { - cachedMeshShapes_.push_back( NextRefConst(nullptr) ); + cachedMeshShapes_.push_back(NextRefConst(nullptr)); } } @@ -286,28 +306,36 @@ namespace Assets { auto render = node->GetComponent(); // bind the mesh shape to the node - if (render && render->GetModelId() < cachedMeshShapes_.size() && cachedMeshShapes_[render->GetModelId()]) + if (render && render->GetModelId() < cachedMeshShapes_.size() && + cachedMeshShapes_[render->GetModelId()]) { auto phys = node->GetComponent(); Node::ENodeMobility mobility = phys ? phys->GetMobility() : Node::ENodeMobility::Static; - + if (mobility != Node::ENodeMobility::Dynamic) { - NextMotionType motionType = mobility == Node::ENodeMobility::Static ? NextMotionType::Static : NextMotionType::Kinematic; - NextObjectLayer layer = mobility == Node::ENodeMobility::Static ? NextLayers::NON_MOVING : NextLayers::MOVING; + NextMotionType motionType = mobility == Node::ENodeMobility::Static ? NextMotionType::Static + : NextMotionType::Kinematic; + NextObjectLayer layer = + mobility == Node::ENodeMobility::Static ? NextLayers::NON_MOVING : NextLayers::MOVING; bool validShape = false; #if WITH_PHYSIC - if (cachedMeshShapes_[render->GetModelId()].GetPtr() && cachedMeshShapes_[render->GetModelId()]->mIndexedTriangles.size() > 0) validShape = true; + if (cachedMeshShapes_[render->GetModelId()].GetPtr() && + cachedMeshShapes_[render->GetModelId()]->mIndexedTriangles.size() > 0) + validShape = true; #endif - if ( validShape ) + if (validShape) { glm::vec3 worldScale = node->WorldScale(); - if (glm::length(worldScale) > 0.01f && glm::abs(worldScale.x) > 0.001 && glm::abs(worldScale.y) > 0.001 && glm::abs(worldScale.z) > 0.001) + if (glm::length(worldScale) > 0.01f && glm::abs(worldScale.x) > 0.001 && + glm::abs(worldScale.y) > 0.001 && glm::abs(worldScale.z) > 0.001) { - NextBodyID id = physicsEngine->CreateMeshBody(cachedMeshShapes_[render->GetModelId()], node->WorldTranslation(), node->WorldRotation(), node->WorldScale(), motionType, layer); - + NextBodyID id = physicsEngine->CreateMeshBody( + cachedMeshShapes_[render->GetModelId()], node->WorldTranslation(), + node->WorldRotation(), node->WorldScale(), motionType, layer); + if (!phys) { phys = std::make_shared(); @@ -322,11 +350,11 @@ namespace Assets } } } - + // create 6 plane bodys, it makes negtive space, so keep the bottom plane only // physicsEngine->CreatePlaneBody(sceneAABBMin_, glm::vec3(1,0,0), NextMotionType::Static); // physicsEngine->CreatePlaneBody(sceneAABBMax_, glm::vec3(-1,0,0), NextMotionType::Static); - physicsEngine->CreatePlaneBody(sceneAABBMin_, glm::vec3(0,1,0), NextMotionType::Static); + physicsEngine->CreatePlaneBody(sceneAABBMin_, glm::vec3(0, 1, 0), NextMotionType::Static); // physicsEngine->CreatePlaneBody(sceneAABBMax_, glm::vec3(0,-1,0), NextMotionType::Static); // physicsEngine->CreatePlaneBody(sceneAABBMin_, glm::vec3(0,0,1), NextMotionType::Static); // physicsEngine->CreatePlaneBody(sceneAABBMax_, glm::vec3(0,0,-1), NextMotionType::Static); @@ -348,7 +376,7 @@ namespace Assets const auto indexOffset = static_cast(indices.size()); const auto vertexOffset = static_cast(vertices.size()); const auto reorderOffset = static_cast(reorders.size()); - + // cpu vertex to gpu vertex for (auto& vertex : model.CPUVertices()) { @@ -358,7 +386,7 @@ namespace Assets simpleVertices.push_back(glm::detail::toFloat16(vertex.Position.z)); simpleVertices.push_back(glm::detail::toFloat16(vertex.Position.x)); } - + const auto& weights = model.CPUWeights(); const auto& joints = model.CPUJoints(); if (!weights.empty() && weights.size() == model.CPUVertices().size()) @@ -371,51 +399,55 @@ namespace Assets allWeights.resize(allWeights.size() + model.CPUVertices().size(), glm::vec4(0)); allJoints.resize(allJoints.size() + model.CPUVertices().size(), glm::uvec4(0)); } - + const std::vector& localVertices = model.CPUVertices(); const std::vector& localIndices = model.CPUIndices(); - + std::vector> slicedIndices; constexpr uint32_t maxIndicesPerSlice = 65535 * 3; - + // 将localIndices分片,每片最多65535*3个索引 - for (size_t i = 0; i < localIndices.size(); i += maxIndicesPerSlice) { + for (size_t i = 0; i < localIndices.size(); i += maxIndicesPerSlice) + { size_t endIndex = std::min(i + maxIndicesPerSlice, localIndices.size()); slicedIndices.emplace_back(localIndices.begin() + i, localIndices.begin() + endIndex); } int emptySection = 10 - int(slicedIndices.size()); - int processSection = std::min( int(slicedIndices.size()), 10); + int processSection = std::min(int(slicedIndices.size()), 10); - for ( int slice = 0; slice < processSection; ++slice ) + for (int slice = 0; slice < processSection; ++slice) { const auto localIndexOffset = static_cast(indices.size()); const auto localReorderOffset = static_cast(reorders.size()); - + const auto& localIndiceCount = slicedIndices[slice]; uint32_t realSize = uint32_t(localIndiceCount.size()); - offsets_.push_back({localIndexOffset, realSize, vertexOffset, model.NumberOfVertices(), vec4(model.GetLocalAABBMin(), 1), vec4(model.GetLocalAABBMax(), 1), 0, 0, localReorderOffset, 0}); + offsets_.push_back({localIndexOffset, realSize, vertexOffset, model.NumberOfVertices(), + vec4(model.GetLocalAABBMin(), 1), vec4(model.GetLocalAABBMax(), 1), 0, 0, + localReorderOffset, 0}); std::vector provoke(localIndiceCount.size()); std::vector reorder(localVertices.size() + localIndiceCount.size() / 3); std::vector primIndices(localIndiceCount.size()); - + if (localIndiceCount.size() > 0) { - reorder.resize(meshopt_generateProvokingIndexBuffer(&provoke[0], &reorder[0], &localIndiceCount[0], realSize, localVertices.size())); + reorder.resize(meshopt_generateProvokingIndexBuffer(&provoke[0], &reorder[0], &localIndiceCount[0], + realSize, localVertices.size())); } - - for ( size_t i = 0; i < provoke.size(); ++i ) + + for (size_t i = 0; i < provoke.size(); ++i) { primIndices[i] += reorder[provoke[i]]; } - + // reorder is absolute vertex index - for ( size_t i = 0; i < reorder.size(); ++i ) + for (size_t i = 0; i < reorder.size(); ++i) { reorder[i] += vertexOffset; } - + indices.insert(indices.end(), provoke.begin(), provoke.end()); reorders.insert(reorders.end(), reorder.begin(), reorder.end()); primitiveIndices.insert(primitiveIndices.end(), primIndices.begin(), primIndices.end()); @@ -425,9 +457,11 @@ namespace Assets { SPDLOG_WARN("more than 10 sections in model"); } - for ( int slice = 0; slice < emptySection; ++slice ) + for (int slice = 0; slice < emptySection; ++slice) { - offsets_.push_back({indexOffset, 0, vertexOffset, model.NumberOfVertices(), vec4(model.GetLocalAABBMin(), 1), vec4(model.GetLocalAABBMax(), 1), 0, 0, reorderOffset, 0}); + offsets_.push_back({indexOffset, 0, vertexOffset, model.NumberOfVertices(), + vec4(model.GetLocalAABBMin(), 1), vec4(model.GetLocalAABBMax(), 1), 0, 0, + reorderOffset, 0}); } model.SetSectionCount(processSection); @@ -438,24 +472,39 @@ namespace Assets model.FreeMemory(); } } - + int flags = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; int rtxFlags = supportRayTracing ? VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR : 0; // this two buffer may change violate, reverse to MAX_NODES and MAX_MATERIALS - Vulkan::BufferUtil::CreateDeviceBufferLocal(commandPool, "Nodes", flags, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, sizeof(NodeProxy) * Assets::MAX_NODES, nodeMatrixBuffer_, nodeMatrixBufferMemory_); - Vulkan::BufferUtil::CreateDeviceBufferLocal(commandPool, "Materials", flags, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,sizeof(Material) * Assets::MAX_MATERIALS, materialBuffer_, materialBufferMemory_); + Vulkan::BufferUtil::CreateDeviceBufferLocal( + commandPool, "Nodes", flags, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + sizeof(NodeProxy) * Assets::MAX_NODES, nodeMatrixBuffer_, nodeMatrixBufferMemory_); + Vulkan::BufferUtil::CreateDeviceBufferLocal( + commandPool, "Materials", flags, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + sizeof(Material) * Assets::MAX_MATERIALS, materialBuffer_, materialBufferMemory_); // this buffer now, no support extended - Vulkan::BufferUtil::CreateDeviceBuffer(commandPool, "Vertices", VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | rtxFlags | flags, vertices, vertexBuffer_, vertexBufferMemory_); - Vulkan::BufferUtil::CreateDeviceBuffer(commandPool, "SimpleVertices", VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | rtxFlags | flags, simpleVertices, simpleVertexBuffer_, simpleVertexBufferMemory_); - Vulkan::BufferUtil::CreateDeviceBuffer(commandPool, "Indices", VK_BUFFER_USAGE_INDEX_BUFFER_BIT | flags, indices, indexBuffer_, indexBufferMemory_); - Vulkan::BufferUtil::CreateDeviceBuffer(commandPool, "Reorder", flags, reorders, reorderBuffer_, reorderBufferMemory_); - Vulkan::BufferUtil::CreateDeviceBuffer(commandPool, "PrimAddress", VK_BUFFER_USAGE_INDEX_BUFFER_BIT | rtxFlags | flags, primitiveIndices, primAddressBuffer_, primAddressBufferMemory_); - Vulkan::BufferUtil::CreateDeviceBuffer(commandPool, "Offsets", flags, offsets_, offsetBuffer_, offsetBufferMemory_); + Vulkan::BufferUtil::CreateDeviceBuffer(commandPool, "Vertices", + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | rtxFlags | flags, vertices, + vertexBuffer_, vertexBufferMemory_); + Vulkan::BufferUtil::CreateDeviceBuffer(commandPool, "SimpleVertices", + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | rtxFlags | flags, simpleVertices, + simpleVertexBuffer_, simpleVertexBufferMemory_); + Vulkan::BufferUtil::CreateDeviceBuffer(commandPool, "Indices", VK_BUFFER_USAGE_INDEX_BUFFER_BIT | flags, + indices, indexBuffer_, indexBufferMemory_); + Vulkan::BufferUtil::CreateDeviceBuffer(commandPool, "Reorder", flags, reorders, reorderBuffer_, + reorderBufferMemory_); + Vulkan::BufferUtil::CreateDeviceBuffer(commandPool, "PrimAddress", + VK_BUFFER_USAGE_INDEX_BUFFER_BIT | rtxFlags | flags, primitiveIndices, + primAddressBuffer_, primAddressBufferMemory_); + Vulkan::BufferUtil::CreateDeviceBuffer(commandPool, "Offsets", flags, offsets_, offsetBuffer_, + offsetBufferMemory_); Vulkan::BufferUtil::CreateDeviceBuffer(commandPool, "Lights", flags, lights_, lightBuffer_, lightBufferMemory_); - Vulkan::BufferUtil::CreateDeviceBuffer(commandPool, "SkinWeights", flags, allWeights, skinWeightBuffer_, skinWeightBufferMemory_); - Vulkan::BufferUtil::CreateDeviceBuffer(commandPool, "SkinJoints", flags, allJoints, skinJointBuffer_, skinJointBufferMemory_); + Vulkan::BufferUtil::CreateDeviceBuffer(commandPool, "SkinWeights", flags, allWeights, skinWeightBuffer_, + skinWeightBufferMemory_); + Vulkan::BufferUtil::CreateDeviceBuffer(commandPool, "SkinJoints", flags, allJoints, skinJointBuffer_, + skinJointBufferMemory_); // 一些数据 lightCount_ = static_cast(lights_.size()); @@ -469,10 +518,7 @@ namespace Assets cpuAccelerationStructure_.AsyncProcessFull(*this, farAmbientCubeBufferMemory_.get(), false); } - void Scene::CleanUp() - { - cpuAccelerationStructure_.ClearAllTasks(); - } + void Scene::CleanUp() { cpuAccelerationStructure_.ClearAllTasks(); } void Scene::AddNode(std::shared_ptr node) { @@ -488,17 +534,23 @@ namespace Assets if (mobility != Node::ENodeMobility::Dynamic) { - NextMotionType motionType = mobility == Node::ENodeMobility::Static ? NextMotionType::Static : NextMotionType::Kinematic; - NextObjectLayer layer = mobility == Node::ENodeMobility::Static ? NextLayers::NON_MOVING : NextLayers::MOVING; + NextMotionType motionType = + mobility == Node::ENodeMobility::Static ? NextMotionType::Static : NextMotionType::Kinematic; + NextObjectLayer layer = + mobility == Node::ENodeMobility::Static ? NextLayers::NON_MOVING : NextLayers::MOVING; bool validShape = false; #if WITH_PHYSIC - if (cachedMeshShapes_[render->GetModelId()].GetPtr() && cachedMeshShapes_[render->GetModelId()]->mIndexedTriangles.size() > 0) validShape = true; + if (cachedMeshShapes_[render->GetModelId()].GetPtr() && + cachedMeshShapes_[render->GetModelId()]->mIndexedTriangles.size() > 0) + validShape = true; #endif if (validShape) { - NextBodyID id = physicsEngine->CreateMeshBody(cachedMeshShapes_[render->GetModelId()], node->WorldTranslation(), node->WorldRotation(), node->WorldScale(), motionType, layer); + NextBodyID id = physicsEngine->CreateMeshBody(cachedMeshShapes_[render->GetModelId()], + node->WorldTranslation(), node->WorldRotation(), + node->WorldScale(), motionType, layer); if (!phys) { @@ -566,7 +618,7 @@ namespace Assets CollectNodeHierarchy(child, outNodes); } } - } + } // namespace std::vector Scene::RemoveNodeHierarchy(uint32_t id, std::shared_ptr& outParent) { @@ -606,14 +658,14 @@ namespace Assets } nodes_.erase(std::remove_if(nodes_.begin(), nodes_.end(), [&removeIds](const std::shared_ptr& node) - { - return removeIds.find(node->GetInstanceId()) != removeIds.end(); - }), nodes_.end()); + { return removeIds.find(node->GetInstanceId()) != removeIds.end(); }), + nodes_.end()); return removedEntries; } - void Scene::RestoreNodes(const std::vector& entries, const std::shared_ptr& parent, const std::shared_ptr& root) + void Scene::RestoreNodes(const std::vector& entries, const std::shared_ptr& parent, + const std::shared_ptr& root) { for (auto it = entries.rbegin(); it != entries.rend(); ++it) { @@ -630,7 +682,8 @@ namespace Assets const Assets::GPUScene& Scene::FetchGPUScene(const uint32_t imageIndex) const { // all gpu device address - gpuScene_.Camera = NextEngine::GetInstance()->GetRenderer().UniformBuffers()[imageIndex].Buffer().GetDeviceAddress(); + gpuScene_.Camera = + NextEngine::GetInstance()->GetRenderer().UniformBuffers()[imageIndex].Buffer().GetDeviceAddress(); gpuScene_.Nodes = nodeMatrixBuffer_->GetDeviceAddress(); gpuScene_.Materials = materialBuffer_->GetDeviceAddress(); gpuScene_.Offsets = offsetBuffer_->GetDeviceAddress(); @@ -668,32 +721,33 @@ namespace Assets void Scene::MarkEnvDirty() { - //cpuAccelerationStructure_.AsyncProcessFull(*this, farAmbientCubeBufferMemory_.get(), true); - //cpuAccelerationStructure_.GenShadowMap(*this); + // cpuAccelerationStructure_.AsyncProcessFull(*this, farAmbientCubeBufferMemory_.get(), true); + // cpuAccelerationStructure_.GenShadowMap(*this); } void Scene::Tick(float deltaSeconds) { - if ( NextEngine::GetInstance()->GetUserSettings().TickAnimation) + if (NextEngine::GetInstance()->GetUserSettings().TickAnimation) { for (auto& node : nodes_) { - if (auto skinnedMesh = node->GetComponent()) - { - skinnedMesh->Update(deltaSeconds); - if (NextEngine::GetInstance()->GetShowFlags().ShowDebugSkeleton) - { - skinnedMesh->DrawDebugSkeleton(node->WorldTransform()); - } - + if (auto skinnedMesh = node->GetComponent()) + { + skinnedMesh->Update(deltaSeconds); + if (NextEngine::GetInstance()->GetShowFlags().ShowDebugSkeleton) + { + skinnedMesh->DrawDebugSkeleton(node->WorldTransform()); + } + if (skinnedMesh->IsPlaying()) { MarkDirty(); - if ( auto renderComponent = node->GetComponent()) + if (auto renderComponent = node->GetComponent()) { - if ( renderComponent->GetModelId() != -1 ) + if (renderComponent->GetModelId() != -1) { - NextEngine::GetInstance()->GetRenderer().RequestSkinUpdate(renderComponent->GetModelId()); + NextEngine::GetInstance()->GetRenderer().RequestSkinUpdate( + renderComponent->GetModelId()); } } } @@ -704,19 +758,21 @@ namespace Assets for (auto& track : tracks_) { - if (!track.Playing()) continue; + if (!track.Playing()) + continue; durationMax = glm::max(durationMax, track.Duration_); } for (auto& track : tracks_) { - if (!track.Playing()) continue; + if (!track.Playing()) + continue; track.Time_ += deltaSeconds * track.PlaySpeed_; if (track.Time_ > durationMax) { track.PlaySpeed_ = -1.0f; } - if ( track.Time_ < 0.0f ) + if (track.Time_ < 0.0f) { track.PlaySpeed_ = 1.0f; } @@ -739,14 +795,16 @@ namespace Assets // to physicSys std::function UpdatePhysicsBodyRecursive = [&](Node* n) { - if (!n) return; + if (!n) + return; auto phys = n->GetComponent(); if (phys) { NextBodyID bodyID = phys->GetPhysicsBody(); if (!bodyID.IsInvalid()) { - NextEngine::GetInstance()->GetPhysicsEngine()->MoveKinematicBody(bodyID, n->WorldTranslation(), n->WorldRotation(), 0.01f); + NextEngine::GetInstance()->GetPhysicsEngine()->MoveKinematicBody( + bodyID, n->WorldTranslation(), n->WorldRotation(), 0.01f); } } @@ -761,14 +819,15 @@ namespace Assets if (node->GetName() == "Shot.BlueCar") { requestOverrideModelView = true; - overrideModelView = glm::lookAtRH(translation, translation + rotation * glm::vec3(0, 0, -1), glm::vec3(0.0f, 1.0f, 0.0f)); + overrideModelView = glm::lookAtRH(translation, translation + rotation * glm::vec3(0, 0, -1), + glm::vec3(0.0f, 1.0f, 0.0f)); } } } } - if (NextEngine::GetInstance()->GetShowFlags().DebugDraw_BoundingBox) - { + if (NextEngine::GetInstance()->GetShowFlags().DebugDraw_BoundingBox) + { for (auto& node : nodes_) { auto render = node->GetComponent(); @@ -809,7 +868,7 @@ namespace Assets } } - if ( NextEngine::GetInstance()->GetTotalFrames() % 10 == 0 ) + if (NextEngine::GetInstance()->GetTotalFrames() % 10 == 0) { // if (sceneDirtyForCpuAS_) // { @@ -818,14 +877,16 @@ namespace Assets // sceneDirtyForCpuAS_ = false; // } // } - - cpuAccelerationStructure_.Tick(*this, ambientCubeBufferMemory_.get(), farAmbientCubeBufferMemory_.get(), pageIndexBufferMemory_.get() ); + + cpuAccelerationStructure_.Tick(*this, ambientCubeBufferMemory_.get(), farAmbientCubeBufferMemory_.get(), + pageIndexBufferMemory_.get()); } } void Scene::UpdateAllMaterials() { - if (materials_.empty()) return; + if (materials_.empty()) + return; gpuMaterials_.clear(); for (auto& material : materials_) @@ -833,16 +894,17 @@ namespace Assets gpuMaterials_.push_back(material.gpuMaterial_); } - Material* data = reinterpret_cast(materialBufferMemory_->Map(0, sizeof(Material) * gpuMaterials_.size())); + Material* data = + reinterpret_cast(materialBufferMemory_->Map(0, sizeof(Material) * gpuMaterials_.size())); std::memcpy(data, gpuMaterials_.data(), gpuMaterials_.size() * sizeof(Material)); materialBufferMemory_->Unmap(); NextEngine::GetInstance()->SetProgressiveRendering(false, false); } - + bool Scene::UpdateNodes() { - GPUDrivenStat zero {}; + GPUDrivenStat zero{}; // read back gpu driven stats const auto data = gpuDrivenStatsBuffer_Memory_->Map(0, sizeof(Assets::GPUDrivenStat)); // download @@ -858,7 +920,7 @@ namespace Assets materialDirty_ = false; UpdateAllMaterials(); } - + return UpdateNodesGpuDriven(); } @@ -867,7 +929,8 @@ namespace Assets auto& shData = GlobalTexturePool::GetInstance()->GetHDRSphericalHarmonics(); if (shData.size() > 0) { - SphericalHarmonics* data = reinterpret_cast(hdrSHBufferMemory_->Map(0, sizeof(SphericalHarmonics) * shData.size())); + SphericalHarmonics* data = reinterpret_cast( + hdrSHBufferMemory_->Map(0, sizeof(SphericalHarmonics) * shData.size())); std::memcpy(data, shData.data(), shData.size() * sizeof(SphericalHarmonics)); hdrSHBufferMemory_->Unmap(); } @@ -878,14 +941,15 @@ namespace Assets if (nodes_.size() > 0) { // do always, no flicker now - //if (sceneDirty_) + // if (sceneDirty_) { sceneDirty_ = false; { - PERFORMANCEAPI_INSTRUMENT_COLOR("Scene::PrepareSceneNodes", PERFORMANCEAPI_MAKE_COLOR(255, 200, 200)); + PERFORMANCEAPI_INSTRUMENT_COLOR("Scene::PrepareSceneNodes", + PERFORMANCEAPI_MAKE_COLOR(255, 200, 200)); nodeProxys.clear(); indirectDrawBatchCount_ = 0; - + uint32_t currentJointOffset = 0; for (auto& node : nodes_) { @@ -897,7 +961,7 @@ namespace Assets if (node->TickVelocity(combined)) { sceneDirty_ = true; - //MarkDirty(); + // MarkDirty(); } auto model = GetModel(render->GetModelId()); @@ -910,7 +974,7 @@ namespace Assets currentJointOffset += (uint32_t)skinnedMesh->GetJointMatrices().size(); } - for ( uint32_t section = 0; section < model->SectionCount(); ++section) + for (uint32_t section = 0; section < model->SectionCount(); ++section) { NodeProxy proxy = node->GetNodeProxy(); proxy.combinedPrevTS = combined; @@ -919,12 +983,13 @@ namespace Assets proxy.jointMatrixOffset = nodeJointOffset; nodeProxys.push_back(proxy); indirectDrawBatchCount_++; - } + } } } } - - NodeProxy* data = reinterpret_cast(nodeMatrixBufferMemory_->Map(0, sizeof(NodeProxy) * nodeProxys.size())); + + NodeProxy* data = reinterpret_cast( + nodeMatrixBufferMemory_->Map(0, sizeof(NodeProxy) * nodeProxys.size())); std::memcpy(data, nodeProxys.data(), nodeProxys.size() * sizeof(NodeProxy)); nodeMatrixBufferMemory_->Unmap(); } @@ -975,10 +1040,11 @@ namespace Assets } } - if (!foundNode) return false; + if (!foundNode) + return false; center = glm::vec3(foundNode->WorldTransform()[3]); - + auto renderComp = foundNode->GetComponent(); if (renderComp) { @@ -993,7 +1059,7 @@ namespace Assets return true; } } - + // Fallback for non-render nodes (default small radius) radius = 1.0f; return true; @@ -1040,7 +1106,8 @@ namespace Assets } } - void Scene::SetSkinningBuffers(VkDeviceAddress skinnedVertices, VkDeviceAddress skinnedVerticesSimple, VkDeviceAddress jointMatrices) + void Scene::SetSkinningBuffers(VkDeviceAddress skinnedVertices, VkDeviceAddress skinnedVerticesSimple, + VkDeviceAddress jointMatrices) { skinnedVerticesAddr_ = skinnedVertices; skinnedVerticesSimpleAddr_ = skinnedVerticesSimple; @@ -1065,13 +1132,7 @@ namespace Assets } } - bool Scene::SaveAsGLB(const std::string& filename) const - { - return FSceneSaver::SaveGLBScene(filename, *this); - } + bool Scene::SaveAsGLB(const std::string& filename) const { return FSceneSaver::SaveGLBScene(filename, *this); } - bool Scene::SaveAsGLTF(const std::string& filename) const - { - return FSceneSaver::SaveGLTFScene(filename, *this); - } -} + bool Scene::SaveAsGLTF(const std::string& filename) const { return FSceneSaver::SaveGLTFScene(filename, *this); } +} // namespace Assets diff --git a/src/Assets/Scene.hpp b/src/Assets/Scene.hpp index 91c0b8ec..3d3eb949 100644 --- a/src/Assets/Scene.hpp +++ b/src/Assets/Scene.hpp @@ -1,258 +1,253 @@ #pragma once -#include "Common/CoreMinimal.hpp" -#include "Vulkan/Vulkan.hpp" +#include #include #include #include -#include +#include "Common/CoreMinimal.hpp" +#include "Vulkan/Vulkan.hpp" #include "CPUAccelerationStructure.h" #include "Model.hpp" -#include "Skeleton.hpp" #include "Runtime/NextPhysics.h" #include "Runtime/NextPhysicsTypes.h" +#include "Skeleton.hpp" namespace Vulkan { - class Buffer; - class CommandPool; - class DeviceMemory; - class Image; - class RenderImage; - class DescriptorSetManager; -} + class Buffer; + class CommandPool; + class DeviceMemory; + class Image; + class RenderImage; + class DescriptorSetManager; +} // namespace Vulkan namespace Assets { - class Node; - class Model; - class Texture; - class TextureImage; - struct Material; - struct LightObject; - - class Scene final - { - public: - - Scene(const Scene&) = delete; - Scene(Scene&&) = delete; - Scene& operator = (const Scene&) = delete; - Scene& operator = (Scene&&) = delete; - - Scene(Vulkan::CommandPool& commandPool, bool supportRayTracing); - ~Scene(); - - void PostLoad(const std::vector& skeletons); - - void Reload(std::vector>& nodes, - std::vector& models, - std::vector& materials, - std::vector& lights, - std::vector& tracks); - void Append(const std::string& sceneName, std::vector>& nodes, - std::vector& models, - std::vector& materials, - std::vector& lights, - std::vector& tracks, - const std::vector& skeletons); - void RebuildMeshBuffer(Vulkan::CommandPool& commandPool, - bool supportRayTracing); - void CleanUp(); - //void RebuildBVH(); - - const Assets::GPUScene& FetchGPUScene(const uint32_t imageIndex) const; - std::vector>& Nodes() { return nodes_; } - const std::vector>& Nodes() const { return nodes_; } - const std::vector& Models() const { return models_; } - std::vector& Materials() { return materials_; } - const std::vector& Materials() const { return materials_; } - const std::vector& Offsets() const { return offsets_; } - const std::vector& Lights() const { return lights_; } - const Vulkan::Buffer& VertexBuffer() const { return *vertexBuffer_; } - const Vulkan::Buffer& SimpleVertexBuffer() const { return *simpleVertexBuffer_; } - const Vulkan::Buffer& IndexBuffer() const { return *indexBuffer_; } - const Vulkan::Buffer& MaterialBuffer() const { return *materialBuffer_; } - const Vulkan::Buffer& OffsetsBuffer() const { return *offsetBuffer_; } - const Vulkan::Buffer& LightBuffer() const { return *lightBuffer_; } - const Vulkan::Buffer& NodeMatrixBuffer() const { return *nodeMatrixBuffer_; } - const Vulkan::Buffer& IndirectDrawBuffer() const { return *indirectDrawBuffer_; } - const Vulkan::Buffer& ReorderBuffer() const { return *reorderBuffer_; } - const Vulkan::Buffer& PrimAddressBuffer() const { return *primAddressBuffer_; } - const glm::vec3 GetSunDir() const { return envSettings_.SunDirection(); } - const bool HasSun() const { return envSettings_.HasSun; } - - const uint32_t GetLightCount() const {return lightCount_;} - const uint32_t GetIndicesCount() const {return indicesCount_;} - const uint32_t GetVerticeCount() const {return verticeCount_;} - const uint32_t GetIndirectDrawBatchCount() const {return indirectDrawBatchCount_;} - - const Assets::GPUDrivenStat& GetGpuDrivenStat() const { return gpuDrivenStat_; } - - uint32_t GetSelectedId() const { return selectedId_; } - void SetSelectedId( uint32_t id ) const { selectedId_ = id; } - bool GetSelectedNodeBounds(glm::vec3& center, float& radius) const; - - void Tick(float DeltaSeconds); - void UpdateAllMaterials(); - bool UpdateNodes(); - void UpdateHDRSH(); - bool UpdateNodesGpuDriven(); - - Node* GetNode(std::string name); - Node* GetNodeByInstanceId(uint32_t id); - const Model* GetModel(uint32_t id) const; - const FMaterial* GetMaterial(uint32_t id) const; - const uint32_t AddMaterial(const FMaterial& material); - - void MarkDirty(); - - std::vector& GetNodeProxys() { return nodeProxys; } - - void OverrideModelView(glm::mat4& OutMatrix); - - const std::vector& GetCameras() const { return envSettings_.cameras; } - const Assets::EnvironmentSetting& GetEnvironmentStrings() const { return envSettings_; } - - Assets::EnvironmentSetting& GetEnvSettings() { return envSettings_; } - void SetEnvSettings(const Assets::EnvironmentSetting& envSettings) { envSettings_ = envSettings; } - - Camera& GetRenderCamera() { return renderCamera_; } - void SetRenderCamera(const Camera& camera) { renderCamera_ = camera; } - - void PlayAllTracks(); - - void MarkEnvDirty(); - - void AddNode(std::shared_ptr node); - std::shared_ptr RemoveNodeByInstanceId(uint32_t id); - std::shared_ptr GetNodeSharedByInstanceId(uint32_t id) const; - uint32_t GenerateInstanceId() const; - - struct RemovedNodeEntry - { - std::shared_ptr node; - size_t index; - }; - std::vector RemoveNodeHierarchy(uint32_t id, std::shared_ptr& outParent); - void RestoreNodes(const std::vector& entries, const std::shared_ptr& parent, const std::shared_ptr& root); - - void SetSkinningBuffers(VkDeviceAddress skinnedVertices, VkDeviceAddress skinnedVerticesSimple, VkDeviceAddress jointMatrices); - - //Assets::RayCastResult RayCastInCPU(glm::vec3 rayOrigin, glm::vec3 rayDir); - - Vulkan::Buffer& AmbientCubeBuffer() const { return *ambientCubeBuffer_; } - Vulkan::Buffer& FarAmbientCubeBuffer() const { return *farAmbientCubeBuffer_; } - Vulkan::Buffer& PageIndexBuffer() const { return *pageIndexBuffer_; } - - Vulkan::Buffer& SkinWeightBuffer() const { return *skinWeightBuffer_; } - Vulkan::Buffer& SkinJointBuffer() const { return *skinJointBuffer_; } - - Vulkan::Buffer& HDRSHBuffer() const { return *hdrSHBuffer_; } - - TextureImage& ShadowMap() const { return *cpuShadowMap_; } - - FCPUAccelerationStructure& GetCPUAccelerationStructure() { return cpuAccelerationStructure_; } - - // Scene saving功能 - bool Save(const std::string& filename) const; - bool SaveAsGLB(const std::string& filename) const; - bool SaveAsGLTF(const std::string& filename) const; - - private: - std::vector materials_; - std::vector gpuMaterials_; - std::vector models_; - std::vector> nodes_; - std::vector lights_; - std::vector tracks_; - std::vector offsets_; - - std::unique_ptr vertexBuffer_; - std::unique_ptr vertexBufferMemory_; - - std::unique_ptr simpleVertexBuffer_; - std::unique_ptr simpleVertexBufferMemory_; + class Node; + class Model; + class Texture; + class TextureImage; + struct Material; + struct LightObject; + + class Scene final + { + public: + Scene(const Scene&) = delete; + Scene(Scene&&) = delete; + Scene& operator=(const Scene&) = delete; + Scene& operator=(Scene&&) = delete; + + Scene(Vulkan::CommandPool& commandPool, bool supportRayTracing); + ~Scene(); + + void PostLoad(const std::vector& skeletons); + + void Reload(std::vector>& nodes, std::vector& models, + std::vector& materials, std::vector& lights, + std::vector& tracks); + std::shared_ptr Append(const std::string& sceneName, std::vector>& nodes, + std::vector& models, std::vector& materials, + std::vector& lights, std::vector& tracks, + const std::vector& skeletons); + void RebuildMeshBuffer(Vulkan::CommandPool& commandPool, bool supportRayTracing); + void CleanUp(); + // void RebuildBVH(); + + const Assets::GPUScene& FetchGPUScene(const uint32_t imageIndex) const; + std::vector>& Nodes() { return nodes_; } + const std::vector>& Nodes() const { return nodes_; } + const std::vector& Models() const { return models_; } + std::vector& Materials() { return materials_; } + const std::vector& Materials() const { return materials_; } + const std::vector& Offsets() const { return offsets_; } + const std::vector& Lights() const { return lights_; } + const Vulkan::Buffer& VertexBuffer() const { return *vertexBuffer_; } + const Vulkan::Buffer& SimpleVertexBuffer() const { return *simpleVertexBuffer_; } + const Vulkan::Buffer& IndexBuffer() const { return *indexBuffer_; } + const Vulkan::Buffer& MaterialBuffer() const { return *materialBuffer_; } + const Vulkan::Buffer& OffsetsBuffer() const { return *offsetBuffer_; } + const Vulkan::Buffer& LightBuffer() const { return *lightBuffer_; } + const Vulkan::Buffer& NodeMatrixBuffer() const { return *nodeMatrixBuffer_; } + const Vulkan::Buffer& IndirectDrawBuffer() const { return *indirectDrawBuffer_; } + const Vulkan::Buffer& ReorderBuffer() const { return *reorderBuffer_; } + const Vulkan::Buffer& PrimAddressBuffer() const { return *primAddressBuffer_; } + const glm::vec3 GetSunDir() const { return envSettings_.SunDirection(); } + const bool HasSun() const { return envSettings_.HasSun; } + + const uint32_t GetLightCount() const { return lightCount_; } + const uint32_t GetIndicesCount() const { return indicesCount_; } + const uint32_t GetVerticeCount() const { return verticeCount_; } + const uint32_t GetIndirectDrawBatchCount() const { return indirectDrawBatchCount_; } + + const Assets::GPUDrivenStat& GetGpuDrivenStat() const { return gpuDrivenStat_; } + + uint32_t GetSelectedId() const { return selectedId_; } + void SetSelectedId(uint32_t id) const { selectedId_ = id; } + bool GetSelectedNodeBounds(glm::vec3& center, float& radius) const; + + void Tick(float DeltaSeconds); + void UpdateAllMaterials(); + bool UpdateNodes(); + void UpdateHDRSH(); + bool UpdateNodesGpuDriven(); + + Node* GetNode(std::string name); + Node* GetNodeByInstanceId(uint32_t id); + const Model* GetModel(uint32_t id) const; + const FMaterial* GetMaterial(uint32_t id) const; + const uint32_t AddMaterial(const FMaterial& material); + + void MarkDirty(); + + std::vector& GetNodeProxys() { return nodeProxys; } + + void OverrideModelView(glm::mat4& OutMatrix); + + const std::vector& GetCameras() const { return envSettings_.cameras; } + const Assets::EnvironmentSetting& GetEnvironmentStrings() const { return envSettings_; } + + Assets::EnvironmentSetting& GetEnvSettings() { return envSettings_; } + void SetEnvSettings(const Assets::EnvironmentSetting& envSettings) { envSettings_ = envSettings; } + + Camera& GetRenderCamera() { return renderCamera_; } + void SetRenderCamera(const Camera& camera) { renderCamera_ = camera; } + + void PlayAllTracks(); + + void MarkEnvDirty(); + + void AddNode(std::shared_ptr node); + std::shared_ptr RemoveNodeByInstanceId(uint32_t id); + std::shared_ptr GetNodeSharedByInstanceId(uint32_t id) const; + uint32_t GenerateInstanceId() const; + + struct RemovedNodeEntry + { + std::shared_ptr node; + size_t index; + }; + std::vector RemoveNodeHierarchy(uint32_t id, std::shared_ptr& outParent); + void RestoreNodes(const std::vector& entries, const std::shared_ptr& parent, + const std::shared_ptr& root); + + void SetSkinningBuffers(VkDeviceAddress skinnedVertices, VkDeviceAddress skinnedVerticesSimple, + VkDeviceAddress jointMatrices); + + // Assets::RayCastResult RayCastInCPU(glm::vec3 rayOrigin, glm::vec3 rayDir); + + Vulkan::Buffer& AmbientCubeBuffer() const { return *ambientCubeBuffer_; } + Vulkan::Buffer& FarAmbientCubeBuffer() const { return *farAmbientCubeBuffer_; } + Vulkan::Buffer& PageIndexBuffer() const { return *pageIndexBuffer_; } + + Vulkan::Buffer& SkinWeightBuffer() const { return *skinWeightBuffer_; } + Vulkan::Buffer& SkinJointBuffer() const { return *skinJointBuffer_; } + + Vulkan::Buffer& HDRSHBuffer() const { return *hdrSHBuffer_; } + + TextureImage& ShadowMap() const { return *cpuShadowMap_; } + + FCPUAccelerationStructure& GetCPUAccelerationStructure() { return cpuAccelerationStructure_; } + + // Scene saving功能 + bool Save(const std::string& filename) const; + bool SaveAsGLB(const std::string& filename) const; + bool SaveAsGLTF(const std::string& filename) const; + + private: + std::vector materials_; + std::vector gpuMaterials_; + std::vector models_; + std::vector> nodes_; + std::vector lights_; + std::vector tracks_; + std::vector offsets_; + + std::unique_ptr vertexBuffer_; + std::unique_ptr vertexBufferMemory_; + + std::unique_ptr simpleVertexBuffer_; + std::unique_ptr simpleVertexBufferMemory_; + + std::unique_ptr indexBuffer_; + std::unique_ptr indexBufferMemory_; + + std::unique_ptr reorderBuffer_; + std::unique_ptr reorderBufferMemory_; + + std::unique_ptr primAddressBuffer_; + std::unique_ptr primAddressBufferMemory_; + + std::unique_ptr materialBuffer_; + std::unique_ptr materialBufferMemory_; - std::unique_ptr indexBuffer_; - std::unique_ptr indexBufferMemory_; + std::unique_ptr offsetBuffer_; + std::unique_ptr offsetBufferMemory_; - std::unique_ptr reorderBuffer_; - std::unique_ptr reorderBufferMemory_; + std::unique_ptr lightBuffer_; + std::unique_ptr lightBufferMemory_; - std::unique_ptr primAddressBuffer_; - std::unique_ptr primAddressBufferMemory_; - - std::unique_ptr materialBuffer_; - std::unique_ptr materialBufferMemory_; + std::unique_ptr nodeMatrixBuffer_; + std::unique_ptr nodeMatrixBufferMemory_; - std::unique_ptr offsetBuffer_; - std::unique_ptr offsetBufferMemory_; + std::unique_ptr indirectDrawBuffer_; + std::unique_ptr indirectDrawBufferMemory_; - std::unique_ptr lightBuffer_; - std::unique_ptr lightBufferMemory_; + std::unique_ptr ambientCubeBuffer_; + std::unique_ptr ambientCubeBufferMemory_; - std::unique_ptr nodeMatrixBuffer_; - std::unique_ptr nodeMatrixBufferMemory_; - - std::unique_ptr indirectDrawBuffer_; - std::unique_ptr indirectDrawBufferMemory_; + std::unique_ptr farAmbientCubeBuffer_; + std::unique_ptr farAmbientCubeBufferMemory_; - std::unique_ptr ambientCubeBuffer_; - std::unique_ptr ambientCubeBufferMemory_; - - std::unique_ptr farAmbientCubeBuffer_; - std::unique_ptr farAmbientCubeBufferMemory_; + std::unique_ptr pageIndexBuffer_; + std::unique_ptr pageIndexBufferMemory_; - std::unique_ptr pageIndexBuffer_; - std::unique_ptr pageIndexBufferMemory_; + std::unique_ptr skinWeightBuffer_; + std::unique_ptr skinWeightBufferMemory_; - std::unique_ptr skinWeightBuffer_; - std::unique_ptr skinWeightBufferMemory_; + std::unique_ptr skinJointBuffer_; + std::unique_ptr skinJointBufferMemory_; - std::unique_ptr skinJointBuffer_; - std::unique_ptr skinJointBufferMemory_; + std::unique_ptr hdrSHBuffer_; + std::unique_ptr hdrSHBufferMemory_; - std::unique_ptr hdrSHBuffer_; - std::unique_ptr hdrSHBufferMemory_; + std::unique_ptr gpuDrivenStatsBuffer_; + std::unique_ptr gpuDrivenStatsBuffer_Memory_; - std::unique_ptr gpuDrivenStatsBuffer_; - std::unique_ptr gpuDrivenStatsBuffer_Memory_; + std::unique_ptr cpuShadowMap_; - std::unique_ptr cpuShadowMap_; + uint32_t lightCount_{}; + uint32_t indicesCount_{}; + uint32_t verticeCount_{}; + uint32_t indirectDrawBatchCount_{}; - uint32_t lightCount_ {}; - uint32_t indicesCount_ {}; - uint32_t verticeCount_ {}; - uint32_t indirectDrawBatchCount_ {}; + mutable uint32_t selectedId_ = -1; - mutable uint32_t selectedId_ = -1; + bool sceneDirtyForCpuAS_ = false; + bool sceneDirty_ = true; + bool materialDirty_ = true; - bool sceneDirtyForCpuAS_ = false; - bool sceneDirty_ = true; - bool materialDirty_ = true; - - std::vector nodeProxys; - std::vector indirectDrawBufferInstanced; + std::vector nodeProxys; + std::vector indirectDrawBufferInstanced; - glm::mat4 overrideModelView; - bool requestOverrideModelView = false; - - Assets::EnvironmentSetting envSettings_; - Camera renderCamera_; + glm::mat4 overrideModelView; + bool requestOverrideModelView = false; - FCPUAccelerationStructure cpuAccelerationStructure_; + Assets::EnvironmentSetting envSettings_; + Camera renderCamera_; - Assets::GPUDrivenStat gpuDrivenStat_; - mutable Assets::GPUScene gpuScene_; + FCPUAccelerationStructure cpuAccelerationStructure_; - glm::vec3 sceneAABBMin_ {FLT_MAX, FLT_MAX, FLT_MAX}; - glm::vec3 sceneAABBMax_ {-FLT_MAX, -FLT_MAX, -FLT_MAX}; - std::vector > cachedMeshShapes_; + Assets::GPUDrivenStat gpuDrivenStat_; + mutable Assets::GPUScene gpuScene_; - VkDeviceAddress skinnedVerticesAddr_ = 0; - VkDeviceAddress skinnedVerticesSimpleAddr_ = 0; - VkDeviceAddress jointMatricesAddr_ = 0; + glm::vec3 sceneAABBMin_{FLT_MAX, FLT_MAX, FLT_MAX}; + glm::vec3 sceneAABBMax_{-FLT_MAX, -FLT_MAX, -FLT_MAX}; + std::vector> cachedMeshShapes_; - }; -} + VkDeviceAddress skinnedVerticesAddr_ = 0; + VkDeviceAddress skinnedVerticesSimpleAddr_ = 0; + VkDeviceAddress jointMatricesAddr_ = 0; + }; +} // namespace Assets diff --git a/src/Editor/EditorDragDrop.hpp b/src/Editor/EditorDragDrop.hpp new file mode 100644 index 00000000..30d6bed8 --- /dev/null +++ b/src/Editor/EditorDragDrop.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "Common/CoreMinimal.hpp" + +#include + +namespace Editor +{ + constexpr const char* kEditorDragDropPayload = "GK_EDITOR_DND"; + + enum class EEditorDragPayloadType : uint8_t + { + Scene = 0, + Material, + }; + + struct EditorDragDropPayload + { + EEditorDragPayloadType type = EEditorDragPayloadType::Scene; + char path[512]{}; + uint32_t materialId = 0; + }; +} // namespace Editor diff --git a/src/Editor/Panels/ContentBrowserPanel.cpp b/src/Editor/Panels/ContentBrowserPanel.cpp index 43ca5a50..0fb27195 100644 --- a/src/Editor/Panels/ContentBrowserPanel.cpp +++ b/src/Editor/Panels/ContentBrowserPanel.cpp @@ -1,5 +1,7 @@ #include "Editor/EditorUi.hpp" +#include "Editor/EditorDragDrop.hpp" + #include "Assets/Scene.hpp" #include "Assets/TextureImage.hpp" #include "Editor/EditorActionDispatcher.hpp" @@ -8,7 +10,9 @@ #include "Utilities/FileHelper.hpp" #include +#include #include +#include #include #include #include @@ -22,6 +26,155 @@ namespace Editor constexpr int kIconSize = 96; constexpr int kIconPadding = 20; + struct ContentBrowserCallbacks + { + std::function onDoubleClick; + std::function onContextMenu; + std::function onDragSource; + }; + + enum class EContentAssetKind + { + Directory, + Scene, + Hdri, + Unsupported, + }; + + struct ContentAssetVisual + { + const char* icon = ICON_FA_FOLDER; + ImU32 color = IM_COL32(0, 172, 255, 255); + EContentAssetKind kind = EContentAssetKind::Directory; + }; + + struct ContentGridLayout + { + int itemsPerRow = 1; + int index = 0; + + void Next() + { + if ((index + 1) % itemsPerRow != 0) + { + ImGui::SameLine(); + } + ++index; + } + }; + + ContentGridLayout BeginContentGrid() + { + const float windowWidth = ImGui::GetContentRegionAvail().x; + const int itemsPerRow = + std::max(1, static_cast(windowWidth / (kIconSize + ImGui::GetStyle().ItemSpacing.x))); + return ContentGridLayout{itemsPerRow, 0}; + } + + const ContentAssetVisual& ResolveAssetVisualForExtension(std::string_view extension) + { + static const ContentAssetVisual kUnsupported{ICON_FA_FOLDER, IM_COL32(0, 172, 255, 255), + EContentAssetKind::Unsupported}; + static const ContentAssetVisual kScene{ICON_FA_CUBE, IM_COL32(255, 172, 0, 255), EContentAssetKind::Scene}; + static const ContentAssetVisual kHdri{ICON_FA_FILE_IMAGE, IM_COL32(200, 64, 64, 255), + EContentAssetKind::Hdri}; + + struct ExtensionVisual + { + std::string_view extension; + const ContentAssetVisual* visual = nullptr; + }; + + static const std::array kVisuals{ + ExtensionVisual{".glb", &kScene}, + ExtensionVisual{".hdr", &kHdri}, + }; + + for (const auto& entry : kVisuals) + { + if (entry.extension == extension) + { + return *entry.visual; + } + } + + return kUnsupported; + } + + ContentAssetVisual ResolveAssetVisual(const std::filesystem::directory_entry& entry) + { + static const ContentAssetVisual kDirectory{}; + static const ContentAssetVisual kUnsupported{ICON_FA_FOLDER, IM_COL32(0, 172, 255, 255), + EContentAssetKind::Unsupported}; + + if (entry.is_directory()) + { + return kDirectory; + } + + if (!entry.is_regular_file()) + { + return kUnsupported; + } + + const std::string ext = entry.path().extension().string(); + return ResolveAssetVisualForExtension(ext); + } + + void DrawContentBrowserNavigation(EditorUiState& ui, const std::filesystem::path& rootPath, + std::filesystem::path& currentPath) + { + const std::string rootStr = rootPath.string(); + const std::string curStr = currentPath.string(); + if (curStr.rfind(rootStr, 0) != 0) + { + currentPath = rootPath; + } + + if (ui.fontIcon) + { + ImGui::PushFont(ui.fontIcon); + } + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 4)); + + if (ImGui::Button(ICON_FA_HOUSE)) + { + currentPath = rootPath; + } + + const std::filesystem::path rel = currentPath.lexically_relative(rootPath); + if (!rel.empty() && rel != ".") + { + std::filesystem::path crumbPath = rootPath; + for (const auto& part : rel) + { + ImGui::SameLine(); + ImGui::TextUnformatted(">"); + ImGui::SameLine(); + + std::string label = part.string(); + if (!label.empty()) + { + label[0] = static_cast(std::toupper(static_cast(label[0]))); + } + + crumbPath /= part; + ImGui::PushID(label.c_str()); + if (ImGui::Button(label.c_str())) + { + currentPath = crumbPath; + } + ImGui::PopID(); + } + } + + ImGui::PopStyleVar(); + if (ui.fontIcon) + { + ImGui::PopFont(); + } + } + uint32_t Fnv1a32(std::string_view s) { uint32_t hash = 2166136261u; @@ -35,8 +188,7 @@ namespace Editor void DrawGeneralContentBrowser(EditorContext& ctx, EditorUiState& ui, uint32_t& selectionId, bool iconOrTex, uint32_t globalId, const std::string& name, const char* icon, ImU32 color, - const std::function& doubleclickAction, - const std::function& contextMenuAction) + const ContentBrowserCallbacks& callbacks) { ImGui::BeginGroup(); if (ui.bigIcon) @@ -56,6 +208,12 @@ namespace Editor ImGui::Image((ImTextureID)(intptr_t)textureId, ImVec2(kIconSize, kIconSize)); } + if (callbacks.onDragSource && ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) + { + callbacks.onDragSource(); + ImGui::EndDragDropSource(); + } + ImGui::PopID(); ImGui::PopStyleColor(); if (ui.bigIcon) @@ -68,7 +226,10 @@ namespace Editor if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { selectionId = InvalidId; - doubleclickAction(); + if (callbacks.onDoubleClick) + { + callbacks.onDoubleClick(); + } } if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { @@ -92,11 +253,11 @@ namespace Editor ImGui::SetCursorPosY(ImGui::GetCursorPosY() + kIconPadding); ImGui::EndGroup(); - if (contextMenuAction) + if (callbacks.onContextMenu) { if (ImGui::BeginPopupContextItem("Context")) { - contextMenuAction(); + callbacks.onContextMenu(); ImGui::EndPopup(); } } @@ -108,23 +269,16 @@ namespace Editor ImGui::Begin("Mesh Browser", nullptr); { auto& allModels = ctx.scene.Models(); - - const float windowWidth = ImGui::GetContentRegionAvail().x; - const int itemsPerRow = - std::max(1, static_cast(windowWidth / (kIconSize + ImGui::GetStyle().ItemSpacing.x))); - + ContentGridLayout grid = BeginContentGrid(); for (uint32_t i = 0; i < allModels.size(); ++i) { auto& model = allModels[i]; const std::string name = fmt::format("{}_#{}", model.Name(), i); uint32_t dummySelection = InvalidId; - DrawGeneralContentBrowser( - ctx, ui, dummySelection, true, i, name, ICON_FA_BOXES_PACKING, IM_COL32(132, 182, 255, 255), - []() {}, nullptr); - if ((i + 1) % itemsPerRow != 0) - { - ImGui::SameLine(); - } + DrawGeneralContentBrowser(ctx, ui, dummySelection, true, i, name, ICON_FA_BOXES_PACKING, + IM_COL32(132, 182, 255, 255), + ContentBrowserCallbacks{.onDoubleClick = []() {}}); + grid.Next(); } } ImGui::End(); @@ -135,29 +289,33 @@ namespace Editor ImGui::Begin("Material Browser", nullptr); { auto& allMaterials = ctx.scene.Materials(); - - const float windowWidth = ImGui::GetContentRegionAvail().x; - const int itemsPerRow = - std::max(1, static_cast(windowWidth / (kIconSize + ImGui::GetStyle().ItemSpacing.x))); - + ContentGridLayout grid = BeginContentGrid(); for (uint32_t i = 0; i < allMaterials.size(); ++i) { auto& mat = allMaterials[i]; - DrawGeneralContentBrowser( - ctx, ui, ui.selectedMaterialId, true, i, mat.name_, ICON_FA_BOWLING_BALL, - IM_COL32(132, 255, 132, 255), - [&]() - { - ui.selected_material = &(ctx.scene.Materials()[i]); - ui.ed_material = true; - OpenMaterialEditor(ctx, ui); - }, - nullptr); - - if ((i + 1) % itemsPerRow != 0) - { - ImGui::SameLine(); - } + DrawGeneralContentBrowser(ctx, ui, ui.selectedMaterialId, true, i, mat.name_, ICON_FA_BOWLING_BALL, + IM_COL32(132, 255, 132, 255), + ContentBrowserCallbacks{ + .onDoubleClick = + [&]() + { + ui.selected_material = &(ctx.scene.Materials()[i]); + ui.ed_material = true; + OpenMaterialEditor(ctx, ui); + }, + .onDragSource = + [&]() + { + EditorDragDropPayload payload{}; + payload.type = EEditorDragPayloadType::Material; + payload.materialId = i; + ImGui::SetDragDropPayload(kEditorDragDropPayload, &payload, + sizeof(payload)); + ImGui::TextUnformatted(mat.name_.c_str()); + }, + }); + + grid.Next(); } } ImGui::End(); @@ -168,22 +326,13 @@ namespace Editor ImGui::Begin("Texture Browser", nullptr); { auto& totalTextureMap = Assets::GlobalTexturePool::GetInstance()->TotalTextureMap(); - - const float windowWidth = ImGui::GetContentRegionAvail().x; - const int itemsPerRow = - std::max(1, static_cast(windowWidth / (kIconSize + ImGui::GetStyle().ItemSpacing.x))); - - int itemIndex = 0; + ContentGridLayout grid = BeginContentGrid(); for (auto& textureGroup : totalTextureMap) { - DrawGeneralContentBrowser( - ctx, ui, ui.selectedTextureId, false, textureGroup.second.GlobalIdx_, textureGroup.first, - ICON_FA_LINK_SLASH, IM_COL32(255, 72, 72, 255), []() {}, nullptr); - - if ((itemIndex++ + 1) % itemsPerRow != 0) - { - ImGui::SameLine(); - } + DrawGeneralContentBrowser(ctx, ui, ui.selectedTextureId, false, textureGroup.second.GlobalIdx_, + textureGroup.first, ICON_FA_LINK_SLASH, IM_COL32(255, 72, 72, 255), + ContentBrowserCallbacks{.onDoubleClick = []() {}}); + grid.Next(); } } ImGui::End(); @@ -197,59 +346,7 @@ namespace Editor std::filesystem::path(Utilities::FileHelper::GetPlatformFilePath("assets")); static std::filesystem::path currentPath = rootPath; - // Safety: keep browsing rooted under assets. - const std::string rootStr = rootPath.string(); - const std::string curStr = currentPath.string(); - if (curStr.rfind(rootStr, 0) != 0) - { - currentPath = rootPath; - } - - if (ui.fontIcon) - { - ImGui::PushFont(ui.fontIcon); - } - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 4)); - if (ImGui::Button(ICON_FA_HOUSE)) - currentPath = rootPath; - - const std::filesystem::path rel = currentPath.lexically_relative(rootPath); - std::vector parts; - if (!rel.empty() && rel != ".") - { - for (const auto& p : rel) - { - parts.push_back(p); - } - } - - for (int i = 0; i < static_cast(parts.size()); ++i) - { - ImGui::SameLine(); - ImGui::TextUnformatted(">"); - ImGui::SameLine(); - - std::string label = parts[i].string(); - if (!label.empty()) - { - label[0] = static_cast(std::toupper(static_cast(label[0]))); - } - - if (ImGui::Button(label.c_str())) - { - std::filesystem::path newPath = rootPath; - for (int j = 0; j <= i; ++j) - { - newPath /= parts[j]; - } - currentPath = newPath; - } - } - ImGui::PopStyleVar(); - if (ui.fontIcon) - { - ImGui::PopFont(); - } + DrawContentBrowserNavigation(ui, rootPath, currentPath); auto cursorPos = ImGui::GetWindowPos() + ImVec2(0, ImGui::GetCursorPos().y + 2); ImGui::NewLine(); @@ -262,78 +359,68 @@ namespace Editor ImGui::BeginChild("Content Items"); std::filesystem::directory_iterator it(currentPath); - const float windowWidth = ImGui::GetContentRegionAvail().x; - const int itemsPerRow = - std::max(1, static_cast(windowWidth / (kIconSize + ImGui::GetStyle().ItemSpacing.x))); - - uint32_t elementIdx = 0; + ContentGridLayout grid = BeginContentGrid(); for (auto& entry : it) { const std::string abspath = absolute(entry.path()).string(); const std::string name = entry.path().filename().string(); - const std::string ext = entry.path().extension().string(); - const char* icon = ICON_FA_FOLDER; - ImU32 color = IM_COL32(0, 172, 255, 255); - if (entry.is_regular_file()) + const ContentAssetVisual visual = ResolveAssetVisual(entry); + if (visual.kind == EContentAssetKind::Unsupported) { - if (ext == ".glb") - { - icon = ICON_FA_CUBE; - color = IM_COL32(255, 172, 0, 255); - } - else if (ext == ".hdr") - { - icon = ICON_FA_FILE_IMAGE; - color = IM_COL32(200, 64, 64, 255); - } - else - { - continue; - } + continue; } const uint32_t stableId = Fnv1a32(abspath); DrawGeneralContentBrowser( - ctx, ui, ui.selectedContentItemId, true, stableId, name, icon, color, - [&]() - { - if (entry.is_directory()) - { - currentPath = entry.path(); - } - else + ctx, ui, ui.selectedContentItemId, true, stableId, name, visual.icon, visual.color, + ContentBrowserCallbacks{ + .onDoubleClick = + [&]() { - if (ext == ".glb") + if (visual.kind == EContentAssetKind::Directory) + { + currentPath = entry.path(); + } + else if (visual.kind == EContentAssetKind::Scene) { ctx.actions.Dispatch(ctx, EEditorAction::IO_LoadScene, abspath); } - else if (ext == ".hdr") + else if (visual.kind == EContentAssetKind::Hdri) { ctx.actions.Dispatch(ctx, EEditorAction::IO_LoadHDRI, abspath); } - } - }, - [&]() - { - if (ext == ".glb") + }, + .onContextMenu = + [&]() { - if (ImGui::MenuItem("Open Scene")) + if (visual.kind == EContentAssetKind::Scene) { - ctx.actions.Dispatch(ctx, EEditorAction::IO_LoadScene, abspath); + if (ImGui::MenuItem("Open Scene")) + { + ctx.actions.Dispatch(ctx, EEditorAction::IO_LoadScene, abspath); + } + if (ImGui::MenuItem("Add To Scene")) + { + ctx.actions.Dispatch(ctx, EEditorAction::IO_LoadSceneAdd, abspath); + } } - if (ImGui::MenuItem("Add To Scene")) + }, + .onDragSource = + [&]() + { + if (visual.kind == EContentAssetKind::Scene) { - ctx.actions.Dispatch(ctx, EEditorAction::IO_LoadSceneAdd, abspath); + EditorDragDropPayload payload{}; + payload.type = EEditorDragPayloadType::Scene; + std::snprintf(payload.path, sizeof(payload.path), "%s", abspath.c_str()); + ImGui::SetDragDropPayload(kEditorDragDropPayload, &payload, sizeof(payload)); + ImGui::TextUnformatted(name.c_str()); } - } + }, }); - if ((elementIdx + 1) % itemsPerRow != 0) - { - ImGui::SameLine(); - } - elementIdx++; + grid.Next(); } ImGui::EndChild(); } diff --git a/src/Editor/Panels/ViewportOverlay.cpp b/src/Editor/Panels/ViewportOverlay.cpp index c99e6442..e23475b2 100644 --- a/src/Editor/Panels/ViewportOverlay.cpp +++ b/src/Editor/Panels/ViewportOverlay.cpp @@ -1,11 +1,19 @@ #include "Editor/EditorUi.hpp" +#include "Editor/EditorDragDrop.hpp" + +#include "Assets/Node.h" #include "Assets/Scene.hpp" +#include "Editor/EditorActionDispatcher.hpp" +#include "Runtime/Components/RenderComponent.h" #include "Runtime/Engine.hpp" +#include "Runtime/NextEngineHelper.h" #include "ThirdParty/fontawesome/IconsFontAwesome6.h" #include "Utilities/ImGui.hpp" #include "Utilities/Math.hpp" +#include + namespace Editor { namespace @@ -28,6 +36,100 @@ namespace Editor return; } + if (ui.viewportOnMainViewport && ImGui::GetDragDropPayload() != nullptr) + { + ImGui::SetNextWindowPos(pos); + ImGui::SetNextWindowSize(size); + ImGui::SetNextWindowViewport(viewport->ID); + ImGui::SetNextWindowBgAlpha(0.0f); + + ImGuiWindowFlags dropFlags = 0 | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollWithMouse; + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + ImGui::Begin("ViewportDropTarget", nullptr, dropFlags); + ImGui::PopStyleVar(); + + ImGui::InvisibleButton("ViewportDropTargetBtn", size); + if (ImGui::BeginDragDropTarget()) + { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(kEditorDragDropPayload)) + { + if (payload->DataSize == sizeof(EditorDragDropPayload)) + { + const auto* data = static_cast(payload->Data); + if (data->type == EEditorDragPayloadType::Scene) + { + std::string path = data->path; + const bool hasScene = !ctx.scene.Nodes().empty(); + if (!hasScene) + { + ctx.actions.Dispatch(ctx, EEditorAction::IO_LoadScene, path); + } + else + { + glm::dvec2 mousePos = ctx.engine.GetMousePos(); + glm::vec3 origin; + glm::vec3 dir; + NextEngineHelper::GetScreenToWorldRay(glm::vec2(mousePos.x, mousePos.y), origin, dir); + + NextEngine* engine = &ctx.engine; + engine->RayCastGPU(origin, dir, + [engine, path](Assets::RayCastResult result) mutable + { + NextEngine::SceneAppendOptions options{}; + if (result.Hitted) + { + options.placeOnHit = true; + options.hitPosition = result.HitPoint; + } + engine->RequestLoadSceneAdd(path, options); + return true; + }); + } + } + else if (data->type == EEditorDragPayloadType::Material) + { + const uint32_t materialId = data->materialId; + glm::dvec2 mousePos = ctx.engine.GetMousePos(); + glm::vec3 origin; + glm::vec3 dir; + NextEngineHelper::GetScreenToWorldRay(glm::vec2(mousePos.x, mousePos.y), origin, dir); + + Assets::Scene* scene = &ctx.scene; + ctx.engine.RayCastGPU(origin, dir, + [scene, materialId](Assets::RayCastResult result) + { + if (result.Hitted) + { + Assets::Node* node = + scene->GetNodeByInstanceId(result.InstanceId); + if (node) + { + auto render = + node->GetComponent(); + if (render && render->IsDrawable()) + { + auto& mats = render->Materials(); + for (auto& mat : mats) + { + mat = materialId; + } + scene->MarkDirty(); + } + } + } + return true; + }); + } + } + } + ImGui::EndDragDropTarget(); + } + ImGui::End(); + } + constexpr float padding = 5.0f; const float statW = std::max(60.0f, std::min(160.0f, size.x - padding * 2.0f)); const float statH = std::max(60.0f, std::min(140.0f, size.y - padding * 2.0f)); diff --git a/src/Runtime/Engine.cpp b/src/Runtime/Engine.cpp index 5db8612f..be9296b9 100644 --- a/src/Runtime/Engine.cpp +++ b/src/Runtime/Engine.cpp @@ -1,44 +1,44 @@ #include "Engine.hpp" -#include "UserInterface.hpp" -#include "UserSettings.hpp" #include "Assets/Model.hpp" #include "Assets/Scene.hpp" #include "Assets/Texture.hpp" #include "Assets/UniformBuffer.hpp" -#include "Vulkan/Window.hpp" -#include "Vulkan/SwapChain.hpp" -#include "Vulkan/Device.hpp" -#include "Vulkan/Instance.hpp" -#include "ScreenShot.hpp" #include "QuickJSEngine.hpp" #include "Runtime/Command/DeleteNodeCommand.hpp" +#include "ScreenShot.hpp" +#include "UserInterface.hpp" +#include "UserSettings.hpp" +#include "Vulkan/Device.hpp" +#include "Vulkan/Instance.hpp" +#include "Vulkan/SwapChain.hpp" +#include "Vulkan/Window.hpp" -#include -#include -#include -#include +#include #include +#include +#include +#include +#include +#include +#include #include -#include #include -#include #include -#include +#include "NextAudio.h" #include "Options.hpp" +#include "Rendering/RayTraceBaseRenderer.hpp" #include "TaskCoordinator.hpp" #include "Utilities/Localization.hpp" -#include "Rendering/RayTraceBaseRenderer.hpp" -#include "NextAudio.h" #define _USE_MATH_DEFINES #include #define BUILDVER(X) std::string buildver(#X); -#include "build.version" #include "NextAnimation.h" #include "NextPhysics.h" #include "Platform/PlatformCommon.h" +#include "build.version" #include "Common/CoreMinimal.hpp" @@ -62,16 +62,14 @@ namespace } return requestedType; } -} +} // namespace namespace NextRenderer { - std::string GetBuildVersion() - { - return buildver; - } + std::string GetBuildVersion() { return buildver; } - Vulkan::VulkanBaseRenderer* CreateRenderer(uint32_t rendererType, Vulkan::Window* window, const VkPresentModeKHR presentMode, const bool enableValidationLayers) + Vulkan::VulkanBaseRenderer* CreateRenderer(uint32_t rendererType, Vulkan::Window* window, + const VkPresentModeKHR presentMode, const bool enableValidationLayers) { std::vector validationLayers; if (enableValidationLayers) @@ -86,7 +84,8 @@ namespace NextRenderer Vulkan::VulkanBaseRenderer* renderer = nullptr; if (useRayTracingRenderer) { - renderer = new Vulkan::RayTracing::RayTraceBaseRenderer(window, presentMode, enableValidationLayers, instance); + renderer = + new Vulkan::RayTracing::RayTraceBaseRenderer(window, presentMode, enableValidationLayers, instance); supportedTypes.emplace_back(Vulkan::ERT_PathTracing); } else @@ -99,17 +98,18 @@ namespace NextRenderer renderer->RegisterLogicRenderer(type); } - auto requestedType = ResolveRendererType(static_cast(rendererType), useRayTracingRenderer); + auto requestedType = + ResolveRendererType(static_cast(rendererType), useRayTracingRenderer); if (std::find(supportedTypes.begin(), supportedTypes.end(), requestedType) == supportedTypes.end()) { requestedType = *supportedTypes.begin(); } - + renderer->SwitchLogicRenderer(requestedType); return renderer; } -} +} // namespace NextRenderer namespace { @@ -119,17 +119,17 @@ namespace float elapsed; std::array outputInfo; }; -} +} // namespace UserSettings CreateUserSettings(const Options& options) { SceneList::ScanScenes(); - + UserSettings userSettings{}; userSettings.RendererType = options.RendererType; userSettings.SceneIndex = 0; - + userSettings.NumberOfSamples = options.Samples; userSettings.NumberOfBounces = options.Bounces; userSettings.MaxNumberOfBounces = options.MaxBounces; @@ -151,7 +151,7 @@ UserSettings CreateUserSettings(const Options& options) userSettings.Denoiser = !options.NoDenoiser; userSettings.PaperWhiteNit = 600.f; - + return userSettings; } @@ -162,17 +162,17 @@ NextEngine::NextEngine(Options& options, void* userdata) spdlog::set_level(spdlog::level::info); spdlog::flush_on(spdlog::level::debug); spdlog::flush_every(std::chrono::seconds(1)); - + SPDLOG_INFO("---- Next Engine Initializing..."); spdlog::stopwatch stopwatch; - + #if ANDROID std::string tag = "gknext"; auto android_logger = spdlog::android_logger_mt("android", tag); android_logger->critical("Use \"adb shell logcat\" to view this message."); spdlog::set_default_logger(android_logger); #endif - + instance_ = this; status_ = NextRenderer::EApplicationStatus::Starting; @@ -181,26 +181,23 @@ NextEngine::NextEngine(Options& options, void* userdata) Vulkan::Window::InitGLFW(); // Create Window - Vulkan::WindowConfig windowConfig - { - "gkNextRenderer " + NextRenderer::GetBuildVersion(), - options.Width, - options.Height, - options.Fullscreen, - options.Fullscreen, - !options.Fullscreen, - options.SaveFile, - userdata, - options.ForceSDR - }; + Vulkan::WindowConfig windowConfig{"gkNextRenderer " + NextRenderer::GetBuildVersion(), + options.Width, + options.Height, + options.Fullscreen, + options.Fullscreen, + !options.Fullscreen, + options.SaveFile, + userdata, + options.ForceSDR}; gameInstance_ = CreateGameInstance(windowConfig, options, this); userSettings_ = CreateUserSettings(options); - window_.reset( new Vulkan::Window(windowConfig)); + window_.reset(new Vulkan::Window(windowConfig)); quickJSEngine_ = std::make_unique(); - + // Initialize Localization Utilities::Localization::ReadLocTexts(fmt::format("assets/locale/{}.txt", GOption->locale).c_str()); - + SPDLOG_INFO("---- Next Engine Initialized in {}", stopwatch.elapsed_ms()); } @@ -218,44 +215,49 @@ NextEngine::~NextEngine() void NextEngine::Start() { PERFORMANCEAPI_INSTRUMENT_FUNCTION(); - + SPDLOG_INFO("---- Next Engine Starting..."); spdlog::stopwatch stopwatch; - + // Initialize Renderer bool shouldEnableValidation = GOption->Validation; #ifndef NDEBUG shouldEnableValidation = true; #endif - renderer_.reset( NextRenderer::CreateRenderer(GOption->RendererType, window_.get(), static_cast(GOption->PresentMode), shouldEnableValidation) ); + renderer_.reset(NextRenderer::CreateRenderer(GOption->RendererType, window_.get(), + static_cast(GOption->PresentMode), + shouldEnableValidation)); userSettings_.RendererType = static_cast(renderer_->CurrentLogicRendererType()); - - renderer_->DelegateOnDeviceSet = [this]()->void{OnRendererDeviceSet();}; - renderer_->DelegateCreateSwapChain = [this]()->void{OnRendererCreateSwapChain();}; - renderer_->DelegateDeleteSwapChain = [this]()->void{OnRendererDeleteSwapChain();}; - renderer_->DelegateBeforeNextTick = [this]()->void{OnRendererBeforeNextFrame();}; - renderer_->DelegateGetUniformBufferObject = [this](VkOffset2D offset, VkExtent2D extend)->Assets::UniformBufferObject{ return GetUniformBufferObject(offset, extend);}; - renderer_->DelegatePostRender = [this](VkCommandBuffer commandBuffer, uint32_t imageIndex)->void{OnRendererPostRender(commandBuffer, imageIndex);}; - + + renderer_->DelegateOnDeviceSet = [this]() -> void { OnRendererDeviceSet(); }; + renderer_->DelegateCreateSwapChain = [this]() -> void { OnRendererCreateSwapChain(); }; + renderer_->DelegateDeleteSwapChain = [this]() -> void { OnRendererDeleteSwapChain(); }; + renderer_->DelegateBeforeNextTick = [this]() -> void { OnRendererBeforeNextFrame(); }; + renderer_->DelegateGetUniformBufferObject = [this](VkOffset2D offset, + VkExtent2D extend) -> Assets::UniformBufferObject + { return GetUniformBufferObject(offset, extend); }; + renderer_->DelegatePostRender = [this](VkCommandBuffer commandBuffer, uint32_t imageIndex) -> void + { OnRendererPostRender(commandBuffer, imageIndex); }; + renderer_->Start(); physicsEngine_.reset(new NextPhysics()); physicsEngine_->Start(); - + animationEngine_ = std::make_unique(); animationEngine_->Start(); audioEngine_ = std::make_unique(); audioEngine_->Start(); - + if (quickJSEngine_) { quickJSEngine_->Initialize(); } gameInstance_->OnInit(); - + SPDLOG_INFO("---- Next Engine Started in {}", stopwatch.elapsed_ms()); } @@ -263,7 +265,7 @@ bool NextEngine::HandleEvent(SDL_Event& event) { userInterface_->HandleEvent(&event); - switch ( event.type ) + switch (event.type) { case SDL_EVENT_WINDOW_CLOSE_REQUESTED: { @@ -297,12 +299,13 @@ bool NextEngine::HandleEvent(SDL_Event& event) bool NextEngine::Tick(bool forcingDelta) { PERFORMANCEAPI_INSTRUMENT_FUNCTION(); - + // make sure the output is flushed std::cout << std::flush; - + // Hot change renderer - auto requestedRendererType = ResolveRendererType(static_cast(userSettings_.RendererType), renderer_->SupportsRayTracing()); + auto requestedRendererType = ResolveRendererType(static_cast(userSettings_.RendererType), + renderer_->SupportsRayTracing()); if (requestedRendererType != static_cast(userSettings_.RendererType)) { userSettings_.RendererType = static_cast(requestedRendererType); @@ -312,27 +315,30 @@ bool NextEngine::Tick(bool forcingDelta) { renderer_->SwitchLogicRenderer(requestedRendererType); } - + // delta time calc const auto prevTime = time_; time_ = GetWindow().GetTime(); deltaSeconds_ = time_ - prevTime; - if (forcingDelta) deltaSeconds_ = 1.0 / 30.0; + if (forcingDelta) + deltaSeconds_ = 1.0 / 30.0; float invDelta = static_cast(deltaSeconds_) / 60.0f; smoothedDeltaSeconds_ = glm::mix(smoothedDeltaSeconds_, deltaSeconds_, invDelta * 100.0f); - + // Scene Update - if(scene_) + if (scene_) { PERFORMANCEAPI_INSTRUMENT_DATA("Engine::TickScene", ""); scene_->Tick(static_cast(deltaSeconds_)); } #if WITH_PHYSIC - if (userSettings_.TickPhysics && physicsEngine_) physicsEngine_->Tick(deltaSeconds_); + if (userSettings_.TickPhysics && physicsEngine_) + physicsEngine_->Tick(deltaSeconds_); #endif - - if (userSettings_.TickAnimation && animationEngine_) animationEngine_->Tick(deltaSeconds_); //pause dev, wait next + + if (userSettings_.TickAnimation && animationEngine_) + animationEngine_->Tick(deltaSeconds_); // pause dev, wait next if (quickJSEngine_) { @@ -350,9 +356,9 @@ bool NextEngine::Tick(bool forcingDelta) PERFORMANCEAPI_INSTRUMENT_DATA("Engine::TickTasks", ""); // iterate the tickedTasks_, if return true, remove it - for( auto it = tickedTasks_.begin(); it != tickedTasks_.end(); ) + for (auto it = tickedTasks_.begin(); it != tickedTasks_.end();) { - if( (*it)(deltaSeconds_) ) + if ((*it)(deltaSeconds_)) { it = tickedTasks_.erase(it); } @@ -362,18 +368,18 @@ bool NextEngine::Tick(bool forcingDelta) } } } - + // iterate the delayedTasks_ , if Time is up, execute it, if return true, remove it - for( auto it = delayedTasks_.begin(); it != delayedTasks_.end(); ) + for (auto it = delayedTasks_.begin(); it != delayedTasks_.end();) { - if( time_ > it->triggerTime ) + if (time_ > it->triggerTime) { // update the next trigger time it->triggerTime = time_ + it->loopTime; // execute - if( it->task() ) + if (it->task()) { it = delayedTasks_.erase(it); } @@ -443,10 +449,7 @@ void NextEngine::RegisterJSCallback(std::function callback) } } -void NextEngine::AddTimerTask(double delay, DelayedTask task) -{ - delayedTasks_.push_back( { time_ + delay, delay, task} ); -} +void NextEngine::AddTimerTask(double delay, DelayedTask task) { delayedTasks_.push_back({time_ + delay, delay, task}); } void NextEngine::PlaySound(const std::string& soundName, bool loop, float volume) { @@ -482,24 +485,15 @@ void NextEngine::SaveScreenShot(const std::string& filename, int x, int y, int w glm::dvec2 NextEngine::GetMousePos() { float fx{}, fy{}; - SDL_GetMouseState(&fx,&fy); - return glm::dvec2(fx,fy); + SDL_GetMouseState(&fx, &fy); + return glm::dvec2(fx, fy); } -void NextEngine::RequestClose() -{ - window_->Close(); -} +void NextEngine::RequestClose() { window_->Close(); } -void NextEngine::RequestMinimize() -{ - window_->Minimize(); -} +void NextEngine::RequestMinimize() { window_->Minimize(); } -bool NextEngine::IsMaximumed() -{ - return window_->IsMaximumed(); -} +bool NextEngine::IsMaximumed() { return window_->IsMaximumed(); } void NextEngine::ToggleMaximize() { @@ -516,12 +510,14 @@ void NextEngine::ToggleMaximize() void NextEngine::RequestScreenShot(std::string filename) { auto time = std::time(nullptr); - std::string screenshotFilename = filename.empty() ? fmt::format("screenshot_{:%Y-%m-%d-%H-%M-%S}", *std::localtime(&time)) : filename; + std::string screenshotFilename = + filename.empty() ? fmt::format("screenshot_{:%Y-%m-%d-%H-%M-%S}", *std::localtime(&time)) : filename; SaveScreenShot(screenshotFilename, 0, 0, 0, 0); } // 生成一个随机抖动偏移 -glm::vec2 GenerateJitter(float screenWidth, float screenHeight) { +glm::vec2 GenerateJitter(float screenWidth, float screenHeight) +{ std::random_device rd; std::mt19937 gen(rd()); std::uniform_real_distribution<> dis(-0.5f, 0.5f); @@ -533,7 +529,8 @@ glm::vec2 GenerateJitter(float screenWidth, float screenHeight) { } // 创建抖动矩阵 -glm::mat4 CreateJitterMatrix(float jitterX, float jitterY) { +glm::mat4 CreateJitterMatrix(float jitterX, float jitterY) +{ glm::mat4 jitterMatrix = glm::mat4(1.0f); jitterMatrix[3][0] = jitterX; jitterMatrix[3][1] = jitterY; @@ -541,17 +538,20 @@ glm::mat4 CreateJitterMatrix(float jitterX, float jitterY) { } // 调制投影矩阵 -glm::mat4 RandomJitterProjectionMatrix(const glm::mat4& projectionMatrix, float screenWidth, float screenHeight) { +glm::mat4 RandomJitterProjectionMatrix(const glm::mat4& projectionMatrix, float screenWidth, float screenHeight) +{ glm::vec2 jitter = GenerateJitter(screenWidth, screenHeight); glm::mat4 jitterMatrix = CreateJitterMatrix(jitter.x, jitter.y); return jitterMatrix * projectionMatrix; } // 生成Halton序列的单一维度 -float HaltonSequence(int index, int base) { +float HaltonSequence(int index, int base) +{ float f = 1.0f; float result = 0.0f; - while (index > 0) { + while (index > 0) + { f = f / base; result = result + f * (index % base); index = index / base; @@ -560,17 +560,20 @@ float HaltonSequence(int index, int base) { } // 生成2D Halton序列 -std::vector GenerateHaltonSequence(int count) { +std::vector GenerateHaltonSequence(int count) +{ std::vector sequence; - for (int i = 0; i < count; ++i) { - float x = HaltonSequence(i + 1, 2); // 基数2 - float y = HaltonSequence(i + 1, 3); // 基数3 + for (int i = 0; i < count; ++i) + { + float x = HaltonSequence(i + 1, 2); // 基数2 + float y = HaltonSequence(i + 1, 3); // 基数3 sequence.push_back(glm::vec2(x, y)); } return sequence; } -glm::mat4 HaltonJitterProjectionMatrix(const glm::mat4& projectionMatrix, float screenWidth, float screenHeight) { +glm::mat4 HaltonJitterProjectionMatrix(const glm::mat4& projectionMatrix, float screenWidth, float screenHeight) +{ glm::vec2 jitter = GenerateJitter(screenWidth, screenHeight); glm::mat4 jitterMatrix = CreateJitterMatrix(jitter.x, jitter.y); return jitterMatrix * projectionMatrix; @@ -578,7 +581,7 @@ glm::mat4 HaltonJitterProjectionMatrix(const glm::mat4& projectionMatrix, float glm::ivec2 NextEngine::GetMonitorSize() const { - glm::ivec2 size{1920,1080}; + glm::ivec2 size{1920, 1080}; SDL_Rect rect; SDL_DisplayID id = SDL_GetPrimaryDisplay(); @@ -590,7 +593,7 @@ glm::ivec2 NextEngine::GetMonitorSize() const } void NextEngine::RayCastGPU(glm::vec3 rayOrigin, glm::vec3 rayDir, - std::function callback) + std::function callback) { // CPU Raycast in scene Assets::RayCastResult result = scene_->GetCPUAccelerationStructure().RayCastInCPU(rayOrigin, rayDir); @@ -604,7 +607,7 @@ void NextEngine::SetProgressiveRendering(bool enable, bool directly) progressiveRendering_ = enable; return; } - + if (enable) { if (progressivePreFrames_ == 0) @@ -621,10 +624,11 @@ void NextEngine::SetProgressiveRendering(bool enable, bool directly) VkDeviceAddress NextEngine::TryGetGPUAccelerationStructureAddress() const { - Vulkan::RayTracing::RayTraceBaseRenderer* rtRender = dynamic_cast(renderer_.get()); + Vulkan::RayTracing::RayTraceBaseRenderer* rtRender = + dynamic_cast(renderer_.get()); if (rtRender) { - return rtRender->TLAS()[0].GetDeviceAddress(); + return rtRender->TLAS()[0].GetDeviceAddress(); } return -1; @@ -632,10 +636,11 @@ VkDeviceAddress NextEngine::TryGetGPUAccelerationStructureAddress() const VkAccelerationStructureKHR NextEngine::TryGetGPUAccelerationStructureHandle() const { - Vulkan::RayTracing::RayTraceBaseRenderer* rtRender = dynamic_cast(renderer_.get()); + Vulkan::RayTracing::RayTraceBaseRenderer* rtRender = + dynamic_cast(renderer_.get()); if (rtRender) { - return rtRender->TLAS()[0].Handle(); + return rtRender->TLAS()[0].Handle(); } return nullptr; @@ -649,17 +654,18 @@ Assets::UniformBufferObject NextEngine::GetUniformBufferObject(const VkOffset2D Assets::Camera renderCam = scene_->GetRenderCamera(); gameInstance_->OverrideRenderCamera(renderCam); ubo.ModelView = renderCam.ModelView; - + scene_->OverrideModelView(ubo.ModelView); - ubo.Projection = glm::perspective(glm::radians(renderCam.FieldOfView), - extent.width / static_cast(extent.height), renderCam.NearPlane, renderCam.FarPlane); - + ubo.Projection = + glm::perspective(glm::radians(renderCam.FieldOfView), extent.width / static_cast(extent.height), + renderCam.NearPlane, renderCam.FarPlane); + ubo.FastGather = userSettings_.FastGather; ubo.SelectedId = scene_->GetSelectedId(); ubo.SuperResolution = GOption->ReferenceMode ? 2 : userSettings_.SuperResolution; ubo.Projection[1][1] *= -1; - glm::mat4x4 projectionUnJit = ubo.Projection; + glm::mat4x4 projectionUnJit = ubo.Projection; // handle android vulkan pre rotation #if ANDROID glm::mat4 pre_rotate_mat = glm::mat4(1.0f); @@ -677,8 +683,8 @@ Assets::UniformBufferObject NextEngine::GetUniformBufferObject(const VkOffset2D if (userSettings_.TAA || userSettings_.DLSS) { std::vector haltonSeq = GenerateHaltonSequence(userSettings_.TemporalFrames); - glm::vec2 jitter = haltonSeq[totalFrames_ % userSettings_.TemporalFrames] - glm::vec2(0.5f,0.5f); - + glm::vec2 jitter = haltonSeq[totalFrames_ % userSettings_.TemporalFrames] - glm::vec2(0.5f, 0.5f); + ubo.Projection[2][0] = jitter.x / static_cast(extent.width) * 2.0f; ubo.Projection[2][1] = jitter.y / static_cast(extent.height) * 2.0f; @@ -688,7 +694,7 @@ Assets::UniformBufferObject NextEngine::GetUniformBufferObject(const VkOffset2D { ubo.Jitter = glm::vec4(0, 0, 0, 0); } - + // Inverting Y for Vulkan, https://matthewwellings.com/blog/the-new-vulkan-coordinate-system/ ubo.ModelViewInverse = glm::inverse(ubo.ModelView); ubo.ProjectionInverse = glm::inverse(ubo.Projection); @@ -696,11 +702,13 @@ Assets::UniformBufferObject NextEngine::GetUniformBufferObject(const VkOffset2D ubo.ViewProjectionUnJit = projectionUnJit * ubo.ModelView; ubo.ProjectionUnJit = projectionUnJit; ubo.ProjectionInverseUnJit = glm::inverse(projectionUnJit); - + ubo.PrevViewProjection = prevUBO_.TotalFrames != 0 ? prevUBO_.ViewProjection : ubo.ViewProjection; ubo.PrevViewProjectionUnJit = prevUBO_.TotalFrames != 0 ? prevUBO_.ViewProjectionUnJit : ubo.ViewProjectionUnJit; - - ubo.ViewportRect = glm::vec4(renderer_->SwapChain().RenderOffset().x, renderer_->SwapChain().RenderOffset().y, renderer_->SwapChain().RenderExtent().width, renderer_->SwapChain().RenderExtent().height); + + ubo.ViewportRect = + glm::vec4(renderer_->SwapChain().RenderOffset().x, renderer_->SwapChain().RenderOffset().y, + renderer_->SwapChain().RenderExtent().width, renderer_->SwapChain().RenderExtent().height); ubo.SunViewProjection = scene_->GetEnvSettings().GetSunViewProjection(); @@ -721,26 +729,26 @@ Assets::UniformBufferObject NextEngine::GetUniformBufferObject(const VkOffset2D ubo.AdaptiveSteps = userSettings_.AdaptiveSteps; ubo.TAA = userSettings_.TAA; ubo.RandomSeed = rand(); - ubo.SunDirection = glm::vec4( scene_->GetEnvSettings().SunDirection(), 0.0f ); - ubo.SunColor = glm::vec4(1,1,1, 0) * scene_->GetEnvSettings().SunIntensity; + ubo.SunDirection = glm::vec4(scene_->GetEnvSettings().SunDirection(), 0.0f); + ubo.SunColor = glm::vec4(1, 1, 1, 0) * scene_->GetEnvSettings().SunIntensity; ubo.SkyIntensity = scene_->GetEnvSettings().SkyIntensity; ubo.SkyIdx = scene_->GetEnvSettings().SkyIdx; ubo.BackGroundColor = glm::vec4(0.4, 0.6, 1.0, 0.0) * 4.0f * scene_->GetEnvSettings().SkyIntensity; ubo.HasSky = scene_->GetEnvSettings().HasSky; - ubo.HasSun =scene_->GetEnvSettings().HasSun && scene_->GetEnvSettings().SunIntensity > 0; - + ubo.HasSun = scene_->GetEnvSettings().HasSun && scene_->GetEnvSettings().SunIntensity > 0; + if (ubo.HasSun != prevUBO_.HasSun || ubo.SunDirection != prevUBO_.SunDirection) { scene_->MarkEnvDirty(); } - ubo.ShowHeatmap = showFlags_.ShowVisualDebug; - ubo.HeatmapScale = userSettings_.HeatmapScale; + ubo.ShowHeatmap = showFlags_.ShowVisualDebug; + ubo.HeatmapScale = userSettings_.HeatmapScale; ubo.DebugDraw_Lighting = showFlags_.DebugDraw_Lighting; ubo.UseCheckerBoard = userSettings_.UseCheckerBoardRendering; ubo.TemporalFrames = progressiveRendering_ ? 256 : userSettings_.TemporalFrames; ubo.HDR = renderer_->SwapChain().IsHDR(); - + ubo.PaperWhiteNit = userSettings_.PaperWhiteNit; ubo.LightCount = scene_->GetLightCount(); @@ -749,18 +757,18 @@ Assets::UniformBufferObject NextEngine::GetUniformBufferObject(const VkOffset2D ubo.BFSigmaNormal = userSettings_.DenoiseSigmaNormal; ubo.BFSize = userSettings_.Denoiser ? userSettings_.DenoiseSize : 0; - + #if WITH_OIDN ubo.BFSize = 0; #endif - - ubo.ShowEdge = showFlags_.ShowEdge; + + ubo.ShowEdge = showFlags_.ShowEdge; ubo.ProgressiveRender = progressiveRendering_; ubo.SceneEpsilonScale = userSettings_.SceneEpsilonScale; // Other Setup renderer_->supportDenoiser_ = userSettings_.Denoiser; - renderer_->visualDebug_ = showFlags_.ShowVisualDebug; + renderer_->visualDebug_ = showFlags_.ShowVisualDebug; // UBO Backup, for motion vector calc prevUBO_ = ubo; @@ -772,14 +780,14 @@ void NextEngine::OnRendererDeviceSet() // global textures // texture id 0: dynamic hdri sky Assets::GlobalTexturePool::LoadHDRTexture("assets/textures/river_road_2.hdr"); - - + + Assets::GlobalTexturePool::LoadHDRTexture("assets/textures/canary_wharf_1k.hdr"); Assets::GlobalTexturePool::LoadHDRTexture("assets/textures/kloppenheim_01_puresky_1k.hdr"); Assets::GlobalTexturePool::LoadHDRTexture("assets/textures/kloppenheim_07_1k.hdr"); Assets::GlobalTexturePool::LoadHDRTexture("assets/textures/std_env.hdr"); - + Assets::GlobalTexturePool::LoadHDRTexture("assets/textures/rainforest_trail_1k.hdr"); Assets::GlobalTexturePool::LoadHDRTexture("assets/textures/studio_small_03_1k.hdr"); @@ -789,13 +797,14 @@ void NextEngine::OnRendererDeviceSet() Assets::GlobalTexturePool::LoadHDRTexture("assets/textures/shanghai_bund_1k.hdr"); // texture id 11 - 99: system texture - //Assets::GlobalTexturePool::LoadTexture("assets/textures/white.png", true); + // Assets::GlobalTexturePool::LoadTexture("assets/textures/white.png", true); // fill to 100, id > 100, general textures - //if(GOption->HDRIfile != "") Assets::GlobalTexturePool::UpdateHDRTexture(0, GOption->HDRIfile.c_str(), Vulkan::SamplerConfig()); - + // if(GOption->HDRIfile != "") Assets::GlobalTexturePool::UpdateHDRTexture(0, GOption->HDRIfile.c_str(), + // Vulkan::SamplerConfig()); + scene_.reset(new Assets::Scene(renderer_->CommandPool(), renderer_->supportRayTracing_)); renderer_->SetScene(scene_); renderer_->OnPostLoadScene(); @@ -805,23 +814,18 @@ void NextEngine::OnRendererDeviceSet() void NextEngine::OnRendererCreateSwapChain() { - if(userInterface_.get() == nullptr) + if (userInterface_.get() == nullptr) { - userInterface_.reset(new UserInterface(this, renderer_->CommandPool(), renderer_->SwapChain(), renderer_->DepthBuffer(), - userSettings_, [this]()->void - { - gameInstance_->OnPreConfigUI(); - }, - [this]()->void{ - gameInstance_->OnInitUI(); - })); + userInterface_.reset(new UserInterface( + this, renderer_->CommandPool(), renderer_->SwapChain(), renderer_->DepthBuffer(), userSettings_, + [this]() -> void { gameInstance_->OnPreConfigUI(); }, [this]() -> void { gameInstance_->OnInitUI(); })); } userInterface_->OnCreateSurface(renderer_->SwapChain(), renderer_->DepthBuffer()); } void NextEngine::OnRendererDeleteSwapChain() { - if(userInterface_.get() != nullptr) + if (userInterface_.get() != nullptr) { userInterface_->OnDestroySurface(); } @@ -831,28 +835,28 @@ void NextEngine::OnRendererPostRender(VkCommandBuffer commandBuffer, uint32_t im { static double lastTimestamp = 0.0; double now = GetWindow().GetTime(); - + // Record delta time between calls to Render. - if(totalFrames_ % 30 == 0) + if (totalFrames_ % 30 == 0) { const auto timeDelta = now - lastFrameTime_; lastFrameTime_ = now; frameRate_ = static_cast(30 / timeDelta); } - + // Render the UI Statistics stats = {}; - + stats.FrameTime = static_cast((now - lastTimestamp) * 1000.0); lastTimestamp = now; - + stats.Stats["gpu"] = renderer_->Device().DeviceProperties().deviceName; - + stats.FramebufferSize = GetWindow().FramebufferSize(); stats.RenderSize = renderer_->SwapChain().RenderExtent(); stats.FrameRate = frameRate_; stats.RenderTime = GetTime(); - + stats.TotalFrames = totalFrames_; stats.InstanceCount = static_cast(scene_->GetNodeProxys().size()); stats.NodeCount = static_cast(scene_->Nodes().size()); @@ -861,9 +865,9 @@ void NextEngine::OnRendererPostRender(VkCommandBuffer commandBuffer, uint32_t im stats.ComputePassCount = 0; stats.LoadingStatus = status_ == NextRenderer::EApplicationStatus::Loading; - //Renderer::visualDebug_ = userSettings_.ShowVisualDebug; + // Renderer::visualDebug_ = userSettings_.ShowVisualDebug; userInterface_->PreRender(); - if( !gameInstance_->OnRenderUI() ) + if (!gameInstance_->OnRenderUI()) { userInterface_->Render(stats, renderer_->GpuTimer(), scene_.get()); } @@ -924,7 +928,7 @@ void NextEngine::OnKey(SDL_Event& event) } } - if( gameInstance_->OnKey(event) ) + if (gameInstance_->OnKey(event)) { return; } @@ -935,48 +939,30 @@ bool NextEngine::ExecuteCommand(std::unique_ptr command) return commandSystem_.ExecuteCommand(std::move(command)); } -bool NextEngine::UndoCommand() -{ - return commandSystem_.Undo(); -} +bool NextEngine::UndoCommand() { return commandSystem_.Undo(); } -bool NextEngine::RedoCommand() -{ - return commandSystem_.Redo(); -} +bool NextEngine::RedoCommand() { return commandSystem_.Redo(); } -bool NextEngine::CanUndo() const -{ - return commandSystem_.CanUndo(); -} +bool NextEngine::CanUndo() const { return commandSystem_.CanUndo(); } -bool NextEngine::CanRedo() const -{ - return commandSystem_.CanRedo(); -} +bool NextEngine::CanRedo() const { return commandSystem_.CanRedo(); } void NextEngine::OnTouch(bool down, double xpos, double ypos) { - //OnMouseButton(GLFW_MOUSE_BUTTON_RIGHT, down ? GLFW_PRESS : GLFW_RELEASE, 0); + // OnMouseButton(GLFW_MOUSE_BUTTON_RIGHT, down ? GLFW_PRESS : GLFW_RELEASE, 0); } -void NextEngine::OnTouchMove(double xpos, double ypos) -{ - OnCursorPosition(xpos, ypos); -} +void NextEngine::OnTouchMove(double xpos, double ypos) { OnCursorPosition(xpos, ypos); } void NextEngine::OnCursorPosition(const double xpos, const double ypos) { - if (!renderer_->HasSwapChain() || - userInterface_->WantsToCaptureKeyboard() || - userInterface_->WantsToCaptureMouse() || - window_->IsCapturingMouse() - ) + if (!renderer_->HasSwapChain() || userInterface_->WantsToCaptureKeyboard() || + userInterface_->WantsToCaptureMouse() || window_->IsCapturingMouse()) { return; } - - if(gameInstance_->OnCursorPosition(xpos, ypos)) + + if (gameInstance_->OnCursorPosition(xpos, ypos)) { return; } @@ -984,13 +970,12 @@ void NextEngine::OnCursorPosition(const double xpos, const double ypos) void NextEngine::OnMouseButton(SDL_Event& event) { - if (!renderer_->HasSwapChain() || - userInterface_->WantsToCaptureMouse()) + if (!renderer_->HasSwapChain() || userInterface_->WantsToCaptureMouse()) { return; } - if(gameInstance_->OnMouseButton(event)) + if (gameInstance_->OnMouseButton(event)) { return; } @@ -998,8 +983,7 @@ void NextEngine::OnMouseButton(SDL_Event& event) void NextEngine::OnScroll(const double xoffset, const double yoffset) { - if (!renderer_->HasSwapChain() || - userInterface_->WantsToCaptureMouse()) + if (!renderer_->HasSwapChain() || userInterface_->WantsToCaptureMouse()) { return; } @@ -1016,15 +1000,15 @@ void NextEngine::OnDropFile(const char* dropPath) if (ext == "glb" || ext == "gltf") { - //userSettings_.SceneIndex = SceneList::AddExternalScene(path); + // userSettings_.SceneIndex = SceneList::AddExternalScene(path); RequestLoadScene(path); } - if( ext == "hdr") + if (ext == "hdr") { uint32_t newTextureId = Assets::GlobalTexturePool::GetInstance()->LoadHDRTexture(path); scene_->GetEnvSettings().SkyIdx = newTextureId; - //userSettings_. = 0; + // userSettings_. = 0; } } void NextEngine::TickGamepadInput() @@ -1036,50 +1020,53 @@ void NextEngine::TickGamepadInput() { SDL_Gamepad* masterGamepad = SDL_GetGamepadFromID(*gamepads); - gameInstance_->OnGamepadInput( - SDL_GetGamepadAxis(masterGamepad, SDL_GAMEPAD_AXIS_LEFTX), - SDL_GetGamepadAxis(masterGamepad, SDL_GAMEPAD_AXIS_LEFTY), - SDL_GetGamepadAxis(masterGamepad, SDL_GAMEPAD_AXIS_RIGHTX), - SDL_GetGamepadAxis(masterGamepad, SDL_GAMEPAD_AXIS_RIGHTY), - SDL_GetGamepadAxis(masterGamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER), - SDL_GetGamepadAxis(masterGamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) - ); + gameInstance_->OnGamepadInput(SDL_GetGamepadAxis(masterGamepad, SDL_GAMEPAD_AXIS_LEFTX), + SDL_GetGamepadAxis(masterGamepad, SDL_GAMEPAD_AXIS_LEFTY), + SDL_GetGamepadAxis(masterGamepad, SDL_GAMEPAD_AXIS_RIGHTX), + SDL_GetGamepadAxis(masterGamepad, SDL_GAMEPAD_AXIS_RIGHTY), + SDL_GetGamepadAxis(masterGamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER), + SDL_GetGamepadAxis(masterGamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)); } SDL_free(gamepads); } -void NextEngine::OnRendererBeforeNextFrame() -{ - TaskCoordinator::GetInstance()->Tick(); -} +void NextEngine::OnRendererBeforeNextFrame() { TaskCoordinator::GetInstance()->Tick(); } void NextEngine::RequestLoadScene(std::string sceneFileName) { - AddTickedTask([this, sceneFileName](double deltaSeconds)->bool - { - if ( status_ != NextRenderer::EApplicationStatus::Running ) + AddTickedTask( + [this, sceneFileName](double deltaSeconds) -> bool { - return false; - } - - LoadScene(sceneFileName); - return true; - }); + if (status_ != NextRenderer::EApplicationStatus::Running) + { + return false; + } + + LoadScene(sceneFileName); + return true; + }); } void NextEngine::RequestLoadSceneAdd(std::string sceneFileName) { - AddTickedTask([this, sceneFileName](double deltaSeconds)->bool - { - if ( status_ != NextRenderer::EApplicationStatus::Running ) + SceneAppendOptions options{}; + RequestLoadSceneAdd(std::move(sceneFileName), options); +} + +void NextEngine::RequestLoadSceneAdd(std::string sceneFileName, const SceneAppendOptions& options) +{ + AddTickedTask( + [this, sceneFileName, options](double deltaSeconds) -> bool { - return false; - } - - LoadSceneAdd(sceneFileName); - return true; - }); + if (status_ != NextRenderer::EApplicationStatus::Running) + { + return false; + } + + LoadSceneAdd(sceneFileName, options); + return true; + }); } void NextEngine::LaunchLoadSceneTask(std::string sceneFileName, std::function onGpuLoad) @@ -1087,58 +1074,64 @@ void NextEngine::LaunchLoadSceneTask(std::string sceneFileName, std::functionCancelAllParralledTasks(); TaskCoordinator::GetInstance()->WaitForAllParralledTask(); - + status_ = NextRenderer::EApplicationStatus::Loading; - + SceneLoadContext ctx; - ctx.models = std::make_shared< std::vector >(); - ctx.nodes = std::make_shared< std::vector< std::shared_ptr > >(); - ctx.materials = std::make_shared< std::vector >(); - ctx.lights = std::make_shared< std::vector >(); - ctx.tracks = std::make_shared< std::vector >(); - ctx.skeletons = std::make_shared< std::vector >(); - ctx.cameraState = std::make_shared< Assets::EnvironmentSetting >(); + ctx.models = std::make_shared>(); + ctx.nodes = std::make_shared>>(); + ctx.materials = std::make_shared>(); + ctx.lights = std::make_shared>(); + ctx.tracks = std::make_shared>(); + ctx.skeletons = std::make_shared>(); + ctx.cameraState = std::make_shared(); // dispatch in thread task and reset in main thread - TaskCoordinator::GetInstance()->AddTask( [ctx, sceneFileName](ResTask& task) - { - SceneTaskContext taskContext {}; - const auto timer = std::chrono::high_resolution_clock::now(); + TaskCoordinator::GetInstance()->AddTask( + [ctx, sceneFileName](ResTask& task) + { + SceneTaskContext taskContext{}; + const auto timer = std::chrono::high_resolution_clock::now(); + + taskContext.success = SceneList::LoadScene(sceneFileName, *ctx.cameraState, *ctx.nodes, *ctx.models, + *ctx.materials, *ctx.lights, *ctx.tracks, *ctx.skeletons); + + taskContext.elapsed = std::chrono::duration( + std::chrono::high_resolution_clock::now() - timer) + .count(); + + std::string info = + fmt::format("parsed scene [{}] on cpu in {:.2f}ms", + std::filesystem::path(sceneFileName).filename().string(), taskContext.elapsed * 1000.f); + std::copy(info.begin(), info.end(), taskContext.outputInfo.data()); + task.SetContext(taskContext); + }, + [this, ctx, sceneFileName, onGpuLoad](ResTask& task) mutable + { + SceneTaskContext taskContext{}; + task.GetContext(taskContext); + if (taskContext.success) + { + SPDLOG_INFO("{}", taskContext.outputInfo.data()); - taskContext.success = SceneList::LoadScene( sceneFileName, *ctx.cameraState, *ctx.nodes, *ctx.models, *ctx.materials, *ctx.lights, *ctx.tracks, *ctx.skeletons); + renderer_->Device().WaitIdle(); + renderer_->DeleteSwapChain(); - taskContext.elapsed = std::chrono::duration(std::chrono::high_resolution_clock::now() - timer).count(); + // Execute the specific GPU load logic + onGpuLoad(ctx); - std::string info = fmt::format("parsed scene [{}] on cpu in {:.2f}ms", std::filesystem::path(sceneFileName).filename().string(), taskContext.elapsed * 1000.f); - std::copy(info.begin(), info.end(), taskContext.outputInfo.data()); - task.SetContext( taskContext ); - }, - [this, ctx, sceneFileName, onGpuLoad](ResTask& task) mutable - { - SceneTaskContext taskContext {}; - task.GetContext( taskContext ); - if (taskContext.success ) - { - SPDLOG_INFO("{}", taskContext.outputInfo.data()); - - renderer_->Device().WaitIdle(); - renderer_->DeleteSwapChain(); - - // Execute the specific GPU load logic - onGpuLoad(ctx); - - totalFrames_ = 0; - renderer_->OnPostLoadScene(); - renderer_->CreateSwapChain(); - } - else - { - SPDLOG_ERROR("failed to load scene [{}]", std::filesystem::path(sceneFileName).filename().string()); - } + totalFrames_ = 0; + renderer_->OnPostLoadScene(); + renderer_->CreateSwapChain(); + } + else + { + SPDLOG_ERROR("failed to load scene [{}]", std::filesystem::path(sceneFileName).filename().string()); + } - status_ = NextRenderer::EApplicationStatus::Running; - }, - 1); + status_ = NextRenderer::EApplicationStatus::Running; + }, + 1); } void NextEngine::LoadScene(std::string sceneFileName) @@ -1147,57 +1140,76 @@ void NextEngine::LoadScene(std::string sceneFileName) physicsEngine_->OnSceneDestroyed(); Assets::GlobalTexturePool::GetInstance()->FreeNonSystemTextures(); - LaunchLoadSceneTask(sceneFileName, [this, sceneFileName](SceneLoadContext& ctx) - { - const auto timer = std::chrono::high_resolution_clock::now(); - scene_->GetEnvSettings().Reset(); - scene_->SetEnvSettings(*ctx.cameraState); - - gameInstance_->OnSceneUnloaded(); - physicsEngine_->OnSceneStarted(); - - renderer_->OnPreLoadScene(); - - gameInstance_->BeforeSceneRebuild(*ctx.nodes, *ctx.models, *ctx.materials, *ctx.lights, *ctx.tracks); - scene_->Reload(*ctx.nodes, *ctx.models, *ctx.materials, *ctx.lights, *ctx.tracks); - scene_->PostLoad(*ctx.skeletons); - scene_->RebuildMeshBuffer(renderer_->CommandPool(), renderer_->supportRayTracing_); - renderer_->SetScene(scene_); - - userSettings_.CameraIdx = 0; - assert(!scene_->GetEnvSettings().cameras.empty()); - scene_->SetRenderCamera(scene_->GetEnvSettings().cameras[0]); - - gameInstance_->OnSceneLoaded(); - - float elapsed = std::chrono::duration(std::chrono::high_resolution_clock::now() - timer).count(); - SPDLOG_INFO("uploaded scene [{}] to gpu in {:.2f}ms", std::filesystem::path(sceneFileName).filename().string(), elapsed * 1000.f); - }); + LaunchLoadSceneTask(sceneFileName, + [this, sceneFileName](SceneLoadContext& ctx) + { + const auto timer = std::chrono::high_resolution_clock::now(); + scene_->GetEnvSettings().Reset(); + scene_->SetEnvSettings(*ctx.cameraState); + + gameInstance_->OnSceneUnloaded(); + physicsEngine_->OnSceneStarted(); + + renderer_->OnPreLoadScene(); + + gameInstance_->BeforeSceneRebuild(*ctx.nodes, *ctx.models, *ctx.materials, *ctx.lights, + *ctx.tracks); + scene_->Reload(*ctx.nodes, *ctx.models, *ctx.materials, *ctx.lights, *ctx.tracks); + scene_->PostLoad(*ctx.skeletons); + scene_->RebuildMeshBuffer(renderer_->CommandPool(), renderer_->supportRayTracing_); + renderer_->SetScene(scene_); + + userSettings_.CameraIdx = 0; + assert(!scene_->GetEnvSettings().cameras.empty()); + scene_->SetRenderCamera(scene_->GetEnvSettings().cameras[0]); + + gameInstance_->OnSceneLoaded(); + + float elapsed = std::chrono::duration( + std::chrono::high_resolution_clock::now() - timer) + .count(); + SPDLOG_INFO("uploaded scene [{}] to gpu in {:.2f}ms", + std::filesystem::path(sceneFileName).filename().string(), elapsed * 1000.f); + }); } void NextEngine::LoadSceneAdd(std::string sceneFileName) { - LaunchLoadSceneTask(sceneFileName, [this, sceneFileName](SceneLoadContext& ctx) - { - const auto timer = std::chrono::high_resolution_clock::now(); - - renderer_->OnPreLoadScene(); - - gameInstance_->BeforeSceneRebuild(*ctx.nodes, *ctx.models, *ctx.materials, *ctx.lights, *ctx.tracks); - - std::string name = std::filesystem::path(sceneFileName).stem().string(); - scene_->Append(name, *ctx.nodes, *ctx.models, *ctx.materials, *ctx.lights, *ctx.tracks, *ctx.skeletons); - scene_->RebuildMeshBuffer(renderer_->CommandPool(), renderer_->supportRayTracing_); - renderer_->SetScene(scene_); - - // gameInstance_->OnSceneLoaded(); // Maybe trigger this too? - - float elapsed = std::chrono::duration(std::chrono::high_resolution_clock::now() - timer).count(); - SPDLOG_INFO("uploaded scene [{}] to gpu in {:.2f}ms", std::filesystem::path(sceneFileName).filename().string(), elapsed * 1000.f); - }); + SceneAppendOptions options{}; + LoadSceneAdd(std::move(sceneFileName), options); } -void NextEngine::InitPhysics() +void NextEngine::LoadSceneAdd(std::string sceneFileName, const SceneAppendOptions& options) { - + LaunchLoadSceneTask( + sceneFileName, + [this, sceneFileName, options](SceneLoadContext& ctx) + { + const auto timer = std::chrono::high_resolution_clock::now(); + + renderer_->OnPreLoadScene(); + + gameInstance_->BeforeSceneRebuild(*ctx.nodes, *ctx.models, *ctx.materials, *ctx.lights, *ctx.tracks); + + std::string name = std::filesystem::path(sceneFileName).stem().string(); + std::shared_ptr rootNode = + scene_->Append(name, *ctx.nodes, *ctx.models, *ctx.materials, *ctx.lights, *ctx.tracks, *ctx.skeletons); + if (options.placeOnHit && rootNode) + { + rootNode->SetTranslation(options.hitPosition); + rootNode->RecalcTransform(true); + } + scene_->RebuildMeshBuffer(renderer_->CommandPool(), renderer_->supportRayTracing_); + renderer_->SetScene(scene_); + + // gameInstance_->OnSceneLoaded(); // Maybe trigger this too? + + float elapsed = std::chrono::duration( + std::chrono::high_resolution_clock::now() - timer) + .count(); + SPDLOG_INFO("uploaded scene [{}] to gpu in {:.2f}ms", + std::filesystem::path(sceneFileName).filename().string(), elapsed * 1000.f); + }); } + +void NextEngine::InitPhysics() {} diff --git a/src/Runtime/Engine.hpp b/src/Runtime/Engine.hpp index a87cebc8..1864faac 100644 --- a/src/Runtime/Engine.hpp +++ b/src/Runtime/Engine.hpp @@ -1,17 +1,17 @@ #pragma once +#include "Assets/Model.hpp" +#include "Assets/UniformBuffer.hpp" #include "Common/CoreMinimal.hpp" +#include "Options.hpp" +#include "Rendering/VulkanBaseRenderer.hpp" +#include "Runtime/Command/CommandSystem.hpp" #include "SceneList.hpp" -#include "UserSettings.hpp" #include "ShowFlags.hpp" -#include "Assets/UniformBuffer.hpp" -#include "Assets/Model.hpp" +#include "UserSettings.hpp" +#include "Utilities/FileHelper.hpp" #include "Vulkan/FrameBuffer.hpp" #include "Vulkan/Window.hpp" -#include "Rendering/VulkanBaseRenderer.hpp" -#include "Options.hpp" -#include "Utilities/FileHelper.hpp" -#include "Runtime/Command/CommandSystem.hpp" class NextPhysics; class QuickJSEngine; @@ -23,267 +23,288 @@ class NextAnimation; class NextGameInstanceBase { public: - NextGameInstanceBase(Vulkan::WindowConfig& config, Options& options, NextEngine* engine){} - virtual ~NextGameInstanceBase() {} - virtual void OnInit() =0; - virtual void OnTick(double deltaSeconds) =0; - virtual void OnDestroy() =0; - virtual bool OnRenderUI() =0; - virtual void OnPreConfigUI() {} - virtual void OnInitUI() {} - virtual void OnRayHitResponse(Assets::RayCastResult& result) {} - - // camera - virtual bool OverrideRenderCamera(Assets::Camera& OutRenderCamera) const {return false;} - - // scene - virtual void BeforeSceneRebuild(std::vector>& nodes, std::vector& models, std::vector& materials, std::vector& lights, - std::vector& tracks) {} - virtual void OnSceneLoaded() {} - virtual void OnSceneUnloaded() {} - - // input - virtual bool OnKey(SDL_Event& event) {return false;} - virtual bool OnCursorPosition(double xpos, double ypos) {return false;} - virtual bool OnMouseButton(SDL_Event& event) {return false;} - virtual bool OnScroll(double xoffset, double yoffset) {return false;} - virtual bool OnGamepadInput(int16_t leftStickX, int16_t leftStickY, - int16_t rightStickX, int16_t rightStickY, - int16_t leftTrigger, int16_t rightTrigger) {return false;} + NextGameInstanceBase(Vulkan::WindowConfig& config, Options& options, NextEngine* engine) {} + virtual ~NextGameInstanceBase() {} + virtual void OnInit() = 0; + virtual void OnTick(double deltaSeconds) = 0; + virtual void OnDestroy() = 0; + virtual bool OnRenderUI() = 0; + virtual void OnPreConfigUI() {} + virtual void OnInitUI() {} + virtual void OnRayHitResponse(Assets::RayCastResult& result) {} + + // camera + virtual bool OverrideRenderCamera(Assets::Camera& OutRenderCamera) const { return false; } + + // scene + virtual void BeforeSceneRebuild(std::vector>& nodes, + std::vector& models, std::vector& materials, + std::vector& lights, + std::vector& tracks) + { + } + virtual void OnSceneLoaded() {} + virtual void OnSceneUnloaded() {} + + // input + virtual bool OnKey(SDL_Event& event) { return false; } + virtual bool OnCursorPosition(double xpos, double ypos) { return false; } + virtual bool OnMouseButton(SDL_Event& event) { return false; } + virtual bool OnScroll(double xoffset, double yoffset) { return false; } + virtual bool OnGamepadInput(int16_t leftStickX, int16_t leftStickY, int16_t rightStickX, int16_t rightStickY, + int16_t leftTrigger, int16_t rightTrigger) + { + return false; + } }; class NextGameInstanceVoid : public NextGameInstanceBase { public: - NextGameInstanceVoid(Vulkan::WindowConfig& config, Options& options, NextEngine* engine):NextGameInstanceBase(config,options,engine){} - ~NextGameInstanceVoid() override = default; - - void OnInit() override {} - void OnTick(double deltaSeconds) override {} - void OnDestroy() override {} - bool OnRenderUI() override {return false;} + NextGameInstanceVoid(Vulkan::WindowConfig& config, Options& options, NextEngine* engine) : + NextGameInstanceBase(config, options, engine) + { + } + ~NextGameInstanceVoid() override = default; + + void OnInit() override {} + void OnTick(double deltaSeconds) override {} + void OnDestroy() override {} + bool OnRenderUI() override { return false; } }; -extern std::unique_ptr CreateGameInstance(Vulkan::WindowConfig& config, Options& options, NextEngine* engine); +extern std::unique_ptr CreateGameInstance(Vulkan::WindowConfig& config, Options& options, + NextEngine* engine); namespace NextRenderer { - enum class EApplicationStatus - { - Starting, - Running, - Loading, - AsyncPreparing, - }; - std::string GetBuildVersion(); - Vulkan::VulkanBaseRenderer* CreateRenderer(uint32_t rendererType, Vulkan::Window* window, const VkPresentModeKHR presentMode, const bool enableValidationLayers); -} - -typedef std::function TickedTask; -typedef std::function DelayedTask; + enum class EApplicationStatus + { + Starting, + Running, + Loading, + AsyncPreparing, + }; + std::string GetBuildVersion(); + Vulkan::VulkanBaseRenderer* CreateRenderer(uint32_t rendererType, Vulkan::Window* window, + const VkPresentModeKHR presentMode, const bool enableValidationLayers); +} // namespace NextRenderer + +typedef std::function TickedTask; +typedef std::function DelayedTask; struct FDelayTaskContext { - double triggerTime; - double loopTime; - DelayedTask task; + double triggerTime; + double loopTime; + DelayedTask task; }; class NextComponent : std::enable_shared_from_this { public: - NextComponent() = default; - std::string name_; - int id_; + NextComponent() = default; + std::string name_; + int id_; }; class NextActor { public: - std::vector components; + std::vector components; }; class NextEngine final { public: - VULKAN_NON_COPIABLE(NextEngine) + VULKAN_NON_COPIABLE(NextEngine) - NextEngine(Options& options, void* userdata = nullptr); - ~NextEngine(); + NextEngine(Options& options, void* userdata = nullptr); + ~NextEngine(); - static NextEngine* instance_; - static NextEngine* GetInstance() { return instance_; } + static NextEngine* instance_; + static NextEngine* GetInstance() { return instance_; } - Vulkan::VulkanBaseRenderer& GetRenderer() { return *renderer_; } + Vulkan::VulkanBaseRenderer& GetRenderer() { return *renderer_; } - void Start(); - bool HandleEvent(SDL_Event& event); - bool Tick(bool forcingDelta = false); - void End(); - - void OnTouch(bool down, double xpos, double ypos); - void OnTouchMove(double xpos, double ypos); + void Start(); + bool HandleEvent(SDL_Event& event); + bool Tick(bool forcingDelta = false); + void End(); - Assets::Scene& GetScene() { return *scene_; } - Assets::Scene* GetScenePtr() { return scene_.get(); } - UserSettings& GetUserSettings() { return userSettings_; } - ShowFlags& GetShowFlags() { return showFlags_; } + void OnTouch(bool down, double xpos, double ypos); + void OnTouchMove(double xpos, double ypos); - float GetTime() const { return static_cast(time_); } - float GetDeltaSeconds() const { return static_cast(deltaSeconds_); } - float GetSmoothDeltaSeconds() const { return static_cast(smoothedDeltaSeconds_); } + Assets::Scene& GetScene() { return *scene_; } + Assets::Scene* GetScenePtr() { return scene_.get(); } + UserSettings& GetUserSettings() { return userSettings_; } + ShowFlags& GetShowFlags() { return showFlags_; } + + float GetTime() const { return static_cast(time_); } + float GetDeltaSeconds() const { return static_cast(deltaSeconds_); } + float GetSmoothDeltaSeconds() const { return static_cast(smoothedDeltaSeconds_); } float GetFrameRate() const { return frameRate_; } - uint32_t GetTotalFrames() const { return totalFrames_; } - uint32_t GetTestNumber() const { return 20; } + uint32_t GetTotalFrames() const { return totalFrames_; } + uint32_t GetTestNumber() const { return 20; } + + void RegisterJSCallback(std::function callback); + + // remove till return true + void AddTickedTask(TickedTask task) { tickedTasks_.push_back(task); } + void AddTimerTask(double delay, DelayedTask task); - void RegisterJSCallback(std::function callback); + // sound + void PlaySound(const std::string& soundName, bool loop = false, float volume = 1.0f); + void PauseSound(const std::string& soundName, bool pause); + bool IsSoundPlaying(const std::string& soundName); - // remove till return true - void AddTickedTask( TickedTask task ) { tickedTasks_.push_back(task); } - void AddTimerTask( double delay, DelayedTask task ); + // screen shot + void SaveScreenShot(const std::string& filename, int x, int y, int width, int height); - // sound - void PlaySound(const std::string& soundName, bool loop = false, float volume = 1.0f); - void PauseSound(const std::string& soundName, bool pause); - bool IsSoundPlaying(const std::string& soundName); + // pak + Utilities::Package::FPackageFileSystem& GetPakSystem() { return *packageFileSystem_; } - // screen shot - void SaveScreenShot(const std::string& filename, int x, int y, int width, int height); + // cursor pos + glm::dvec2 GetMousePos(); - // pak - Utilities::Package::FPackageFileSystem& GetPakSystem() { return *packageFileSystem_; } + // life cycle + void RequestClose(); + void RequestMinimize(); + bool IsMaximumed(); + void ToggleMaximize(); - // cursor pos - glm::dvec2 GetMousePos(); + // capture + void RequestScreenShot(std::string filename); - // life cycle - void RequestClose(); - void RequestMinimize(); - bool IsMaximumed(); - void ToggleMaximize(); + // scene loading + void RequestLoadScene(std::string sceneFileName); + void RequestLoadSceneAdd(std::string sceneFileName); - // capture - void RequestScreenShot(std::string filename); + struct SceneAppendOptions + { + bool placeOnHit = false; + glm::vec3 hitPosition{0.0f, 0.0f, 0.0f}; + }; - // scene loading - void RequestLoadScene(std::string sceneFileName); - void RequestLoadSceneAdd(std::string sceneFileName); + void RequestLoadSceneAdd(std::string sceneFileName, const SceneAppendOptions& options); - // command system - bool ExecuteCommand(std::unique_ptr command); - bool UndoCommand(); - bool RedoCommand(); - bool CanUndo() const; - bool CanRedo() const; - CommandSystem& GetCommandSystem() { return commandSystem_; } - const CommandSystem& GetCommandSystem() const { return commandSystem_; } + // command system + bool ExecuteCommand(std::unique_ptr command); + bool UndoCommand(); + bool RedoCommand(); + bool CanUndo() const; + bool CanRedo() const; + CommandSystem& GetCommandSystem() { return commandSystem_; } + const CommandSystem& GetCommandSystem() const { return commandSystem_; } - Vulkan::Window& GetWindow() const {return *window_;} - - class UserInterface* GetUserInterface() {return userInterface_.get();} + Vulkan::Window& GetWindow() const { return *window_; } - // monitor info - glm::ivec2 GetMonitorSize() const; + class UserInterface* GetUserInterface() { return userInterface_.get(); } - // gpu raycast - void RayCastGPU(glm::vec3 rayOrigin, glm::vec3 rayDir, std::function callback ); + // monitor info + glm::ivec2 GetMonitorSize() const; - void SetProgressiveRendering(bool enable, bool directly); - bool IsProgressiveRendering() const { return progressiveRendering_; } + // gpu raycast + void RayCastGPU(glm::vec3 rayOrigin, glm::vec3 rayDir, + std::function callback); - NextRenderer::EApplicationStatus GetEngineStatus() const { return status_; } + void SetProgressiveRendering(bool enable, bool directly); + bool IsProgressiveRendering() const { return progressiveRendering_; } - NextPhysics* GetPhysicsEngine() { return physicsEngine_.get(); } + NextRenderer::EApplicationStatus GetEngineStatus() const { return status_; } - Assets::UniformBufferObject& GetUniformBufferObject() { return prevUBO_; } + NextPhysics* GetPhysicsEngine() { return physicsEngine_.get(); } + + Assets::UniformBufferObject& GetUniformBufferObject() { return prevUBO_; } + + VkDeviceAddress TryGetGPUAccelerationStructureAddress() const; + VkAccelerationStructureKHR TryGetGPUAccelerationStructureHandle() const; - VkDeviceAddress TryGetGPUAccelerationStructureAddress() const; - VkAccelerationStructureKHR TryGetGPUAccelerationStructureHandle() const; - protected: - Assets::UniformBufferObject GetUniformBufferObject(const VkOffset2D offset, const VkExtent2D extent); - void OnRendererDeviceSet(); - void OnRendererCreateSwapChain(); - void OnRendererDeleteSwapChain(); - void OnRendererPostRender(VkCommandBuffer commandBuffer, uint32_t imageIndex); - void OnRendererBeforeNextFrame(); - - const Assets::Scene& GetScene() const { return *scene_; } - - void OnKey(SDL_Event& event); - void OnCursorPosition(double xpos, double ypos); - void OnMouseButton(SDL_Event& event); - void OnScroll(double xoffset, double yoffset); - void OnDropFile(const char* path); + Assets::UniformBufferObject GetUniformBufferObject(const VkOffset2D offset, const VkExtent2D extent); + void OnRendererDeviceSet(); + void OnRendererCreateSwapChain(); + void OnRendererDeleteSwapChain(); + void OnRendererPostRender(VkCommandBuffer commandBuffer, uint32_t imageIndex); + void OnRendererBeforeNextFrame(); + + const Assets::Scene& GetScene() const { return *scene_; } + + void OnKey(SDL_Event& event); + void OnCursorPosition(double xpos, double ypos); + void OnMouseButton(SDL_Event& event); + void OnScroll(double xoffset, double yoffset); + void OnDropFile(const char* path); void TickGamepadInput(); - + private: - struct SceneLoadContext - { - std::shared_ptr< std::vector > models; - std::shared_ptr< std::vector< std::shared_ptr > > nodes; - std::shared_ptr< std::vector > materials; - std::shared_ptr< std::vector > lights; - std::shared_ptr< std::vector > tracks; - std::shared_ptr< std::vector > skeletons; - std::shared_ptr< Assets::EnvironmentSetting > cameraState; - }; - - void LaunchLoadSceneTask(std::string sceneFileName, std::function onGpuLoad); - - void LoadScene(std::string sceneFileName); - void LoadSceneAdd(std::string sceneFileName); - - void InitPhysics(); - - // engine stuff - std::unique_ptr window_; - std::unique_ptr renderer_; - - // settings, may move into scene - mutable UserSettings userSettings_{}; - mutable ShowFlags showFlags_{}; - mutable Assets::UniformBufferObject prevUBO_ {}; - - // scene, maybe multiple at a time - std::shared_ptr scene_; - - // timing - uint32_t totalFrames_{}; - double time_{}; - double deltaSeconds_{}; - double smoothedDeltaSeconds_{}; + struct SceneLoadContext + { + std::shared_ptr> models; + std::shared_ptr>> nodes; + std::shared_ptr> materials; + std::shared_ptr> lights; + std::shared_ptr> tracks; + std::shared_ptr> skeletons; + std::shared_ptr cameraState; + }; + + void LaunchLoadSceneTask(std::string sceneFileName, std::function onGpuLoad); + + void LoadScene(std::string sceneFileName); + void LoadSceneAdd(std::string sceneFileName); + void LoadSceneAdd(std::string sceneFileName, const SceneAppendOptions& options); + + void InitPhysics(); + + // engine stuff + std::unique_ptr window_; + std::unique_ptr renderer_; + + // settings, may move into scene + mutable UserSettings userSettings_{}; + mutable ShowFlags showFlags_{}; + mutable Assets::UniformBufferObject prevUBO_{}; + + // scene, maybe multiple at a time + std::shared_ptr scene_; + + // timing + uint32_t totalFrames_{}; + double time_{}; + double deltaSeconds_{}; + double smoothedDeltaSeconds_{}; float frameRate_{}; double lastFrameTime_{}; - bool progressiveRendering_{}; - uint32_t progressivePreFrames_{}; + bool progressiveRendering_{}; + uint32_t progressivePreFrames_{}; + + // game instance + std::unique_ptr gameInstance_; + + // tasks + std::vector tickedTasks_; + std::vector delayedTasks_; - // game instance - std::unique_ptr gameInstance_; + // internal ui + std::unique_ptr userInterface_; - // tasks - std::vector tickedTasks_; - std::vector delayedTasks_; + // audio + std::unique_ptr audioEngine_; - // internal ui - std::unique_ptr userInterface_; + // physics + std::unique_ptr physicsEngine_; - // audio - std::unique_ptr audioEngine_; - - // physics - std::unique_ptr physicsEngine_; + // animation + std::unique_ptr animationEngine_; - // animation - std::unique_ptr animationEngine_; + // package + std::unique_ptr packageFileSystem_; - // package - std::unique_ptr packageFileSystem_; + std::unique_ptr quickJSEngine_; - std::unique_ptr quickJSEngine_; + CommandSystem commandSystem_{}; - CommandSystem commandSystem_{}; - - // engine status - NextRenderer::EApplicationStatus status_{}; + // engine status + NextRenderer::EApplicationStatus status_{}; }; From d39997352c600a68928ba702b9b07f10e007889f Mon Sep 17 00:00:00 2001 From: gameKnife Date: Fri, 30 Jan 2026 01:14:34 +0800 Subject: [PATCH 16/53] feat(reflection): add unified property reflection system with entt::meta - Add PropertyAccessor, PropertyTypes, PropertyMeta for reflection core - Add PropertyWidgets for auto-generated ImGui property editing - Add CommandHistory and PropertyCommand for undo/redo support - Register RenderComponent, PhysicsComponent, SkinnedMeshComponent properties - Support arrays (std::array, std::vector) and enum types - Refactor DrawArray() with template helper to reduce duplication - Add centralized ContainerTypeInfo registry in PropertyAccessor - Add AGENT_GUIDE/ReflectionSystem.md documentation Co-authored-by: claude-opus-4.5 --- AGENT_GUIDE/ReflectionSystem.md | 276 +++++ src/Assets/CPUAccelerationStructure.cpp | 4 +- src/Assets/Component.h | 8 + src/Assets/Node.cpp | 2 +- src/Assets/Node.h | 9 + src/Assets/Scene.cpp | 8 +- src/Editor/Commands/CommandHistory.cpp | 238 +++++ src/Editor/Commands/CommandHistory.h | 112 ++ src/Editor/Commands/ICommand.h | 49 + src/Editor/Commands/PropertyCommand.cpp | 86 ++ src/Editor/Commands/PropertyCommand.h | 193 ++++ src/Editor/Core/EditorUiState.hpp | 4 + src/Editor/EditorInterface.cpp | 17 + src/Editor/EditorMain.cpp | 4 +- src/Editor/Overlays/TitleBarOverlay.cpp | 22 + src/Editor/Panels/PropertiesPanel.cpp | 22 + src/Editor/Panels/PropertyWidgets.cpp | 961 ++++++++++++++++++ src/Editor/Panels/PropertyWidgets.h | 122 +++ src/Runtime/Command/DuplicateNodeCommand.cpp | 4 +- src/Runtime/Components/PhysicsComponent.cpp | 31 + src/Runtime/Components/PhysicsComponent.h | 5 +- src/Runtime/Components/RenderComponent.cpp | 30 + src/Runtime/Components/RenderComponent.h | 13 +- .../Components/SkinnedMeshComponent.cpp | 24 + src/Runtime/Components/SkinnedMeshComponent.h | 4 + src/Runtime/Engine.cpp | 4 + src/Runtime/Reflection/GlmJsonConverter.h | 85 ++ src/Runtime/Reflection/GlmTypeSupport.h | 71 ++ src/Runtime/Reflection/PropertyAccessor.cpp | 284 ++++++ src/Runtime/Reflection/PropertyAccessor.h | 35 + src/Runtime/Reflection/PropertyMeta.h | 117 +++ src/Runtime/Reflection/PropertyTypes.h | 76 ++ .../Reflection/QuickJSReflectionBridge.h | 124 +++ .../Reflection/QuickJSTypeConverter.cpp | 402 ++++++++ src/Runtime/Reflection/QuickJSTypeConverter.h | 55 + src/Runtime/Reflection/ReflectionMacros.h | 19 + src/Runtime/Reflection/ReflectionRegistry.cpp | 36 + src/Runtime/Reflection/ReflectionRegistry.h | 11 + src/Tests/Test_ComponentSystem.cpp | 5 + src/Tests/Test_RenderComponent.cpp | 4 +- src/Utilities/Glm.hpp | 1 + vcpkg.json | 1 + 42 files changed, 3561 insertions(+), 17 deletions(-) create mode 100644 AGENT_GUIDE/ReflectionSystem.md create mode 100644 src/Editor/Commands/CommandHistory.cpp create mode 100644 src/Editor/Commands/CommandHistory.h create mode 100644 src/Editor/Commands/ICommand.h create mode 100644 src/Editor/Commands/PropertyCommand.cpp create mode 100644 src/Editor/Commands/PropertyCommand.h create mode 100644 src/Editor/Panels/PropertyWidgets.cpp create mode 100644 src/Editor/Panels/PropertyWidgets.h create mode 100644 src/Runtime/Components/PhysicsComponent.cpp create mode 100644 src/Runtime/Components/RenderComponent.cpp create mode 100644 src/Runtime/Reflection/GlmJsonConverter.h create mode 100644 src/Runtime/Reflection/GlmTypeSupport.h create mode 100644 src/Runtime/Reflection/PropertyAccessor.cpp create mode 100644 src/Runtime/Reflection/PropertyAccessor.h create mode 100644 src/Runtime/Reflection/PropertyMeta.h create mode 100644 src/Runtime/Reflection/PropertyTypes.h create mode 100644 src/Runtime/Reflection/QuickJSReflectionBridge.h create mode 100644 src/Runtime/Reflection/QuickJSTypeConverter.cpp create mode 100644 src/Runtime/Reflection/QuickJSTypeConverter.h create mode 100644 src/Runtime/Reflection/ReflectionMacros.h create mode 100644 src/Runtime/Reflection/ReflectionRegistry.cpp create mode 100644 src/Runtime/Reflection/ReflectionRegistry.h diff --git a/AGENT_GUIDE/ReflectionSystem.md b/AGENT_GUIDE/ReflectionSystem.md new file mode 100644 index 00000000..6b2f069d --- /dev/null +++ b/AGENT_GUIDE/ReflectionSystem.md @@ -0,0 +1,276 @@ +# Reflection System Documentation + +This document describes the unified property reflection system using **entt::meta** for gkNextRenderer. + +## Overview + +The reflection system provides: +1. **Editor PropertyPanel** - Auto-generate property editing UI +2. **QuickJS JavaScript bindings** - Auto-expose component properties to JS +3. **Undo/Redo system** - Property modification history with Ctrl+Z/Ctrl+Y + +## Architecture + +``` +src/Runtime/Reflection/ +├── PropertyTypes.h # PropertyType enum and PropertyInfo struct +├── PropertyMeta.h # PropertyMeta (displayName, category, flags) +├── PropertyAccessor.h/cpp # Get/set properties via reflection +├── GlmTypeSupport.h # GLM types + container types registration +├── ReflectionMacros.h # REFLECT_COMPONENT macro +└── ReflectionRegistry.h/cpp # Central registration + +src/Editor/Panels/ +├── PropertyWidgets.h/cpp # ImGui widgets based on PropertyType + +src/Editor/Commands/ +├── CommandHistory.h/cpp # Undo/redo stack +└── PropertyCommand.h/cpp # Property modification command +``` + +## Supported Property Types + +| PropertyType | C++ Type | ImGui Widget | +|--------------|----------|--------------| +| Bool | `bool` | Checkbox | +| Int32 | `int32_t` | DragInt | +| UInt32 | `uint32_t` | DragInt (clamped) | +| Float | `float` | DragFloat | +| Double | `double` | DragFloat | +| String | `std::string` | InputText | +| Vec2 | `glm::vec2` | DragFloat2 | +| Vec3 | `glm::vec3` | DragFloat3 / ColorEdit3 | +| Vec4 | `glm::vec4` | DragFloat4 / ColorEdit4 | +| Quat | `glm::quat` | DragFloat3 (euler) | +| Enum | Any registered enum | Combo dropdown | +| Array | std::array/std::vector | TreeNode with elements | + +## How to Add New Component Properties + +### 1. Register the Component + +In your component's `.cpp` file, add registration in an anonymous namespace: + +```cpp +#include "Runtime/Reflection/ReflectionRegistry.h" +#include "Runtime/Reflection/ReflectionMacros.h" + +namespace +{ + struct MyComponentReflection + { + MyComponentReflection() + { + REFLECT_COMPONENT(MyComponent) + .data<&MyComponent::myProperty>("MyProperty"_hs) + .custom("My Property", "General") + .data<&MyComponent::anotherProp>("AnotherProp"_hs) + .custom("Another Prop", "Settings"); + } + }; + + static MyComponentReflection registration; +} +``` + +### 2. Key Points + +- Use `.data<&Class::member>("Name"_hs)` - the string literal sets both ID and name +- Add `.custom(displayName, category)` for UI metadata +- Registration happens automatically at static initialization time +- The component must inherit from `Assets::Component` and implement `GetMetaType()` + +### 3. PropertyMeta Options + +```cpp +PropertyMeta meta; +meta.displayName = "Display Name"; // Shown in UI +meta.category = "Category"; // Groups properties under headers +meta.flags = PropertyFlags::ReadOnly; // Make read-only +meta.flags = PropertyFlags::Hidden; // Hide from UI +``` + +## How to Add New Property Types + +### 1. Add to PropertyType Enum + +In `PropertyTypes.h`: +```cpp +enum class PropertyType +{ + // ... existing types ... + NewType, +}; +``` + +### 2. Register the Type in GlmTypeSupport.h + +```cpp +inline void RegisterContainerTypes() +{ + entt::meta() + .type("NewCppType"_hs); +} +``` + +### 3. Update DeducePropertyType() + +In `PropertyAccessor.cpp`: +```cpp +if (typeId == entt::resolve().id()) + return PropertyType::NewType; +``` + +### 4. Add Widget Drawing + +In `PropertyWidgets.cpp`: +```cpp +case PropertyType::NewType: +{ + if (auto* ptr = currentValue.try_cast()) + { + NewCppType val = *ptr; + if (DrawNewType(label, val, isReadOnly)) + { + changed = true; + currentValue = entt::meta_any{val}; + } + } + break; +} +``` + +## Array/Container Support + +### Supported Container Types + +The system explicitly supports: +- `std::array` (for Materials) +- `std::vector` +- `std::vector` +- `std::vector` +- `std::vector` + +### Adding New Container Types + +1. Register in `GlmTypeSupport.h`: +```cpp +entt::meta>() + .type("std::vector"_hs); +``` + +2. Add to container registry in `PropertyAccessor.cpp`: +```cpp +static const ContainerTypeInfo containerTypes[] = { + // ... existing types ... + { entt::resolve>().id(), PropertyType::MyType }, +}; +``` + +3. Add handling in `PropertyWidgets.cpp::DrawArray()`: +```cpp +if (auto* vec = arrayValue.try_cast>()) +{ + return DrawContainerElements, MyType>( + label, *vec, vec->size(), readOnly, + [](const char* lbl, MyType& val, bool ro) { + return DrawMyType(lbl, val, ro); + }); +} +``` + +## Enum Registration + +### Registering an Enum + +```cpp +entt::meta() + .type("EMyEnum"_hs) + .data("Value1"_hs) + .data("Value2"_hs); +``` + +### Using in Components + +```cpp +REFLECT_COMPONENT(MyComponent) + .data<&MyComponent::myEnum>("MyEnum"_hs) + .custom("My Enum", "Settings"); +``` + +The enum will automatically render as a dropdown in the property panel. + +## Undo/Redo System + +### How It Works + +1. `PropertyWidgets::DrawProperty()` captures old value before drawing +2. If value changes, creates a `PropertyCommand` with old and new values +3. `CommandHistory::Execute()` runs the command and pushes to undo stack +4. Ctrl+Z calls `CommandHistory::Undo()` to restore previous value +5. Ctrl+Y calls `CommandHistory::Redo()` to reapply the change + +### Using in Editor + +```cpp +// In your panel/editor code: +static CommandHistory history; + +// Draw properties with undo support +PropertyWidgets::DrawComponentProperties(component, &history); + +// Handle hotkeys +if (ImGui::IsKeyPressed(ImGuiKey_Z) && ImGui::GetIO().KeyCtrl) + history.Undo(); +if (ImGui::IsKeyPressed(ImGuiKey_Y) && ImGui::GetIO().KeyCtrl) + history.Redo(); +``` + +## Technical Notes + +### entt v3.x API + +- Use string literals `.data<>("Name")` to set both ID and name +- Access metadata via `.custom()` (not `.prop()`) +- Use `meta_type::from_void(void*)` to wrap raw pointers + +### std::array Limitation + +`std::array` doesn't work automatically with `as_sequence_container()` - the system uses explicit `try_cast<>()` for known array types. + +### Unity Build + +The project uses unity builds. New `.cpp` files are auto-detected via `GLOB_RECURSE` in CMake. + +## Example: Complete Component Registration + +```cpp +// PhysicsComponent.cpp +#include "PhysicsComponent.h" +#include "Runtime/Reflection/ReflectionRegistry.h" +#include "Runtime/Reflection/ReflectionMacros.h" + +namespace +{ + struct PhysicsComponentReflection + { + PhysicsComponentReflection() + { + // Register the enum first + entt::meta() + .type("ENodeMobility"_hs) + .data("Static"_hs) + .data("Movable"_hs); + + // Register the component + REFLECT_COMPONENT(PhysicsComponent) + .data<&PhysicsComponent::Mobility>("Mobility"_hs) + .custom("Mobility", "Physics") + .data<&PhysicsComponent::PhysicsOffset>("PhysicsOffset"_hs) + .custom("Physics Offset", "Physics"); + } + }; + + static PhysicsComponentReflection registration; +} +``` diff --git a/src/Assets/CPUAccelerationStructure.cpp b/src/Assets/CPUAccelerationStructure.cpp index 3cce55a3..40b8ee00 100644 --- a/src/Assets/CPUAccelerationStructure.cpp +++ b/src/Assets/CPUAccelerationStructure.cpp @@ -248,8 +248,8 @@ void FCPUAccelerationStructure::UpdateBVH(Scene& scene) if (!render) continue; uint32_t modelId = render->GetModelId(); if (modelId == -1) continue; - if (!render->IsVisible()) continue; - if (!render->IsRayCastVisible()) continue; + if (!render->GetVisible()) continue; + if (!render->GetRayCastVisible()) continue; node->RecalcTransform(true); mat4 worldTS = node->WorldTransform(); diff --git a/src/Assets/Component.h b/src/Assets/Component.h index 4d38b3c9..775fdadc 100644 --- a/src/Assets/Component.h +++ b/src/Assets/Component.h @@ -1,6 +1,8 @@ #pragma once #include #include +#include +#include namespace Assets { @@ -11,6 +13,12 @@ namespace Assets public: virtual ~Component() = default; + // Get component type name for reflection lookup + virtual std::string_view GetTypeName() const = 0; + + // Get entt meta type for property access + virtual entt::meta_type GetMetaType() const = 0; + void SetOwner(Node* owner) { owner_ = owner; } Node* GetOwner() const { return owner_; } diff --git a/src/Assets/Node.cpp b/src/Assets/Node.cpp index 3b1f5a25..aee79011 100644 --- a/src/Assets/Node.cpp +++ b/src/Assets/Node.cpp @@ -161,7 +161,7 @@ namespace Assets if (renderComp) { proxy.modelId = renderComp->GetModelId(); - proxy.visible = renderComp->IsVisible() ? 1 : 0; + proxy.visible = renderComp->GetVisible() ? 1 : 0; const auto& mats = renderComp->Materials(); for ( int i = 0; i < 16; i++ ) { diff --git a/src/Assets/Node.h b/src/Assets/Node.h index 3b1951bf..0c7aeed8 100644 --- a/src/Assets/Node.h +++ b/src/Assets/Node.h @@ -97,6 +97,15 @@ namespace Assets return nullptr; } + /** + * Get all components attached to this node. + * Useful for reflection-based iteration. + */ + const std::vector>& GetComponents() const + { + return components_; + } + private: std::string name_; diff --git a/src/Assets/Scene.cpp b/src/Assets/Scene.cpp index d95d4517..bfe5c739 100644 --- a/src/Assets/Scene.cpp +++ b/src/Assets/Scene.cpp @@ -253,7 +253,7 @@ namespace Assets for (auto& node : nodes_) { auto render = node->GetComponent(); - if (render && render->IsVisible() && render->GetModelId() != -1) + if (render && render->GetVisible() && render->GetModelId() != -1) { glm::vec3 localaabbMin = models_[render->GetModelId()].GetLocalAABBMin(); glm::vec3 localaabbMax = models_[render->GetModelId()].GetLocalAABBMax(); @@ -344,7 +344,7 @@ namespace Assets } phys->BindPhysicsBody(id); - physicsEngine->SetBodyActive(id, render->IsVisible()); + physicsEngine->SetBodyActive(id, render->GetVisible()); } } } @@ -560,7 +560,7 @@ namespace Assets } phys->BindPhysicsBody(id); - physicsEngine->SetBodyActive(id, render->IsVisible()); + physicsEngine->SetBodyActive(id, render->GetVisible()); } } } @@ -831,7 +831,7 @@ namespace Assets for (auto& node : nodes_) { auto render = node->GetComponent(); - if (!render || !render->IsVisible() || !render->IsDrawable()) + if (!render || !render->GetVisible() || !render->IsDrawable()) { continue; } diff --git a/src/Editor/Commands/CommandHistory.cpp b/src/Editor/Commands/CommandHistory.cpp new file mode 100644 index 00000000..018adfdd --- /dev/null +++ b/src/Editor/Commands/CommandHistory.cpp @@ -0,0 +1,238 @@ +#include "CommandHistory.h" +#include + +namespace Editor +{ + /** + * GroupCommand combines multiple commands into a single undoable unit. + */ + class GroupCommand : public ICommand + { + public: + GroupCommand(std::vector&& commands, std::string description) + : commands_(std::move(commands)) + , description_(std::move(description)) + { + } + + bool Execute() override + { + for (auto& cmd : commands_) + { + if (!cmd->Execute()) + { + return false; + } + } + return true; + } + + bool Undo() override + { + // Undo in reverse order + for (auto it = commands_.rbegin(); it != commands_.rend(); ++it) + { + if (!(*it)->Undo()) + { + return false; + } + } + return true; + } + + std::string GetDescription() const override + { + return description_; + } + + private: + std::vector commands_; + std::string description_; + }; + + void CommandHistory::Execute(CommandPtr command) + { + if (!command) + { + return; + } + + // If in a command group, add to group instead of executing directly + if (inGroup_) + { + command->Execute(); + groupCommands_.push_back(std::move(command)); + NotifyHistoryChanged(); + return; + } + + // Try to merge with previous command if enabled + if (mergeEnabled_ && !undoStack_.empty()) + { + ICommand* prevCommand = undoStack_.back().get(); + if (prevCommand->CanMergeWith(command.get())) + { + prevCommand->MergeWith(command.get()); + command->Execute(); // Execute the new command to update state + NotifyHistoryChanged(); + return; + } + } + + // Execute the command + if (command->Execute()) + { + SPDLOG_DEBUG("Executed command: {}", command->GetDescription()); + + // Clear redo stack when new command is executed + redoStack_.clear(); + + // Add to undo stack + undoStack_.push_back(std::move(command)); + + // Enforce max history size + while (undoStack_.size() > maxHistorySize) + { + undoStack_.pop_front(); + } + + NotifyHistoryChanged(); + } + else + { + SPDLOG_WARN("Command execution failed: {}", command->GetDescription()); + } + } + + bool CommandHistory::Undo() + { + if (undoStack_.empty()) + { + return false; + } + + auto command = std::move(undoStack_.back()); + undoStack_.pop_back(); + + if (command->Undo()) + { + SPDLOG_DEBUG("Undone command: {}", command->GetDescription()); + redoStack_.push_back(std::move(command)); + NotifyHistoryChanged(); + return true; + } + else + { + SPDLOG_WARN("Undo failed: {}", command->GetDescription()); + // Put command back if undo failed + undoStack_.push_back(std::move(command)); + return false; + } + } + + bool CommandHistory::Redo() + { + if (redoStack_.empty()) + { + return false; + } + + auto command = std::move(redoStack_.back()); + redoStack_.pop_back(); + + if (command->Execute()) + { + SPDLOG_DEBUG("Redone command: {}", command->GetDescription()); + undoStack_.push_back(std::move(command)); + NotifyHistoryChanged(); + return true; + } + else + { + SPDLOG_WARN("Redo failed: {}", command->GetDescription()); + // Put command back if redo failed + redoStack_.push_back(std::move(command)); + return false; + } + } + + std::string CommandHistory::GetUndoDescription() const + { + if (undoStack_.empty()) + { + return ""; + } + return undoStack_.back()->GetDescription(); + } + + std::string CommandHistory::GetRedoDescription() const + { + if (redoStack_.empty()) + { + return ""; + } + return redoStack_.back()->GetDescription(); + } + + void CommandHistory::Clear() + { + undoStack_.clear(); + redoStack_.clear(); + groupCommands_.clear(); + inGroup_ = false; + NotifyHistoryChanged(); + } + + void CommandHistory::BeginGroup(const std::string& description) + { + if (inGroup_) + { + SPDLOG_WARN("Already in a command group, ending previous group"); + EndGroup(); + } + inGroup_ = true; + groupDescription_ = description; + groupCommands_.clear(); + } + + void CommandHistory::EndGroup() + { + if (!inGroup_) + { + return; + } + + inGroup_ = false; + + if (groupCommands_.empty()) + { + return; + } + + // Create a group command from all accumulated commands + auto groupCmd = std::make_unique( + std::move(groupCommands_), + groupDescription_ + ); + + // Don't re-execute, the commands were already executed + // Just add to undo stack + redoStack_.clear(); + undoStack_.push_back(std::move(groupCmd)); + + while (undoStack_.size() > maxHistorySize) + { + undoStack_.pop_front(); + } + + NotifyHistoryChanged(); + } + + void CommandHistory::NotifyHistoryChanged() + { + if (onHistoryChanged_) + { + onHistoryChanged_(); + } + } +} diff --git a/src/Editor/Commands/CommandHistory.h b/src/Editor/Commands/CommandHistory.h new file mode 100644 index 00000000..4bdca987 --- /dev/null +++ b/src/Editor/Commands/CommandHistory.h @@ -0,0 +1,112 @@ +#pragma once + +#include "ICommand.h" +#include +#include + +namespace Editor +{ + /** + * Manages the undo/redo stack for editor commands. + * Provides functionality for executing, undoing, and redoing commands. + */ + class CommandHistory + { + public: + // Maximum number of commands to keep in history + static constexpr size_t maxHistorySize = 100; + + /** + * Execute a command and add it to the history. + * This clears the redo stack. + */ + void Execute(CommandPtr command); + + /** + * Undo the most recent command. + * @return true if there was a command to undo + */ + bool Undo(); + + /** + * Redo the most recently undone command. + * @return true if there was a command to redo + */ + bool Redo(); + + /** + * Check if there are commands that can be undone. + */ + bool CanUndo() const { return !undoStack_.empty(); } + + /** + * Check if there are commands that can be redone. + */ + bool CanRedo() const { return !redoStack_.empty(); } + + /** + * Get description of the command that would be undone. + */ + std::string GetUndoDescription() const; + + /** + * Get description of the command that would be redone. + */ + std::string GetRedoDescription() const; + + /** + * Clear all history. + */ + void Clear(); + + /** + * Get number of commands in undo stack. + */ + size_t GetUndoCount() const { return undoStack_.size(); } + + /** + * Get number of commands in redo stack. + */ + size_t GetRedoCount() const { return redoStack_.size(); } + + /** + * Set callback to be invoked when history changes (for UI updates). + */ + void SetOnHistoryChanged(std::function callback) { onHistoryChanged_ = callback; } + + /** + * Enable or disable command merging for continuous operations. + */ + void SetMergeEnabled(bool enabled) { mergeEnabled_ = enabled; } + bool IsMergeEnabled() const { return mergeEnabled_; } + + /** + * Begin a group of commands that should be treated as a single undo unit. + */ + void BeginGroup(const std::string& description); + + /** + * End the current command group. + */ + void EndGroup(); + + /** + * Check if currently in a command group. + */ + bool IsInGroup() const { return inGroup_; } + + private: + void NotifyHistoryChanged(); + + std::deque undoStack_; + std::deque redoStack_; + + std::function onHistoryChanged_; + bool mergeEnabled_ = true; + bool inGroup_ = false; + + // For grouped commands + std::vector groupCommands_; + std::string groupDescription_; + }; +} diff --git a/src/Editor/Commands/ICommand.h b/src/Editor/Commands/ICommand.h new file mode 100644 index 00000000..580fa0b0 --- /dev/null +++ b/src/Editor/Commands/ICommand.h @@ -0,0 +1,49 @@ +#pragma once + +#include "Common/CoreMinimal.hpp" +#include +#include + +namespace Editor +{ + /** + * Base interface for all editor commands supporting undo/redo. + * Commands encapsulate a single atomic change that can be executed and reverted. + */ + class ICommand + { + public: + virtual ~ICommand() = default; + + /** + * Execute the command (do/redo) + * @return true if execution was successful + */ + virtual bool Execute() = 0; + + /** + * Undo the command, reverting to the state before execution + * @return true if undo was successful + */ + virtual bool Undo() = 0; + + /** + * Get a human-readable description of this command for UI display + */ + virtual std::string GetDescription() const = 0; + + /** + * Check if this command can be merged with another command of the same type. + * Useful for continuous property changes (e.g., dragging a slider). + */ + virtual bool CanMergeWith(const ICommand* other) const { return false; } + + /** + * Merge another command into this one. + * Only called if CanMergeWith returns true. + */ + virtual void MergeWith(const ICommand* other) {} + }; + + using CommandPtr = std::unique_ptr; +} diff --git a/src/Editor/Commands/PropertyCommand.cpp b/src/Editor/Commands/PropertyCommand.cpp new file mode 100644 index 00000000..c99d6268 --- /dev/null +++ b/src/Editor/Commands/PropertyCommand.cpp @@ -0,0 +1,86 @@ +#include "PropertyCommand.h" +#include "Assets/Node.h" + +namespace Editor +{ + bool TransformCommand::Execute() + { + if (!node_) + { + return false; + } + + switch (type_) + { + case TransformType::Translation: + node_->SetTranslation(newValue_); + break; + case TransformType::Rotation: + if (isQuat_) + { + node_->SetRotation(newRotation_); + } + else + { + node_->SetRotation(glm::quat(newValue_)); + } + break; + case TransformType::Scale: + node_->SetScale(newValue_); + break; + } + + node_->RecalcTransform(true); + return true; + } + + bool TransformCommand::Undo() + { + if (!node_) + { + return false; + } + + switch (type_) + { + case TransformType::Translation: + node_->SetTranslation(oldValue_); + break; + case TransformType::Rotation: + if (isQuat_) + { + node_->SetRotation(oldRotation_); + } + else + { + node_->SetRotation(glm::quat(oldValue_)); + } + break; + case TransformType::Scale: + node_->SetScale(oldValue_); + break; + } + + node_->RecalcTransform(true); + return true; + } + + std::string TransformCommand::GetDescription() const + { + const char* typeStr = "Unknown"; + switch (type_) + { + case TransformType::Translation: + typeStr = "Translation"; + break; + case TransformType::Rotation: + typeStr = "Rotation"; + break; + case TransformType::Scale: + typeStr = "Scale"; + break; + } + + return fmt::format("Set {} on {}", typeStr, node_ ? node_->GetName() : "null"); + } +} diff --git a/src/Editor/Commands/PropertyCommand.h b/src/Editor/Commands/PropertyCommand.h new file mode 100644 index 00000000..afbe3027 --- /dev/null +++ b/src/Editor/Commands/PropertyCommand.h @@ -0,0 +1,193 @@ +#pragma once + +#include "ICommand.h" +#include "Runtime/Reflection/PropertyAccessor.h" +#include "Assets/Component.h" +#include "Assets/Node.h" +#include +#include +#include +#include +#include +#include + +namespace Editor +{ + /** + * Command for modifying a property on a component through reflection. + * Supports undo/redo and command merging for continuous edits. + */ + class PropertyCommand : public ICommand + { + public: + /** + * Create a property command. + * @param component The component to modify + * @param propertyName Name of the property to modify + * @param newValue The new value to set + * @param oldValue The old value (for undo) + */ + PropertyCommand( + Assets::Component* component, + const std::string& propertyName, + entt::meta_any newValue, + entt::meta_any oldValue + ) + : component_(component) + , propertyName_(propertyName) + , newValue_(std::move(newValue)) + , oldValue_(std::move(oldValue)) + { + } + + bool Execute() override + { + if (!component_) + { + return false; + } + + auto metaType = component_->GetMetaType(); + return Reflection::PropertyAccessor::SetPropertyValue( + metaType, + component_, + propertyName_, + newValue_ + ); + } + + bool Undo() override + { + if (!component_) + { + return false; + } + + auto metaType = component_->GetMetaType(); + return Reflection::PropertyAccessor::SetPropertyValue( + metaType, + component_, + propertyName_, + oldValue_ + ); + } + + std::string GetDescription() const override + { + return fmt::format("Set {} on {}", propertyName_, component_ ? component_->GetTypeName() : "null"); + } + + bool CanMergeWith(const ICommand* other) const override + { + auto* otherCmd = dynamic_cast(other); + if (!otherCmd) + { + return false; + } + + // Merge if same component and same property + return component_ == otherCmd->component_ && + propertyName_ == otherCmd->propertyName_; + } + + void MergeWith(const ICommand* other) override + { + auto* otherCmd = dynamic_cast(other); + if (otherCmd) + { + // Keep our old value (from the original change), update new value + newValue_ = otherCmd->newValue_; + } + } + + // Accessors for inspection + Assets::Component* GetComponent() const { return component_; } + const std::string& GetPropertyName() const { return propertyName_; } + + private: + Assets::Component* component_ = nullptr; + std::string propertyName_; + entt::meta_any newValue_; + entt::meta_any oldValue_; + }; + + /** + * Command for modifying Node transform (position, rotation, scale). + * Separate from PropertyCommand as transforms are not component properties. + */ + class TransformCommand : public ICommand + { + public: + enum class TransformType + { + Translation, + Rotation, + Scale + }; + + TransformCommand( + Assets::Node* node, + TransformType type, + glm::vec3 newValue, + glm::vec3 oldValue + ) + : node_(node) + , type_(type) + , newValue_(newValue) + , oldValue_(oldValue) + { + } + + TransformCommand( + Assets::Node* node, + glm::quat newRotation, + glm::quat oldRotation + ) + : node_(node) + , type_(TransformType::Rotation) + , newRotation_(newRotation) + , oldRotation_(oldRotation) + , isQuat_(true) + { + } + + bool Execute() override; + bool Undo() override; + std::string GetDescription() const override; + + bool CanMergeWith(const ICommand* other) const override + { + auto* otherCmd = dynamic_cast(other); + if (!otherCmd) + { + return false; + } + return node_ == otherCmd->node_ && type_ == otherCmd->type_; + } + + void MergeWith(const ICommand* other) override + { + auto* otherCmd = dynamic_cast(other); + if (otherCmd) + { + if (isQuat_) + { + newRotation_ = otherCmd->newRotation_; + } + else + { + newValue_ = otherCmd->newValue_; + } + } + } + + private: + Assets::Node* node_ = nullptr; + TransformType type_; + glm::vec3 newValue_{0.0f}; + glm::vec3 oldValue_{0.0f}; + glm::quat newRotation_{1.0f, 0.0f, 0.0f, 0.0f}; + glm::quat oldRotation_{1.0f, 0.0f, 0.0f, 0.0f}; + bool isQuat_ = false; + }; +} diff --git a/src/Editor/Core/EditorUiState.hpp b/src/Editor/Core/EditorUiState.hpp index 735f9c5c..e81ee9ab 100644 --- a/src/Editor/Core/EditorUiState.hpp +++ b/src/Editor/Core/EditorUiState.hpp @@ -1,6 +1,7 @@ #pragma once #include "Common/CoreMinimal.hpp" +#include "Editor/Commands/CommandHistory.h" #include @@ -60,5 +61,8 @@ namespace Editor // Fonts ImFont* fontIcon = nullptr; ImFont* bigIcon = nullptr; + + // Command history for undo/redo + CommandHistory commandHistory; }; } // namespace Editor diff --git a/src/Editor/EditorInterface.cpp b/src/Editor/EditorInterface.cpp index fbe448b7..62c0d38e 100644 --- a/src/Editor/EditorInterface.cpp +++ b/src/Editor/EditorInterface.cpp @@ -242,6 +242,23 @@ void EditorInterface::Render() ImGui::GetIO().UserData = &ctx; uiState_.selected_obj_id = ctx.scene.GetSelectedId(); + + // Handle global keyboard shortcuts + auto& io = ImGui::GetIO(); + if (!io.WantTextInput) + { + // Undo: Ctrl+Z + if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_Z) && !io.KeyShift) + { + uiState_.commandHistory.Undo(); + } + // Redo: Ctrl+Y or Ctrl+Shift+Z + if ((io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_Y)) || + (io.KeyCtrl && io.KeyShift && ImGui::IsKeyPressed(ImGuiKey_Z))) + { + uiState_.commandHistory.Redo(); + } + } ImGuiID id = DockSpaceUI(); ToolbarUI(); diff --git a/src/Editor/EditorMain.cpp b/src/Editor/EditorMain.cpp index 7f391733..abdb7719 100644 --- a/src/Editor/EditorMain.cpp +++ b/src/Editor/EditorMain.cpp @@ -87,8 +87,8 @@ void EditorGameInstance::OnInit() void EditorGameInstance::OnTick(double deltaSeconds) { - bool moving = modelViewController_.UpdateCamera(1.0f, deltaSeconds); - GetEngine().SetProgressiveRendering(!moving, false); + // bool moving = modelViewController_.UpdateCamera(1.0f, deltaSeconds); + // GetEngine().SetProgressiveRendering(!moving, false); } void EditorGameInstance::OnSceneLoaded() { modelViewController_.Reset(GetEngine().GetScene().GetRenderCamera()); } diff --git a/src/Editor/Overlays/TitleBarOverlay.cpp b/src/Editor/Overlays/TitleBarOverlay.cpp index ca2b8fb8..e5cf0a6e 100644 --- a/src/Editor/Overlays/TitleBarOverlay.cpp +++ b/src/Editor/Overlays/TitleBarOverlay.cpp @@ -71,6 +71,28 @@ namespace Editor if (ImGui::BeginMenu("Edit")) { + // Undo/Redo + bool canUndo = ui.commandHistory.CanUndo(); + bool canRedo = ui.commandHistory.CanRedo(); + + std::string undoLabel = canUndo + ? fmt::format("Undo {}", ui.commandHistory.GetUndoDescription()) + : "Undo"; + std::string redoLabel = canRedo + ? fmt::format("Redo {}", ui.commandHistory.GetRedoDescription()) + : "Redo"; + + if (ImGui::MenuItem(undoLabel.c_str(), "Ctrl+Z", false, canUndo)) + { + ui.commandHistory.Undo(); + } + if (ImGui::MenuItem(redoLabel.c_str(), "Ctrl+Y", false, canRedo)) + { + ui.commandHistory.Redo(); + } + + ImGui::Separator(); + if (ImGui::BeginMenu("Layout")) { ImGui::MenuItem("Reset"); diff --git a/src/Editor/Panels/PropertiesPanel.cpp b/src/Editor/Panels/PropertiesPanel.cpp index 69d2faed..e4c7db0c 100644 --- a/src/Editor/Panels/PropertiesPanel.cpp +++ b/src/Editor/Panels/PropertiesPanel.cpp @@ -1,8 +1,11 @@ #include "Editor/EditorUi.hpp" +#include "Editor/Panels/PropertyWidgets.h" #include "Assets/Node.h" #include "Assets/Scene.hpp" #include "Runtime/Components/RenderComponent.h" +#include "Runtime/Components/PhysicsComponent.h" +#include "Runtime/Components/SkinnedMeshComponent.h" #include "ThirdParty/fontawesome/IconsFontAwesome6.h" @@ -130,6 +133,25 @@ namespace Editor } } } + + // Draw component properties using reflection + ImGui::NewLine(); + ImGui::Text(ICON_FA_PUZZLE_PIECE " Components"); + ImGui::Separator(); + + const auto& components = selectedObj->GetComponents(); + for (const auto& component : components) + { + if (!component) continue; + + std::string headerName = std::string(component->GetTypeName()); + if (ImGui::CollapsingHeader(headerName.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Indent(); + PropertyWidgets::DrawComponentProperties(component.get(), &ui.commandHistory); + ImGui::Unindent(); + } + } } ImGui::End(); diff --git a/src/Editor/Panels/PropertyWidgets.cpp b/src/Editor/Panels/PropertyWidgets.cpp new file mode 100644 index 00000000..d4b244af --- /dev/null +++ b/src/Editor/Panels/PropertyWidgets.cpp @@ -0,0 +1,961 @@ +#include "PropertyWidgets.h" +#include "Editor/Commands/PropertyCommand.h" +#include "Runtime/Reflection/ReflectionMacros.h" + +#include +#include +#include +#include +#include +#include + +namespace Editor +{ + using namespace Reflection; + + // ============================================================================ + // UI Helper functions for consistent property panel styling + // ============================================================================ + + // Begin a property row with label on left (50:50 ratio) + static void BeginPropertyRow(const char* label) + { + float labelWidth = ImGui::GetContentRegionAvail().x * 0.5f; // 50% for label + + // Draw label + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted(label); + ImGui::SameLine(labelWidth); + + // Set width for the value widget + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } + + // Wrapper for property drawing with label-value layout + static bool DrawPropertyRow(const char* label, const std::function& drawWidget) + { + BeginPropertyRow(label); + ImGui::PushID(label); + bool result = drawWidget(); + ImGui::PopID(); + return result; + } + + // Draw a category header with distinctive style + static bool DrawCategoryHeader(const char* category) + { + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.2f, 0.2f, 0.2f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.3f, 0.3f, 0.3f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.25f, 0.25f, 0.25f, 1.0f)); + + bool open = ImGui::CollapsingHeader(category, ImGuiTreeNodeFlags_DefaultOpen); + + ImGui::PopStyleColor(3); + + return open; + } + + // ============================================================================ + // Main property drawing + // ============================================================================ + + bool PropertyWidgets::DrawProperty( + const PropertyInfo& propInfo, + Assets::Component* component, + CommandHistory* history, + WidgetConfig config + ) + { + if (!component) + { + return false; + } + + bool changed = false; + bool isReadOnly = config.readOnly || propInfo.meta.IsReadOnly(); + + // Get current value + auto metaType = component->GetMetaType(); + auto currentValue = PropertyAccessor::GetPropertyValue(metaType, component, propInfo.name); + + if (!currentValue) + { + ImGui::Text("%s: ", propInfo.name.c_str()); + return false; + } + + const char* label = propInfo.meta.displayName.empty() + ? propInfo.name.c_str() + : propInfo.meta.displayName.c_str(); + + // Store old value for undo + entt::meta_any oldValue = currentValue; + + switch (propInfo.type) + { + case PropertyType::Bool: + { + if (auto* ptr = currentValue.try_cast()) + { + bool val = *ptr; + if (DrawBool(label, val, isReadOnly)) + { + changed = true; + currentValue = entt::meta_any{val}; + } + } + break; + } + + case PropertyType::Int32: + { + if (auto* ptr = currentValue.try_cast()) + { + int32_t val = *ptr; + if (DrawInt(label, val, config.dragSpeed, + static_cast(config.minValue), + static_cast(config.maxValue), + isReadOnly)) + { + changed = true; + currentValue = entt::meta_any{val}; + } + } + break; + } + + case PropertyType::UInt32: + { + if (auto* ptr = currentValue.try_cast()) + { + uint32_t val = *ptr; + if (DrawUInt(label, val, config.dragSpeed, + static_cast(std::max(0.0f, config.minValue)), + static_cast(config.maxValue), + isReadOnly)) + { + changed = true; + currentValue = entt::meta_any{val}; + } + } + break; + } + + case PropertyType::Float: + { + if (auto* ptr = currentValue.try_cast()) + { + float val = *ptr; + if (DrawFloat(label, val, config.dragSpeed, config.minValue, config.maxValue, + isReadOnly, config.format ? config.format : "%.3f")) + { + changed = true; + currentValue = entt::meta_any{val}; + } + } + break; + } + + case PropertyType::Double: + { + if (auto* ptr = currentValue.try_cast()) + { + double val = *ptr; + if (DrawDouble(label, val, config.dragSpeed, + static_cast(config.minValue), + static_cast(config.maxValue), + isReadOnly, config.format ? config.format : "%.6f")) + { + changed = true; + currentValue = entt::meta_any{val}; + } + } + break; + } + + case PropertyType::String: + { + if (auto* ptr = currentValue.try_cast()) + { + std::string val = *ptr; + if (DrawString(label, val, isReadOnly)) + { + changed = true; + currentValue = entt::meta_any{val}; + } + } + break; + } + + case PropertyType::Vec2: + { + if (auto* ptr = currentValue.try_cast()) + { + glm::vec2 val = *ptr; + if (DrawVec2(label, val, config.dragSpeed, isReadOnly)) + { + changed = true; + currentValue = entt::meta_any{val}; + } + } + break; + } + + case PropertyType::Vec3: + { + if (auto* ptr = currentValue.try_cast()) + { + glm::vec3 val = *ptr; + // Check if this is a color property + if (propInfo.meta.category == "Color") + { + if (DrawColor3(label, val, isReadOnly)) + { + changed = true; + currentValue = entt::meta_any{val}; + } + } + else + { + if (DrawVec3(label, val, config.dragSpeed, isReadOnly)) + { + changed = true; + currentValue = entt::meta_any{val}; + } + } + } + break; + } + + case PropertyType::Vec4: + { + if (auto* ptr = currentValue.try_cast()) + { + glm::vec4 val = *ptr; + // Check if this is a color property + if (propInfo.meta.category == "Color") + { + if (DrawColor4(label, val, isReadOnly)) + { + changed = true; + currentValue = entt::meta_any{val}; + } + } + else + { + if (DrawVec4(label, val, config.dragSpeed, isReadOnly)) + { + changed = true; + currentValue = entt::meta_any{val}; + } + } + } + break; + } + + case PropertyType::Quat: + { + if (auto* ptr = currentValue.try_cast()) + { + glm::quat val = *ptr; + if (DrawQuat(label, val, config.dragSpeed, isReadOnly)) + { + changed = true; + currentValue = entt::meta_any{val}; + } + } + break; + } + + case PropertyType::Enum: + { + if (propInfo.enumTypeId != 0) + { + auto enumType = entt::resolve(propInfo.enumTypeId); + if (enumType && DrawEnum(label, currentValue, enumType, isReadOnly)) + { + changed = true; + } + } + else + { + ImGui::Text("%s: ", label); + } + break; + } + + case PropertyType::Array: + { + if (DrawArray(label, propInfo, currentValue, isReadOnly)) + { + changed = true; + } + break; + } + + default: + ImGui::Text("%s: ", label); + break; + } + + // Apply change with command for undo + if (changed && !isReadOnly) + { + if (history) + { + auto command = std::make_unique( + component, + propInfo.name, + currentValue, + oldValue + ); + history->Execute(std::move(command)); + } + else + { + // Direct set without undo + PropertyAccessor::SetPropertyValue(metaType, component, propInfo.name, currentValue); + } + } + + return changed; + } + + bool PropertyWidgets::DrawComponentProperties( + Assets::Component* component, + CommandHistory* history, + WidgetConfig config + ) + { + if (!component) + { + return false; + } + + bool anyChanged = false; + auto metaType = component->GetMetaType(); + auto properties = PropertyAccessor::GetProperties(metaType); + + // Group properties by category + std::map> categorized; + for (const auto& prop : properties) + { + std::string category = prop.meta.category.empty() ? "General" : prop.meta.category; + categorized[category].push_back(prop); + } + + // Reduce indent for property rows + float indent = ImGui::GetStyle().IndentSpacing; + ImGui::GetStyle().IndentSpacing = 8.0f; + + // Draw each category + for (const auto& [category, props] : categorized) + { + if (DrawCategoryHeader(category.c_str())) + { + ImGui::Indent(); + for (const auto& prop : props) + { + if (DrawProperty(prop, component, history, config)) + { + anyChanged = true; + } + } + ImGui::Unindent(); + } + } + + // Restore indent + ImGui::GetStyle().IndentSpacing = indent; + + return anyChanged; + } + + // Individual widget implementations - with property row layout + + bool PropertyWidgets::DrawBool(const char* label, bool& value, bool readOnly) + { + return DrawPropertyRow(label, [&]() { + if (readOnly) + { + bool temp = value; + ImGui::BeginDisabled(); + bool result = ImGui::Checkbox("##v", &temp); + ImGui::EndDisabled(); + return false; + } + return ImGui::Checkbox("##v", &value); + }); + } + + bool PropertyWidgets::DrawInt(const char* label, int32_t& value, float speed, int min, int max, bool readOnly) + { + return DrawPropertyRow(label, [&]() { + if (readOnly) + { + int temp = value; + ImGui::BeginDisabled(); + ImGui::DragInt("##v", &temp, speed, min, max); + ImGui::EndDisabled(); + return false; + } + return ImGui::DragInt("##v", &value, speed, min, max); + }); + } + + bool PropertyWidgets::DrawUInt(const char* label, uint32_t& value, float speed, uint32_t min, uint32_t max, bool readOnly) + { + return DrawPropertyRow(label, [&]() { + int intVal = static_cast(value); + int intMin = static_cast(std::min(min, static_cast(INT_MAX))); + int intMax = static_cast(std::min(max, static_cast(INT_MAX))); + + bool changed = false; + if (readOnly) + { + ImGui::BeginDisabled(); + ImGui::DragInt("##v", &intVal, speed, intMin, intMax); + ImGui::EndDisabled(); + } + else + { + if (ImGui::DragInt("##v", &intVal, speed, intMin, intMax)) + { + value = static_cast(std::max(0, intVal)); + changed = true; + } + } + return changed; + }); + } + + bool PropertyWidgets::DrawFloat(const char* label, float& value, float speed, float min, float max, bool readOnly, const char* format) + { + return DrawPropertyRow(label, [&]() { + if (readOnly) + { + float temp = value; + ImGui::BeginDisabled(); + ImGui::DragFloat("##v", &temp, speed, min, max, format); + ImGui::EndDisabled(); + return false; + } + return ImGui::DragFloat("##v", &value, speed, min, max, format); + }); + } + + bool PropertyWidgets::DrawDouble(const char* label, double& value, float speed, double min, double max, bool readOnly, const char* format) + { + float floatVal = static_cast(value); + bool changed = DrawFloat(label, floatVal, speed, static_cast(min), static_cast(max), readOnly, format); + if (changed) + { + value = static_cast(floatVal); + } + return changed; + } + + bool PropertyWidgets::DrawString(const char* label, std::string& value, bool readOnly) + { + return DrawPropertyRow(label, [&]() { + if (readOnly) + { + ImGui::BeginDisabled(); + ImGui::InputText("##v", &value, ImGuiInputTextFlags_ReadOnly); + ImGui::EndDisabled(); + return false; + } + return ImGui::InputText("##v", &value); + }); + } + + bool PropertyWidgets::DrawVec2(const char* label, glm::vec2& value, float speed, bool readOnly) + { + return DrawPropertyRow(label, [&]() { + if (readOnly) + { + glm::vec2 temp = value; + ImGui::BeginDisabled(); + ImGui::DragFloat2("##v", &temp.x, speed); + ImGui::EndDisabled(); + return false; + } + return ImGui::DragFloat2("##v", &value.x, speed); + }); + } + + bool PropertyWidgets::DrawVec3(const char* label, glm::vec3& value, float speed, bool readOnly) + { + return DrawPropertyRow(label, [&]() { + if (readOnly) + { + glm::vec3 temp = value; + ImGui::BeginDisabled(); + ImGui::DragFloat3("##v", &temp.x, speed); + ImGui::EndDisabled(); + return false; + } + return ImGui::DragFloat3("##v", &value.x, speed); + }); + } + + bool PropertyWidgets::DrawVec4(const char* label, glm::vec4& value, float speed, bool readOnly) + { + return DrawPropertyRow(label, [&]() { + if (readOnly) + { + glm::vec4 temp = value; + ImGui::BeginDisabled(); + ImGui::DragFloat4("##v", &temp.x, speed); + ImGui::EndDisabled(); + return false; + } + return ImGui::DragFloat4("##v", &value.x, speed); + }); + } + + bool PropertyWidgets::DrawQuat(const char* label, glm::quat& value, float speed, bool readOnly) + { + // Convert to euler angles for editing + glm::vec3 euler = glm::degrees(glm::eulerAngles(value)); + + bool changed = DrawPropertyRow(label, [&]() { + if (readOnly) + { + ImGui::BeginDisabled(); + ImGui::DragFloat3("##v", &euler.x, speed); + ImGui::EndDisabled(); + return false; + } + return ImGui::DragFloat3("##v", &euler.x, speed); + }); + + if (changed) + { + value = glm::quat(glm::radians(euler)); + } + return changed; + } + + bool PropertyWidgets::DrawColor3(const char* label, glm::vec3& value, bool readOnly) + { + return DrawPropertyRow(label, [&]() { + if (readOnly) + { + glm::vec3 temp = value; + ImGui::BeginDisabled(); + ImGui::ColorEdit3("##v", &temp.x); + ImGui::EndDisabled(); + return false; + } + return ImGui::ColorEdit3("##v", &value.x); + }); + } + + bool PropertyWidgets::DrawColor4(const char* label, glm::vec4& value, bool readOnly) + { + return DrawPropertyRow(label, [&]() { + if (readOnly) + { + glm::vec4 temp = value; + ImGui::BeginDisabled(); + ImGui::ColorEdit4("##v", &temp.x); + ImGui::EndDisabled(); + return false; + } + return ImGui::ColorEdit4("##v", &value.x); + }); + } + + bool PropertyWidgets::DrawEnum( + const char* label, + entt::meta_any& value, + entt::meta_type enumType, + bool readOnly + ) + { + auto enumValues = GetEnumValues(enumType); + if (enumValues.empty()) + { + BeginPropertyRow(label); + ImGui::Text(""); + return false; + } + + // Find current selection + int currentIndex = 0; + for (size_t i = 0; i < enumValues.size(); ++i) + { + if (enumValues[i].second == value) + { + currentIndex = static_cast(i); + break; + } + } + + // Create combo items + std::vector items; + items.reserve(enumValues.size()); + for (const auto& [name, _] : enumValues) + { + items.push_back(name.c_str()); + } + + return DrawPropertyRow(label, [&]() { + bool changed = false; + if (readOnly) + { + ImGui::BeginDisabled(); + ImGui::Combo("##v", ¤tIndex, items.data(), static_cast(items.size())); + ImGui::EndDisabled(); + } + else + { + if (ImGui::Combo("##v", ¤tIndex, items.data(), static_cast(items.size()))) + { + value = enumValues[currentIndex].second; + changed = true; + } + } + return changed; + }); + } + + std::vector> PropertyWidgets::GetEnumValues(entt::meta_type enumType) + { + std::vector> result; + + if (!enumType.is_enum()) + { + return result; + } + + // Simplified implementation - iterate over enum data members + // In entt v3.x, we use .custom<>() for metadata, not .prop() + for (auto&& [id, data] : enumType.data()) + { + // Use the data name if available, otherwise use a generated name + const char* name = data.name(); + if (name) + { + result.emplace_back(name, data.get({})); + } + else + { + // Generate a name from the id + result.emplace_back(std::to_string(id), data.get({})); + } + } + + return result; + } + + // ============================================================================ + // Array Drawing Helper + // ============================================================================ + + // Helper template to draw a container with known element type + // Reduces code duplication for std::array and std::vector handling + template + static bool DrawContainerElements( + const char* label, + ContainerT& container, + size_t size, + bool readOnly, + DrawFunc drawElement + ) + { + bool changed = false; + std::string headerLabel = std::string(label) + " [" + std::to_string(size) + "]"; + + if (ImGui::TreeNodeEx(headerLabel.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) + { + for (size_t i = 0; i < size; ++i) + { + ImGui::PushID(static_cast(i)); + + std::string indexLabel = "[" + std::to_string(i) + "]"; + ElementT val = container[i]; + + if (drawElement(indexLabel.c_str(), val, readOnly)) + { + container[i] = val; + changed = true; + } + + ImGui::PopID(); + } + ImGui::TreePop(); + } + return changed; + } + + // RAII helper for indent management + class ScopedIndent + { + public: + explicit ScopedIndent(float newIndent) + : oldIndent_(ImGui::GetStyle().IndentSpacing) + { + ImGui::GetStyle().IndentSpacing = newIndent; + } + + ~ScopedIndent() + { + ImGui::GetStyle().IndentSpacing = oldIndent_; + } + + ScopedIndent(const ScopedIndent&) = delete; + ScopedIndent& operator=(const ScopedIndent&) = delete; + + private: + float oldIndent_; + }; + + bool PropertyWidgets::DrawArray( + const char* label, + const PropertyInfo& propInfo, + entt::meta_any& arrayValue, + bool readOnly + ) + { + bool changed = false; + + // RAII indent management + ScopedIndent indent(8.0f); + + // Try specific container types with their optimized handlers + // std::array (for Materials) + if (auto* arr = arrayValue.try_cast>()) + { + return DrawContainerElements, uint32_t>( + label, *arr, 16, readOnly, + [](const char* lbl, uint32_t& val, bool ro) { + return DrawUInt(lbl, val, 1.0f, 0, UINT_MAX, ro); + }); + } + + // std::vector + if (auto* vec = arrayValue.try_cast>()) + { + return DrawContainerElements, uint32_t>( + label, *vec, vec->size(), readOnly, + [](const char* lbl, uint32_t& val, bool ro) { + return DrawUInt(lbl, val, 1.0f, 0, UINT_MAX, ro); + }); + } + + // std::vector + if (auto* vec = arrayValue.try_cast>()) + { + return DrawContainerElements, int32_t>( + label, *vec, vec->size(), readOnly, + [](const char* lbl, int32_t& val, bool ro) { + return DrawInt(lbl, val, 1.0f, INT_MIN, INT_MAX, ro); + }); + } + + // std::vector + if (auto* vec = arrayValue.try_cast>()) + { + return DrawContainerElements, float>( + label, *vec, vec->size(), readOnly, + [](const char* lbl, float& val, bool ro) { + return DrawFloat(lbl, val, 0.1f, -FLT_MAX, FLT_MAX, ro); + }); + } + + // std::vector + if (auto* vec = arrayValue.try_cast>()) + { + return DrawContainerElements, std::string>( + label, *vec, vec->size(), readOnly, + [](const char* lbl, std::string& val, bool ro) { + return DrawString(lbl, val, ro); + }); + } + + // Fallback: try using entt's sequence container API + auto container = arrayValue.as_sequence_container(); + if (!container) + { + BeginPropertyRow(label); + ImGui::Text(""); + return false; + } + + size_t size = container.size(); + std::string headerLabel = std::string(label) + " [" + std::to_string(size) + "]"; + + if (ImGui::TreeNodeEx(headerLabel.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) + { + for (size_t i = 0; i < size; ++i) + { + ImGui::PushID(static_cast(i)); + + entt::meta_any element = container[i]; + + if (DrawArrayElement(i, element, propInfo.elementType, propInfo.elementEnumTypeId, readOnly)) + { + changed = true; + } + + ImGui::PopID(); + } + + ImGui::TreePop(); + } + + return changed; + } + + bool PropertyWidgets::DrawArrayElement( + size_t index, + entt::meta_any& element, + PropertyType elementType, + uint32_t enumTypeId, + bool readOnly + ) + { + std::string indexLabel = "[" + std::to_string(index) + "]"; + bool changed = false; + + switch (elementType) + { + case PropertyType::Bool: + { + if (auto* ptr = element.try_cast()) + { + bool val = *ptr; + if (DrawBool(indexLabel.c_str(), val, readOnly)) + { + *ptr = val; + changed = true; + } + } + break; + } + + case PropertyType::Int32: + { + if (auto* ptr = element.try_cast()) + { + int32_t val = *ptr; + if (DrawInt(indexLabel.c_str(), val, 0.1f, INT_MIN, INT_MAX, readOnly)) + { + *ptr = val; + changed = true; + } + } + break; + } + + case PropertyType::UInt32: + { + if (auto* ptr = element.try_cast()) + { + uint32_t val = *ptr; + if (DrawUInt(indexLabel.c_str(), val, 0.1f, 0, UINT_MAX, readOnly)) + { + *ptr = val; + changed = true; + } + } + break; + } + + case PropertyType::Float: + { + if (auto* ptr = element.try_cast()) + { + float val = *ptr; + if (DrawFloat(indexLabel.c_str(), val, 0.1f, -FLT_MAX, FLT_MAX, readOnly, "%.3f")) + { + *ptr = val; + changed = true; + } + } + break; + } + + case PropertyType::Double: + { + if (auto* ptr = element.try_cast()) + { + double val = *ptr; + if (DrawDouble(indexLabel.c_str(), val, 0.1f, -DBL_MAX, DBL_MAX, readOnly, "%.6f")) + { + *ptr = val; + changed = true; + } + } + break; + } + + case PropertyType::String: + { + if (auto* ptr = element.try_cast()) + { + std::string val = *ptr; + if (DrawString(indexLabel.c_str(), val, readOnly)) + { + *ptr = val; + changed = true; + } + } + break; + } + + case PropertyType::Vec3: + { + if (auto* ptr = element.try_cast()) + { + glm::vec3 val = *ptr; + if (DrawVec3(indexLabel.c_str(), val, 0.1f, readOnly)) + { + *ptr = val; + changed = true; + } + } + break; + } + + case PropertyType::Vec4: + { + if (auto* ptr = element.try_cast()) + { + glm::vec4 val = *ptr; + if (DrawVec4(indexLabel.c_str(), val, 0.1f, readOnly)) + { + *ptr = val; + changed = true; + } + } + break; + } + + case PropertyType::Enum: + { + if (enumTypeId != 0) + { + auto enumType = entt::resolve(enumTypeId); + if (enumType && DrawEnum(indexLabel.c_str(), element, enumType, readOnly)) + { + changed = true; + } + } + else + { + ImGui::Text("%s: ", indexLabel.c_str()); + } + break; + } + + default: + ImGui::Text("%s: ", indexLabel.c_str()); + break; + } + + return changed; + } +} diff --git a/src/Editor/Panels/PropertyWidgets.h b/src/Editor/Panels/PropertyWidgets.h new file mode 100644 index 00000000..0c3749f3 --- /dev/null +++ b/src/Editor/Panels/PropertyWidgets.h @@ -0,0 +1,122 @@ +#pragma once + +#include "Common/CoreMinimal.hpp" +#include "Runtime/Reflection/PropertyTypes.h" +#include "Runtime/Reflection/PropertyAccessor.h" +#include "Editor/Commands/CommandHistory.h" +#include "Assets/Component.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Editor +{ + // Forward declaration + class CommandHistory; + + /** + * Utility class for rendering property editor widgets based on reflection. + * Automatically selects appropriate ImGui widgets based on property type. + */ + class PropertyWidgets + { + public: + /** + * Configuration for property widget rendering. + */ + struct WidgetConfig + { + float dragSpeed; + float minValue; + float maxValue; + bool readOnly; + const char* format; + + WidgetConfig() + : dragSpeed(0.1f) + , minValue(-FLT_MAX) + , maxValue(FLT_MAX) + , readOnly(false) + , format(nullptr) + {} + }; + + /** + * Draw an appropriate widget for a property info. + * Returns true if the value was changed. + */ + static bool DrawProperty( + const Reflection::PropertyInfo& propInfo, + Assets::Component* component, + CommandHistory* history = nullptr, + WidgetConfig config = WidgetConfig() + ); + + /** + * Draw all properties of a component using reflection. + * Returns true if any value was changed. + */ + static bool DrawComponentProperties( + Assets::Component* component, + CommandHistory* history = nullptr, + WidgetConfig config = WidgetConfig() + ); + + /** + * Draw a widget for a specific value type. + * These can be used directly for non-reflected properties. + */ + static bool DrawBool(const char* label, bool& value, bool readOnly = false); + static bool DrawInt(const char* label, int32_t& value, float speed = 0.1f, int min = INT_MIN, int max = INT_MAX, bool readOnly = false); + static bool DrawUInt(const char* label, uint32_t& value, float speed = 0.1f, uint32_t min = 0, uint32_t max = UINT_MAX, bool readOnly = false); + static bool DrawFloat(const char* label, float& value, float speed = 0.1f, float min = -FLT_MAX, float max = FLT_MAX, bool readOnly = false, const char* format = "%.3f"); + static bool DrawDouble(const char* label, double& value, float speed = 0.1f, double min = -DBL_MAX, double max = DBL_MAX, bool readOnly = false, const char* format = "%.6f"); + static bool DrawString(const char* label, std::string& value, bool readOnly = false); + static bool DrawVec2(const char* label, glm::vec2& value, float speed = 0.1f, bool readOnly = false); + static bool DrawVec3(const char* label, glm::vec3& value, float speed = 0.1f, bool readOnly = false); + static bool DrawVec4(const char* label, glm::vec4& value, float speed = 0.1f, bool readOnly = false); + static bool DrawQuat(const char* label, glm::quat& value, float speed = 0.1f, bool readOnly = false); + static bool DrawColor3(const char* label, glm::vec3& value, bool readOnly = false); + static bool DrawColor4(const char* label, glm::vec4& value, bool readOnly = false); + + /** + * Draw an enum dropdown using reflection. + */ + static bool DrawEnum( + const char* label, + entt::meta_any& value, + entt::meta_type enumType, + bool readOnly = false + ); + + /** + * Draw an array/sequence container property. + * Returns true if any element was changed. + */ + static bool DrawArray( + const char* label, + const Reflection::PropertyInfo& propInfo, + entt::meta_any& arrayValue, + bool readOnly = false + ); + + private: + // Internal helper to get enum value names + static std::vector> GetEnumValues(entt::meta_type enumType); + + // Internal helper to draw a single array element + static bool DrawArrayElement( + size_t index, + entt::meta_any& element, + Reflection::PropertyType elementType, + uint32_t enumTypeId, + bool readOnly + ); + }; +} diff --git a/src/Runtime/Command/DuplicateNodeCommand.cpp b/src/Runtime/Command/DuplicateNodeCommand.cpp index 7efd2a91..deeb2a8e 100644 --- a/src/Runtime/Command/DuplicateNodeCommand.cpp +++ b/src/Runtime/Command/DuplicateNodeCommand.cpp @@ -21,8 +21,8 @@ namespace auto newRender = std::make_shared(); newRender->SetModelId(render->GetModelId()); newRender->SetMaterial(render->Materials()); - newRender->SetVisible(render->IsVisible()); - newRender->SetRayCastVisible(render->IsRayCastVisible()); + newRender->SetVisible(render->GetVisible()); + newRender->SetRayCastVisible(render->GetRayCastVisible()); newRender->SetSkinIndex(render->GetSkinIndex()); clone->AddComponent(newRender); } diff --git a/src/Runtime/Components/PhysicsComponent.cpp b/src/Runtime/Components/PhysicsComponent.cpp new file mode 100644 index 00000000..64d0026d --- /dev/null +++ b/src/Runtime/Components/PhysicsComponent.cpp @@ -0,0 +1,31 @@ +#include "PhysicsComponent.h" +#include "Runtime/Reflection/PropertyMeta.h" +#include + +namespace Runtime +{ + void PhysicsComponent::RegisterReflection() + { + using namespace entt::literals; + using namespace Reflection; + + // Register ENodeMobility enum first (use string literals for names) + entt::meta_factory() + .type("ENodeMobility"_hs) + .data("Static") + .data("Dynamic") + .data("Kinematic"); + + // Register PhysicsComponent (use string literals for names) + entt::meta_factory() + .type("PhysicsComponent"_hs) + // Mobility property - editable enum + .data<&PhysicsComponent::SetMobility, &PhysicsComponent::GetMobility>("Mobility") + .custom(PropertyPresets::Editable("Mobility", "Physics", "Physics body mobility type")) + // PhysicsOffset property - editable vec3 + .data<&PhysicsComponent::SetPhysicsOffset, &PhysicsComponent::GetPhysicsOffset>("PhysicsOffset") + .custom(PropertyPresets::Editable("Physics Offset", "Physics", "Offset from node origin for physics body")); + + // Note: PhysicsBody ID is internal and not exposed to editor + } +} diff --git a/src/Runtime/Components/PhysicsComponent.h b/src/Runtime/Components/PhysicsComponent.h index 7db1842b..c17cd47c 100644 --- a/src/Runtime/Components/PhysicsComponent.h +++ b/src/Runtime/Components/PhysicsComponent.h @@ -1,5 +1,6 @@ #pragma once #include "Assets/Component.h" +#include "Runtime/Reflection/ReflectionMacros.h" #include "Runtime/NextPhysics.h" #include @@ -15,6 +16,8 @@ namespace Runtime class PhysicsComponent : public Assets::Component { public: + REFLECT_COMPONENT(PhysicsComponent) + PhysicsComponent() = default; void BindPhysicsBody(NextBodyID bodyId) { physicsBodyTemp_ = bodyId; } @@ -24,7 +27,7 @@ namespace Runtime ENodeMobility GetMobility() const { return mobility_; } void SetPhysicsOffset(const glm::vec3& offset) { physicsOffset_ = offset; } - const glm::vec3& GetPhysicsOffset() const { return physicsOffset_; } + glm::vec3 GetPhysicsOffset() const { return physicsOffset_; } private: NextBodyID physicsBodyTemp_; diff --git a/src/Runtime/Components/RenderComponent.cpp b/src/Runtime/Components/RenderComponent.cpp new file mode 100644 index 00000000..ab0eb30c --- /dev/null +++ b/src/Runtime/Components/RenderComponent.cpp @@ -0,0 +1,30 @@ +#include "RenderComponent.h" +#include "Runtime/Reflection/PropertyMeta.h" +#include + +namespace Runtime +{ + void RenderComponent::RegisterReflection() + { + using namespace entt::literals; + using namespace Reflection; + + entt::meta_factory() + .type("RenderComponent"_hs) + // Visible property - editable (use string literal for name) + .data<&RenderComponent::SetVisible, &RenderComponent::GetVisible>("Visible") + .custom(PropertyPresets::Editable("Visible", "Rendering", "Whether the object is visible")) + // RayCastVisible property - editable + .data<&RenderComponent::SetRayCastVisible, &RenderComponent::GetRayCastVisible>("RayCastVisible") + .custom(PropertyPresets::Editable("Raycast Visible", "Rendering", "Whether the object is visible to raycasts")) + // ModelId property - read-only (set through scene loading) + .data("ModelId") + .custom(PropertyPresets::ReadOnly("Model ID", "Mesh", "The model resource ID")) + // SkinIndex property - read-only + .data("SkinIndex") + .custom(PropertyPresets::ReadOnly("Skin Index", "Animation", "Skinning data index for skeletal animation")) + // Materials array - editable (array of material indices) + .data<&RenderComponent::SetMaterials, &RenderComponent::GetMaterials>("Materials") + .custom(PropertyPresets::Editable("Materials", "Rendering", "Material indices for each submesh")); + } +} diff --git a/src/Runtime/Components/RenderComponent.h b/src/Runtime/Components/RenderComponent.h index d3dae481..664a7068 100644 --- a/src/Runtime/Components/RenderComponent.h +++ b/src/Runtime/Components/RenderComponent.h @@ -1,5 +1,6 @@ #pragma once #include "Assets/Component.h" +#include "Runtime/Reflection/ReflectionMacros.h" #include #include @@ -8,6 +9,8 @@ namespace Runtime class RenderComponent : public Assets::Component { public: + REFLECT_COMPONENT(RenderComponent) + RenderComponent() = default; void SetModelId(uint32_t modelId) { modelId_ = modelId; } @@ -16,14 +19,18 @@ namespace Runtime void SetMaterial(const std::array& materials) { materialIdx_ = materials; } std::array& Materials() { return materialIdx_; } const std::array& Materials() const { return materialIdx_; } + + // Getter/Setter for materials array (for reflection) + const std::array& GetMaterials() const { return materialIdx_; } + void SetMaterials(const std::array& materials) { materialIdx_ = materials; } void SetVisible(bool visible) { visible_ = visible; } - bool IsVisible() const { return visible_; } + bool GetVisible() const { return visible_; } void SetRayCastVisible(bool visible) { rayCastVisible_ = visible; } - bool IsRayCastVisible() const { return rayCastVisible_; } + bool GetRayCastVisible() const { return rayCastVisible_; } - bool IsDrawable() const { return modelId_ != -1; } + bool IsDrawable() const { return modelId_ != static_cast(-1); } void SetSkinIndex(int32_t skinIndex) { skinIndex_ = skinIndex; } int32_t GetSkinIndex() const { return skinIndex_; } diff --git a/src/Runtime/Components/SkinnedMeshComponent.cpp b/src/Runtime/Components/SkinnedMeshComponent.cpp index 76630a31..e247bdf2 100644 --- a/src/Runtime/Components/SkinnedMeshComponent.cpp +++ b/src/Runtime/Components/SkinnedMeshComponent.cpp @@ -1,9 +1,11 @@ #include "SkinnedMeshComponent.h" #include "Runtime/Engine.hpp" #include "Runtime/NextEngineHelper.h" +#include "Runtime/Reflection/PropertyMeta.h" #include #include #include +#include #define GLM_ENABLE_EXPERIMENTAL #include @@ -11,6 +13,28 @@ namespace Runtime { + void SkinnedMeshComponent::RegisterReflection() + { + using namespace entt::literals; + using namespace Reflection; + + entt::meta_factory() + .type("SkinnedMeshComponent"_hs) + // PlaySpeed property - editable (use string literal for name) + .data<&SkinnedMeshComponent::SetPlaySpeed, &SkinnedMeshComponent::GetPlaySpeed>("PlaySpeed") + .custom(PropertyPresets::Range("Play Speed", "Animation", 0.0f, 10.0f, "Animation playback speed multiplier")) + // IsPlaying property - read-only + .data("IsPlaying") + .custom(PropertyPresets::ReadOnly("Is Playing", "Animation", "Whether an animation is currently playing")) + // CurrentAnimationName - read-only (use PlayAnimation to change) + .data("CurrentAnimation") + .custom(PropertyPresets::ReadOnly("Current Animation", "Animation", "Name of the currently playing animation")) + // Methods for JS + .func<&SkinnedMeshComponent::PlayAnimation>("PlayAnimation"_hs) + .func<&SkinnedMeshComponent::StopAnimation>("StopAnimation"_hs) + .func<&SkinnedMeshComponent::GetAnimationNames>("GetAnimationNames"_hs); + } + SkinnedMeshComponent::SkinnedMeshComponent(const Assets::Skeleton& skeleton) : skeleton_(skeleton) { diff --git a/src/Runtime/Components/SkinnedMeshComponent.h b/src/Runtime/Components/SkinnedMeshComponent.h index 8066ac73..562a2ab6 100644 --- a/src/Runtime/Components/SkinnedMeshComponent.h +++ b/src/Runtime/Components/SkinnedMeshComponent.h @@ -4,6 +4,7 @@ #include "Assets/Animation.hpp" #include "Assets/Model.hpp" #include "Assets/Component.h" +#include "Runtime/Reflection/ReflectionMacros.h" #include #include #include @@ -13,6 +14,8 @@ namespace Runtime class SkinnedMeshComponent : public Assets::Component { public: + REFLECT_COMPONENT(SkinnedMeshComponent) + SkinnedMeshComponent(const Assets::Skeleton& skeleton); void Update(float deltaTime); @@ -33,6 +36,7 @@ namespace Runtime std::string GetCurrentAnimationName() const { return currentState_.Playing ? currentState_.Name : ""; } bool IsPlaying() const { return currentState_.Playing; } + bool GetIsPlaying() const { return currentState_.Playing; } private: void UpdateJoints(); diff --git a/src/Runtime/Engine.cpp b/src/Runtime/Engine.cpp index be9296b9..13486605 100644 --- a/src/Runtime/Engine.cpp +++ b/src/Runtime/Engine.cpp @@ -41,6 +41,7 @@ #include "build.version" #include "Common/CoreMinimal.hpp" +#include "Reflection/ReflectionRegistry.h" // spdlog logging #include @@ -174,6 +175,9 @@ NextEngine::NextEngine(Options& options, void* userdata) #endif instance_ = this; + + // Initialize reflection system first + Reflection::RegisterAllReflection(); status_ = NextRenderer::EApplicationStatus::Starting; diff --git a/src/Runtime/Reflection/GlmJsonConverter.h b/src/Runtime/Reflection/GlmJsonConverter.h new file mode 100644 index 00000000..742db48a --- /dev/null +++ b/src/Runtime/Reflection/GlmJsonConverter.h @@ -0,0 +1,85 @@ +#pragma once +#include +#include +#include + +namespace glm +{ + // JSON serialization for glm::vec2 + inline void to_json(nlohmann::json& j, const vec2& v) + { + j = nlohmann::json{{"x", v.x}, {"y", v.y}}; + } + + inline void from_json(const nlohmann::json& j, vec2& v) + { + v.x = j.at("x").get(); + v.y = j.at("y").get(); + } + + // JSON serialization for glm::vec3 + inline void to_json(nlohmann::json& j, const vec3& v) + { + j = nlohmann::json{{"x", v.x}, {"y", v.y}, {"z", v.z}}; + } + + inline void from_json(const nlohmann::json& j, vec3& v) + { + v.x = j.at("x").get(); + v.y = j.at("y").get(); + v.z = j.at("z").get(); + } + + // JSON serialization for glm::vec4 + inline void to_json(nlohmann::json& j, const vec4& v) + { + j = nlohmann::json{{"x", v.x}, {"y", v.y}, {"z", v.z}, {"w", v.w}}; + } + + inline void from_json(const nlohmann::json& j, vec4& v) + { + v.x = j.at("x").get(); + v.y = j.at("y").get(); + v.z = j.at("z").get(); + v.w = j.at("w").get(); + } + + // JSON serialization for glm::quat + inline void to_json(nlohmann::json& j, const quat& q) + { + j = nlohmann::json{{"x", q.x}, {"y", q.y}, {"z", q.z}, {"w", q.w}}; + } + + inline void from_json(const nlohmann::json& j, quat& q) + { + q.x = j.at("x").get(); + q.y = j.at("y").get(); + q.z = j.at("z").get(); + q.w = j.at("w").get(); + } + + // JSON serialization for glm::mat4 + inline void to_json(nlohmann::json& j, const mat4& m) + { + j = nlohmann::json::array(); + for (int i = 0; i < 4; ++i) + { + for (int k = 0; k < 4; ++k) + { + j.push_back(m[i][k]); + } + } + } + + inline void from_json(const nlohmann::json& j, mat4& m) + { + int idx = 0; + for (int i = 0; i < 4; ++i) + { + for (int k = 0; k < 4; ++k) + { + m[i][k] = j.at(idx++).get(); + } + } + } +} diff --git a/src/Runtime/Reflection/GlmTypeSupport.h b/src/Runtime/Reflection/GlmTypeSupport.h new file mode 100644 index 00000000..5f327688 --- /dev/null +++ b/src/Runtime/Reflection/GlmTypeSupport.h @@ -0,0 +1,71 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace Reflection +{ + inline void RegisterGlmTypes() + { + using namespace entt::literals; + + // Register glm::vec2 + entt::meta_factory() + .type("vec2") + .data<&glm::vec2::x>("x") + .data<&glm::vec2::y>("y"); + + // Register glm::vec3 + entt::meta_factory() + .type("vec3") + .data<&glm::vec3::x>("x") + .data<&glm::vec3::y>("y") + .data<&glm::vec3::z>("z"); + + // Register glm::vec4 + entt::meta_factory() + .type("vec4") + .data<&glm::vec4::x>("x") + .data<&glm::vec4::y>("y") + .data<&glm::vec4::z>("z") + .data<&glm::vec4::w>("w"); + + // Register glm::quat + entt::meta_factory() + .type("quat") + .data<&glm::quat::x>("x") + .data<&glm::quat::y>("y") + .data<&glm::quat::z>("z") + .data<&glm::quat::w>("w"); + + // Register glm::mat4 (only type for simplicity) + entt::meta_factory() + .type("mat4"); + } + + // Register common container types for sequence container reflection + inline void RegisterContainerTypes() + { + using namespace entt::literals; + + // Register std::array for RenderComponent::Materials + entt::meta_factory>() + .type("array_uint32_16"); + + // Register common vector types that might be used + entt::meta_factory>() + .type("vector_uint32"); + + entt::meta_factory>() + .type("vector_int32"); + + entt::meta_factory>() + .type("vector_float"); + + entt::meta_factory>() + .type("vector_string"); + } +} diff --git a/src/Runtime/Reflection/PropertyAccessor.cpp b/src/Runtime/Reflection/PropertyAccessor.cpp new file mode 100644 index 00000000..61733f39 --- /dev/null +++ b/src/Runtime/Reflection/PropertyAccessor.cpp @@ -0,0 +1,284 @@ +#include "PropertyAccessor.h" +#include "PropertyMeta.h" +#include +#include +#include +#include +#include +#include + +namespace Reflection +{ + using namespace entt::literals; + + // ============================================================================ + // Container Type Registry + // ============================================================================ + + // Centralized container type information to avoid duplication + // between DeducePropertyType() and GetArrayElementType() + struct ContainerTypeInfo + { + entt::id_type typeId; + PropertyType elementType; + }; + + // Returns container type info if typeId matches a known container type + // Returns nullptr if not a known container type + static const ContainerTypeInfo* FindContainerTypeInfo(entt::id_type typeId) + { + // Static registry of known container types + static const ContainerTypeInfo containerTypes[] = { + { entt::resolve>().id(), PropertyType::UInt32 }, + { entt::resolve>().id(), PropertyType::UInt32 }, + { entt::resolve>().id(), PropertyType::Int32 }, + { entt::resolve>().id(), PropertyType::Float }, + { entt::resolve>().id(), PropertyType::String }, + }; + + for (const auto& info : containerTypes) + { + if (info.typeId == typeId) + { + return &info; + } + } + return nullptr; + } + + // ============================================================================ + // PropertyAccessor Implementation + // ============================================================================ + + std::vector PropertyAccessor::GetProperties(entt::meta_type type) + { + std::vector result; + + if (!type) + { + return result; + } + + // Iterate over all data members registered with entt::meta + for (auto&& [id, data] : type.data()) + { + PropertyInfo info; + info.propId = id; + + // Get property name + const char* name = data.name(); + info.name = name ? name : std::to_string(id); + + // Get property type + auto propType = data.type(); + info.type = DeducePropertyType(propType); + + // Get custom metadata if available + auto custom = data.custom(); + if (PropertyMeta* meta = custom) + { + info.meta = *meta; + } + else + { + // Default metadata + info.meta.displayName = info.name; + info.meta.category = "General"; + } + + // Check for enum type + if (info.type == PropertyType::Enum) + { + info.enumTypeId = propType.id(); + } + + // Check for array type - get element type info + if (info.type == PropertyType::Array && propType.is_sequence_container()) + { + // Create a temporary to get the value_type + // We need to check the template argument type + // For now, use a workaround by checking common element types + auto valueType = GetArrayElementType(propType); + info.elementType = valueType.first; + info.elementEnumTypeId = valueType.second; + } + + // Set read-only based on const-ness + if (data.is_const()) + { + info.meta.flags |= PropertyFlags::ReadOnly; + } + + result.push_back(info); + } + + return result; + } + + entt::meta_any PropertyAccessor::GetPropertyValue(entt::meta_type type, void* instance, const std::string& propName) + { + if (!instance || !type) + { + return {}; + } + + // Wrap the void* instance using meta_type::from_void + entt::meta_any instanceAny = type.from_void(instance); + if (!instanceAny) + { + spdlog::warn("PropertyAccessor::GetPropertyValue: failed to wrap instance"); + return {}; + } + + // Find property by name - iterate through data and check names + for (auto&& [id, data] : type.data()) + { + const char* name = data.name(); + if (name && propName == name) + { + return data.get(instanceAny); + } + } + + // Try by hashed name + auto hashedName = entt::hashed_string::value(propName.c_str()); + auto data = type.data(hashedName); + if (data) + { + return data.get(instanceAny); + } + + spdlog::warn("PropertyAccessor::GetPropertyValue: property '{}' not found", propName); + return {}; + } + + bool PropertyAccessor::SetPropertyValue(entt::meta_type type, void* instance, const std::string& propName, const entt::meta_any& value) + { + if (!instance || !type || !value) + { + return false; + } + + // Wrap the void* instance using meta_type::from_void + entt::meta_any instanceAny = type.from_void(instance); + if (!instanceAny) + { + spdlog::warn("PropertyAccessor::SetPropertyValue: failed to wrap instance"); + return false; + } + + // Find property by name - iterate through data and check names + for (auto&& [id, data] : type.data()) + { + const char* name = data.name(); + if (name && propName == name) + { + if (data.is_const()) + { + spdlog::warn("PropertyAccessor::SetPropertyValue: property '{}' is read-only", propName); + return false; + } + return data.set(instanceAny, value); + } + } + + // Try by hashed name + auto hashedName = entt::hashed_string::value(propName.c_str()); + auto data = type.data(hashedName); + if (data) + { + if (data.is_const()) + { + spdlog::warn("PropertyAccessor::SetPropertyValue: property '{}' is read-only", propName); + return false; + } + return data.set(instanceAny, value); + } + + spdlog::warn("PropertyAccessor::SetPropertyValue: property '{}' not found", propName); + return false; + } + + PropertyType PropertyAccessor::DeducePropertyType(entt::meta_type type) + { + auto typeId = type.id(); + + // Check basic types + if (typeId == entt::resolve().id()) + return PropertyType::Bool; + if (typeId == entt::resolve().id()) + return PropertyType::Int32; + if (typeId == entt::resolve().id()) + return PropertyType::UInt32; + if (typeId == entt::resolve().id()) + return PropertyType::Float; + if (typeId == entt::resolve().id()) + return PropertyType::Double; + if (typeId == entt::resolve().id()) + return PropertyType::String; + + // Check GLM types + if (typeId == entt::resolve().id()) + return PropertyType::Vec2; + if (typeId == entt::resolve().id()) + return PropertyType::Vec3; + if (typeId == entt::resolve().id()) + return PropertyType::Vec4; + if (typeId == entt::resolve().id()) + return PropertyType::Quat; + if (typeId == entt::resolve().id()) + return PropertyType::Mat4; + + // Check if it's an enum + if (type.is_enum()) + return PropertyType::Enum; + + // Check for known container types using centralized registry + if (FindContainerTypeInfo(typeId) != nullptr) + return PropertyType::Array; + + // Fallback: check if it's a sequence container (for any other registered types) + if (type.is_sequence_container()) + return PropertyType::Array; + + return PropertyType::Unknown; + } + + std::pair PropertyAccessor::GetArrayElementType(entt::meta_type containerType) + { + auto typeId = containerType.id(); + + // Check known container types using centralized registry + if (const auto* info = FindContainerTypeInfo(typeId)) + { + return {info->elementType, 0}; + } + + // Fallback: try using entt's sequence container API + if (containerType.is_sequence_container()) + { + auto defaultValue = containerType.construct(); + if (defaultValue) + { + auto container = defaultValue.as_sequence_container(); + if (container) + { + auto valueType = container.value_type(); + if (valueType) + { + PropertyType elemType = DeducePropertyType(valueType); + uint32_t enumId = 0; + if (elemType == PropertyType::Enum) + { + enumId = valueType.id(); + } + return {elemType, enumId}; + } + } + } + } + + spdlog::warn("GetArrayElementType: unknown container type"); + return {PropertyType::Unknown, 0}; + } +} diff --git a/src/Runtime/Reflection/PropertyAccessor.h b/src/Runtime/Reflection/PropertyAccessor.h new file mode 100644 index 00000000..cac3d4e8 --- /dev/null +++ b/src/Runtime/Reflection/PropertyAccessor.h @@ -0,0 +1,35 @@ +#pragma once +#include "PropertyTypes.h" +#include "PropertyMeta.h" +#include +#include +#include +#include +#include + +namespace Assets +{ + class Component; +} + +namespace Reflection +{ + class PropertyAccessor + { + public: + // Get all properties of a component type + static std::vector GetProperties(entt::meta_type type); + + // Get a property value from a component + static entt::meta_any GetPropertyValue(entt::meta_type type, void* instance, const std::string& propName); + + // Set a property value on a component + static bool SetPropertyValue(entt::meta_type type, void* instance, const std::string& propName, const entt::meta_any& value); + + // Deduce PropertyType from entt::meta_type + static PropertyType DeducePropertyType(entt::meta_type type); + + // Get array element type info (returns {elementType, enumTypeId}) + static std::pair GetArrayElementType(entt::meta_type containerType); + }; +} diff --git a/src/Runtime/Reflection/PropertyMeta.h b/src/Runtime/Reflection/PropertyMeta.h new file mode 100644 index 00000000..0033fecd --- /dev/null +++ b/src/Runtime/Reflection/PropertyMeta.h @@ -0,0 +1,117 @@ +#pragma once +#include +#include + +namespace Reflection +{ + // Property flags for metadata + enum class PropertyFlags : uint32_t + { + None = 0, + ReadOnly = 1 << 0, // Read-only in editor + Hidden = 1 << 1, // Hidden from editor + JSExposed = 1 << 2, // Exposed to JavaScript + Transient = 1 << 3, // Not serialized + HasRange = 1 << 4, // Has min/max range + }; + + inline PropertyFlags operator|(PropertyFlags a, PropertyFlags b) + { + return static_cast(static_cast(a) | static_cast(b)); + } + + inline PropertyFlags operator&(PropertyFlags a, PropertyFlags b) + { + return static_cast(static_cast(a) & static_cast(b)); + } + + inline PropertyFlags& operator|=(PropertyFlags& a, PropertyFlags b) + { + a = a | b; + return a; + } + + // Property metadata structure + struct PropertyMeta + { + std::string displayName; // Display name in editor + std::string category; // Category/group name + std::string tooltip; // Tooltip text + PropertyFlags flags = PropertyFlags::JSExposed; // Default: exposed to JS + float minValue = 0.0f; // Min value for numeric types + float maxValue = 0.0f; // Max value for numeric types + + PropertyMeta() = default; + + PropertyMeta(const char* display, const char* cat, const char* tip = "", + PropertyFlags f = PropertyFlags::JSExposed, + float minVal = 0.0f, float maxVal = 0.0f) + : displayName(display) + , category(cat) + , tooltip(tip) + , flags(f) + , minValue(minVal) + , maxValue(maxVal) + { + } + + bool IsReadOnly() const + { + return HasFlag(PropertyFlags::ReadOnly); + } + + bool IsHidden() const + { + return HasFlag(PropertyFlags::Hidden); + } + + bool IsJSExposed() const + { + return HasFlag(PropertyFlags::JSExposed); + } + + bool IsTransient() const + { + return HasFlag(PropertyFlags::Transient); + } + + bool HasRangeLimit() const + { + return HasFlag(PropertyFlags::HasRange); + } + + bool HasFlag(PropertyFlags f) const + { + return (static_cast(flags) & static_cast(f)) != 0; + } + }; + + // Helper to create common property configurations + namespace PropertyPresets + { + inline PropertyMeta ReadOnly(const char* display, const char* cat, const char* tip = "") + { + return PropertyMeta(display, cat, tip, PropertyFlags::ReadOnly | PropertyFlags::JSExposed); + } + + inline PropertyMeta Editable(const char* display, const char* cat, const char* tip = "") + { + return PropertyMeta(display, cat, tip, PropertyFlags::JSExposed); + } + + inline PropertyMeta Range(const char* display, const char* cat, float minVal, float maxVal, const char* tip = "") + { + return PropertyMeta(display, cat, tip, PropertyFlags::JSExposed | PropertyFlags::HasRange, minVal, maxVal); + } + + inline PropertyMeta Hidden() + { + return PropertyMeta("", "", "", PropertyFlags::Hidden); + } + + inline PropertyMeta Transient(const char* display, const char* cat, const char* tip = "") + { + return PropertyMeta(display, cat, tip, PropertyFlags::JSExposed | PropertyFlags::Transient); + } + } +} diff --git a/src/Runtime/Reflection/PropertyTypes.h b/src/Runtime/Reflection/PropertyTypes.h new file mode 100644 index 00000000..219f5ad2 --- /dev/null +++ b/src/Runtime/Reflection/PropertyTypes.h @@ -0,0 +1,76 @@ +#pragma once +#include "PropertyMeta.h" +#include +#include + +namespace Reflection +{ + // Property type enumeration for UI widget selection + enum class PropertyType + { + Unknown, + Bool, + Int32, + UInt32, + Float, + Double, + String, + Vec2, + Vec3, + Vec4, + Quat, + Mat4, + Enum, + Array, + AssetRef + }; + + // Property information at runtime + struct PropertyInfo + { + std::string name; // Property name (identifier) + PropertyType type; // Deduced property type + PropertyMeta meta; // Property metadata + uint32_t propId; // entt hash id for get/set operations + uint32_t enumTypeId; // For enum types, the type id for lookup (0 otherwise) + PropertyType elementType; // For array types, the type of elements + uint32_t elementEnumTypeId; // For array of enums, the enum type id + + PropertyInfo() + : type(PropertyType::Unknown) + , propId(0) + , enumTypeId(0) + , elementType(PropertyType::Unknown) + , elementEnumTypeId(0) + {} + + PropertyInfo(const std::string& n, PropertyType t, const PropertyMeta& m, uint32_t id, uint32_t eTypeId = 0) + : name(n), type(t), meta(m), propId(id), enumTypeId(eTypeId) + , elementType(PropertyType::Unknown), elementEnumTypeId(0) + { + } + }; + + // Convert PropertyType to string for debugging + inline const char* PropertyTypeToString(PropertyType type) + { + switch (type) + { + case PropertyType::Bool: return "Bool"; + case PropertyType::Int32: return "Int32"; + case PropertyType::UInt32: return "UInt32"; + case PropertyType::Float: return "Float"; + case PropertyType::Double: return "Double"; + case PropertyType::String: return "String"; + case PropertyType::Vec2: return "Vec2"; + case PropertyType::Vec3: return "Vec3"; + case PropertyType::Vec4: return "Vec4"; + case PropertyType::Quat: return "Quat"; + case PropertyType::Mat4: return "Mat4"; + case PropertyType::Enum: return "Enum"; + case PropertyType::Array: return "Array"; + case PropertyType::AssetRef: return "AssetRef"; + default: return "Unknown"; + } + } +} diff --git a/src/Runtime/Reflection/QuickJSReflectionBridge.h b/src/Runtime/Reflection/QuickJSReflectionBridge.h new file mode 100644 index 00000000..72e64ad7 --- /dev/null +++ b/src/Runtime/Reflection/QuickJSReflectionBridge.h @@ -0,0 +1,124 @@ +#pragma once + +#if WITH_QUICKJS +#include "PropertyAccessor.h" +#include "QuickJSTypeConverter.h" +#include +#include +#include +#include +#include +#include + +namespace Reflection +{ + class QuickJSReflectionBridge + { + public: + // Automatically bind a component class to QuickJS using reflection + template + static void AutoBindComponent(qjs::Context::Module& module, const char* jsClassName) + { + auto metaType = entt::resolve(); + if (!metaType) + { + SPDLOG_WARN("Failed to resolve type for {}", jsClassName); + return; + } + + // Create class registrar + auto& registrar = module.class_(jsClassName); + + // Get all properties + auto properties = PropertyAccessor::GetProperties(metaType); + + // For each property, we need to register getters/setters + // Since quickjspp requires compile-time function pointers, + // we use a different approach: register helper methods + + SPDLOG_INFO("Registered JS class {} with {} properties", jsClassName, properties.size()); + } + + // Generate TypeScript definition for a component type + template + static std::string GenerateTypeScriptDef(const char* className) + { + auto metaType = entt::resolve(); + if (!metaType) + { + return ""; + } + + std::string result = "export class "; + result += className; + result += " {\n"; + + auto properties = PropertyAccessor::GetProperties(metaType); + for (const auto& prop : properties) + { + if (!prop.meta.IsJSExposed()) + { + continue; + } + + std::string tsType = QuickJSTypeConverter::ToTypeScriptType(prop.type); + + if (prop.meta.IsReadOnly()) + { + result += " readonly "; + } + else + { + result += " "; + } + + result += prop.name; + result += ": "; + result += tsType; + result += ";\n"; + } + + result += "}\n"; + return result; + } + + // Generate TypeScript definition for an enum type + template + static std::string GenerateEnumTypeScriptDef(const char* enumName) + { + auto metaType = entt::resolve(); + if (!metaType || !metaType.is_enum()) + { + return ""; + } + + std::string result = "export type "; + result += enumName; + result += " = "; + + bool first = true; + for (auto&& [id, data] : metaType.data()) + { + auto nameProp = data.prop(kPropertyNameKey); + if (nameProp) + { + if (auto* name = nameProp.value().try_cast()) + { + if (!first) + { + result += " | "; + } + result += "\""; + result += *name; + result += "\""; + first = false; + } + } + } + + result += ";\n"; + return result; + } + }; +} +#endif // WITH_QUICKJS diff --git a/src/Runtime/Reflection/QuickJSTypeConverter.cpp b/src/Runtime/Reflection/QuickJSTypeConverter.cpp new file mode 100644 index 00000000..8fbeb7e2 --- /dev/null +++ b/src/Runtime/Reflection/QuickJSTypeConverter.cpp @@ -0,0 +1,402 @@ +#include "QuickJSTypeConverter.h" + +#if WITH_QUICKJS +#include "PropertyAccessor.h" +#include "ReflectionMacros.h" +#include + +namespace Reflection +{ + using namespace entt::literals; + + JSValue QuickJSTypeConverter::ToJSValue(JSContext* ctx, const entt::meta_any& value) + { + if (!value) + { + return JS_UNDEFINED; + } + + auto type = value.type(); + auto typeId = type.id(); + + // Basic types + if (typeId == entt::resolve().id()) + { + return BoolToJS(ctx, value.cast()); + } + if (typeId == entt::resolve().id()) + { + return IntToJS(ctx, value.cast()); + } + if (typeId == entt::resolve().id()) + { + return UIntToJS(ctx, value.cast()); + } + if (typeId == entt::resolve().id()) + { + return FloatToJS(ctx, value.cast()); + } + if (typeId == entt::resolve().id()) + { + return DoubleToJS(ctx, value.cast()); + } + if (typeId == entt::resolve().id()) + { + return StringToJS(ctx, value.cast()); + } + + // GLM types + if (typeId == entt::resolve().id()) + { + return Vec2ToJS(ctx, value.cast()); + } + if (typeId == entt::resolve().id()) + { + return Vec3ToJS(ctx, value.cast()); + } + if (typeId == entt::resolve().id()) + { + return Vec4ToJS(ctx, value.cast()); + } + if (typeId == entt::resolve().id()) + { + return QuatToJS(ctx, value.cast()); + } + + // Enum types + if (type.is_enum()) + { + return EnumToJS(ctx, value, type); + } + + return JS_UNDEFINED; + } + + entt::meta_any QuickJSTypeConverter::FromJSValue(JSContext* ctx, JSValue jsValue, entt::meta_type targetType) + { + auto typeId = targetType.id(); + + // Basic types + if (typeId == entt::resolve().id()) + { + return entt::meta_any{JSTooBool(ctx, jsValue)}; + } + if (typeId == entt::resolve().id()) + { + return entt::meta_any{JSToInt(ctx, jsValue)}; + } + if (typeId == entt::resolve().id()) + { + return entt::meta_any{JSToUInt(ctx, jsValue)}; + } + if (typeId == entt::resolve().id()) + { + return entt::meta_any{JSToFloat(ctx, jsValue)}; + } + if (typeId == entt::resolve().id()) + { + return entt::meta_any{JSToDouble(ctx, jsValue)}; + } + if (typeId == entt::resolve().id()) + { + return entt::meta_any{JSToString(ctx, jsValue)}; + } + + // GLM types + if (typeId == entt::resolve().id()) + { + return entt::meta_any{JSToVec2(ctx, jsValue)}; + } + if (typeId == entt::resolve().id()) + { + return entt::meta_any{JSToVec3(ctx, jsValue)}; + } + if (typeId == entt::resolve().id()) + { + return entt::meta_any{JSToVec4(ctx, jsValue)}; + } + if (typeId == entt::resolve().id()) + { + return entt::meta_any{JSToQuat(ctx, jsValue)}; + } + + // Enum types + if (targetType.is_enum()) + { + return JSToEnum(ctx, jsValue, targetType); + } + + return {}; + } + + std::string QuickJSTypeConverter::ToTypeScriptType(PropertyType type) + { + switch (type) + { + case PropertyType::Bool: return "boolean"; + case PropertyType::Int32: + case PropertyType::UInt32: + case PropertyType::Float: + case PropertyType::Double: return "number"; + case PropertyType::String: return "string"; + case PropertyType::Vec2: return "Vec2"; + case PropertyType::Vec3: return "Vec3"; + case PropertyType::Vec4: return "Vec4"; + case PropertyType::Quat: return "Quat"; + case PropertyType::Mat4: return "Mat4"; + case PropertyType::Enum: return "string"; // Enums are strings in TS + case PropertyType::Array: return "number[]"; // Simplified + default: return "any"; + } + } + + bool QuickJSTypeConverter::IsTypeSupported(entt::meta_type type) + { + auto typeId = type.id(); + + return typeId == entt::resolve().id() || + typeId == entt::resolve().id() || + typeId == entt::resolve().id() || + typeId == entt::resolve().id() || + typeId == entt::resolve().id() || + typeId == entt::resolve().id() || + typeId == entt::resolve().id() || + typeId == entt::resolve().id() || + typeId == entt::resolve().id() || + typeId == entt::resolve().id() || + type.is_enum(); + } + + // Helper implementations + JSValue QuickJSTypeConverter::BoolToJS(JSContext* ctx, bool value) + { + return JS_NewBool(ctx, value); + } + + JSValue QuickJSTypeConverter::IntToJS(JSContext* ctx, int32_t value) + { + return JS_NewInt32(ctx, value); + } + + JSValue QuickJSTypeConverter::UIntToJS(JSContext* ctx, uint32_t value) + { + return JS_NewUint32(ctx, value); + } + + JSValue QuickJSTypeConverter::FloatToJS(JSContext* ctx, float value) + { + return JS_NewFloat64(ctx, static_cast(value)); + } + + JSValue QuickJSTypeConverter::DoubleToJS(JSContext* ctx, double value) + { + return JS_NewFloat64(ctx, value); + } + + JSValue QuickJSTypeConverter::StringToJS(JSContext* ctx, const std::string& value) + { + return JS_NewStringLen(ctx, value.c_str(), value.length()); + } + + JSValue QuickJSTypeConverter::Vec2ToJS(JSContext* ctx, const glm::vec2& value) + { + JSValue obj = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, obj, "x", JS_NewFloat64(ctx, value.x)); + JS_SetPropertyStr(ctx, obj, "y", JS_NewFloat64(ctx, value.y)); + return obj; + } + + JSValue QuickJSTypeConverter::Vec3ToJS(JSContext* ctx, const glm::vec3& value) + { + JSValue obj = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, obj, "x", JS_NewFloat64(ctx, value.x)); + JS_SetPropertyStr(ctx, obj, "y", JS_NewFloat64(ctx, value.y)); + JS_SetPropertyStr(ctx, obj, "z", JS_NewFloat64(ctx, value.z)); + return obj; + } + + JSValue QuickJSTypeConverter::Vec4ToJS(JSContext* ctx, const glm::vec4& value) + { + JSValue obj = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, obj, "x", JS_NewFloat64(ctx, value.x)); + JS_SetPropertyStr(ctx, obj, "y", JS_NewFloat64(ctx, value.y)); + JS_SetPropertyStr(ctx, obj, "z", JS_NewFloat64(ctx, value.z)); + JS_SetPropertyStr(ctx, obj, "w", JS_NewFloat64(ctx, value.w)); + return obj; + } + + JSValue QuickJSTypeConverter::QuatToJS(JSContext* ctx, const glm::quat& value) + { + JSValue obj = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, obj, "x", JS_NewFloat64(ctx, value.x)); + JS_SetPropertyStr(ctx, obj, "y", JS_NewFloat64(ctx, value.y)); + JS_SetPropertyStr(ctx, obj, "z", JS_NewFloat64(ctx, value.z)); + JS_SetPropertyStr(ctx, obj, "w", JS_NewFloat64(ctx, value.w)); + return obj; + } + + JSValue QuickJSTypeConverter::EnumToJS(JSContext* ctx, const entt::meta_any& value, entt::meta_type type) + { + // Try to find the enum value name + for (auto&& [id, data] : type.data()) + { + auto enumVal = data.get({}); + if (enumVal == value) + { + // Use data.name() instead of .prop() which doesn't exist in entt v3.x + const char* name = data.name(); + if (name) + { + return JS_NewString(ctx, name); + } + } + } + return JS_UNDEFINED; + } + + bool QuickJSTypeConverter::JSTooBool(JSContext* ctx, JSValue value) + { + return JS_ToBool(ctx, value) != 0; + } + + int32_t QuickJSTypeConverter::JSToInt(JSContext* ctx, JSValue value) + { + int32_t result = 0; + JS_ToInt32(ctx, &result, value); + return result; + } + + uint32_t QuickJSTypeConverter::JSToUInt(JSContext* ctx, JSValue value) + { + uint32_t result = 0; + JS_ToUint32(ctx, &result, value); + return result; + } + + float QuickJSTypeConverter::JSToFloat(JSContext* ctx, JSValue value) + { + double result = 0; + JS_ToFloat64(ctx, &result, value); + return static_cast(result); + } + + double QuickJSTypeConverter::JSToDouble(JSContext* ctx, JSValue value) + { + double result = 0; + JS_ToFloat64(ctx, &result, value); + return result; + } + + std::string QuickJSTypeConverter::JSToString(JSContext* ctx, JSValue value) + { + const char* str = JS_ToCString(ctx, value); + if (str) + { + std::string result(str); + JS_FreeCString(ctx, str); + return result; + } + return {}; + } + + glm::vec2 QuickJSTypeConverter::JSToVec2(JSContext* ctx, JSValue value) + { + glm::vec2 result(0.0f); + JSValue x = JS_GetPropertyStr(ctx, value, "x"); + JSValue y = JS_GetPropertyStr(ctx, value, "y"); + JS_ToFloat64(ctx, reinterpret_cast(&result.x), x); + JS_ToFloat64(ctx, reinterpret_cast(&result.y), y); + // Convert from double + double dx, dy; + JS_ToFloat64(ctx, &dx, x); + JS_ToFloat64(ctx, &dy, y); + result.x = static_cast(dx); + result.y = static_cast(dy); + JS_FreeValue(ctx, x); + JS_FreeValue(ctx, y); + return result; + } + + glm::vec3 QuickJSTypeConverter::JSToVec3(JSContext* ctx, JSValue value) + { + glm::vec3 result(0.0f); + JSValue x = JS_GetPropertyStr(ctx, value, "x"); + JSValue y = JS_GetPropertyStr(ctx, value, "y"); + JSValue z = JS_GetPropertyStr(ctx, value, "z"); + double dx, dy, dz; + JS_ToFloat64(ctx, &dx, x); + JS_ToFloat64(ctx, &dy, y); + JS_ToFloat64(ctx, &dz, z); + result.x = static_cast(dx); + result.y = static_cast(dy); + result.z = static_cast(dz); + JS_FreeValue(ctx, x); + JS_FreeValue(ctx, y); + JS_FreeValue(ctx, z); + return result; + } + + glm::vec4 QuickJSTypeConverter::JSToVec4(JSContext* ctx, JSValue value) + { + glm::vec4 result(0.0f); + JSValue x = JS_GetPropertyStr(ctx, value, "x"); + JSValue y = JS_GetPropertyStr(ctx, value, "y"); + JSValue z = JS_GetPropertyStr(ctx, value, "z"); + JSValue w = JS_GetPropertyStr(ctx, value, "w"); + double dx, dy, dz, dw; + JS_ToFloat64(ctx, &dx, x); + JS_ToFloat64(ctx, &dy, y); + JS_ToFloat64(ctx, &dz, z); + JS_ToFloat64(ctx, &dw, w); + result.x = static_cast(dx); + result.y = static_cast(dy); + result.z = static_cast(dz); + result.w = static_cast(dw); + JS_FreeValue(ctx, x); + JS_FreeValue(ctx, y); + JS_FreeValue(ctx, z); + JS_FreeValue(ctx, w); + return result; + } + + glm::quat QuickJSTypeConverter::JSToQuat(JSContext* ctx, JSValue value) + { + glm::quat result(1.0f, 0.0f, 0.0f, 0.0f); // w, x, y, z + JSValue x = JS_GetPropertyStr(ctx, value, "x"); + JSValue y = JS_GetPropertyStr(ctx, value, "y"); + JSValue z = JS_GetPropertyStr(ctx, value, "z"); + JSValue w = JS_GetPropertyStr(ctx, value, "w"); + double dx, dy, dz, dw; + JS_ToFloat64(ctx, &dx, x); + JS_ToFloat64(ctx, &dy, y); + JS_ToFloat64(ctx, &dz, z); + JS_ToFloat64(ctx, &dw, w); + result.x = static_cast(dx); + result.y = static_cast(dy); + result.z = static_cast(dz); + result.w = static_cast(dw); + JS_FreeValue(ctx, x); + JS_FreeValue(ctx, y); + JS_FreeValue(ctx, z); + JS_FreeValue(ctx, w); + return result; + } + + entt::meta_any QuickJSTypeConverter::JSToEnum(JSContext* ctx, JSValue value, entt::meta_type type) + { + std::string enumName = JSToString(ctx, value); + + // Find matching enum value by name + for (auto&& [id, data] : type.data()) + { + // Use data.name() instead of .prop() which doesn't exist in entt v3.x + const char* name = data.name(); + if (name && enumName == name) + { + return data.get({}); + } + } + return {}; + } +} +#endif // WITH_QUICKJS diff --git a/src/Runtime/Reflection/QuickJSTypeConverter.h b/src/Runtime/Reflection/QuickJSTypeConverter.h new file mode 100644 index 00000000..2c25ce49 --- /dev/null +++ b/src/Runtime/Reflection/QuickJSTypeConverter.h @@ -0,0 +1,55 @@ +#pragma once + +#if WITH_QUICKJS +#include "PropertyTypes.h" +#include +#include +#include +#include +#include + +namespace Reflection +{ + class QuickJSTypeConverter + { + public: + // Convert entt::meta_any to JSValue + static JSValue ToJSValue(JSContext* ctx, const entt::meta_any& value); + + // Convert JSValue to entt::meta_any of the specified type + static entt::meta_any FromJSValue(JSContext* ctx, JSValue jsValue, entt::meta_type targetType); + + // Convert PropertyType to TypeScript type string + static std::string ToTypeScriptType(PropertyType type); + + // Check if a type is supported for JS conversion + static bool IsTypeSupported(entt::meta_type type); + + private: + // Helper functions for specific types + static JSValue BoolToJS(JSContext* ctx, bool value); + static JSValue IntToJS(JSContext* ctx, int32_t value); + static JSValue UIntToJS(JSContext* ctx, uint32_t value); + static JSValue FloatToJS(JSContext* ctx, float value); + static JSValue DoubleToJS(JSContext* ctx, double value); + static JSValue StringToJS(JSContext* ctx, const std::string& value); + static JSValue Vec2ToJS(JSContext* ctx, const glm::vec2& value); + static JSValue Vec3ToJS(JSContext* ctx, const glm::vec3& value); + static JSValue Vec4ToJS(JSContext* ctx, const glm::vec4& value); + static JSValue QuatToJS(JSContext* ctx, const glm::quat& value); + static JSValue EnumToJS(JSContext* ctx, const entt::meta_any& value, entt::meta_type type); + + static bool JSTooBool(JSContext* ctx, JSValue value); + static int32_t JSToInt(JSContext* ctx, JSValue value); + static uint32_t JSToUInt(JSContext* ctx, JSValue value); + static float JSToFloat(JSContext* ctx, JSValue value); + static double JSToDouble(JSContext* ctx, JSValue value); + static std::string JSToString(JSContext* ctx, JSValue value); + static glm::vec2 JSToVec2(JSContext* ctx, JSValue value); + static glm::vec3 JSToVec3(JSContext* ctx, JSValue value); + static glm::vec4 JSToVec4(JSContext* ctx, JSValue value); + static glm::quat JSToQuat(JSContext* ctx, JSValue value); + static entt::meta_any JSToEnum(JSContext* ctx, JSValue value, entt::meta_type type); + }; +} +#endif // WITH_QUICKJS diff --git a/src/Runtime/Reflection/ReflectionMacros.h b/src/Runtime/Reflection/ReflectionMacros.h new file mode 100644 index 00000000..9b985c28 --- /dev/null +++ b/src/Runtime/Reflection/ReflectionMacros.h @@ -0,0 +1,19 @@ +#pragma once +#include + +// Macro to define component type name and meta type accessor +#define REFLECT_COMPONENT(ClassName) \ + std::string_view GetTypeName() const override { return #ClassName; } \ + entt::meta_type GetMetaType() const override \ + { \ + return entt::resolve(); \ + } \ + static void RegisterReflection(); + +// Hash literal for string to entt::id_type conversion +using namespace entt::literals; + +// Property name storage key +constexpr auto kPropertyNameKey = "prop_name"_hs; +constexpr auto kPropertyMetaKey = "prop_meta"_hs; + diff --git a/src/Runtime/Reflection/ReflectionRegistry.cpp b/src/Runtime/Reflection/ReflectionRegistry.cpp new file mode 100644 index 00000000..d8d745e2 --- /dev/null +++ b/src/Runtime/Reflection/ReflectionRegistry.cpp @@ -0,0 +1,36 @@ +#include "ReflectionRegistry.h" +#include "GlmTypeSupport.h" +#include "Runtime/Components/RenderComponent.h" +#include "Runtime/Components/PhysicsComponent.h" +#include "Runtime/Components/SkinnedMeshComponent.h" + +namespace Reflection +{ + static bool sReflectionInitialized = false; + + void RegisterAllReflection() + { + if (sReflectionInitialized) + { + return; + } + + // Register GLM types first + RegisterGlmTypes(); + + // Register container types for array reflection + RegisterContainerTypes(); + + // Register all component types + Runtime::RenderComponent::RegisterReflection(); + Runtime::PhysicsComponent::RegisterReflection(); + Runtime::SkinnedMeshComponent::RegisterReflection(); + + sReflectionInitialized = true; + } + + bool IsReflectionInitialized() + { + return sReflectionInitialized; + } +} diff --git a/src/Runtime/Reflection/ReflectionRegistry.h b/src/Runtime/Reflection/ReflectionRegistry.h new file mode 100644 index 00000000..f56a1afc --- /dev/null +++ b/src/Runtime/Reflection/ReflectionRegistry.h @@ -0,0 +1,11 @@ +#pragma once + +namespace Reflection +{ + // Register all reflection metadata + // Must be called once at engine initialization + void RegisterAllReflection(); + + // Check if reflection has been initialized + bool IsReflectionInitialized(); +} diff --git a/src/Tests/Test_ComponentSystem.cpp b/src/Tests/Test_ComponentSystem.cpp index c1c0c6a8..1dc046f4 100644 --- a/src/Tests/Test_ComponentSystem.cpp +++ b/src/Tests/Test_ComponentSystem.cpp @@ -1,6 +1,8 @@ #include #include "Assets/Node.h" #include "Assets/Component.h" +#include +#include #include // Define a concrete component for testing @@ -8,6 +10,9 @@ class TestComponent : public Assets::Component { public: TestComponent() = default; int value = 0; + + std::string_view GetTypeName() const override { return "TestComponent"; } + entt::meta_type GetMetaType() const override { return entt::resolve(); } }; TEST_CASE("Component System Basics", "[Unit][Component]") { diff --git a/src/Tests/Test_RenderComponent.cpp b/src/Tests/Test_RenderComponent.cpp index 2636e478..4cf64c50 100644 --- a/src/Tests/Test_RenderComponent.cpp +++ b/src/Tests/Test_RenderComponent.cpp @@ -23,8 +23,8 @@ TEST_CASE("RenderComponent Usage", "[Unit][RenderComponent]") { auto retrieved = node->GetComponent(); REQUIRE(retrieved != nullptr); CHECK(retrieved->GetModelId() == 123); - CHECK(retrieved->IsVisible() == true); - CHECK(retrieved->IsRayCastVisible() == false); + CHECK(retrieved->GetVisible() == true); + CHECK(retrieved->GetRayCastVisible() == false); CHECK(retrieved->Materials()[0] == 7); } } diff --git a/src/Utilities/Glm.hpp b/src/Utilities/Glm.hpp index cf055670..498dadfb 100644 --- a/src/Utilities/Glm.hpp +++ b/src/Utilities/Glm.hpp @@ -6,4 +6,5 @@ //#define GLM_FORCE_MESSAGES #include #include +#include diff --git a/vcpkg.json b/vcpkg.json index 667fb88c..7223bb38 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -46,6 +46,7 @@ "spdlog", "cpp-base64", "catch2", + "entt", "libwebp", { "name": "vulkan-loader", From 14ba228116784a25c9981ec9012d9806ce9b9251 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Sat, 31 Jan 2026 00:09:43 +0800 Subject: [PATCH 17/53] refactor: unify command history and add panel Moves command system into Runtime and adds an editor history panel for debugging, while tightening merge and undo handling consistency. Co-authored-by: gpt-5.2-codex --- src/Editor/Commands/CommandHistory.cpp | 238 ------------------ src/Editor/Commands/CommandHistory.h | 112 --------- src/Editor/Commands/ICommand.h | 49 ---- src/Editor/Commands/PropertyCommand.cpp | 86 ------- src/Editor/Commands/PropertyCommand.h | 193 --------------- src/Editor/Core/EditorUiState.hpp | 5 +- src/Editor/EditorInterface.cpp | 20 +- src/Editor/EditorUi.hpp | 1 + src/Editor/Overlays/TitleBarOverlay.cpp | 16 +- src/Editor/Panels/CommandHistoryPanel.cpp | 78 ++++++ src/Editor/Panels/PropertiesPanel.cpp | 3 +- src/Editor/Panels/PropertyWidgets.cpp | 2 +- src/Editor/Panels/PropertyWidgets.h | 5 +- src/Runtime/Command/CommandHistory.cpp | 241 +++++++++++++++++++ src/Runtime/Command/CommandHistory.hpp | 125 ++++++++++ src/Runtime/Command/CommandSystem.cpp | 89 ------- src/Runtime/Command/CommandSystem.hpp | 39 --- src/Runtime/Command/DeleteNodeCommand.cpp | 19 +- src/Runtime/Command/DeleteNodeCommand.hpp | 10 +- src/Runtime/Command/DuplicateNodeCommand.cpp | 23 +- src/Runtime/Command/DuplicateNodeCommand.hpp | 10 +- src/Runtime/Command/ICommand.hpp | 23 ++ src/Runtime/Command/PropertyCommand.hpp | 147 +++++++++++ src/Runtime/Command/TransformNodeCommand.cpp | 8 +- src/Runtime/Command/TransformNodeCommand.hpp | 10 +- src/Runtime/Engine.cpp | 16 +- src/Runtime/Engine.hpp | 11 +- 27 files changed, 676 insertions(+), 903 deletions(-) delete mode 100644 src/Editor/Commands/CommandHistory.cpp delete mode 100644 src/Editor/Commands/CommandHistory.h delete mode 100644 src/Editor/Commands/ICommand.h delete mode 100644 src/Editor/Commands/PropertyCommand.cpp delete mode 100644 src/Editor/Commands/PropertyCommand.h create mode 100644 src/Editor/Panels/CommandHistoryPanel.cpp create mode 100644 src/Runtime/Command/CommandHistory.cpp create mode 100644 src/Runtime/Command/CommandHistory.hpp delete mode 100644 src/Runtime/Command/CommandSystem.cpp delete mode 100644 src/Runtime/Command/CommandSystem.hpp create mode 100644 src/Runtime/Command/ICommand.hpp create mode 100644 src/Runtime/Command/PropertyCommand.hpp diff --git a/src/Editor/Commands/CommandHistory.cpp b/src/Editor/Commands/CommandHistory.cpp deleted file mode 100644 index 018adfdd..00000000 --- a/src/Editor/Commands/CommandHistory.cpp +++ /dev/null @@ -1,238 +0,0 @@ -#include "CommandHistory.h" -#include - -namespace Editor -{ - /** - * GroupCommand combines multiple commands into a single undoable unit. - */ - class GroupCommand : public ICommand - { - public: - GroupCommand(std::vector&& commands, std::string description) - : commands_(std::move(commands)) - , description_(std::move(description)) - { - } - - bool Execute() override - { - for (auto& cmd : commands_) - { - if (!cmd->Execute()) - { - return false; - } - } - return true; - } - - bool Undo() override - { - // Undo in reverse order - for (auto it = commands_.rbegin(); it != commands_.rend(); ++it) - { - if (!(*it)->Undo()) - { - return false; - } - } - return true; - } - - std::string GetDescription() const override - { - return description_; - } - - private: - std::vector commands_; - std::string description_; - }; - - void CommandHistory::Execute(CommandPtr command) - { - if (!command) - { - return; - } - - // If in a command group, add to group instead of executing directly - if (inGroup_) - { - command->Execute(); - groupCommands_.push_back(std::move(command)); - NotifyHistoryChanged(); - return; - } - - // Try to merge with previous command if enabled - if (mergeEnabled_ && !undoStack_.empty()) - { - ICommand* prevCommand = undoStack_.back().get(); - if (prevCommand->CanMergeWith(command.get())) - { - prevCommand->MergeWith(command.get()); - command->Execute(); // Execute the new command to update state - NotifyHistoryChanged(); - return; - } - } - - // Execute the command - if (command->Execute()) - { - SPDLOG_DEBUG("Executed command: {}", command->GetDescription()); - - // Clear redo stack when new command is executed - redoStack_.clear(); - - // Add to undo stack - undoStack_.push_back(std::move(command)); - - // Enforce max history size - while (undoStack_.size() > maxHistorySize) - { - undoStack_.pop_front(); - } - - NotifyHistoryChanged(); - } - else - { - SPDLOG_WARN("Command execution failed: {}", command->GetDescription()); - } - } - - bool CommandHistory::Undo() - { - if (undoStack_.empty()) - { - return false; - } - - auto command = std::move(undoStack_.back()); - undoStack_.pop_back(); - - if (command->Undo()) - { - SPDLOG_DEBUG("Undone command: {}", command->GetDescription()); - redoStack_.push_back(std::move(command)); - NotifyHistoryChanged(); - return true; - } - else - { - SPDLOG_WARN("Undo failed: {}", command->GetDescription()); - // Put command back if undo failed - undoStack_.push_back(std::move(command)); - return false; - } - } - - bool CommandHistory::Redo() - { - if (redoStack_.empty()) - { - return false; - } - - auto command = std::move(redoStack_.back()); - redoStack_.pop_back(); - - if (command->Execute()) - { - SPDLOG_DEBUG("Redone command: {}", command->GetDescription()); - undoStack_.push_back(std::move(command)); - NotifyHistoryChanged(); - return true; - } - else - { - SPDLOG_WARN("Redo failed: {}", command->GetDescription()); - // Put command back if redo failed - redoStack_.push_back(std::move(command)); - return false; - } - } - - std::string CommandHistory::GetUndoDescription() const - { - if (undoStack_.empty()) - { - return ""; - } - return undoStack_.back()->GetDescription(); - } - - std::string CommandHistory::GetRedoDescription() const - { - if (redoStack_.empty()) - { - return ""; - } - return redoStack_.back()->GetDescription(); - } - - void CommandHistory::Clear() - { - undoStack_.clear(); - redoStack_.clear(); - groupCommands_.clear(); - inGroup_ = false; - NotifyHistoryChanged(); - } - - void CommandHistory::BeginGroup(const std::string& description) - { - if (inGroup_) - { - SPDLOG_WARN("Already in a command group, ending previous group"); - EndGroup(); - } - inGroup_ = true; - groupDescription_ = description; - groupCommands_.clear(); - } - - void CommandHistory::EndGroup() - { - if (!inGroup_) - { - return; - } - - inGroup_ = false; - - if (groupCommands_.empty()) - { - return; - } - - // Create a group command from all accumulated commands - auto groupCmd = std::make_unique( - std::move(groupCommands_), - groupDescription_ - ); - - // Don't re-execute, the commands were already executed - // Just add to undo stack - redoStack_.clear(); - undoStack_.push_back(std::move(groupCmd)); - - while (undoStack_.size() > maxHistorySize) - { - undoStack_.pop_front(); - } - - NotifyHistoryChanged(); - } - - void CommandHistory::NotifyHistoryChanged() - { - if (onHistoryChanged_) - { - onHistoryChanged_(); - } - } -} diff --git a/src/Editor/Commands/CommandHistory.h b/src/Editor/Commands/CommandHistory.h deleted file mode 100644 index 4bdca987..00000000 --- a/src/Editor/Commands/CommandHistory.h +++ /dev/null @@ -1,112 +0,0 @@ -#pragma once - -#include "ICommand.h" -#include -#include - -namespace Editor -{ - /** - * Manages the undo/redo stack for editor commands. - * Provides functionality for executing, undoing, and redoing commands. - */ - class CommandHistory - { - public: - // Maximum number of commands to keep in history - static constexpr size_t maxHistorySize = 100; - - /** - * Execute a command and add it to the history. - * This clears the redo stack. - */ - void Execute(CommandPtr command); - - /** - * Undo the most recent command. - * @return true if there was a command to undo - */ - bool Undo(); - - /** - * Redo the most recently undone command. - * @return true if there was a command to redo - */ - bool Redo(); - - /** - * Check if there are commands that can be undone. - */ - bool CanUndo() const { return !undoStack_.empty(); } - - /** - * Check if there are commands that can be redone. - */ - bool CanRedo() const { return !redoStack_.empty(); } - - /** - * Get description of the command that would be undone. - */ - std::string GetUndoDescription() const; - - /** - * Get description of the command that would be redone. - */ - std::string GetRedoDescription() const; - - /** - * Clear all history. - */ - void Clear(); - - /** - * Get number of commands in undo stack. - */ - size_t GetUndoCount() const { return undoStack_.size(); } - - /** - * Get number of commands in redo stack. - */ - size_t GetRedoCount() const { return redoStack_.size(); } - - /** - * Set callback to be invoked when history changes (for UI updates). - */ - void SetOnHistoryChanged(std::function callback) { onHistoryChanged_ = callback; } - - /** - * Enable or disable command merging for continuous operations. - */ - void SetMergeEnabled(bool enabled) { mergeEnabled_ = enabled; } - bool IsMergeEnabled() const { return mergeEnabled_; } - - /** - * Begin a group of commands that should be treated as a single undo unit. - */ - void BeginGroup(const std::string& description); - - /** - * End the current command group. - */ - void EndGroup(); - - /** - * Check if currently in a command group. - */ - bool IsInGroup() const { return inGroup_; } - - private: - void NotifyHistoryChanged(); - - std::deque undoStack_; - std::deque redoStack_; - - std::function onHistoryChanged_; - bool mergeEnabled_ = true; - bool inGroup_ = false; - - // For grouped commands - std::vector groupCommands_; - std::string groupDescription_; - }; -} diff --git a/src/Editor/Commands/ICommand.h b/src/Editor/Commands/ICommand.h deleted file mode 100644 index 580fa0b0..00000000 --- a/src/Editor/Commands/ICommand.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#include "Common/CoreMinimal.hpp" -#include -#include - -namespace Editor -{ - /** - * Base interface for all editor commands supporting undo/redo. - * Commands encapsulate a single atomic change that can be executed and reverted. - */ - class ICommand - { - public: - virtual ~ICommand() = default; - - /** - * Execute the command (do/redo) - * @return true if execution was successful - */ - virtual bool Execute() = 0; - - /** - * Undo the command, reverting to the state before execution - * @return true if undo was successful - */ - virtual bool Undo() = 0; - - /** - * Get a human-readable description of this command for UI display - */ - virtual std::string GetDescription() const = 0; - - /** - * Check if this command can be merged with another command of the same type. - * Useful for continuous property changes (e.g., dragging a slider). - */ - virtual bool CanMergeWith(const ICommand* other) const { return false; } - - /** - * Merge another command into this one. - * Only called if CanMergeWith returns true. - */ - virtual void MergeWith(const ICommand* other) {} - }; - - using CommandPtr = std::unique_ptr; -} diff --git a/src/Editor/Commands/PropertyCommand.cpp b/src/Editor/Commands/PropertyCommand.cpp deleted file mode 100644 index c99d6268..00000000 --- a/src/Editor/Commands/PropertyCommand.cpp +++ /dev/null @@ -1,86 +0,0 @@ -#include "PropertyCommand.h" -#include "Assets/Node.h" - -namespace Editor -{ - bool TransformCommand::Execute() - { - if (!node_) - { - return false; - } - - switch (type_) - { - case TransformType::Translation: - node_->SetTranslation(newValue_); - break; - case TransformType::Rotation: - if (isQuat_) - { - node_->SetRotation(newRotation_); - } - else - { - node_->SetRotation(glm::quat(newValue_)); - } - break; - case TransformType::Scale: - node_->SetScale(newValue_); - break; - } - - node_->RecalcTransform(true); - return true; - } - - bool TransformCommand::Undo() - { - if (!node_) - { - return false; - } - - switch (type_) - { - case TransformType::Translation: - node_->SetTranslation(oldValue_); - break; - case TransformType::Rotation: - if (isQuat_) - { - node_->SetRotation(oldRotation_); - } - else - { - node_->SetRotation(glm::quat(oldValue_)); - } - break; - case TransformType::Scale: - node_->SetScale(oldValue_); - break; - } - - node_->RecalcTransform(true); - return true; - } - - std::string TransformCommand::GetDescription() const - { - const char* typeStr = "Unknown"; - switch (type_) - { - case TransformType::Translation: - typeStr = "Translation"; - break; - case TransformType::Rotation: - typeStr = "Rotation"; - break; - case TransformType::Scale: - typeStr = "Scale"; - break; - } - - return fmt::format("Set {} on {}", typeStr, node_ ? node_->GetName() : "null"); - } -} diff --git a/src/Editor/Commands/PropertyCommand.h b/src/Editor/Commands/PropertyCommand.h deleted file mode 100644 index afbe3027..00000000 --- a/src/Editor/Commands/PropertyCommand.h +++ /dev/null @@ -1,193 +0,0 @@ -#pragma once - -#include "ICommand.h" -#include "Runtime/Reflection/PropertyAccessor.h" -#include "Assets/Component.h" -#include "Assets/Node.h" -#include -#include -#include -#include -#include -#include - -namespace Editor -{ - /** - * Command for modifying a property on a component through reflection. - * Supports undo/redo and command merging for continuous edits. - */ - class PropertyCommand : public ICommand - { - public: - /** - * Create a property command. - * @param component The component to modify - * @param propertyName Name of the property to modify - * @param newValue The new value to set - * @param oldValue The old value (for undo) - */ - PropertyCommand( - Assets::Component* component, - const std::string& propertyName, - entt::meta_any newValue, - entt::meta_any oldValue - ) - : component_(component) - , propertyName_(propertyName) - , newValue_(std::move(newValue)) - , oldValue_(std::move(oldValue)) - { - } - - bool Execute() override - { - if (!component_) - { - return false; - } - - auto metaType = component_->GetMetaType(); - return Reflection::PropertyAccessor::SetPropertyValue( - metaType, - component_, - propertyName_, - newValue_ - ); - } - - bool Undo() override - { - if (!component_) - { - return false; - } - - auto metaType = component_->GetMetaType(); - return Reflection::PropertyAccessor::SetPropertyValue( - metaType, - component_, - propertyName_, - oldValue_ - ); - } - - std::string GetDescription() const override - { - return fmt::format("Set {} on {}", propertyName_, component_ ? component_->GetTypeName() : "null"); - } - - bool CanMergeWith(const ICommand* other) const override - { - auto* otherCmd = dynamic_cast(other); - if (!otherCmd) - { - return false; - } - - // Merge if same component and same property - return component_ == otherCmd->component_ && - propertyName_ == otherCmd->propertyName_; - } - - void MergeWith(const ICommand* other) override - { - auto* otherCmd = dynamic_cast(other); - if (otherCmd) - { - // Keep our old value (from the original change), update new value - newValue_ = otherCmd->newValue_; - } - } - - // Accessors for inspection - Assets::Component* GetComponent() const { return component_; } - const std::string& GetPropertyName() const { return propertyName_; } - - private: - Assets::Component* component_ = nullptr; - std::string propertyName_; - entt::meta_any newValue_; - entt::meta_any oldValue_; - }; - - /** - * Command for modifying Node transform (position, rotation, scale). - * Separate from PropertyCommand as transforms are not component properties. - */ - class TransformCommand : public ICommand - { - public: - enum class TransformType - { - Translation, - Rotation, - Scale - }; - - TransformCommand( - Assets::Node* node, - TransformType type, - glm::vec3 newValue, - glm::vec3 oldValue - ) - : node_(node) - , type_(type) - , newValue_(newValue) - , oldValue_(oldValue) - { - } - - TransformCommand( - Assets::Node* node, - glm::quat newRotation, - glm::quat oldRotation - ) - : node_(node) - , type_(TransformType::Rotation) - , newRotation_(newRotation) - , oldRotation_(oldRotation) - , isQuat_(true) - { - } - - bool Execute() override; - bool Undo() override; - std::string GetDescription() const override; - - bool CanMergeWith(const ICommand* other) const override - { - auto* otherCmd = dynamic_cast(other); - if (!otherCmd) - { - return false; - } - return node_ == otherCmd->node_ && type_ == otherCmd->type_; - } - - void MergeWith(const ICommand* other) override - { - auto* otherCmd = dynamic_cast(other); - if (otherCmd) - { - if (isQuat_) - { - newRotation_ = otherCmd->newRotation_; - } - else - { - newValue_ = otherCmd->newValue_; - } - } - } - - private: - Assets::Node* node_ = nullptr; - TransformType type_; - glm::vec3 newValue_{0.0f}; - glm::vec3 oldValue_{0.0f}; - glm::quat newRotation_{1.0f, 0.0f, 0.0f, 0.0f}; - glm::quat oldRotation_{1.0f, 0.0f, 0.0f, 0.0f}; - bool isQuat_ = false; - }; -} diff --git a/src/Editor/Core/EditorUiState.hpp b/src/Editor/Core/EditorUiState.hpp index e81ee9ab..1644a156 100644 --- a/src/Editor/Core/EditorUiState.hpp +++ b/src/Editor/Core/EditorUiState.hpp @@ -1,7 +1,7 @@ #pragma once #include "Common/CoreMinimal.hpp" -#include "Editor/Commands/CommandHistory.h" +#include "Runtime/Command/CommandHistory.hpp" #include @@ -25,6 +25,7 @@ namespace Editor // Panels bool sidebar = true; bool properties = true; + bool commandHistoryPanel = false; bool viewport = true; bool contentBrowser = true; bool materialBrowser = true; @@ -62,7 +63,5 @@ namespace Editor ImFont* fontIcon = nullptr; ImFont* bigIcon = nullptr; - // Command history for undo/redo - CommandHistory commandHistory; }; } // namespace Editor diff --git a/src/Editor/EditorInterface.cpp b/src/Editor/EditorInterface.cpp index 62c0d38e..1082b4c1 100644 --- a/src/Editor/EditorInterface.cpp +++ b/src/Editor/EditorInterface.cpp @@ -243,22 +243,7 @@ void EditorInterface::Render() uiState_.selected_obj_id = ctx.scene.GetSelectedId(); - // Handle global keyboard shortcuts - auto& io = ImGui::GetIO(); - if (!io.WantTextInput) - { - // Undo: Ctrl+Z - if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_Z) && !io.KeyShift) - { - uiState_.commandHistory.Undo(); - } - // Redo: Ctrl+Y or Ctrl+Shift+Z - if ((io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_Y)) || - (io.KeyCtrl && io.KeyShift && ImGui::IsKeyPressed(ImGuiKey_Z))) - { - uiState_.commandHistory.Redo(); - } - } + // Global keyboard shortcuts are handled by NextEngine. ImGuiID id = DockSpaceUI(); ToolbarUI(); @@ -271,6 +256,7 @@ void EditorInterface::Render() ImGui::DockBuilderDockWindow("Outliner", dock1); ImGui::DockBuilderDockWindow("Properties", dock2); + ImGui::DockBuilderDockWindow("Command History", dock2); ImGui::DockBuilderDockWindow("Content Browser", dock3); ImGui::DockBuilderDockWindow("Material Browser", dock3); ImGui::DockBuilderDockWindow("Texture Browser", dock3); @@ -292,6 +278,8 @@ void EditorInterface::Render() Editor::DrawTextureBrowserPanel(ctx, uiState_); if (uiState_.meshBrowser) Editor::DrawMeshBrowserPanel(ctx, uiState_); + if (uiState_.commandHistoryPanel) + Editor::DrawCommandHistoryPanel(ctx, uiState_); if (uiState_.viewport) Editor::DrawViewportOverlay(ctx, uiState_); diff --git a/src/Editor/EditorUi.hpp b/src/Editor/EditorUi.hpp index 097ca710..2f02832a 100644 --- a/src/Editor/EditorUi.hpp +++ b/src/Editor/EditorUi.hpp @@ -19,6 +19,7 @@ namespace Editor void DrawMaterialBrowserPanel(EditorContext& ctx, EditorUiState& ui); void DrawTextureBrowserPanel(EditorContext& ctx, EditorUiState& ui); void DrawMeshBrowserPanel(EditorContext& ctx, EditorUiState& ui); + void DrawCommandHistoryPanel(EditorContext& ctx, EditorUiState& ui); // Viewport overlay widgets (stats/tools) void DrawViewportOverlay(EditorContext& ctx, EditorUiState& ui); diff --git a/src/Editor/Overlays/TitleBarOverlay.cpp b/src/Editor/Overlays/TitleBarOverlay.cpp index e5cf0a6e..1e2945bb 100644 --- a/src/Editor/Overlays/TitleBarOverlay.cpp +++ b/src/Editor/Overlays/TitleBarOverlay.cpp @@ -8,6 +8,8 @@ #include +#include "Runtime/Engine.hpp" + namespace Editor { namespace @@ -72,23 +74,24 @@ namespace Editor if (ImGui::BeginMenu("Edit")) { // Undo/Redo - bool canUndo = ui.commandHistory.CanUndo(); - bool canRedo = ui.commandHistory.CanRedo(); + CommandHistory& history = ctx.engine.GetCommandHistory(); + bool canUndo = history.CanUndo(); + bool canRedo = history.CanRedo(); std::string undoLabel = canUndo - ? fmt::format("Undo {}", ui.commandHistory.GetUndoDescription()) + ? fmt::format("Undo {}", history.GetUndoDescription()) : "Undo"; std::string redoLabel = canRedo - ? fmt::format("Redo {}", ui.commandHistory.GetRedoDescription()) + ? fmt::format("Redo {}", history.GetRedoDescription()) : "Redo"; if (ImGui::MenuItem(undoLabel.c_str(), "Ctrl+Z", false, canUndo)) { - ui.commandHistory.Undo(); + history.Undo(); } if (ImGui::MenuItem(redoLabel.c_str(), "Ctrl+Y", false, canRedo)) { - ui.commandHistory.Redo(); + history.Redo(); } ImGui::Separator(); @@ -118,6 +121,7 @@ namespace Editor ImGui::MenuItem("Metrics", nullptr, &ui.child_metrics); ImGui::MenuItem("Stack Tool", nullptr, &ui.child_stack); ImGui::MenuItem("Color Export", nullptr, &ui.child_color); + ImGui::MenuItem("Command History", nullptr, &ui.commandHistoryPanel); ImGui::MenuItem("Material Editor", nullptr, &ui.child_mat_editor); ImGui::EndMenu(); } diff --git a/src/Editor/Panels/CommandHistoryPanel.cpp b/src/Editor/Panels/CommandHistoryPanel.cpp new file mode 100644 index 00000000..e3131870 --- /dev/null +++ b/src/Editor/Panels/CommandHistoryPanel.cpp @@ -0,0 +1,78 @@ +#include "Editor/EditorUi.hpp" + +#include + +#include "Runtime/Engine.hpp" + +namespace Editor +{ + void DrawCommandHistoryPanel(EditorContext& ctx, EditorUiState& ui) + { + if (!ImGui::Begin("Command History", &ui.commandHistoryPanel)) + { + ImGui::End(); + return; + } + + CommandHistory& history = ctx.engine.GetCommandHistory(); + + const bool canUndo = history.CanUndo(); + const bool canRedo = history.CanRedo(); + + ImGui::Text("Undo: %zu", history.GetUndoCount()); + ImGui::SameLine(); + ImGui::Text("Redo: %zu", history.GetRedoCount()); + + bool mergeEnabled = history.IsMergeEnabled(); + if (ImGui::Checkbox("Merge Continuous Edits", &mergeEnabled)) + { + history.SetMergeEnabled(mergeEnabled); + } + + ImGui::SameLine(); + const bool inGroup = history.IsInGroup(); + ImGui::TextDisabled("Grouping: %s", inGroup ? "On" : "Off"); + + if (ImGui::Button("Undo", ImVec2(80.0f, 0.0f))) + { + if (canUndo) + { + history.Undo(); + } + } + ImGui::SameLine(); + if (ImGui::Button("Redo", ImVec2(80.0f, 0.0f))) + { + if (canRedo) + { + history.Redo(); + } + } + ImGui::SameLine(); + if (ImGui::Button("Clear", ImVec2(80.0f, 0.0f))) + { + history.Clear(); + } + + ImGui::Separator(); + + ImGui::BeginChild("CommandHistoryList", ImVec2(0.0f, 0.0f), true); + ImGui::TextDisabled("Undo Stack (top = most recent)"); + const auto undoDescriptions = history.GetUndoStackDescriptions(); + for (size_t i = 0; i < undoDescriptions.size(); ++i) + { + ImGui::Text("%zu: %s", i + 1, undoDescriptions[i].c_str()); + } + + ImGui::Separator(); + ImGui::TextDisabled("Redo Stack (top = most recent)"); + const auto redoDescriptions = history.GetRedoStackDescriptions(); + for (size_t i = 0; i < redoDescriptions.size(); ++i) + { + ImGui::Text("%zu: %s", i + 1, redoDescriptions[i].c_str()); + } + ImGui::EndChild(); + + ImGui::End(); + } +} // namespace Editor diff --git a/src/Editor/Panels/PropertiesPanel.cpp b/src/Editor/Panels/PropertiesPanel.cpp index e4c7db0c..679069cc 100644 --- a/src/Editor/Panels/PropertiesPanel.cpp +++ b/src/Editor/Panels/PropertiesPanel.cpp @@ -6,6 +6,7 @@ #include "Runtime/Components/RenderComponent.h" #include "Runtime/Components/PhysicsComponent.h" #include "Runtime/Components/SkinnedMeshComponent.h" +#include "Runtime/Engine.hpp" #include "ThirdParty/fontawesome/IconsFontAwesome6.h" @@ -148,7 +149,7 @@ namespace Editor if (ImGui::CollapsingHeader(headerName.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::Indent(); - PropertyWidgets::DrawComponentProperties(component.get(), &ui.commandHistory); + PropertyWidgets::DrawComponentProperties(component.get(), &ctx.engine.GetCommandHistory()); ImGui::Unindent(); } } diff --git a/src/Editor/Panels/PropertyWidgets.cpp b/src/Editor/Panels/PropertyWidgets.cpp index d4b244af..e38b15ac 100644 --- a/src/Editor/Panels/PropertyWidgets.cpp +++ b/src/Editor/Panels/PropertyWidgets.cpp @@ -1,5 +1,5 @@ #include "PropertyWidgets.h" -#include "Editor/Commands/PropertyCommand.h" +#include "Runtime/Command/PropertyCommand.hpp" #include "Runtime/Reflection/ReflectionMacros.h" #include diff --git a/src/Editor/Panels/PropertyWidgets.h b/src/Editor/Panels/PropertyWidgets.h index 0c3749f3..c6a0534f 100644 --- a/src/Editor/Panels/PropertyWidgets.h +++ b/src/Editor/Panels/PropertyWidgets.h @@ -3,7 +3,7 @@ #include "Common/CoreMinimal.hpp" #include "Runtime/Reflection/PropertyTypes.h" #include "Runtime/Reflection/PropertyAccessor.h" -#include "Editor/Commands/CommandHistory.h" +#include "Runtime/Command/CommandHistory.hpp" #include "Assets/Component.h" #include @@ -17,9 +17,6 @@ namespace Editor { - // Forward declaration - class CommandHistory; - /** * Utility class for rendering property editor widgets based on reflection. * Automatically selects appropriate ImGui widgets based on property type. diff --git a/src/Runtime/Command/CommandHistory.cpp b/src/Runtime/Command/CommandHistory.cpp new file mode 100644 index 00000000..d20f9f2d --- /dev/null +++ b/src/Runtime/Command/CommandHistory.cpp @@ -0,0 +1,241 @@ +#include "Runtime/Command/CommandHistory.hpp" +#include "Runtime/Command/CommandHistory.hpp" + +#include + +namespace +{ + class GroupCommand final : public ICommand + { + public: + GroupCommand(std::vector&& commands, std::string description) + : commands_(std::move(commands)) + , description_(std::move(description)) + { + } + + bool Execute() override + { + for (auto& cmd : commands_) + { + if (!cmd->Execute()) + { + return false; + } + } + return true; + } + + bool Undo() override + { + for (auto it = commands_.rbegin(); it != commands_.rend(); ++it) + { + if (!(*it)->Undo()) + { + return false; + } + } + return true; + } + + std::string GetDescription() const override + { + return description_; + } + + private: + std::vector commands_; + std::string description_; + }; +} + +bool CommandHistory::Execute(CommandPtr command) +{ + if (!command) + { + return false; + } + + if (inGroup_) + { + command->Execute(); + groupCommands_.push_back(std::move(command)); + NotifyHistoryChanged(); + return true; + } + + if (mergeEnabled_ && !undoStack_.empty()) + { + ICommand* prevCommand = undoStack_.back().get(); + if (prevCommand->CanMergeWith(command.get())) + { + prevCommand->MergeWith(command.get()); + command->Execute(); + NotifyHistoryChanged(); + return true; + } + } + + if (command->Execute()) + { + SPDLOG_DEBUG("Executed command: {}", command->GetDescription()); + + redoStack_.clear(); + undoStack_.push_back(std::move(command)); + + while (undoStack_.size() > maxHistorySize) + { + undoStack_.pop_front(); + } + + NotifyHistoryChanged(); + return true; + } + + SPDLOG_WARN("Command execution failed: {}", command->GetDescription()); + return false; +} + +bool CommandHistory::Undo() +{ + if (undoStack_.empty()) + { + return false; + } + + auto command = std::move(undoStack_.back()); + undoStack_.pop_back(); + + if (command->Undo()) + { + SPDLOG_DEBUG("Undone command: {}", command->GetDescription()); + redoStack_.push_back(std::move(command)); + NotifyHistoryChanged(); + return true; + } + + SPDLOG_WARN("Undo failed: {}", command->GetDescription()); + undoStack_.push_back(std::move(command)); + return false; +} + +bool CommandHistory::Redo() +{ + if (redoStack_.empty()) + { + return false; + } + + auto command = std::move(redoStack_.back()); + redoStack_.pop_back(); + + if (command->Execute()) + { + SPDLOG_DEBUG("Redone command: {}", command->GetDescription()); + undoStack_.push_back(std::move(command)); + NotifyHistoryChanged(); + return true; + } + + SPDLOG_WARN("Redo failed: {}", command->GetDescription()); + redoStack_.push_back(std::move(command)); + return false; +} + +std::string CommandHistory::GetUndoDescription() const +{ + if (undoStack_.empty()) + { + return ""; + } + return undoStack_.back()->GetDescription(); +} + +std::string CommandHistory::GetRedoDescription() const +{ + if (redoStack_.empty()) + { + return ""; + } + return redoStack_.back()->GetDescription(); +} + +std::vector CommandHistory::GetUndoStackDescriptions() const +{ + std::vector descriptions; + descriptions.reserve(undoStack_.size()); + for (auto it = undoStack_.rbegin(); it != undoStack_.rend(); ++it) + { + descriptions.push_back((*it)->GetDescription()); + } + return descriptions; +} + +std::vector CommandHistory::GetRedoStackDescriptions() const +{ + std::vector descriptions; + descriptions.reserve(redoStack_.size()); + for (auto it = redoStack_.rbegin(); it != redoStack_.rend(); ++it) + { + descriptions.push_back((*it)->GetDescription()); + } + return descriptions; +} + +void CommandHistory::Clear() +{ + undoStack_.clear(); + redoStack_.clear(); + groupCommands_.clear(); + inGroup_ = false; + NotifyHistoryChanged(); +} + +void CommandHistory::BeginGroup(const std::string& description) +{ + if (inGroup_) + { + SPDLOG_WARN("Already in a command group, ending previous group"); + EndGroup(); + } + inGroup_ = true; + groupDescription_ = description; + groupCommands_.clear(); +} + +void CommandHistory::EndGroup() +{ + if (!inGroup_) + { + return; + } + + inGroup_ = false; + + if (groupCommands_.empty()) + { + return; + } + + auto groupCmd = std::make_unique( + std::move(groupCommands_), + groupDescription_); + + redoStack_.clear(); + undoStack_.push_back(std::move(groupCmd)); + + while (undoStack_.size() > maxHistorySize) + { + undoStack_.pop_front(); + } + + NotifyHistoryChanged(); +} + +void CommandHistory::NotifyHistoryChanged() +{ + if (onHistoryChanged_) + { + onHistoryChanged_(); + } +} diff --git a/src/Runtime/Command/CommandHistory.hpp b/src/Runtime/Command/CommandHistory.hpp new file mode 100644 index 00000000..7191a0f9 --- /dev/null +++ b/src/Runtime/Command/CommandHistory.hpp @@ -0,0 +1,125 @@ +#pragma once + +#pragma once + +#include "Common/CoreMinimal.hpp" +#include "Runtime/Command/ICommand.hpp" + +#include +#include +#include +#include + +/** + * Manages the undo/redo stack for commands. + * Provides functionality for executing, undoing, and redoing commands. + */ +class CommandHistory +{ +public: + // Maximum number of commands to keep in history + static constexpr size_t maxHistorySize = 100; + + /** + * Execute a command and add it to the history. + * This clears the redo stack. + */ + bool Execute(CommandPtr command); + + /** + * Undo the most recent command. + * @return true if there was a command to undo + */ + bool Undo(); + + /** + * Redo the most recently undone command. + * @return true if there was a command to redo + */ + bool Redo(); + + /** + * Check if there are commands that can be undone. + */ + bool CanUndo() const { return !undoStack_.empty(); } + + /** + * Check if there are commands that can be redone. + */ + bool CanRedo() const { return !redoStack_.empty(); } + + /** + * Get description of the command that would be undone. + */ + std::string GetUndoDescription() const; + + /** + * Get description of the command that would be redone. + */ + std::string GetRedoDescription() const; + + /** + * Get undo stack descriptions (most recent first). + */ + std::vector GetUndoStackDescriptions() const; + + /** + * Get redo stack descriptions (most recent first). + */ + std::vector GetRedoStackDescriptions() const; + + /** + * Clear all history. + */ + void Clear(); + + /** + * Get number of commands in undo stack. + */ + size_t GetUndoCount() const { return undoStack_.size(); } + + /** + * Get number of commands in redo stack. + */ + size_t GetRedoCount() const { return redoStack_.size(); } + + /** + * Set callback to be invoked when history changes (for UI updates). + */ + void SetOnHistoryChanged(std::function callback) { onHistoryChanged_ = callback; } + + /** + * Enable or disable command merging for continuous operations. + */ + void SetMergeEnabled(bool enabled) { mergeEnabled_ = enabled; } + bool IsMergeEnabled() const { return mergeEnabled_; } + + /** + * Begin a group of commands that should be treated as a single undo unit. + */ + void BeginGroup(const std::string& description); + + /** + * End the current command group. + */ + void EndGroup(); + + /** + * Check if currently in a command group. + */ + bool IsInGroup() const { return inGroup_; } + +private: + void NotifyHistoryChanged(); + + std::deque undoStack_; + std::deque redoStack_; + + std::function onHistoryChanged_; + bool mergeEnabled_ = true; + bool inGroup_ = false; + + // For grouped commands + std::vector groupCommands_; + std::string groupDescription_; +}; diff --git a/src/Runtime/Command/CommandSystem.cpp b/src/Runtime/Command/CommandSystem.cpp deleted file mode 100644 index 58ff8c2c..00000000 --- a/src/Runtime/Command/CommandSystem.cpp +++ /dev/null @@ -1,89 +0,0 @@ -#include "Runtime/Command/CommandSystem.hpp" - -CommandSystem::CommandSystem(size_t historyLimit) - : historyLimit_(historyLimit) -{ -} - -bool CommandSystem::ExecuteCommand(std::unique_ptr command) -{ - if (!command) - { - return false; - } - - if (!command->Execute()) - { - return false; - } - - redoStack_.clear(); - undoStack_.push_back(std::move(command)); - TrimHistory(); - return true; -} - -bool CommandSystem::Undo() -{ - if (undoStack_.empty()) - { - return false; - } - - auto command = std::move(undoStack_.back()); - undoStack_.pop_back(); - command->Undo(); - redoStack_.push_back(std::move(command)); - return true; -} - -bool CommandSystem::Redo() -{ - if (redoStack_.empty()) - { - return false; - } - - auto command = std::move(redoStack_.back()); - redoStack_.pop_back(); - command->Redo(); - undoStack_.push_back(std::move(command)); - TrimHistory(); - return true; -} - -bool CommandSystem::CanUndo() const -{ - return !undoStack_.empty(); -} - -bool CommandSystem::CanRedo() const -{ - return !redoStack_.empty(); -} - -void CommandSystem::Clear() -{ - undoStack_.clear(); - redoStack_.clear(); -} - -void CommandSystem::SetHistoryLimit(size_t limit) -{ - historyLimit_ = limit; - TrimHistory(); -} - -void CommandSystem::TrimHistory() -{ - if (historyLimit_ == 0) - { - Clear(); - return; - } - - while (undoStack_.size() > historyLimit_) - { - undoStack_.erase(undoStack_.begin()); - } -} diff --git a/src/Runtime/Command/CommandSystem.hpp b/src/Runtime/Command/CommandSystem.hpp deleted file mode 100644 index abf1a2fa..00000000 --- a/src/Runtime/Command/CommandSystem.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include "Common/CoreMinimal.hpp" - -#include -#include - -class ICommand -{ -public: - virtual ~ICommand() = default; - virtual bool Execute() = 0; - virtual void Undo() = 0; - virtual void Redo() = 0; - virtual const char* Name() const { return "Command"; } -}; - -class CommandSystem -{ -public: - explicit CommandSystem(size_t historyLimit = 100); - - bool ExecuteCommand(std::unique_ptr command); - bool Undo(); - bool Redo(); - bool CanUndo() const; - bool CanRedo() const; - void Clear(); - - size_t HistoryLimit() const { return historyLimit_; } - void SetHistoryLimit(size_t limit); - -private: - void TrimHistory(); - - std::vector> undoStack_; - std::vector> redoStack_; - size_t historyLimit_ = 100; -}; diff --git a/src/Runtime/Command/DeleteNodeCommand.cpp b/src/Runtime/Command/DeleteNodeCommand.cpp index f40856d0..fcabf522 100644 --- a/src/Runtime/Command/DeleteNodeCommand.cpp +++ b/src/Runtime/Command/DeleteNodeCommand.cpp @@ -35,29 +35,16 @@ bool DeleteNodeCommand::Execute() return true; } -void DeleteNodeCommand::Undo() +bool DeleteNodeCommand::Undo() { if (!scene_ || removedEntries_.empty()) { - return; + return false; } scene_->RestoreNodes(removedEntries_, parent_, root_); scene_->SetSelectedId(previousSelectedId_); scene_->MarkDirty(); scene_->GetCPUAccelerationStructure().UpdateBVH(*scene_); -} - -void DeleteNodeCommand::Redo() -{ - if (!scene_ || !root_) - { - return; - } - - removedEntries_.clear(); - removedEntries_ = scene_->RemoveNodeHierarchy(instanceId_, parent_); - scene_->SetSelectedId(static_cast(-1)); - scene_->MarkDirty(); - scene_->GetCPUAccelerationStructure().UpdateBVH(*scene_); + return true; } diff --git a/src/Runtime/Command/DeleteNodeCommand.hpp b/src/Runtime/Command/DeleteNodeCommand.hpp index 49c6f17c..f1176a33 100644 --- a/src/Runtime/Command/DeleteNodeCommand.hpp +++ b/src/Runtime/Command/DeleteNodeCommand.hpp @@ -1,10 +1,13 @@ #pragma once +#pragma once + #include "Common/CoreMinimal.hpp" -#include "Runtime/Command/CommandSystem.hpp" +#include "Runtime/Command/ICommand.hpp" #include "Assets/Scene.hpp" #include +#include #include class DeleteNodeCommand final : public ICommand @@ -13,9 +16,8 @@ class DeleteNodeCommand final : public ICommand DeleteNodeCommand(Assets::Scene& scene, uint32_t instanceId); bool Execute() override; - void Undo() override; - void Redo() override; - const char* Name() const override { return "DeleteNode"; } + bool Undo() override; + std::string GetDescription() const override { return "Delete Node"; } private: Assets::Scene* scene_ = nullptr; diff --git a/src/Runtime/Command/DuplicateNodeCommand.cpp b/src/Runtime/Command/DuplicateNodeCommand.cpp index deeb2a8e..9e6e017e 100644 --- a/src/Runtime/Command/DuplicateNodeCommand.cpp +++ b/src/Runtime/Command/DuplicateNodeCommand.cpp @@ -83,33 +83,16 @@ bool DuplicateNodeCommand::Execute() return true; } -void DuplicateNodeCommand::Undo() +bool DuplicateNodeCommand::Undo() { if (!scene_ || !newNode_) { - return; + return false; } scene_->RemoveNodeByInstanceId(newInstanceId_); scene_->SetSelectedId(previousSelectedId_); scene_->MarkDirty(); scene_->GetCPUAccelerationStructure().UpdateBVH(*scene_); -} - -void DuplicateNodeCommand::Redo() -{ - if (!scene_ || !newNode_) - { - return; - } - - if (parent_) - { - newNode_->SetParent(parent_); - } - - scene_->AddNode(newNode_); - scene_->SetSelectedId(newInstanceId_); - scene_->MarkDirty(); - scene_->GetCPUAccelerationStructure().UpdateBVH(*scene_); + return true; } diff --git a/src/Runtime/Command/DuplicateNodeCommand.hpp b/src/Runtime/Command/DuplicateNodeCommand.hpp index f1b18b55..72ada195 100644 --- a/src/Runtime/Command/DuplicateNodeCommand.hpp +++ b/src/Runtime/Command/DuplicateNodeCommand.hpp @@ -1,9 +1,12 @@ #pragma once +#pragma once + #include "Common/CoreMinimal.hpp" -#include "Runtime/Command/CommandSystem.hpp" +#include "Runtime/Command/ICommand.hpp" #include +#include namespace Assets { @@ -17,9 +20,8 @@ class DuplicateNodeCommand final : public ICommand DuplicateNodeCommand(Assets::Scene& scene, uint32_t sourceInstanceId); bool Execute() override; - void Undo() override; - void Redo() override; - const char* Name() const override { return "DuplicateNode"; } + bool Undo() override; + std::string GetDescription() const override { return "Duplicate Node"; } uint32_t GetNewInstanceId() const { return newInstanceId_; } diff --git a/src/Runtime/Command/ICommand.hpp b/src/Runtime/Command/ICommand.hpp new file mode 100644 index 00000000..3d2e4c54 --- /dev/null +++ b/src/Runtime/Command/ICommand.hpp @@ -0,0 +1,23 @@ +#pragma once + +#pragma once + +#include "Common/CoreMinimal.hpp" + +#include +#include + +class ICommand +{ +public: + virtual ~ICommand() = default; + + virtual bool Execute() = 0; + virtual bool Undo() = 0; + virtual std::string GetDescription() const = 0; + + virtual bool CanMergeWith(const ICommand* other) const { return false; } + virtual void MergeWith(const ICommand* other) {} +}; + +using CommandPtr = std::unique_ptr; diff --git a/src/Runtime/Command/PropertyCommand.hpp b/src/Runtime/Command/PropertyCommand.hpp new file mode 100644 index 00000000..b2d795e9 --- /dev/null +++ b/src/Runtime/Command/PropertyCommand.hpp @@ -0,0 +1,147 @@ +#pragma once + +#pragma once + +#include "Common/CoreMinimal.hpp" +#include "Runtime/Command/ICommand.hpp" +#include "Runtime/Reflection/PropertyAccessor.h" +#include "Assets/Component.h" +#include "Assets/Node.h" + +#include +#include +#include +#include +#include +#include +#include + +/** + * Command for modifying a property on a component through reflection. + * Supports undo/redo and command merging for continuous edits. + */ +class PropertyCommand : public ICommand +{ +public: + /** + * Create a property command. + * @param component The component to modify + * @param propertyName Name of the property to modify + * @param newValue The new value to set + * @param oldValue The old value (for undo) + */ + PropertyCommand( + Assets::Component* component, + const std::string& propertyName, + entt::meta_any newValue, + entt::meta_any oldValue) + : component_(component) + , propertyName_(propertyName) + , newValue_(std::move(newValue)) + , oldValue_(std::move(oldValue)) + { + } + + bool Execute() override + { + if (!component_) + { + return false; + } + + auto metaType = component_->GetMetaType(); + return Reflection::PropertyAccessor::SetPropertyValue( + metaType, + component_, + propertyName_, + newValue_); + } + + bool Undo() override + { + if (!component_) + { + return false; + } + + auto metaType = component_->GetMetaType(); + return Reflection::PropertyAccessor::SetPropertyValue( + metaType, + component_, + propertyName_, + oldValue_); + } + + std::string GetDescription() const override + { + return fmt::format("Set {} on {}", propertyName_, component_ ? component_->GetTypeName() : "null"); + } + + bool CanMergeWith(const ICommand* other) const override + { + auto* otherCmd = dynamic_cast(other); + if (!otherCmd) + { + return false; + } + + const auto type = newValue_.type(); + if (!type) + { + return false; + } + + if (type == entt::resolve() || type.is_enum() || type.is_sequence_container()) + { + return false; + } + + if (type == entt::resolve() || + type == entt::resolve() || + type == entt::resolve()) + { + return false; + } + + const bool isNumeric = + type == entt::resolve() || + type == entt::resolve() || + type == entt::resolve() || + type == entt::resolve() || + type == entt::resolve() || + type == entt::resolve() || + type == entt::resolve() || + type == entt::resolve() || + type == entt::resolve() || + type == entt::resolve() || + type == entt::resolve() || + type == entt::resolve() || + type == entt::resolve() || + type == entt::resolve(); + + if (!isNumeric) + { + return false; + } + + return component_ == otherCmd->component_ && propertyName_ == otherCmd->propertyName_; + } + + void MergeWith(const ICommand* other) override + { + auto* otherCmd = dynamic_cast(other); + if (otherCmd) + { + newValue_ = otherCmd->newValue_; + } + } + + Assets::Component* GetComponent() const { return component_; } + const std::string& GetPropertyName() const { return propertyName_; } + +private: + Assets::Component* component_ = nullptr; + std::string propertyName_; + entt::meta_any newValue_; + entt::meta_any oldValue_; +}; diff --git a/src/Runtime/Command/TransformNodeCommand.cpp b/src/Runtime/Command/TransformNodeCommand.cpp index ec9975d4..c4cad626 100644 --- a/src/Runtime/Command/TransformNodeCommand.cpp +++ b/src/Runtime/Command/TransformNodeCommand.cpp @@ -19,14 +19,10 @@ bool TransformNodeCommand::Execute() return true; } -void TransformNodeCommand::Undo() +bool TransformNodeCommand::Undo() { Apply(before_); -} - -void TransformNodeCommand::Redo() -{ - Apply(after_); + return true; } bool TransformNodeCommand::IsDifferent(const TransformSnapshot& before, const TransformSnapshot& after) diff --git a/src/Runtime/Command/TransformNodeCommand.hpp b/src/Runtime/Command/TransformNodeCommand.hpp index 337ffb8b..e2e000c8 100644 --- a/src/Runtime/Command/TransformNodeCommand.hpp +++ b/src/Runtime/Command/TransformNodeCommand.hpp @@ -1,10 +1,13 @@ #pragma once +#pragma once + #include "Common/CoreMinimal.hpp" -#include "Runtime/Command/CommandSystem.hpp" +#include "Runtime/Command/ICommand.hpp" #include #include +#include namespace Assets { @@ -24,9 +27,8 @@ class TransformNodeCommand final : public ICommand TransformNodeCommand(Assets::Scene& scene, uint32_t instanceId, const TransformSnapshot& before, const TransformSnapshot& after); bool Execute() override; - void Undo() override; - void Redo() override; - const char* Name() const override { return "TransformNode"; } + bool Undo() override; + std::string GetDescription() const override { return "Transform Node"; } static bool IsDifferent(const TransformSnapshot& before, const TransformSnapshot& after); diff --git a/src/Runtime/Engine.cpp b/src/Runtime/Engine.cpp index 13486605..6806a2f3 100644 --- a/src/Runtime/Engine.cpp +++ b/src/Runtime/Engine.cpp @@ -896,14 +896,14 @@ void NextEngine::OnKey(SDL_Event& event) { if (hasShift) { - if (commandSystem_.Redo()) + if (commandHistory_.Redo()) { return; } } else { - if (commandSystem_.Undo()) + if (commandHistory_.Undo()) { return; } @@ -911,7 +911,7 @@ void NextEngine::OnKey(SDL_Event& event) } else if (event.key.key == SDLK_Y) { - if (commandSystem_.Redo()) + if (commandHistory_.Redo()) { return; } @@ -940,16 +940,16 @@ void NextEngine::OnKey(SDL_Event& event) bool NextEngine::ExecuteCommand(std::unique_ptr command) { - return commandSystem_.ExecuteCommand(std::move(command)); + return commandHistory_.Execute(std::move(command)); } -bool NextEngine::UndoCommand() { return commandSystem_.Undo(); } +bool NextEngine::UndoCommand() { return commandHistory_.Undo(); } -bool NextEngine::RedoCommand() { return commandSystem_.Redo(); } +bool NextEngine::RedoCommand() { return commandHistory_.Redo(); } -bool NextEngine::CanUndo() const { return commandSystem_.CanUndo(); } +bool NextEngine::CanUndo() const { return commandHistory_.CanUndo(); } -bool NextEngine::CanRedo() const { return commandSystem_.CanRedo(); } +bool NextEngine::CanRedo() const { return commandHistory_.CanRedo(); } void NextEngine::OnTouch(bool down, double xpos, double ypos) { diff --git a/src/Runtime/Engine.hpp b/src/Runtime/Engine.hpp index 1864faac..20ae9ef7 100644 --- a/src/Runtime/Engine.hpp +++ b/src/Runtime/Engine.hpp @@ -5,7 +5,7 @@ #include "Common/CoreMinimal.hpp" #include "Options.hpp" #include "Rendering/VulkanBaseRenderer.hpp" -#include "Runtime/Command/CommandSystem.hpp" +#include "Runtime/Command/CommandHistory.hpp" #include "SceneList.hpp" #include "ShowFlags.hpp" #include "UserSettings.hpp" @@ -194,8 +194,11 @@ class NextEngine final bool RedoCommand(); bool CanUndo() const; bool CanRedo() const; - CommandSystem& GetCommandSystem() { return commandSystem_; } - const CommandSystem& GetCommandSystem() const { return commandSystem_; } + CommandHistory& GetCommandHistory() { return commandHistory_; } + const CommandHistory& GetCommandHistory() const { return commandHistory_; } + + CommandHistory& GetCommandSystem() { return commandHistory_; } + const CommandHistory& GetCommandSystem() const { return commandHistory_; } Vulkan::Window& GetWindow() const { return *window_; } @@ -303,7 +306,7 @@ class NextEngine final std::unique_ptr quickJSEngine_; - CommandSystem commandSystem_{}; + CommandHistory commandHistory_{}; // engine status NextRenderer::EApplicationStatus status_{}; From 83693bb7187962d53548d1b9ba100f6098c7bcde Mon Sep 17 00:00:00 2001 From: gameKnife Date: Sat, 31 Jan 2026 01:17:46 +0800 Subject: [PATCH 18/53] ui: align viewport overlay and gizmo toolbar Refines the viewport status bar styling and drop-target padding, and matches the gizmo toolbar background and bottom-center placement. Co-authored-by: gpt-5.2-codex --- src/Editor/EditorMain.cpp | 4 +- src/Editor/EditorMain.h | 3 ++ src/Editor/Panels/ViewportOverlay.cpp | 54 +++++++++++++++++++-------- src/Runtime/GizmoController.cpp | 17 +++++++-- 4 files changed, 56 insertions(+), 22 deletions(-) diff --git a/src/Editor/EditorMain.cpp b/src/Editor/EditorMain.cpp index abdb7719..1308eff6 100644 --- a/src/Editor/EditorMain.cpp +++ b/src/Editor/EditorMain.cpp @@ -87,8 +87,8 @@ void EditorGameInstance::OnInit() void EditorGameInstance::OnTick(double deltaSeconds) { - // bool moving = modelViewController_.UpdateCamera(1.0f, deltaSeconds); - // GetEngine().SetProgressiveRendering(!moving, false); + modelViewController_.UpdateCamera(1.0f, deltaSeconds); + //GetEngine().SetProgressiveRendering(!moving, false); } void EditorGameInstance::OnSceneLoaded() { modelViewController_.Reset(GetEngine().GetScene().GetRenderCamera()); } diff --git a/src/Editor/EditorMain.h b/src/Editor/EditorMain.h index faf234c0..77a2b5da 100644 --- a/src/Editor/EditorMain.h +++ b/src/Editor/EditorMain.h @@ -5,6 +5,9 @@ #include "Runtime/GizmoController.hpp" #include "Runtime/ModelViewController.hpp" +#include +#include + class EditorInterface; namespace Assets diff --git a/src/Editor/Panels/ViewportOverlay.cpp b/src/Editor/Panels/ViewportOverlay.cpp index e23475b2..e1d5e412 100644 --- a/src/Editor/Panels/ViewportOverlay.cpp +++ b/src/Editor/Panels/ViewportOverlay.cpp @@ -47,9 +47,11 @@ namespace Editor ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollWithMouse; + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); ImGui::Begin("ViewportDropTarget", nullptr, dropFlags); ImGui::PopStyleVar(); + ImGui::PopStyleVar(); ImGui::InvisibleButton("ViewportDropTargetBtn", size); if (ImGui::BeginDragDropTarget()) @@ -131,14 +133,17 @@ namespace Editor } constexpr float padding = 5.0f; - const float statW = std::max(60.0f, std::min(160.0f, size.x - padding * 2.0f)); - const float statH = std::max(60.0f, std::min(140.0f, size.y - padding * 2.0f)); + constexpr float statPadX = 8.0f; + constexpr float statPadY = 6.0f; + const float statW = 240.0f; + const float statH = ImGui::GetFrameHeight() + statPadY * 2.0f; ImGui::SetNextWindowPos(pos + ImVec2(padding, padding)); ImGui::SetNextWindowSize(ImVec2(statW, statH)); ImGui::SetNextWindowViewport(viewport->ID); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(statPadX, statPadY)); ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.5f)); ImGuiWindowFlags windowFlags = 0 | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | @@ -149,21 +154,36 @@ namespace Editor const double smoothDelta = ctx.engine.GetSmoothDeltaSeconds(); const double frameRate = smoothDelta > 0.0 ? (1.0 / smoothDelta) : 0.0; - ImGui::Text("Realtime Statistics:"); - ImGui::Text("Frame rate: %.0f fps", frameRate); - ImGui::Text("Progressive: %d", ctx.engine.IsProgressiveRendering()); - - auto& gpuDrivenStat = ctx.scene.GetGpuDrivenStat(); - const uint32_t instanceCount = gpuDrivenStat.ProcessedCount - gpuDrivenStat.CulledCount; - const uint32_t triangleCount = gpuDrivenStat.TriangleCount - gpuDrivenStat.CulledTriangleCount; - ImGui::Text("Tris: %s/%s", Utilities::metricFormatter(static_cast(triangleCount), "").c_str(), - Utilities::metricFormatter(static_cast(gpuDrivenStat.TriangleCount), "").c_str()); - ImGui::Text("Draw: %s/%s", Utilities::metricFormatter(static_cast(instanceCount), "").c_str(), - Utilities::metricFormatter(static_cast(gpuDrivenStat.ProcessedCount), "").c_str()); + const ImGuiIO& io = ImGui::GetIO(); + + auto DrawBoolDot = [](const char* label, bool value) + { + ImDrawList* drawList = ImGui::GetWindowDrawList(); + const float radius = 4.0f; + const float spacing = 6.0f; + const ImVec2 cursor = ImGui::GetCursorScreenPos(); + const float centerY = cursor.y + ImGui::GetFrameHeight() * 0.5f; + const ImU32 color = value ? IM_COL32(80, 220, 120, 255) : IM_COL32(220, 80, 80, 255); + drawList->AddCircleFilled(ImVec2(cursor.x + radius, centerY), radius, color); + ImGui::Dummy(ImVec2(radius * 2.0f + spacing, ImGui::GetFrameHeight())); + ImGui::SameLine(0.0f, 4.0f); + ImGui::TextUnformatted(label); + }; + + ImGui::AlignTextToFramePadding(); + ImGui::Text("FPS %.0f", frameRate); + ImGui::SameLine(); + DrawBoolDot("Mouse", io.WantCaptureMouse); + ImGui::SameLine(); + DrawBoolDot("Key", io.WantCaptureKeyboard); + ImGui::SameLine(); + DrawBoolDot("Text", io.WantTextInput); ImGui::End(); + ImGui::PopStyleColor(); + ImGui::PopStyleVar(2); - const float toolH = kToolIconWidth + 8.0f; + const float toolH = kToolIconWidth; float toolW = kToolIconWidth + 16.0f; toolW = std::max(60.0f, std::min(toolW, size.x - padding * 2.0f)); @@ -171,9 +191,12 @@ namespace Editor ImGui::SetNextWindowSize(ImVec2(toolW, toolH)); ImGui::SetNextWindowViewport(viewport->ID); ImGui::SetNextWindowBgAlpha(0); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); ImGui::Begin("ViewportTool", nullptr, windowFlags); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.5f)); ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); @@ -200,8 +223,7 @@ namespace Editor ImGui::PopStyleColor(); ImGui::PopStyleVar(); - - ImGui::PopStyleColor(); + ImGui::PopStyleVar(); ImGui::PopStyleVar(); ImGui::End(); diff --git a/src/Runtime/GizmoController.cpp b/src/Runtime/GizmoController.cpp index 21efc2c7..100ff0f8 100644 --- a/src/Runtime/GizmoController.cpp +++ b/src/Runtime/GizmoController.cpp @@ -14,7 +14,7 @@ namespace { - constexpr float kToolbarOffsetY = 48.0f; + constexpr float kToolbarEdgePadding = 5.0f; } void GizmoController::EnsureDefaults() @@ -63,7 +63,12 @@ void GizmoController::DrawToolbar() { EnsureDefaults(); - ImGui::SetNextWindowBgAlpha(0.65f); + constexpr float kToolbarPadX = 8.0f; + constexpr float kToolbarPadY = 6.0f; + + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(kToolbarPadX, kToolbarPadY)); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.5f)); ImGui::Begin("GizmoToolbar", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | @@ -88,6 +93,8 @@ void GizmoController::DrawToolbar() } ImGui::End(); + ImGui::PopStyleColor(); + ImGui::PopStyleVar(2); } void GizmoController::Draw(NextEngine& engine, const glm::vec2& viewportPos, const glm::vec2& viewportSize) @@ -119,9 +126,11 @@ void GizmoController::Draw(NextEngine& engine, const glm::vec2& viewportPos, con HandleShortcuts(io); ImGuiViewport* viewport = ImGui::GetMainViewport(); - const ImVec2 toolbarPos(viewportPos.x + viewportSize.x * 0.5f, viewportPos.y + kToolbarOffsetY); + const ImVec2 toolbarPos(viewportPos.x + viewportSize.x * 0.5f, + viewportPos.y + viewportSize.y - kToolbarEdgePadding); ImGui::SetNextWindowViewport(viewport->ID); - ImGui::SetNextWindowPos(toolbarPos, ImGuiCond_Always, ImVec2(0.5f, 0.0f)); + ImGui::SetNextWindowPos(toolbarPos, ImGuiCond_Always, ImVec2(0.5f, 1.0f)); + ImGui::SetNextWindowBgAlpha(0.5f); DrawToolbar(); const auto& ubo = engine.GetUniformBufferObject(); From f58252017b1adc0f119664b6d4270dd1dbf048b3 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Sun, 1 Feb 2026 10:40:34 +0800 Subject: [PATCH 19/53] docs: update agent guidelines and remove line length limits - Remove line length limit requirements from all agent docs - Update build flags to use preset system (minimal/default/full) - Clarify shader uses ray query not ray pipeline (remove rmiss.slang) - Expand CLAUDE.md with subprojects, architecture tree, and test commands Co-Authored-By: Claude Opus 4.5 --- AGENTS.md | 1 - AGENT_GUIDE/coding-standards.md | 4 +-- CLAUDE.md | 55 ++++++++++++++++++++++++--------- 3 files changed, 41 insertions(+), 19 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 46b46d60..8908e003 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -108,7 +108,6 @@ Language and formatting: - C++20 and C11; prefer modern C++ (RAII, smart pointers, range-based for). - Indentation: 4 spaces, no tabs. - Braces: Allman style (opening brace on the next line). -- Line length: keep under 120 columns where practical. Naming (enforced by `.clang-tidy`): - Types (class/struct/enum/typedef/namespace): PascalCase. diff --git a/AGENT_GUIDE/coding-standards.md b/AGENT_GUIDE/coding-standards.md index a5517eff..91ef40e0 100644 --- a/AGENT_GUIDE/coding-standards.md +++ b/AGENT_GUIDE/coding-standards.md @@ -63,18 +63,16 @@ void RenderFrame() { // ✅ // ... } - + void RenderFrame() // ❌ { // ... } ``` -- 每行最大长度:建议 120 字符 - 使用现代 C++ 特性:智能指针、range-based for、auto(适度) **审查建议:** - [ ] 检查缩进一致性(4 空格) -- [ ] 避免过长行(超过 120 字符建议换行) - [ ] 优先使用 STL 容器和算法,避免重复造轮子 --- diff --git a/CLAUDE.md b/CLAUDE.md index 979fcf3e..854fa5b6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,6 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with this repository. +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Communication Preference @@ -9,7 +9,9 @@ Always communicate with the user in Chinese (中文). ## Project Overview -gkNextRenderer is a cross-platform 3D game engine built with modern C++20 and Vulkan, featuring hardware/software ray tracing, real-time global illumination, and GPU-driven rendering. +gkNextRenderer is a cross-platform 3D game engine built with modern C++20 and Vulkan, featuring hardware/software ray tracing, real-time global illumination, and GPU-driven rendering. Target codebase size is <50k LOC (currently ~15k). + +**Subprojects:** gkNextRenderer (main renderer), gkNextEditor (ImGui editor), MagicaLego (voxel prototype), gkNextBenchmark, Packager ## Build Commands @@ -24,7 +26,9 @@ gkNextRenderer is a cross-platform 3D game engine built with modern C++20 and Vu - Android: `./build.bat --android` (Windows) or `./build.sh --android` - Clean rebuild: add `--clean` -**Optional flags:** `--avif`, `--dlss`, `--oidn` +**Presets:** `minimal-*` (fewest deps), `default-*` (standard), `full-*` (all features incl. DLSS/OIDN) + +**List presets:** `cmake --list-presets=configure` ## Run Commands @@ -32,6 +36,8 @@ gkNextRenderer is a cross-platform 3D game engine built with modern C++20 and Vu - macOS/Linux: `./run.sh --preset ` - Specific target: `./run.sh --preset default-macos-arm64 --target gkNextEditor` +**Runtime success indicator:** Log shows `uploaded scene [...] to gpu` + ## Testing **CRITICAL: Tests must be run from the bin directory.** @@ -40,34 +46,53 @@ gkNextRenderer is a cross-platform 3D game engine built with modern C++20 and Vu # Unit tests (Catch2) cd out/build//bin && ./gkNextUnitTests -# Run specific test +# Run specific test by name or tag +cd out/build//bin && ./gkNextUnitTests "RenderComponent Usage" cd out/build//bin && ./gkNextUnitTests "[Unit][RenderComponent]" -# Visual tests +# List available tests/tags +cd out/build//bin && ./gkNextUnitTests --list-tests + +# Visual tests (renders scenes, generates screenshots + report) cd out/build//bin && ./gkNextVisualTest ``` ## Code Style (Summary) -- **First include:** `Common/CoreMinimal.hpp` -- **Platform abstraction:** Use `PlatformCommon.h`, not direct platform headers -- **Naming:** +- **First include:** `Common/CoreMinimal.hpp` (includes std, fmt, spdlog, platform detection) +- **Platform abstraction:** Use `PlatformCommon.h`, not direct platform headers; use `#if ANDROID` not `#ifdef` +- **Naming (enforced by .clang-tidy):** - Types/functions: PascalCase - Variables/parameters: camelCase - Private members: camelCase_ (trailing underscore) + - Global variables: PascalCase (e.g., `GOption`) - Macros: UPPER_CASE - **Braces:** Allman style (opening brace on new line) - **Indentation:** 4 spaces, no tabs -- **Shaders:** Slang (`.vert.slang`, `.frag.slang`, `.rgen.slang`) +- **Shaders:** Slang (`.vert.slang`, `.frag.slang`, `.rgen.slang`); uses ray query, not ray pipeline +- **Vulkan:** Always check VkResult with `VK_CHECK_RESULT`; RAII for resource cleanup ## Architecture Overview -- `src/Runtime/` - Core engine runtime -- `src/Runtime/Platform/` - Platform-specific code -- `src/Vulkan/` - Vulkan backend -- `src/Tests/` - Catch2 unit tests -- `assets/shaders/` - Slang shaders -- `assets/configs/` - Runtime configuration +``` +src/ +├── Runtime/ # Core engine runtime +│ ├── Platform/ # Platform abstraction (via PlatformCommon.h) +│ ├── Components/ # ECS components (entt) +│ ├── Reflection/ # Property reflection (entt::meta) +│ └── Command/ # Command history system +├── Vulkan/ # Vulkan backend +│ └── RayTracing/ # Hardware ray tracing +├── Rendering/ # Render pipelines (PathTracing, SoftwareTracing, SoftwareModern) +├── Editor/ # ImGui editor +├── Tests/ # Catch2 unit tests +└── Application/ # App entry points (gkNextRenderer, Packager, etc.) + +assets/ +├── shaders/ # Slang shaders +├── configs/ # Runtime config (visual_test.json) +└── models/ # glTF scenes +``` ## Key References From 770a9e70d871936764836dd3ed6736db79db9f7e Mon Sep 17 00:00:00 2001 From: gameKnife Date: Sun, 1 Feb 2026 11:21:00 +0800 Subject: [PATCH 20/53] fix(cpu-as): trigger voxelization update when scene is dirty after gizmo transform When moving nodes via gizmo, the CPU acceleration structure (BVH) was updated but voxelization tasks were not queued, causing probe/voxel data to remain stale. Now AsyncProcessFull is called when sceneDirtyForCpuAS_ is set, which properly queues voxelization tasks. Also removed redundant UpdateBVH call from TransformNodeCommand since AsyncProcessFull already handles it. Co-Authored-By: Claude Opus 4.5 --- src/Assets/Scene.cpp | 16 ++++++++-------- src/Runtime/Command/TransformNodeCommand.cpp | 1 - 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Assets/Scene.cpp b/src/Assets/Scene.cpp index bfe5c739..2c20aa95 100644 --- a/src/Assets/Scene.cpp +++ b/src/Assets/Scene.cpp @@ -868,15 +868,15 @@ namespace Assets } } - if (NextEngine::GetInstance()->GetTotalFrames() % 10 == 0) + if (NextEngine::GetInstance()->GetTotalFrames() % 30 == 0) { - // if (sceneDirtyForCpuAS_) - // { - // if ( cpuAccelerationStructure_.AsyncProcessFull(*this, farAmbientCubeBufferMemory_.get(), true) ) - // { - // sceneDirtyForCpuAS_ = false; - // } - // } + if (sceneDirtyForCpuAS_) + { + if (cpuAccelerationStructure_.AsyncProcessFull(*this, farAmbientCubeBufferMemory_.get(), true)) + { + sceneDirtyForCpuAS_ = false; + } + } cpuAccelerationStructure_.Tick(*this, ambientCubeBufferMemory_.get(), farAmbientCubeBufferMemory_.get(), pageIndexBufferMemory_.get()); diff --git a/src/Runtime/Command/TransformNodeCommand.cpp b/src/Runtime/Command/TransformNodeCommand.cpp index c4cad626..af1254a0 100644 --- a/src/Runtime/Command/TransformNodeCommand.cpp +++ b/src/Runtime/Command/TransformNodeCommand.cpp @@ -54,5 +54,4 @@ void TransformNodeCommand::Apply(const TransformSnapshot& snapshot) node->SetScale(snapshot.scale); node->RecalcTransform(true); scene_->MarkDirty(); - scene_->GetCPUAccelerationStructure().UpdateBVH(*scene_); } From 2ef549af2eca7524b1cb801fa48f057fb3287df2 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Sun, 1 Feb 2026 13:07:32 +0800 Subject: [PATCH 21/53] fix(runtime): enable Linux TypeScript compilation Run tsc on Linux during runtime and resolve local bin copy before PATH. Co-authored-by: gpt-5.2-codex --- src/Runtime/Platform/PlatformLinux.h | 8 +++++--- src/Runtime/QuickJSEngine.cpp | 7 ++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Runtime/Platform/PlatformLinux.h b/src/Runtime/Platform/PlatformLinux.h index e9ef525c..eb71d932 100644 --- a/src/Runtime/Platform/PlatformLinux.h +++ b/src/Runtime/Platform/PlatformLinux.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + namespace NextRenderer { inline void PlatformInit() @@ -21,8 +24,7 @@ namespace NextRenderer inline void OSProcess(const char* exe) { - //int result = system(exe); - //(void)result; + int result = std::system(exe); + (void)result; } } - diff --git a/src/Runtime/QuickJSEngine.cpp b/src/Runtime/QuickJSEngine.cpp index f5a915ac..c1697978 100644 --- a/src/Runtime/QuickJSEngine.cpp +++ b/src/Runtime/QuickJSEngine.cpp @@ -255,7 +255,12 @@ void QuickJSEngine::CompileTypeScriptSources() #if WIN32 commands.emplace_back(fmt::format("tsc -p \"{}\"", projectDir.string())); #else - commands.emplace_back(fmt::format("./tsc -p \"{}\"", projectDir.string())); + const fs::path localTsc = fs::current_path() / "tsc"; + if (fs::exists(localTsc, ec)) + { + commands.emplace_back(fmt::format("\"{}\" -p \"{}\"", localTsc.string(), projectDir.string())); + } + commands.emplace_back(fmt::format("tsc -p \"{}\"", projectDir.string())); #endif for (const std::string& command : commands) From c0e636d7a5f002de9ffbbcddc31c995e6e764df5 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Sun, 1 Feb 2026 14:53:01 +0800 Subject: [PATCH 22/53] feat(script): expose reflected components to QuickJS Add JS bridge helpers, TS definition generation, and update test.ts to use object-style component access. Co-authored-by: gpt-5.2-codex --- assets/typescript/Engine.d.ts | 39 +- assets/typescript/test.ts | 41 +- src/Runtime/Components/RenderComponent.cpp | 4 +- src/Runtime/Components/RenderComponent.h | 6 + src/Runtime/QuickJSEngine.cpp | 644 ++++++++++++++++++ .../Reflection/QuickJSReflectionBridge.h | 19 +- 6 files changed, 726 insertions(+), 27 deletions(-) diff --git a/assets/typescript/Engine.d.ts b/assets/typescript/Engine.d.ts index dd8343bb..ef17666b 100644 --- a/assets/typescript/Engine.d.ts +++ b/assets/typescript/Engine.d.ts @@ -1,3 +1,8 @@ +export interface Vec2 { x: number; y: number; } +export interface Vec3 { x: number; y: number; z: number; } +export interface Vec4 { x: number; y: number; z: number; w: number; } +export interface Quat { x: number; y: number; z: number; w: number; } + export class NextEngine { GetTotalFrames(): number; GetTestNumber(): number; @@ -6,14 +11,42 @@ export class NextEngine { } export class NextComponent { - name_ : string - id_ : number + name_: string; + id_: number; } export class Scene { GetIndicesCount(): number; } +export class RenderComponent { + Visible: boolean; + RayCastVisible: boolean; + readonly ModelId: number; + readonly SkinIndex: number; + Materials: number[]; + ToggleVisible(): boolean; +} + +export class PhysicsComponent { + Mobility: string; + PhysicsOffset: Vec3; +} + +export class SkinnedMeshComponent { + PlaySpeed: number; + readonly IsPlaying: boolean; + readonly CurrentAnimation: string; +} + +export type ENodeMobility = "Static" | "Dynamic" | "Kinematic"; export function println(...args: any[]): void; -export function GetEngine(): NextEngine; \ No newline at end of file +export function GetEngine(): NextEngine; +export function FindNodeIdWithComponent(componentType: string): number; +export function GetNodeName(nodeId: number): string; +export function GetNodeTranslation(nodeId: number): Vec3; +export function GetComponent(nodeId: number, componentType: string): any; +export function GetComponentProperty(nodeId: number, componentType: string, propertyName: string): any; +export function SetComponentProperty(nodeId: number, componentType: string, propertyName: string, value: any): boolean; +export function CallComponentFunction(nodeId: number, componentType: string, functionName: string, ...args: any[]): any; diff --git a/assets/typescript/test.ts b/assets/typescript/test.ts index b4b75bec..ae9d3dc2 100644 --- a/assets/typescript/test.ts +++ b/assets/typescript/test.ts @@ -1,17 +1,34 @@ import * as NE from "./Engine"; -class ScriptComponent extends NE.NextComponent { - constructor() { - super(); - this.name_ = "ScriptComponent"; - this.id_ = 1; + +let hasRun = false; + +function tryRunTest(): boolean { + const nodeId = NE.FindNodeIdWithComponent("RenderComponent"); + if (nodeId < 0) { + return false; } - get_info() { - let frame = NE.GetEngine().GetTestNumber(); - return `[test.ts] ${this.name_} ${this.id_} at ${frame}`; + + const nodeName = NE.GetNodeName(nodeId); + const translation = NE.GetNodeTranslation(nodeId) as NE.Vec3; + const render = NE.GetComponent(nodeId, "RenderComponent") as NE.RenderComponent; + if (!render) { + return false; } + + NE.println(`[test.ts] ${nodeName} pos=(${translation.x}, ${translation.y}, ${translation.z}) visible=${render.Visible}`); + + render.Visible = !render.Visible; + const toggled = render.ToggleVisible(); + NE.println(`[test.ts] ToggleVisible() => ${toggled} visible=${render.Visible}`); + return true; } -let testComponent = new ScriptComponent(); -NE.println(testComponent.get_info()); -//NE.println("Hello World from typescript"); -//NE.println("Frame: ", frame); \ No newline at end of file +NE.GetEngine().RegisterJSCallback((_delta: number) => { + if (hasRun) { + return; + } + + if (tryRunTest()) { + hasRun = true; + } +}); diff --git a/src/Runtime/Components/RenderComponent.cpp b/src/Runtime/Components/RenderComponent.cpp index ab0eb30c..7f1f4efa 100644 --- a/src/Runtime/Components/RenderComponent.cpp +++ b/src/Runtime/Components/RenderComponent.cpp @@ -25,6 +25,8 @@ namespace Runtime .custom(PropertyPresets::ReadOnly("Skin Index", "Animation", "Skinning data index for skeletal animation")) // Materials array - editable (array of material indices) .data<&RenderComponent::SetMaterials, &RenderComponent::GetMaterials>("Materials") - .custom(PropertyPresets::Editable("Materials", "Rendering", "Material indices for each submesh")); + .custom(PropertyPresets::Editable("Materials", "Rendering", "Material indices for each submesh")) + // Methods for JS + .func<&RenderComponent::ToggleVisible>("ToggleVisible"); } } diff --git a/src/Runtime/Components/RenderComponent.h b/src/Runtime/Components/RenderComponent.h index 664a7068..c0d98968 100644 --- a/src/Runtime/Components/RenderComponent.h +++ b/src/Runtime/Components/RenderComponent.h @@ -29,6 +29,12 @@ namespace Runtime void SetRayCastVisible(bool visible) { rayCastVisible_ = visible; } bool GetRayCastVisible() const { return rayCastVisible_; } + + bool ToggleVisible() + { + visible_ = !visible_; + return visible_; + } bool IsDrawable() const { return modelId_ != static_cast(-1); } diff --git a/src/Runtime/QuickJSEngine.cpp b/src/Runtime/QuickJSEngine.cpp index c1697978..52f26faf 100644 --- a/src/Runtime/QuickJSEngine.cpp +++ b/src/Runtime/QuickJSEngine.cpp @@ -2,11 +2,20 @@ #include "Engine.hpp" #include "Assets/Scene.hpp" +#include "Assets/Node.h" +#include "Runtime/Components/RenderComponent.h" +#include "Runtime/Components/PhysicsComponent.h" +#include "Runtime/Components/SkinnedMeshComponent.h" +#include "Runtime/Reflection/PropertyAccessor.h" +#include "Runtime/Reflection/QuickJSReflectionBridge.h" +#include "Runtime/Reflection/QuickJSTypeConverter.h" #include "Utilities/FileHelper.hpp" #include "Platform/PlatformCommon.h" #include #include +#include +#include #if WITH_QUICKJS #include @@ -139,6 +148,428 @@ namespace { return NextEngine::GetInstance(); } + + Assets::Component* FindComponentByTypeName(Assets::Scene& scene, uint32_t nodeId, const std::string& componentType) + { + auto* node = scene.GetNodeByInstanceId(nodeId); + if (!node) + { + return nullptr; + } + + for (const auto& component : node->GetComponents()) + { + if (!component) + { + continue; + } + + if (component->GetTypeName() == componentType) + { + return component.get(); + } + } + + return nullptr; + } + + Assets::Node* FindNodeById(Assets::Scene& scene, uint32_t nodeId) + { + return scene.GetNodeByInstanceId(nodeId); + } + + JSValue ComponentPropertyGetter(JSContext* ctx, JSValueConst thisVal, int argc, JSValueConst* argv, + int magic, JSValueConst* data) + { + (void)thisVal; + (void)argc; + (void)argv; + (void)magic; + + uint32_t nodeId = 0; + JS_ToUint32(ctx, &nodeId, data[0]); + + const char* componentType = JS_ToCString(ctx, data[1]); + const char* propertyName = JS_ToCString(ctx, data[2]); + if (!componentType || !propertyName) + { + if (componentType) + { + JS_FreeCString(ctx, componentType); + } + if (propertyName) + { + JS_FreeCString(ctx, propertyName); + } + return JS_UNDEFINED; + } + + auto* engine = NextEngine::GetInstance(); + if (!engine) + { + JS_FreeCString(ctx, componentType); + JS_FreeCString(ctx, propertyName); + return JS_UNDEFINED; + } + + auto* scene = engine->GetScenePtr(); + if (!scene) + { + JS_FreeCString(ctx, componentType); + JS_FreeCString(ctx, propertyName); + return JS_UNDEFINED; + } + + Assets::Component* component = FindComponentByTypeName(*scene, nodeId, componentType); + if (!component) + { + JS_FreeCString(ctx, componentType); + JS_FreeCString(ctx, propertyName); + return JS_UNDEFINED; + } + + entt::meta_type metaType = component->GetMetaType(); + entt::meta_any value = Reflection::PropertyAccessor::GetPropertyValue(metaType, component, propertyName); + JS_FreeCString(ctx, componentType); + JS_FreeCString(ctx, propertyName); + + if (!value) + { + return JS_UNDEFINED; + } + + return Reflection::QuickJSTypeConverter::ToJSValue(ctx, value); + } + + JSValue ComponentPropertySetter(JSContext* ctx, JSValueConst thisVal, int argc, JSValueConst* argv, + int magic, JSValueConst* data) + { + (void)thisVal; + (void)magic; + + if (argc < 1) + { + return JS_UNDEFINED; + } + + uint32_t nodeId = 0; + JS_ToUint32(ctx, &nodeId, data[0]); + + const char* componentType = JS_ToCString(ctx, data[1]); + const char* propertyName = JS_ToCString(ctx, data[2]); + if (!componentType || !propertyName) + { + if (componentType) + { + JS_FreeCString(ctx, componentType); + } + if (propertyName) + { + JS_FreeCString(ctx, propertyName); + } + return JS_UNDEFINED; + } + + auto* engine = NextEngine::GetInstance(); + if (!engine) + { + JS_FreeCString(ctx, componentType); + JS_FreeCString(ctx, propertyName); + return JS_UNDEFINED; + } + + auto* scene = engine->GetScenePtr(); + if (!scene) + { + JS_FreeCString(ctx, componentType); + JS_FreeCString(ctx, propertyName); + return JS_UNDEFINED; + } + + Assets::Component* component = FindComponentByTypeName(*scene, nodeId, componentType); + if (!component) + { + JS_FreeCString(ctx, componentType); + JS_FreeCString(ctx, propertyName); + return JS_UNDEFINED; + } + + entt::meta_type metaType = component->GetMetaType(); + auto dataEntry = metaType.data(entt::hashed_string::value(propertyName)); + if (!dataEntry) + { + JS_FreeCString(ctx, componentType); + JS_FreeCString(ctx, propertyName); + return JS_UNDEFINED; + } + + entt::meta_type valueType = dataEntry.type(); + entt::meta_any converted = Reflection::QuickJSTypeConverter::FromJSValue(ctx, argv[0], valueType); + JS_FreeCString(ctx, componentType); + JS_FreeCString(ctx, propertyName); + + if (!converted) + { + return JS_UNDEFINED; + } + + Reflection::PropertyAccessor::SetPropertyValue(metaType, component, dataEntry.name(), converted); + return JS_UNDEFINED; + } + + JSValue ComponentMethodInvoker(JSContext* ctx, JSValueConst thisVal, int argc, JSValueConst* argv, + int magic, JSValueConst* data) + { + (void)thisVal; + (void)magic; + + uint32_t nodeId = 0; + JS_ToUint32(ctx, &nodeId, data[0]); + + const char* componentType = JS_ToCString(ctx, data[1]); + const char* functionName = JS_ToCString(ctx, data[2]); + if (!componentType || !functionName) + { + if (componentType) + { + JS_FreeCString(ctx, componentType); + } + if (functionName) + { + JS_FreeCString(ctx, functionName); + } + return JS_UNDEFINED; + } + + auto* engine = NextEngine::GetInstance(); + if (!engine) + { + JS_FreeCString(ctx, componentType); + JS_FreeCString(ctx, functionName); + return JS_UNDEFINED; + } + + auto* scene = engine->GetScenePtr(); + if (!scene) + { + JS_FreeCString(ctx, componentType); + JS_FreeCString(ctx, functionName); + return JS_UNDEFINED; + } + + Assets::Component* component = FindComponentByTypeName(*scene, nodeId, componentType); + if (!component) + { + JS_FreeCString(ctx, componentType); + JS_FreeCString(ctx, functionName); + return JS_UNDEFINED; + } + + entt::meta_type metaType = component->GetMetaType(); + auto function = metaType.func(entt::hashed_string::value(functionName)); + JS_FreeCString(ctx, componentType); + JS_FreeCString(ctx, functionName); + + if (!function) + { + return JS_UNDEFINED; + } + + if (function.arity() != static_cast(argc)) + { + JS_ThrowTypeError(ctx, "Invalid argument count"); + return JS_EXCEPTION; + } + + entt::meta_any instanceAny = metaType.from_void(component); + entt::meta_any result; + if (argc == 0) + { + result = function.invoke(instanceAny); + } + else + { + std::vector args; + args.reserve(static_cast(argc)); + for (int idx = 0; idx < argc; ++idx) + { + entt::meta_type argType = function.arg(static_cast(idx)); + args.emplace_back(Reflection::QuickJSTypeConverter::FromJSValue(ctx, argv[idx], argType)); + } + + result = function.invoke(instanceAny, args.data(), args.size()); + } + + if (!result) + { + return JS_UNDEFINED; + } + + return Reflection::QuickJSTypeConverter::ToJSValue(ctx, result); + } + + JSValue CreateComponentObject(JSContext* ctx, Assets::Component* component, uint32_t nodeId, const std::string& componentType) + { + if (!component) + { + return JS_UNDEFINED; + } + + entt::meta_type metaType = component->GetMetaType(); + JSValue obj = JS_NewObject(ctx); + + auto properties = Reflection::PropertyAccessor::GetProperties(metaType); + for (const auto& prop : properties) + { + if (!prop.meta.IsJSExposed()) + { + continue; + } + + JSValue data[3]; + data[0] = JS_NewUint32(ctx, nodeId); + data[1] = JS_NewString(ctx, componentType.c_str()); + data[2] = JS_NewString(ctx, prop.name.c_str()); + + JSValue getter = JS_NewCFunctionData(ctx, ComponentPropertyGetter, 0, 0, 3, data); + JSValue setter = JS_UNDEFINED; + if (!prop.meta.IsReadOnly()) + { + setter = JS_NewCFunctionData(ctx, ComponentPropertySetter, 1, 0, 3, data); + } + + JSAtom propAtom = JS_NewAtom(ctx, prop.name.c_str()); + JS_DefinePropertyGetSet(ctx, obj, propAtom, getter, setter, + JS_PROP_ENUMERABLE | JS_PROP_CONFIGURABLE); + JS_FreeAtom(ctx, propAtom); + } + + for (auto&& [id, func] : metaType.func()) + { + const char* funcName = func.name(); + if (!funcName) + { + continue; + } + + JSValue data[3]; + data[0] = JS_NewUint32(ctx, nodeId); + data[1] = JS_NewString(ctx, componentType.c_str()); + data[2] = JS_NewString(ctx, funcName); + + JSValue jsFunc = JS_NewCFunctionData(ctx, ComponentMethodInvoker, func.arity(), 0, 3, data); + JS_SetPropertyStr(ctx, obj, funcName, jsFunc); + } + + return obj; + } + + int32_t FindNodeIdWithComponent(Assets::Scene& scene, const std::string& componentType) + { + for (const auto& node : scene.Nodes()) + { + if (!node) + { + continue; + } + + for (const auto& component : node->GetComponents()) + { + if (component && component->GetTypeName() == componentType) + { + return static_cast(node->GetInstanceId()); + } + } + } + + return -1; + } + + std::string BuildTypeScriptDefinitions() + { + std::string result; + result += "export interface Vec2 { x: number; y: number; }\n"; + result += "export interface Vec3 { x: number; y: number; z: number; }\n"; + result += "export interface Vec4 { x: number; y: number; z: number; w: number; }\n"; + result += "export interface Quat { x: number; y: number; z: number; w: number; }\n\n"; + + result += "export class NextEngine {\n"; + result += " GetTotalFrames(): number;\n"; + result += " GetTestNumber(): number;\n"; + result += " RegisterJSCallback(callback: (param: number) => void): void;\n"; + result += " GetScenePtr(): Scene;\n"; + result += "}\n\n"; + + result += "export class NextComponent {\n"; + result += " name_: string;\n"; + result += " id_: number;\n"; + result += "}\n\n"; + + result += "export class Scene {\n"; + result += " GetIndicesCount(): number;\n"; + result += "}\n\n"; + + std::string renderDef = Reflection::QuickJSReflectionBridge::GenerateTypeScriptDef("RenderComponent"); + const std::string renderMethod = " ToggleVisible(): boolean;\n"; + const std::string renderClassEnd = "}\n"; + const size_t insertPos = renderDef.rfind(renderClassEnd); + if (insertPos != std::string::npos) + { + renderDef.insert(insertPos, renderMethod); + } + else + { + renderDef += renderMethod; + } + result += renderDef; + result += Reflection::QuickJSReflectionBridge::GenerateTypeScriptDef("PhysicsComponent"); + result += Reflection::QuickJSReflectionBridge::GenerateTypeScriptDef("SkinnedMeshComponent"); + result += Reflection::QuickJSReflectionBridge::GenerateEnumTypeScriptDef("ENodeMobility"); + + result += "\nexport function println(...args: any[]): void;\n"; + result += "export function GetEngine(): NextEngine;\n"; + result += "export function FindNodeIdWithComponent(componentType: string): number;\n"; + result += "export function GetNodeName(nodeId: number): string;\n"; + result += "export function GetNodeTranslation(nodeId: number): Vec3;\n"; + result += "export function GetComponent(nodeId: number, componentType: string): any;\n"; + result += "export function GetComponentProperty(nodeId: number, componentType: string, propertyName: string): any;\n"; + result += "export function SetComponentProperty(nodeId: number, componentType: string, propertyName: string, value: any): boolean;\n"; + result += "export function CallComponentFunction(nodeId: number, componentType: string, functionName: string, ...args: any[]): any;\n"; + + return result; + } + + void UpdateTypeScriptDefinitions(const std::filesystem::path& tsconfigPath) + { + namespace fs = std::filesystem; + + if (tsconfigPath.empty()) + { + return; + } + + const fs::path outputPath = tsconfigPath.parent_path() / "Engine.d.ts"; + const std::string content = BuildTypeScriptDefinitions(); + + std::ifstream reader(outputPath, std::ios::binary); + if (reader) + { + std::string existing((std::istreambuf_iterator(reader)), std::istreambuf_iterator()); + if (existing == content) + { + return; + } + } + + std::ofstream writer(outputPath, std::ios::binary); + if (!writer) + { + SPDLOG_WARN("Failed to write TypeScript definitions to {}", outputPath.string()); + return; + } + + writer << content; + } #endif } @@ -172,6 +603,217 @@ void QuickJSEngine::Initialize() .fun<&NextComponent::name_> ("name_") .fun<&NextComponent::id_> ("id_"); + qjs::Context* jsContext = context_.get(); + module.function("FindNodeIdWithComponent", [](const std::string& componentType) -> int32_t { + auto* engine = NextEngine::GetInstance(); + if (!engine) + { + return -1; + } + + auto* scene = engine->GetScenePtr(); + if (!scene) + { + return -1; + } + + return FindNodeIdWithComponent(*scene, componentType); + }); + + module.function("GetNodeName", [](uint32_t nodeId) -> std::string { + auto* engine = NextEngine::GetInstance(); + if (!engine) + { + return {}; + } + + auto* scene = engine->GetScenePtr(); + if (!scene) + { + return {}; + } + + auto* node = FindNodeById(*scene, nodeId); + if (!node) + { + return {}; + } + + return node->GetName(); + }); + + module.function("GetNodeTranslation", [jsContext](uint32_t nodeId) -> JSValue { + auto* engine = NextEngine::GetInstance(); + if (!engine) + { + return JS_UNDEFINED; + } + + auto* scene = engine->GetScenePtr(); + if (!scene) + { + return JS_UNDEFINED; + } + + auto* node = FindNodeById(*scene, nodeId); + if (!node) + { + return JS_UNDEFINED; + } + + const glm::vec3 translation = node->Translation(); + JSValue obj = JS_NewObject(jsContext->ctx); + JS_SetPropertyStr(jsContext->ctx, obj, "x", JS_NewFloat64(jsContext->ctx, translation.x)); + JS_SetPropertyStr(jsContext->ctx, obj, "y", JS_NewFloat64(jsContext->ctx, translation.y)); + JS_SetPropertyStr(jsContext->ctx, obj, "z", JS_NewFloat64(jsContext->ctx, translation.z)); + return obj; + }); + + module.function("GetComponent", [jsContext](uint32_t nodeId, const std::string& componentType) -> JSValue { + auto* engine = NextEngine::GetInstance(); + if (!engine) + { + return JS_UNDEFINED; + } + + auto* scene = engine->GetScenePtr(); + if (!scene) + { + return JS_UNDEFINED; + } + + Assets::Component* component = FindComponentByTypeName(*scene, nodeId, componentType); + if (!component) + { + return JS_UNDEFINED; + } + + return CreateComponentObject(jsContext->ctx, component, nodeId, componentType); + }); + + module.function("GetComponentProperty", [jsContext](uint32_t nodeId, const std::string& componentType, + const std::string& propertyName) -> JSValue { + auto* engine = NextEngine::GetInstance(); + if (!engine) + { + return JS_UNDEFINED; + } + + auto* scene = engine->GetScenePtr(); + if (!scene) + { + return JS_UNDEFINED; + } + + Assets::Component* component = FindComponentByTypeName(*scene, nodeId, componentType); + if (!component) + { + SPDLOG_WARN("Component '{}' not found on node {}", componentType, nodeId); + return JS_UNDEFINED; + } + + entt::meta_type metaType = component->GetMetaType(); + entt::meta_any value = Reflection::PropertyAccessor::GetPropertyValue(metaType, component, propertyName); + if (!value) + { + SPDLOG_WARN("Property '{}' not found on component '{}'", propertyName, componentType); + return JS_UNDEFINED; + } + + return Reflection::QuickJSTypeConverter::ToJSValue(jsContext->ctx, value); + }); + + module.function("SetComponentProperty", [jsContext](uint32_t nodeId, const std::string& componentType, + const std::string& propertyName, qjs::Value value) -> bool { + auto* engine = NextEngine::GetInstance(); + if (!engine) + { + return false; + } + + auto* scene = engine->GetScenePtr(); + if (!scene) + { + return false; + } + + Assets::Component* component = FindComponentByTypeName(*scene, nodeId, componentType); + if (!component) + { + SPDLOG_WARN("Component '{}' not found on node {}", componentType, nodeId); + return false; + } + + entt::meta_type metaType = component->GetMetaType(); + auto data = metaType.data(entt::hashed_string::value(propertyName.c_str())); + if (!data) + { + SPDLOG_WARN("Property '{}' not found on component '{}'", propertyName, componentType); + return false; + } + + entt::meta_type valueType = data.type(); + if (!Reflection::QuickJSTypeConverter::IsTypeSupported(valueType)) + { + SPDLOG_WARN("Property '{}' on component '{}' is not supported for JS conversion", propertyName, componentType); + return false; + } + entt::meta_any converted = Reflection::QuickJSTypeConverter::FromJSValue(jsContext->ctx, value.v, valueType); + if (!converted) + { + SPDLOG_WARN("Failed to convert value for property '{}' on component '{}'", propertyName, componentType); + return false; + } + + return Reflection::PropertyAccessor::SetPropertyValue(metaType, component, propertyName, converted); + }); + + module.function("CallComponentFunction", [jsContext](uint32_t nodeId, const std::string& componentType, + const std::string& functionName, qjs::rest args) -> JSValue { + auto* engine = NextEngine::GetInstance(); + if (!engine) + { + return JS_UNDEFINED; + } + + auto* scene = engine->GetScenePtr(); + if (!scene) + { + return JS_UNDEFINED; + } + + Assets::Component* component = FindComponentByTypeName(*scene, nodeId, componentType); + if (!component) + { + SPDLOG_WARN("Component '{}' not found on node {}", componentType, nodeId); + return JS_UNDEFINED; + } + + entt::meta_type metaType = component->GetMetaType(); + auto function = metaType.func(entt::hashed_string::value(functionName.c_str())); + if (!function) + { + SPDLOG_WARN("Function '{}' not found on component '{}'", functionName, componentType); + return JS_UNDEFINED; + } + + if (function.arity() != 0 || !args.empty()) + { + SPDLOG_WARN("Function '{}' on component '{}' expects {} args, but {} provided", functionName, + componentType, function.arity(), args.size()); + return JS_UNDEFINED; + } + + entt::meta_any instanceAny = metaType.from_void(component); + entt::meta_any result = function.invoke(instanceAny); + if (!result) + { + return JS_UNDEFINED; + } + + return Reflection::QuickJSTypeConverter::ToJSValue(jsContext->ctx, result); + }); + std::vector scriptBuffer; if (Utilities::Package::FPackageFileSystem::GetInstance().LoadFile("assets/scripts/test.js", scriptBuffer)) { @@ -225,6 +867,8 @@ void QuickJSEngine::CompileTypeScriptSources() return; } + UpdateTypeScriptDefinitions(tsconfigPath); + std::error_code ec; if (!fs::exists(tsconfigPath, ec)) { diff --git a/src/Runtime/Reflection/QuickJSReflectionBridge.h b/src/Runtime/Reflection/QuickJSReflectionBridge.h index 72e64ad7..d2968f5a 100644 --- a/src/Runtime/Reflection/QuickJSReflectionBridge.h +++ b/src/Runtime/Reflection/QuickJSReflectionBridge.h @@ -99,20 +99,17 @@ namespace Reflection bool first = true; for (auto&& [id, data] : metaType.data()) { - auto nameProp = data.prop(kPropertyNameKey); - if (nameProp) + const char* name = data.name(); + if (name) { - if (auto* name = nameProp.value().try_cast()) + if (!first) { - if (!first) - { - result += " | "; - } - result += "\""; - result += *name; - result += "\""; - first = false; + result += " | "; } + result += "\""; + result += name; + result += "\""; + first = false; } } From b3590fd2860fb94c8dcc2401fde09cf21658c27b Mon Sep 17 00:00:00 2001 From: gameKnife Date: Sun, 1 Feb 2026 16:27:10 +0800 Subject: [PATCH 23/53] feat(script): stabilize TS hot reload Avoid redundant tsc runs, improve output path handling, and expand reflected d.ts coverage. Co-authored-by: gpt-5.2-codex --- assets/typescript/Engine.d.ts | 7 +- assets/typescript/test.ts | 1 + .../MagicaLego/MagicaLegoUserInterface.cpp | 3 +- src/CMakeLists.txt | 1 + src/Runtime/Components/RenderComponent.cpp | 3 +- src/Runtime/Components/RenderComponent.h | 6 + .../Components/SkinnedMeshComponent.cpp | 6 +- src/Runtime/Platform/PlatformAndroid.h | 5 +- src/Runtime/Platform/PlatformLinux.h | 5 +- src/Runtime/Platform/PlatformWindows.h | 8 +- src/Runtime/QuickJSEngine.cpp | 221 ++++++++++++++---- src/Runtime/QuickJSEngine.hpp | 8 +- .../Reflection/QuickJSReflectionBridge.h | 60 +++++ 13 files changed, 278 insertions(+), 56 deletions(-) diff --git a/assets/typescript/Engine.d.ts b/assets/typescript/Engine.d.ts index ef17666b..ee7dee9d 100644 --- a/assets/typescript/Engine.d.ts +++ b/assets/typescript/Engine.d.ts @@ -26,19 +26,20 @@ export class RenderComponent { readonly SkinIndex: number; Materials: number[]; ToggleVisible(): boolean; + ToggleRayCastVisible(): boolean; } - export class PhysicsComponent { Mobility: string; PhysicsOffset: Vec3; } - export class SkinnedMeshComponent { PlaySpeed: number; readonly IsPlaying: boolean; readonly CurrentAnimation: string; + PlayAnimation(arg0: string, arg1: boolean): void; + StopAnimation(): void; + GetAnimationNames(): string[]; } - export type ENodeMobility = "Static" | "Dynamic" | "Kinematic"; export function println(...args: any[]): void; diff --git a/assets/typescript/test.ts b/assets/typescript/test.ts index ae9d3dc2..63e49318 100644 --- a/assets/typescript/test.ts +++ b/assets/typescript/test.ts @@ -20,6 +20,7 @@ function tryRunTest(): boolean { render.Visible = !render.Visible; const toggled = render.ToggleVisible(); NE.println(`[test.ts] ToggleVisible() => ${toggled} visible=${render.Visible}`); + return true; } diff --git a/src/Application/MagicaLego/MagicaLegoUserInterface.cpp b/src/Application/MagicaLego/MagicaLegoUserInterface.cpp index a4f85432..e6e74f48 100644 --- a/src/Application/MagicaLego/MagicaLegoUserInterface.cpp +++ b/src/Application/MagicaLego/MagicaLegoUserInterface.cpp @@ -635,7 +635,8 @@ void MagicaLegoUserInterface::RecordTimeline(bool autoRotate) waiting_ = false; PopLayout(); // sleep os for a while - NextRenderer::OSProcess(fmt::format("ffmpeg -framerate 30 -i {}/video_%d.jpg -c:v libx264 -pix_fmt yuv420p {}.mp4", localTempPath, filename).c_str()); + int result = NextRenderer::OSProcess(fmt::format("ffmpeg -framerate 30 -i {}/video_%d.jpg -c:v libx264 -pix_fmt yuv420p {}.mp4", localTempPath, filename).c_str()); + (void)result; // delete all *.jpg using std::filesystem std::filesystem::remove_all(localTempPath); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fe7ae7f5..81636053 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -178,6 +178,7 @@ foreach(target IN LISTS AllTargets) # link engine if ( ${target} STREQUAL gkNextEngine ) target_compile_definitions(${target} PRIVATE ENGINE_EXPORTS) + target_compile_definitions(${target} PRIVATE GK_NEXT_SOURCE_DIR="${CMAKE_SOURCE_DIR}") # Enable Unity Build for gkNextEngine unless explicitly disabled (Android disabled by default) # Group files for Unity Build optimization diff --git a/src/Runtime/Components/RenderComponent.cpp b/src/Runtime/Components/RenderComponent.cpp index 7f1f4efa..691419ac 100644 --- a/src/Runtime/Components/RenderComponent.cpp +++ b/src/Runtime/Components/RenderComponent.cpp @@ -27,6 +27,7 @@ namespace Runtime .data<&RenderComponent::SetMaterials, &RenderComponent::GetMaterials>("Materials") .custom(PropertyPresets::Editable("Materials", "Rendering", "Material indices for each submesh")) // Methods for JS - .func<&RenderComponent::ToggleVisible>("ToggleVisible"); + .func<&RenderComponent::ToggleVisible>("ToggleVisible") + .func<&RenderComponent::ToggleRayCastVisible>("ToggleRayCastVisible"); } } diff --git a/src/Runtime/Components/RenderComponent.h b/src/Runtime/Components/RenderComponent.h index c0d98968..0fcd884d 100644 --- a/src/Runtime/Components/RenderComponent.h +++ b/src/Runtime/Components/RenderComponent.h @@ -35,6 +35,12 @@ namespace Runtime visible_ = !visible_; return visible_; } + + bool ToggleRayCastVisible() + { + rayCastVisible_ = !rayCastVisible_; + return rayCastVisible_; + } bool IsDrawable() const { return modelId_ != static_cast(-1); } diff --git a/src/Runtime/Components/SkinnedMeshComponent.cpp b/src/Runtime/Components/SkinnedMeshComponent.cpp index e247bdf2..824940b9 100644 --- a/src/Runtime/Components/SkinnedMeshComponent.cpp +++ b/src/Runtime/Components/SkinnedMeshComponent.cpp @@ -30,9 +30,9 @@ namespace Runtime .data("CurrentAnimation") .custom(PropertyPresets::ReadOnly("Current Animation", "Animation", "Name of the currently playing animation")) // Methods for JS - .func<&SkinnedMeshComponent::PlayAnimation>("PlayAnimation"_hs) - .func<&SkinnedMeshComponent::StopAnimation>("StopAnimation"_hs) - .func<&SkinnedMeshComponent::GetAnimationNames>("GetAnimationNames"_hs); + .func<&SkinnedMeshComponent::PlayAnimation>("PlayAnimation") + .func<&SkinnedMeshComponent::StopAnimation>("StopAnimation") + .func<&SkinnedMeshComponent::GetAnimationNames>("GetAnimationNames"); } SkinnedMeshComponent::SkinnedMeshComponent(const Assets::Skeleton& skeleton) diff --git a/src/Runtime/Platform/PlatformAndroid.h b/src/Runtime/Platform/PlatformAndroid.h index b1299d06..81d602aa 100644 --- a/src/Runtime/Platform/PlatformAndroid.h +++ b/src/Runtime/Platform/PlatformAndroid.h @@ -17,8 +17,9 @@ namespace NextRenderer } - inline void OSProcess(const char* exe) + inline int OSProcess(const char* exe) { - + (void)exe; + return -1; } } diff --git a/src/Runtime/Platform/PlatformLinux.h b/src/Runtime/Platform/PlatformLinux.h index eb71d932..a2cfe05d 100644 --- a/src/Runtime/Platform/PlatformLinux.h +++ b/src/Runtime/Platform/PlatformLinux.h @@ -22,9 +22,8 @@ namespace NextRenderer //system(commandline.c_str()); } - inline void OSProcess(const char* exe) + inline int OSProcess(const char* exe) { - int result = std::system(exe); - (void)result; + return std::system(exe); } } diff --git a/src/Runtime/Platform/PlatformWindows.h b/src/Runtime/Platform/PlatformWindows.h index b1708f3c..f431d1ea 100644 --- a/src/Runtime/Platform/PlatformWindows.h +++ b/src/Runtime/Platform/PlatformWindows.h @@ -31,7 +31,7 @@ namespace NextRenderer ); } - inline void OSProcess(const char* commandline) + inline int OSProcess(const char* commandline) { STARTUPINFOA si; PROCESS_INFORMATION pi; @@ -52,11 +52,15 @@ namespace NextRenderer &pi )) { - return; + return -1; } WaitForSingleObject(pi.hProcess, INFINITE); + DWORD exitCode = 0; + GetExitCodeProcess(pi.hProcess, &exitCode); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); + + return static_cast(exitCode); } } diff --git a/src/Runtime/QuickJSEngine.cpp b/src/Runtime/QuickJSEngine.cpp index 52f26faf..3b080b63 100644 --- a/src/Runtime/QuickJSEngine.cpp +++ b/src/Runtime/QuickJSEngine.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #if WITH_QUICKJS #include @@ -24,6 +25,13 @@ namespace { #if WITH_QUICKJS + struct TypeScriptPaths + { + std::filesystem::path tsconfigPath; + std::filesystem::path projectDir; + std::filesystem::path outputDir; + }; + bool HasExtension(const std::filesystem::path& path, std::initializer_list extensions) { const std::string extension = path.extension().string(); @@ -119,21 +127,73 @@ namespace return hasTimestamp; } - bool HasNewerTypeScriptSources(const std::filesystem::path& projectDir, const std::filesystem::path& outputDir) + bool GetLatestTypeScriptTimestamp(const std::filesystem::path& projectDir, + const std::filesystem::path& tsconfigPath, + std::filesystem::file_time_type& outTimestamp) { - std::filesystem::file_time_type latestSource{}; - if (!FindLatestTimestamp(projectDir, { ".ts", ".tsx" }, latestSource)) + outTimestamp = std::filesystem::file_time_type{}; + if (!FindLatestTimestamp(projectDir, { ".ts", ".d.ts" }, outTimestamp)) + { + return false; + } + + std::error_code ec; + if (!tsconfigPath.empty() && std::filesystem::exists(tsconfigPath, ec)) + { + auto tsconfigTime = std::filesystem::last_write_time(tsconfigPath, ec); + if (!ec && tsconfigTime > outTimestamp) + { + outTimestamp = tsconfigTime; + } + } + + return true; + } + + TypeScriptPaths ResolveTypeScriptPaths() + { + namespace fs = std::filesystem; + + TypeScriptPaths paths; + paths.outputDir = fs::absolute(fs::path(Utilities::FileHelper::GetPlatformFilePath("assets/scripts"))); + + fs::path tsconfigPath = fs::path(Utilities::FileHelper::GetNormalizedFilePath("assets/typescript/tsconfig.json")); +#if defined(GK_NEXT_SOURCE_DIR) + const fs::path sourceRoot = fs::path(GK_NEXT_SOURCE_DIR); + const fs::path sourceTsconfig = sourceRoot / "assets/typescript/tsconfig.json"; + if (fs::exists(sourceTsconfig)) + { + tsconfigPath = sourceTsconfig; + } +#endif + + paths.tsconfigPath = tsconfigPath; + paths.projectDir = tsconfigPath.parent_path(); + return paths; + } + + bool HasNewerTypeScriptSources(const std::filesystem::path& projectDir, + const std::filesystem::path& outputDir, + const std::filesystem::path& tsconfigPath, + std::filesystem::file_time_type& outLatestSource) + { + if (!GetLatestTypeScriptTimestamp(projectDir, tsconfigPath, outLatestSource)) { return false; } - std::filesystem::file_time_type latestOutput{}; - if (!FindLatestTimestamp(outputDir, { ".js", ".mjs" }, latestOutput)) + const std::filesystem::path stampPath = outputDir / ".tsc.stamp"; + std::error_code ec; + if (std::filesystem::exists(stampPath, ec)) { - return true; + auto stampTime = std::filesystem::last_write_time(stampPath, ec); + if (!ec && stampTime >= outLatestSource) + { + return false; + } } - return latestSource > latestOutput; + return true; } void Println(qjs::rest args) @@ -509,19 +569,7 @@ namespace result += " GetIndicesCount(): number;\n"; result += "}\n\n"; - std::string renderDef = Reflection::QuickJSReflectionBridge::GenerateTypeScriptDef("RenderComponent"); - const std::string renderMethod = " ToggleVisible(): boolean;\n"; - const std::string renderClassEnd = "}\n"; - const size_t insertPos = renderDef.rfind(renderClassEnd); - if (insertPos != std::string::npos) - { - renderDef.insert(insertPos, renderMethod); - } - else - { - renderDef += renderMethod; - } - result += renderDef; + result += Reflection::QuickJSReflectionBridge::GenerateTypeScriptDef("RenderComponent"); result += Reflection::QuickJSReflectionBridge::GenerateTypeScriptDef("PhysicsComponent"); result += Reflection::QuickJSReflectionBridge::GenerateTypeScriptDef("SkinnedMeshComponent"); result += Reflection::QuickJSReflectionBridge::GenerateEnumTypeScriptDef("ENodeMobility"); @@ -580,13 +628,23 @@ QuickJSEngine::~QuickJSEngine() = default; void QuickJSEngine::Initialize() { #if WITH_QUICKJS + CompileTypeScriptSources(); + ResetContextAndLoadScript(); +#endif +} + +#if WITH_QUICKJS +void QuickJSEngine::ResetContextAndLoadScript() +{ + tickCallback_ = nullptr; + context_.reset(); + runtime_.reset(); + runtime_ = std::make_unique(); context_ = std::make_unique(*runtime_); try { - CompileTypeScriptSources(); - auto& module = context_->addModule("Engine"); module.function<&Println>("println"); module.function<&GetEngine>("GetEngine"); @@ -600,8 +658,8 @@ void QuickJSEngine::Initialize() .fun<&Assets::Scene::GetIndicesCount>("GetIndicesCount"); module.class_("NextComponent") .constructor<>() - .fun<&NextComponent::name_> ("name_") - .fun<&NextComponent::id_> ("id_"); + .fun<&NextComponent::name_>("name_") + .fun<&NextComponent::id_>("id_"); qjs::Context* jsContext = context_.get(); module.function("FindNodeIdWithComponent", [](const std::string& componentType) -> int32_t { @@ -829,8 +887,8 @@ void QuickJSEngine::Initialize() std::cerr << static_cast(exc["stack"]) << std::endl; } } -#endif } +#endif void QuickJSEngine::Tick(double deltaSeconds) { @@ -839,6 +897,8 @@ void QuickJSEngine::Tick(double deltaSeconds) { tickCallback_(deltaSeconds); } + + TickHotReload(deltaSeconds); #else (void)deltaSeconds; #endif @@ -854,17 +914,70 @@ void QuickJSEngine::RegisterTickCallback(std::function callback) } #if WITH_QUICKJS -void QuickJSEngine::CompileTypeScriptSources() +void QuickJSEngine::TickHotReload(double deltaSeconds) +{ +#if ANDROID + (void)deltaSeconds; + return; +#else + constexpr double hotReloadIntervalSeconds = 0.5; + hotReloadElapsed_ += deltaSeconds; + if (hotReloadElapsed_ < hotReloadIntervalSeconds) + { + return; + } + + hotReloadElapsed_ = 0.0; + if (CompileTypeScriptSources()) + { + SPDLOG_INFO("TypeScript outputs updated. Reloading QuickJS context."); + ResetContextAndLoadScript(); + } +#endif +} + +bool QuickJSEngine::EnsureTscAvailable(const std::filesystem::path& localTsc) +{ + if (tscChecked_) + { + return tscAvailable_; + } + + tscChecked_ = true; + if (!localTsc.empty() && std::filesystem::exists(localTsc)) + { + tscAvailable_ = true; + return true; + } + +#if WIN32 + int result = std::system("where tsc >nul 2>&1"); + tscAvailable_ = (result == 0); +#else + int result = std::system("command -v tsc >/dev/null 2>&1"); + tscAvailable_ = (result == 0); +#endif + + if (!tscAvailable_) + { + SPDLOG_WARN("TypeScript compiler not found; hot reload disabled."); + } + + return tscAvailable_; +} + +bool QuickJSEngine::CompileTypeScriptSources() { namespace fs = std::filesystem; try { - const fs::path tsconfigPath = fs::path(Utilities::FileHelper::GetNormalizedFilePath("assets/typescript/tsconfig.json")); + const TypeScriptPaths paths = ResolveTypeScriptPaths(); + const fs::path tsconfigPath = paths.tsconfigPath; if (tsconfigPath.empty()) { SPDLOG_DEBUG("TypeScript tsconfig not found; skipping compilation."); - return; + return false; } UpdateTypeScriptDefinitions(tsconfigPath); @@ -873,17 +986,22 @@ void QuickJSEngine::CompileTypeScriptSources() if (!fs::exists(tsconfigPath, ec)) { SPDLOG_DEBUG("TypeScript tsconfig missing at {}", tsconfigPath.string()); - return; + return false; } - const fs::path projectDir = tsconfigPath.parent_path(); - const fs::path outputDir = fs::absolute(projectDir / "../../assets/scripts"); + const fs::path projectDir = paths.projectDir; + const fs::path outputDir = paths.outputDir; + if (outputDir.empty()) + { + SPDLOG_WARN("TypeScript output directory is not available."); + return false; + } const bool forceCompile = std::getenv("NEXTENGINE_FORCE_TSC") != nullptr; - if (!forceCompile && !HasNewerTypeScriptSources(projectDir, outputDir)) + std::filesystem::file_time_type latestSource{}; + if (!forceCompile && !HasNewerTypeScriptSources(projectDir, outputDir, tsconfigPath, latestSource)) { - SPDLOG_INFO("TypeScript outputs are up to date; skipping compilation."); - return; + return false; } if (!fs::exists(outputDir, ec)) @@ -895,16 +1013,26 @@ void QuickJSEngine::CompileTypeScriptSources() } } + if (forceCompile) + { + GetLatestTypeScriptTimestamp(projectDir, tsconfigPath, latestSource); + } + + const fs::path localTsc = fs::current_path() / "tsc"; + if (!EnsureTscAvailable(localTsc)) + { + return false; + } + std::vector commands; #if WIN32 - commands.emplace_back(fmt::format("tsc -p \"{}\"", projectDir.string())); + commands.emplace_back(fmt::format("tsc -p \"{}\" --outDir \"{}\"", tsconfigPath.string(), outputDir.string())); #else - const fs::path localTsc = fs::current_path() / "tsc"; if (fs::exists(localTsc, ec)) { - commands.emplace_back(fmt::format("\"{}\" -p \"{}\"", localTsc.string(), projectDir.string())); + commands.emplace_back(fmt::format("\"{}\" -p \"{}\" --outDir \"{}\"", localTsc.string(), tsconfigPath.string(), outputDir.string())); } - commands.emplace_back(fmt::format("tsc -p \"{}\"", projectDir.string())); + commands.emplace_back(fmt::format("tsc -p \"{}\" --outDir \"{}\"", tsconfigPath.string(), outputDir.string())); #endif for (const std::string& command : commands) @@ -916,9 +1044,20 @@ void QuickJSEngine::CompileTypeScriptSources() SPDLOG_INFO("Compiling TypeScript scripts using: {}", command); spdlog::stopwatch stopwatch; - NextRenderer::OSProcess(command.c_str()); + int result = NextRenderer::OSProcess(command.c_str()); SPDLOG_INFO("---- Compiling TypeScript in {}", stopwatch.elapsed_ms()); - return; + if (result == 0) + { + const fs::path stampPath = outputDir / ".tsc.stamp"; + std::ofstream writer(stampPath, std::ios::binary | std::ios::trunc); + if (!writer) + { + SPDLOG_WARN("Failed to update TypeScript stamp at {}", stampPath.string()); + } + return true; + } + + SPDLOG_WARN("TypeScript compile command failed with code {}", result); } SPDLOG_WARN("Unable to compile TypeScript sources; continuing with existing JavaScript outputs."); @@ -927,5 +1066,7 @@ void QuickJSEngine::CompileTypeScriptSources() { SPDLOG_WARN("Exception while compiling TypeScript sources: {}", e.what()); } + + return false; } #endif diff --git a/src/Runtime/QuickJSEngine.hpp b/src/Runtime/QuickJSEngine.hpp index 13effd02..d9ef08da 100644 --- a/src/Runtime/QuickJSEngine.hpp +++ b/src/Runtime/QuickJSEngine.hpp @@ -22,10 +22,16 @@ class QuickJSEngine final private: #if WITH_QUICKJS - void CompileTypeScriptSources(); + bool CompileTypeScriptSources(); + void ResetContextAndLoadScript(); + void TickHotReload(double deltaSeconds); + bool EnsureTscAvailable(const std::filesystem::path& localTsc); std::unique_ptr runtime_; std::unique_ptr context_; std::function tickCallback_; + double hotReloadElapsed_ = 0.0; + bool tscChecked_ = false; + bool tscAvailable_ = false; #endif }; diff --git a/src/Runtime/Reflection/QuickJSReflectionBridge.h b/src/Runtime/Reflection/QuickJSReflectionBridge.h index d2968f5a..f54b2d66 100644 --- a/src/Runtime/Reflection/QuickJSReflectionBridge.h +++ b/src/Runtime/Reflection/QuickJSReflectionBridge.h @@ -15,6 +15,35 @@ namespace Reflection class QuickJSReflectionBridge { public: + static std::string ToTypeScriptType(entt::meta_type metaType) + { + if (!metaType) + { + return "any"; + } + + if (metaType == entt::resolve()) + { + return "void"; + } + + const PropertyType type = PropertyAccessor::DeducePropertyType(metaType); + if (type == PropertyType::Array) + { + auto [elementType, elementEnumTypeId] = PropertyAccessor::GetArrayElementType(metaType); + (void)elementEnumTypeId; + std::string elementTypeName = QuickJSTypeConverter::ToTypeScriptType(elementType); + return elementTypeName + "[]"; + } + + if (metaType.is_enum()) + { + return "string"; + } + + return QuickJSTypeConverter::ToTypeScriptType(type); + } + // Automatically bind a component class to QuickJS using reflection template static void AutoBindComponent(qjs::Context::Module& module, const char* jsClassName) @@ -77,6 +106,37 @@ namespace Reflection result += tsType; result += ";\n"; } + + for (auto&& [id, func] : metaType.func()) + { + const char* funcName = func.name(); + if (!funcName) + { + continue; + } + + result += " "; + result += funcName; + result += "("; + + const auto argCount = func.arity(); + for (std::size_t argIndex = 0; argIndex < argCount; ++argIndex) + { + if (argIndex > 0) + { + result += ", "; + } + + result += "arg"; + result += std::to_string(argIndex); + result += ": "; + result += ToTypeScriptType(func.arg(argIndex)); + } + + result += "): "; + result += ToTypeScriptType(func.ret()); + result += ";\n"; + } result += "}\n"; return result; From f7ffbabbccd94654c5ed45e4f26533ecdfc13365 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Sun, 1 Feb 2026 16:27:36 +0800 Subject: [PATCH 24/53] refactor(camera): improve ModelViewController with MovementInput and FocusAnimation - Extract FocusAnimation class for cleaner separation of concerns - Replace 12 bool movement flags with MovementInput struct (25->18 members) - Optimize Get*() methods to return cached vectors instead of computing matrix inverse - Add WASDQE camera movement only when right mouse pressed (Unreal Engine style) - Add double-click to focus on node in Outliner panel - Add Camera_FocusSelected action with node ID parameter support - Disable gizmo shortcuts (W/E/R) when right mouse is pressed - Add Scene::GetNodeBounds() for direct node bounds query Co-Authored-By: Claude Opus 4.5 --- .../gkNextRenderer/gkNextRenderer.cpp | 6 +- src/Assets/Scene.cpp | 11 +- src/Assets/Scene.hpp | 1 + src/Editor/EditorActionDispatcher.hpp | 2 + src/Editor/EditorMain.cpp | 24 +- src/Editor/Panels/OutlinerPanel.cpp | 26 +- src/Runtime/FocusAnimation.cpp | 34 ++ src/Runtime/FocusAnimation.hpp | 31 ++ src/Runtime/GizmoController.cpp | 6 + src/Runtime/ModelViewController.cpp | 307 ++++++++---------- src/Runtime/ModelViewController.hpp | 59 ++-- 11 files changed, 284 insertions(+), 223 deletions(-) create mode 100644 src/Runtime/FocusAnimation.cpp create mode 100644 src/Runtime/FocusAnimation.hpp diff --git a/src/Application/gkNextRenderer/gkNextRenderer.cpp b/src/Application/gkNextRenderer/gkNextRenderer.cpp index 23eb1bf2..3768e5bd 100644 --- a/src/Application/gkNextRenderer/gkNextRenderer.cpp +++ b/src/Application/gkNextRenderer/gkNextRenderer.cpp @@ -245,10 +245,8 @@ bool NextRendererGameInstance::OverrideRenderCamera(Assets::Camera& outRenderCam bool NextRendererGameInstance::OnKey(SDL_Event& event) { - if (!gizmoController_.IsShowing()) - { - modelViewController_.OnKey(event); - } + // WASDQE camera movement (only active when right mouse is pressed) + modelViewController_.OnKey(event); if (event.key.type == SDL_EVENT_KEY_DOWN) { diff --git a/src/Assets/Scene.cpp b/src/Assets/Scene.cpp index 2c20aa95..0ff683e9 100644 --- a/src/Assets/Scene.cpp +++ b/src/Assets/Scene.cpp @@ -1023,9 +1023,9 @@ namespace Assets return nullptr; } - bool Scene::GetSelectedNodeBounds(glm::vec3& center, float& radius) const + bool Scene::GetNodeBounds(uint32_t nodeId, glm::vec3& center, float& radius) const { - if (selectedId_ == static_cast(-1)) + if (nodeId == static_cast(-1)) { return false; } @@ -1033,7 +1033,7 @@ namespace Assets const Node* foundNode = nullptr; for (const auto& node : nodes_) { - if (node->GetInstanceId() == selectedId_) + if (node->GetInstanceId() == nodeId) { foundNode = node.get(); break; @@ -1065,6 +1065,11 @@ namespace Assets return true; } + bool Scene::GetSelectedNodeBounds(glm::vec3& center, float& radius) const + { + return GetNodeBounds(selectedId_, center, radius); + } + const Model* Scene::GetModel(uint32_t id) const { if (id < models_.size()) diff --git a/src/Assets/Scene.hpp b/src/Assets/Scene.hpp index 3d3eb949..893b9997 100644 --- a/src/Assets/Scene.hpp +++ b/src/Assets/Scene.hpp @@ -86,6 +86,7 @@ namespace Assets uint32_t GetSelectedId() const { return selectedId_; } void SetSelectedId(uint32_t id) const { selectedId_ = id; } bool GetSelectedNodeBounds(glm::vec3& center, float& radius) const; + bool GetNodeBounds(uint32_t nodeId, glm::vec3& center, float& radius) const; void Tick(float DeltaSeconds); void UpdateAllMaterials(); diff --git a/src/Editor/EditorActionDispatcher.hpp b/src/Editor/EditorActionDispatcher.hpp index 9cb7ce4c..b7462a9d 100644 --- a/src/Editor/EditorActionDispatcher.hpp +++ b/src/Editor/EditorActionDispatcher.hpp @@ -17,6 +17,8 @@ enum class EEditorAction IO_LoadScene, IO_LoadSceneAdd, IO_LoadHDRI, + + Camera_FocusSelected, }; class EditorActionDispatcher final diff --git a/src/Editor/EditorMain.cpp b/src/Editor/EditorMain.cpp index 1308eff6..d8685a4a 100644 --- a/src/Editor/EditorMain.cpp +++ b/src/Editor/EditorMain.cpp @@ -82,6 +82,22 @@ void EditorGameInstance::OnInit() return true; }); + actions_.RegisterAction(EEditorAction::Camera_FocusSelected, + [this](EditorContext& ctx, std::string_view args) -> bool + { + glm::vec3 center; + float radius; + bool found = args.empty() + ? ctx.scene.GetSelectedNodeBounds(center, radius) + : ctx.scene.GetNodeBounds(static_cast(std::stoul(std::string(args))), center, radius); + + if (found) + { + modelViewController_.Focus(center, radius); + } + return found; + }); + GetEngine().GetShowFlags().ShowEdge = true; } @@ -105,10 +121,9 @@ void EditorGameInstance::OnInitUI() { editorUserInterface_->Init(); } bool EditorGameInstance::OnKey(SDL_Event& event) { - if (!gizmoController_.IsShowing()) - { - modelViewController_.OnKey(event); - } + // WASDQE camera movement (only active when right mouse is pressed) + modelViewController_.OnKey(event); + if (event.key.type == SDL_EVENT_KEY_DOWN) { switch (event.key.key) @@ -118,6 +133,7 @@ bool EditorGameInstance::OnKey(SDL_Event& event) break; case SDLK_F: { + // Focus on selected node (F key shortcut) glm::vec3 focusCenter; float radius; if (GetEngine().GetScene().GetSelectedNodeBounds(focusCenter, radius)) diff --git a/src/Editor/Panels/OutlinerPanel.cpp b/src/Editor/Panels/OutlinerPanel.cpp index fb1f345a..3e9ad84f 100644 --- a/src/Editor/Panels/OutlinerPanel.cpp +++ b/src/Editor/Panels/OutlinerPanel.cpp @@ -13,13 +13,16 @@ namespace Editor { namespace { - void DrawNode(Assets::Scene& scene, Assets::Node& node) + void DrawNode(EditorContext& ctx, Assets::Node& node) { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - const bool selected = scene.GetSelectedId() == node.GetInstanceId(); - ImGuiTreeNodeFlags flag = ImGuiTreeNodeFlags_FramePadding | (selected ? ImGuiTreeNodeFlags_Selected : 0) | + const bool selected = ctx.scene.GetSelectedId() == node.GetInstanceId(); + ImGuiTreeNodeFlags flag = ImGuiTreeNodeFlags_FramePadding | + ImGuiTreeNodeFlags_OpenOnArrow | // Only expand on arrow click + ImGuiTreeNodeFlags_SpanAvailWidth | // Make the whole row clickable + (selected ? ImGuiTreeNodeFlags_Selected : 0) | (node.Children().empty() ? ImGuiTreeNodeFlags_Leaf : 0); ImGui::PushID(static_cast(node.GetInstanceId())); @@ -33,16 +36,25 @@ namespace Editor ImGui::PopStyleColor(); - if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) + // Single click to select + if (ImGui::IsItemClicked(ImGuiMouseButton_Left) && !ImGui::IsItemToggledOpen()) { - scene.SetSelectedId(node.GetInstanceId()); + ctx.scene.SetSelectedId(node.GetInstanceId()); + } + + // Double-click to focus camera on the node + if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + { + ctx.scene.SetSelectedId(node.GetInstanceId()); + ctx.actions.Dispatch(ctx, EEditorAction::Camera_FocusSelected, + std::to_string(node.GetInstanceId())); } if (opened) { for (auto& child : node.Children()) { - DrawNode(scene, *child); + DrawNode(ctx, *child); } ImGui::TreePop(); } @@ -79,7 +91,7 @@ namespace Editor continue; } - DrawNode(ctx.scene, *node); + DrawNode(ctx, *node); if (limit-- <= 0) { diff --git a/src/Runtime/FocusAnimation.cpp b/src/Runtime/FocusAnimation.cpp new file mode 100644 index 00000000..34feb468 --- /dev/null +++ b/src/Runtime/FocusAnimation.cpp @@ -0,0 +1,34 @@ +#include "FocusAnimation.hpp" + +void FocusAnimation::Start(glm::vec3 startPos, glm::quat startRot, + glm::vec3 targetPos, glm::quat targetRot) +{ + isActive_ = true; + timer_ = 0.0f; + startPos_ = startPos; + targetPos_ = targetPos; + startRot_ = startRot; + targetRot_ = targetRot; +} + +bool FocusAnimation::Update(float deltaTime, glm::vec3& outPos, glm::quat& outRot) +{ + if (!isActive_) + { + return false; + } + + timer_ += deltaTime; + float t = glm::clamp(timer_ / DURATION, 0.0f, 1.0f); + t = SmoothStep(t); + + outPos = glm::mix(startPos_, targetPos_, t); + outRot = glm::slerp(startRot_, targetRot_, t); + + if (t >= 1.0f) + { + isActive_ = false; + } + + return true; +} diff --git a/src/Runtime/FocusAnimation.hpp b/src/Runtime/FocusAnimation.hpp new file mode 100644 index 00000000..781a7a2f --- /dev/null +++ b/src/Runtime/FocusAnimation.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "Utilities/Glm.hpp" + +class FocusAnimation +{ +public: + void Start(glm::vec3 startPos, glm::quat startRot, + glm::vec3 targetPos, glm::quat targetRot); + + // Returns true if animation is still active + bool Update(float deltaTime, glm::vec3& outPos, glm::quat& outRot); + + bool IsActive() const { return isActive_; } + void Cancel() { isActive_ = false; } + +private: + static float SmoothStep(float t) + { + return t * t * (3.0f - 2.0f * t); + } + + bool isActive_ = false; + float timer_ = 0.0f; + static constexpr float DURATION = 0.5f; + + glm::vec3 startPos_{}; + glm::vec3 targetPos_{}; + glm::quat startRot_{}; + glm::quat targetRot_{}; +}; diff --git a/src/Runtime/GizmoController.cpp b/src/Runtime/GizmoController.cpp index 100ff0f8..7a12a3ae 100644 --- a/src/Runtime/GizmoController.cpp +++ b/src/Runtime/GizmoController.cpp @@ -36,6 +36,12 @@ void GizmoController::HandleShortcuts(const ImGuiIO& io) return; } + // Don't handle gizmo shortcuts when right mouse is pressed (camera movement mode) + if (io.MouseDown[1]) + { + return; + } + if (ImGui::IsKeyPressed(ImGuiKey_W)) { operation_ = static_cast(ImGuizmo::TRANSLATE); diff --git a/src/Runtime/ModelViewController.cpp b/src/Runtime/ModelViewController.cpp index 34e659ee..b0134b15 100644 --- a/src/Runtime/ModelViewController.cpp +++ b/src/Runtime/ModelViewController.cpp @@ -46,97 +46,87 @@ glm::mat4 ModelViewController::ModelView() const bool ModelViewController::OnKey(SDL_Event& event) { + // WASDQE movement only when right mouse button is pressed (like Unreal Engine) + if (!mouseRightPressed_) + { + return false; + } + // Any manual input cancels focus animation - if (isFocusing_) isFocusing_ = false; + if (focusAnimation_.IsActive()) + { + focusAnimation_.Cancel(); + } + + const bool isDown = event.key.type != SDL_EVENT_KEY_UP; + const float value = isDown ? 1.0f : 0.0f; switch (event.key.key) { - case SDLK_S: cameraMovingBackward_ = event.key.type != SDL_EVENT_KEY_UP; cameraMovingSpeed_ = {1.0f, 1.0f}; + case SDLK_W: + keyboardInput_.forward = value; return true; - case SDLK_W: cameraMovingForward_ =event.key.type != SDL_EVENT_KEY_UP; cameraMovingSpeed_ = {1.0f, 1.0f}; + case SDLK_S: + keyboardInput_.forward = isDown ? -1.0f : 0.0f; return true; - case SDLK_A: cameraMovingLeft_ = event.key.type != SDL_EVENT_KEY_UP; cameraMovingSpeed_ = {1.0f, 1.0f}; + case SDLK_D: + keyboardInput_.right = value; return true; - case SDLK_D: cameraMovingRight_ = event.key.type != SDL_EVENT_KEY_UP; cameraMovingSpeed_ = {1.0f, 1.0f}; + case SDLK_A: + keyboardInput_.right = isDown ? -1.0f : 0.0f; return true; - case SDLK_Q: cameraMovingDown_ = event.key.type != SDL_EVENT_KEY_UP; cameraMovingSpeed_ = {1.0f, 1.0f}; + case SDLK_Q: + keyboardInput_.up = value; return true; - case SDLK_E: cameraMovingUp_ = event.key.type != SDL_EVENT_KEY_UP; cameraMovingSpeed_ = {1.0f, 1.0f}; + case SDLK_E: + keyboardInput_.up = isDown ? -1.0f : 0.0f; return true; - default: return false; + default: + return false; } - return false; } -// 新增手柄输入处理函数 bool ModelViewController::OnGamepadInput(const int16_t leftStickX, const int16_t leftStickY, const int16_t rightStickX, const int16_t rightStickY, const int16_t leftTrigger, const int16_t rightTrigger) { - const float stickSensitivity = 0.0000352f; // 1 / 32767 - const int16_t deadZone = 3000; // 摇杆死区 - const double stickThreshold = 0.7; // 摇杆阈值 + constexpr float STICK_SENSITIVITY = 1.0f / 32767.0f; + constexpr int16_t DEAD_ZONE = 3000; + constexpr double STICK_THRESHOLD = 0.7; + bool inputDetected = false; - - // Any manual input cancels focus animation - if (std::abs(leftStickX) > deadZone || std::abs(leftStickY) > deadZone || - std::abs(rightStickX) > deadZone || std::abs(rightStickY) > deadZone || - leftTrigger > deadZone || rightTrigger > deadZone) + + // Cancel focus on significant input + if (std::abs(leftStickX) > DEAD_ZONE || std::abs(leftStickY) > DEAD_ZONE || + std::abs(rightStickX) > DEAD_ZONE || std::abs(rightStickY) > DEAD_ZONE || + leftTrigger > DEAD_ZONE || rightTrigger > DEAD_ZONE) { - isFocusing_ = false; + focusAnimation_.Cancel(); } - // 左摇杆控制前后左右移动 - if (std::abs(leftStickX) > deadZone) { - cameraMovingRightJoystick_ = leftStickX > 0; - cameraMovingLeftJoystick_ = leftStickX < 0; - cameraMovingSpeed_.x = std::abs(leftStickX) * stickSensitivity; - inputDetected = true; - } - else { - cameraMovingRightJoystick_ = false; - cameraMovingLeftJoystick_ = false; - cameraMovingSpeed_.x = 1.0f; - } - - if (std::abs(leftStickY) > deadZone) { - cameraMovingForwardJoystick_ = leftStickY < 0; - cameraMovingBackwardJoystick_ = leftStickY > 0; - cameraMovingSpeed_.y = std::abs(leftStickY) * stickSensitivity; - inputDetected = true; - } - else { - cameraMovingForwardJoystick_ = false; - cameraMovingBackwardJoystick_ = false; - cameraMovingSpeed_.y = 1.0f; - } - - // 扳机键控制上下移动 - if (leftTrigger > deadZone) { - cameraMovingDownJoystick_ = true; - inputDetected = true; - } - else { - cameraMovingDownJoystick_ = false; - } - - if (rightTrigger > deadZone) { - cameraMovingUpJoystick_ = true; - inputDetected = true; - } - else { - cameraMovingUpJoystick_ = false; - } - - // 右摇杆可以用于视角旋转 - if (std::abs(rightStickX) > deadZone || std::abs(rightStickY) > deadZone) { - cameraRotX_ = rightStickX * stickSensitivity; // 根据需要调整灵敏度 - cameraRotX_ = glm::sign(cameraRotX_) * glm::min(stickThreshold, cameraRotX_ * cameraRotX_); - cameraRotY_ = rightStickY * stickSensitivity; - cameraRotY_ = glm::sign(cameraRotY_) * glm::min(stickThreshold, cameraRotY_ * cameraRotY_); + // Left stick: movement + gamepadInput_.right = std::abs(leftStickX) > DEAD_ZONE + ? leftStickX * STICK_SENSITIVITY : 0.0f; + gamepadInput_.forward = std::abs(leftStickY) > DEAD_ZONE + ? -leftStickY * STICK_SENSITIVITY : 0.0f; // Y is inverted + + // Triggers: up/down + float upInput = rightTrigger > DEAD_ZONE ? rightTrigger * STICK_SENSITIVITY : 0.0f; + float downInput = leftTrigger > DEAD_ZONE ? leftTrigger * STICK_SENSITIVITY : 0.0f; + gamepadInput_.up = upInput - downInput; + + inputDetected = gamepadInput_.IsActive(); + + // Right stick: rotation + if (std::abs(rightStickX) > DEAD_ZONE || std::abs(rightStickY) > DEAD_ZONE) + { + cameraRotX_ = rightStickX * STICK_SENSITIVITY; + cameraRotX_ = glm::sign(cameraRotX_) * glm::min(STICK_THRESHOLD, cameraRotX_ * cameraRotX_); + cameraRotY_ = rightStickY * STICK_SENSITIVITY; + cameraRotY_ = glm::sign(cameraRotY_) * glm::min(STICK_THRESHOLD, cameraRotY_ * cameraRotY_); inputDetected = true; } - + return inputDetected; } @@ -156,7 +146,7 @@ bool ModelViewController::OnCursorPosition(const double xpos, const double ypos) if (mouseRightPressed_) { // Cancel focus on manual rotation - isFocusing_ = false; + focusAnimation_.Cancel(); if (altPressed_ && orbitTarget_.has_value()) { @@ -184,9 +174,8 @@ bool ModelViewController::OnMouseButton(SDL_Event& event) if (event.button.button == SDL_BUTTON_LEFT) { mouseLeftPressed_ = event.button.type == SDL_EVENT_MOUSE_BUTTON_DOWN; - // Left button does not trigger camera reset or movement anymore } - + if (event.button.button == SDL_BUTTON_RIGHT) { mouseRightPressed_ = event.button.type == SDL_EVENT_MOUSE_BUTTON_DOWN; @@ -194,80 +183,56 @@ bool ModelViewController::OnMouseButton(SDL_Event& event) { resetMousePos_ = true; } + else + { + // Reset keyboard movement when right mouse is released + keyboardInput_.Reset(); + } } return true; } void ModelViewController::Focus(const glm::vec3& focusPoint, float radius) { - // Move camera to look at the object from a fixed distance based on radius - - // Calculate distance to fit the object - // half_size = distance * tan(fov/2) - // distance = half_size / tan(fov/2) - + // Calculate distance to fit the object in view float fovRad = glm::radians(fieldOfView_); - float dist = radius / glm::sin(fovRad * 0.5f); - - // Add a little margin - dist *= 1.1f; - - // Preserve current viewing angle: - // Move the camera to a position such that it looks at 'focusPoint' - // with the SAME orientation it currently has. - // This means moving along the backward vector of the camera. - - glm::vec3 fwd = GetForward(); - glm::vec3 currentPos = glm::vec3(position_); - - // Target position is simply back from the focus point along the forward vector + float dist = radius / glm::sin(fovRad * 0.5f) * 1.1f; + + // Target position: back from focus point along forward vector + glm::vec3 fwd = GetForward(); glm::vec3 targetPos = focusPoint - fwd * dist; - // Setup interpolation - isFocusing_ = true; - focusTimer_ = 0.0f; - - focusStartPos_ = currentPos; - focusTargetPos_ = targetPos; - - focusStartRot_ = glm::quat_cast(orientation_); - focusTargetRot_ = focusStartRot_; // Keep rotation constant + // Start animation (keep current rotation) + glm::quat currentRot = glm::quat_cast(orientation_); + focusAnimation_.Start( + glm::vec3(position_), + currentRot, + targetPos, + currentRot + ); } void ModelViewController::Orbit(float deltaX, float deltaY) { - if (!orbitTarget_.has_value()) return; + if (!orbitTarget_.has_value()) + { + return; + } glm::vec3 target = orbitTarget_.value(); - glm::vec3 camPos = glm::vec3(position_); - - // Calculate Rotation Matrices - // Yaw (World Y) - // Drag Right (deltaX > 0) -> Rotate Camera Left (CW around Y) -> Negative Angle + + // Yaw around world Y, Pitch around camera right glm::mat4 yaw = glm::rotate(glm::mat4(1.0f), -deltaX * 5.0f, glm::vec3(0, 1, 0)); - - // Pitch (Camera Right) - // Drag Down (deltaY > 0) -> Rotate Camera Up (CW around Right?) - // Right(1,0,0). Z(0,0,1). Y(0,1,0). - // +90 around X: Z -> -Y (Down). - // -90 around X: Z -> Y (Up). - // So Negative Angle for Drag Down. glm::mat4 pitch = glm::rotate(glm::mat4(1.0f), -deltaY * 5.0f, glm::vec3(GetRight())); - - glm::mat4 R = yaw * pitch; + glm::mat4 rotation = yaw * pitch; - // Update Position - // R rotates the Arm vector in World Space + // Rotate position around target glm::vec3 arm = glm::vec3(position_) - target; - position_ = glm::vec4(target + glm::vec3(R * glm::vec4(arm, 0.0f)), 1.0f); - - // Update Orientation - // The Camera Frame must also rotate by R in World Space to maintain relative alignment. - // C2W_new = R * C2W_old - // W2C_new = inv(C2W_new) = inv(R * C2W_old) = W2C_old * inv(R) - // Since R is a rotation matrix, inv(R) == transpose(R) - orientation_ = orientation_ * glm::transpose(R); - + position_ = glm::vec4(target + glm::vec3(rotation * glm::vec4(arm, 0.0f)), 1.0f); + + // Update orientation to maintain view direction + orientation_ = orientation_ * glm::transpose(rotation); + UpdateVectors(); } @@ -283,43 +248,46 @@ bool ModelViewController::OnTouch(bool down, double xpos, double ypos) void ModelViewController::OnScroll(double xoffset, double yoffset) { - fieldOfView_ -= static_cast(yoffset); - fieldOfView_ = glm::clamp(fieldOfView_, 1.0f, 90.0f); + // Cancel focus on scroll + focusAnimation_.Cancel(); + + const float scrollSpeed = 0.5f; + MoveForward(static_cast(yoffset) * scrollSpeed); movedByEvent_ = true; } bool ModelViewController::UpdateCamera(const double speed, const double timeDelta) { - if (isFocusing_) + // Handle focus animation + glm::vec3 focusPos; + glm::quat focusRot; + if (focusAnimation_.Update(static_cast(timeDelta), focusPos, focusRot)) { - focusTimer_ += static_cast(timeDelta); - float t = glm::clamp(focusTimer_ / focusDuration_, 0.0f, 1.0f); - - // Smooth step - t = t * t * (3.0f - 2.0f * t); - - position_ = glm::vec4(glm::mix(focusStartPos_, focusTargetPos_, t), 1.0f); - glm::quat currentRot = glm::slerp(focusStartRot_, focusTargetRot_, t); - orientation_ = glm::mat4(glm::mat3(currentRot)); - + position_ = glm::vec4(focusPos, 1.0f); + orientation_ = glm::mat4(glm::mat3(focusRot)); UpdateVectors(); - - if (t >= 1.0f) - { - isFocusing_ = false; - } - return true; } const auto d = static_cast(speed * timeDelta); - if (cameraMovingLeft_ || cameraMovingLeftJoystick_) MoveRight(-d * cameraMovingSpeed_.x); - if (cameraMovingRight_ || cameraMovingRightJoystick_) MoveRight(d * cameraMovingSpeed_.x); - if (cameraMovingBackward_ || cameraMovingBackwardJoystick_) MoveForward(-d * cameraMovingSpeed_.y); - if (cameraMovingForward_ || cameraMovingForwardJoystick_) MoveForward(d * cameraMovingSpeed_.y); - if (cameraMovingDown_ || cameraMovingDownJoystick_) MoveUp(-d); - if (cameraMovingUp_ || cameraMovingUpJoystick_) MoveUp(d); + // Combine keyboard and gamepad input + float totalRight = keyboardInput_.right + gamepadInput_.right; + float totalForward = keyboardInput_.forward + gamepadInput_.forward; + float totalUp = keyboardInput_.up + gamepadInput_.up; + + if (totalRight != 0.0f) + { + MoveRight(d * totalRight); + } + if (totalForward != 0.0f) + { + MoveForward(d * totalForward); + } + if (totalUp != 0.0f) + { + MoveUp(d * totalUp); + } modelRotX_ = glm::mix(modelRotX_, rawModelRotX_, 0.5); modelRotY_ = glm::mix(modelRotY_, rawModelRotY_, 0.5); @@ -327,53 +295,42 @@ bool ModelViewController::UpdateCamera(const double speed, const double timeDelt const double rotationDiv = 1 / timeDelta; Rotate(static_cast(cameraRotX_ / rotationDiv + cameraRotXAbs_), static_cast(cameraRotY_ / rotationDiv + cameraRotYAbs_)); + const bool hasMovement = keyboardInput_.IsActive() || gamepadInput_.IsActive(); const bool updated = - cameraMovingLeft_ || cameraMovingLeftJoystick_ || - cameraMovingRight_ || cameraMovingRightJoystick_ || - cameraMovingBackward_ || cameraMovingBackwardJoystick_ || - cameraMovingForward_ || cameraMovingForwardJoystick_ || - cameraMovingDown_ || cameraMovingDownJoystick_ || - cameraMovingUp_ || cameraMovingUpJoystick_ || + hasMovement || (cameraRotY_ + cameraRotYAbs_) != 0.0 || (cameraRotX_ + cameraRotXAbs_) != 0.0 || glm::abs(rawModelRotX_ - modelRotX_) > 0.01 || - glm::abs(rawModelRotY_ - modelRotY_) > 0.01 || movedByEvent_;; + glm::abs(rawModelRotY_ - modelRotY_) > 0.01 || + movedByEvent_; cameraRotY_ = 0; cameraRotX_ = 0; cameraRotXAbs_ = 0; cameraRotYAbs_ = 0; movedByEvent_ = false; - + return updated; } -glm::vec3 ModelViewController::GetRight() +glm::vec3 ModelViewController::GetRight() const { - glm::mat4 mvi = inverse(ModelView()); - glm::vec4 origin = mvi * glm::vec4(1, 0, 0,0); - return origin; + return glm::vec3(right_); } -glm::vec3 ModelViewController::GetUp() +glm::vec3 ModelViewController::GetUp() const { - glm::mat4 mvi = inverse(ModelView()); - glm::vec4 origin = mvi * glm::vec4(0, 1, 0,0); - return origin; + return glm::vec3(up_); } -glm::vec3 ModelViewController::GetForward() +glm::vec3 ModelViewController::GetForward() const { - glm::mat4 mvi = inverse(ModelView()); - glm::vec4 origin = mvi * glm::vec4(0, 0, -1, 0); - return origin; + return glm::vec3(forward_); } -glm::vec3 ModelViewController::GetPosition() +glm::vec3 ModelViewController::GetPosition() const { - glm::mat4 mvi = inverse(ModelView()); - glm::vec4 origin = mvi * glm::vec4(0, 0, 0, 1); - return origin; + return glm::vec3(position_); } void ModelViewController::MoveForward(const float d) diff --git a/src/Runtime/ModelViewController.hpp b/src/Runtime/ModelViewController.hpp index ad8e5d3b..6ed738da 100644 --- a/src/Runtime/ModelViewController.hpp +++ b/src/Runtime/ModelViewController.hpp @@ -1,15 +1,33 @@ #pragma once +#include "FocusAnimation.hpp" #include "Utilities/Glm.hpp" #include namespace Assets { - struct Camera; + struct Camera; } union SDL_Event; +struct MovementInput +{ + float forward = 0.0f; // [-1, 1] + float right = 0.0f; // [-1, 1] + float up = 0.0f; // [-1, 1] + + bool IsActive() const + { + return forward != 0.0f || right != 0.0f || up != 0.0f; + } + + void Reset() + { + forward = right = up = 0.0f; + } +}; + class ModelViewController final { public: @@ -41,10 +59,10 @@ class ModelViewController final void SetAltPressed(bool pressed) { altPressed_ = pressed; } void Focus(const glm::vec3& focusPoint, float radius = 0.5f); - glm::vec3 GetRight(); - glm::vec3 GetUp(); - glm::vec3 GetForward(); - glm::vec3 GetPosition(); + glm::vec3 GetRight() const; + glm::vec3 GetUp() const; + glm::vec3 GetForward() const; + glm::vec3 GetPosition() const; private: @@ -67,31 +85,12 @@ class ModelViewController final std::optional orbitTarget_; bool altPressed_{}; - // Control states. - bool isFocusing_{}; - float focusTimer_{}; - const float focusDuration_ = 0.5f; - - glm::vec3 focusStartPos_{}; - glm::vec3 focusTargetPos_{}; - glm::quat focusStartRot_{}; - glm::quat focusTargetRot_{}; - - bool cameraMovingLeft_{}; - bool cameraMovingRight_{}; - bool cameraMovingBackward_{}; - bool cameraMovingForward_{}; - bool cameraMovingDown_{}; - bool cameraMovingUp_{}; - - bool cameraMovingLeftJoystick_{}; - bool cameraMovingRightJoystick_{}; - bool cameraMovingBackwardJoystick_{}; - bool cameraMovingForwardJoystick_{}; - bool cameraMovingDownJoystick_{}; - bool cameraMovingUpJoystick_{}; - - glm::vec2 cameraMovingSpeed_{}; + // Focus animation + FocusAnimation focusAnimation_; + + // Movement input (replaces 12 bools) + MovementInput keyboardInput_; + MovementInput gamepadInput_; // with smooth movement double cameraRotX_{}; From 6a775285655002d978e8b92230c5cd9e320e488d Mon Sep 17 00:00:00 2001 From: gameKnife Date: Sun, 1 Feb 2026 23:54:25 +0800 Subject: [PATCH 25/53] feat(script): reflect engine scene node API Move scene/node access to reflection, add node component access, and streamline QuickJS bindings and d.ts. Co-authored-by: gpt-5.2-codex --- assets/typescript/Engine.d.ts | 37 +- assets/typescript/test.ts | 16 +- src/Assets/Node.cpp | 42 ++ src/Assets/Node.h | 3 + src/Assets/Scene.cpp | 38 ++ src/Assets/Scene.hpp | 4 + src/Runtime/Engine.cpp | 14 + src/Runtime/Engine.hpp | 2 + src/Runtime/QuickJSEngine.cpp | 594 ++++++++++-------- .../Reflection/QuickJSReflectionBridge.h | 14 + src/Runtime/Reflection/ReflectionRegistry.cpp | 7 + 11 files changed, 485 insertions(+), 286 deletions(-) diff --git a/assets/typescript/Engine.d.ts b/assets/typescript/Engine.d.ts index ee7dee9d..e2ae493d 100644 --- a/assets/typescript/Engine.d.ts +++ b/assets/typescript/Engine.d.ts @@ -3,22 +3,32 @@ export interface Vec3 { x: number; y: number; z: number; } export interface Vec4 { x: number; y: number; z: number; w: number; } export interface Quat { x: number; y: number; z: number; w: number; } +export class NextComponent { + name_: string; + id_: number; +} + export class NextEngine { GetTotalFrames(): number; GetTestNumber(): number; - RegisterJSCallback(callback: (param: number) => void): void; + RegisterJSCallback(arg0: any): void; GetScenePtr(): Scene; } - -export class NextComponent { - name_: string; - id_: number; +export class Node { + readonly Name: string; + readonly InstanceId: number; + Translation: Vec3; + Rotation: Quat; + Scale: Vec3; + GetName(): string; + GetInstanceId(): number; + GetComponent(arg0: string): any; } - export class Scene { GetIndicesCount(): number; + FindNodeIdWithComponent(arg0: string): number; + GetNodeById(arg0: number): Node; } - export class RenderComponent { Visible: boolean; RayCastVisible: boolean; @@ -42,12 +52,7 @@ export class SkinnedMeshComponent { } export type ENodeMobility = "Static" | "Dynamic" | "Kinematic"; -export function println(...args: any[]): void; -export function GetEngine(): NextEngine; -export function FindNodeIdWithComponent(componentType: string): number; -export function GetNodeName(nodeId: number): string; -export function GetNodeTranslation(nodeId: number): Vec3; -export function GetComponent(nodeId: number, componentType: string): any; -export function GetComponentProperty(nodeId: number, componentType: string, propertyName: string): any; -export function SetComponentProperty(nodeId: number, componentType: string, propertyName: string, value: any): boolean; -export function CallComponentFunction(nodeId: number, componentType: string, functionName: string, ...args: any[]): any; +export namespace Global { + function spdlog(level: string, ...args: any[]): void; + function GetEngine(): NextEngine; +} diff --git a/assets/typescript/test.ts b/assets/typescript/test.ts index 63e49318..b6b9c12d 100644 --- a/assets/typescript/test.ts +++ b/assets/typescript/test.ts @@ -3,28 +3,30 @@ import * as NE from "./Engine"; let hasRun = false; function tryRunTest(): boolean { - const nodeId = NE.FindNodeIdWithComponent("RenderComponent"); + const scene = NE.Global.GetEngine().GetScenePtr(); + const nodeId = scene.FindNodeIdWithComponent("RenderComponent"); if (nodeId < 0) { return false; } - const nodeName = NE.GetNodeName(nodeId); - const translation = NE.GetNodeTranslation(nodeId) as NE.Vec3; - const render = NE.GetComponent(nodeId, "RenderComponent") as NE.RenderComponent; + const node = scene.GetNodeById(nodeId) as NE.Node; + const nodeName = node.Name; + const translation = node.Translation as NE.Vec3; + const render = node.GetComponent("RenderComponent") as NE.RenderComponent; if (!render) { return false; } - NE.println(`[test.ts] ${nodeName} pos=(${translation.x}, ${translation.y}, ${translation.z}) visible=${render.Visible}`); + NE.Global.spdlog("info", `[test.ts] ${nodeName} pos=(${translation.x}, ${translation.y}, ${translation.z}) visible=${render.Visible}`); render.Visible = !render.Visible; const toggled = render.ToggleVisible(); - NE.println(`[test.ts] ToggleVisible() => ${toggled} visible=${render.Visible}`); + NE.Global.spdlog("info", `[test.ts] ToggleVisible() => ${toggled} visible=${render.Visible}`); return true; } -NE.GetEngine().RegisterJSCallback((_delta: number) => { +NE.Global.GetEngine().RegisterJSCallback((_delta: number) => { if (hasRun) { return; } diff --git a/src/Assets/Node.cpp b/src/Assets/Node.cpp index aee79011..7594a0fd 100644 --- a/src/Assets/Node.cpp +++ b/src/Assets/Node.cpp @@ -4,12 +4,54 @@ #include "Runtime/Components/SkinnedMeshComponent.h" #include "Runtime/Engine.hpp" +#include "Runtime/Reflection/PropertyMeta.h" +#include #define GLM_ENABLE_EXPERIMENTAL #include #include namespace Assets { + void Node::RegisterReflection() + { + using namespace entt::literals; + using namespace Reflection; + + entt::meta_factory() + .type("Node"_hs) + .data("Name") + .custom(PropertyPresets::ReadOnly("Name", "Transform", "Node name")) + .data("InstanceId") + .custom(PropertyPresets::ReadOnly("InstanceId", "Transform", "Node instance id")) + .data<&Node::SetTranslation, &Node::Translation>("Translation") + .custom(PropertyPresets::Editable("Translation", "Transform", "Local translation")) + .data<&Node::SetRotation, &Node::Rotation>("Rotation") + .custom(PropertyPresets::Editable("Rotation", "Transform", "Local rotation")) + .data<&Node::SetScale, &Node::Scale>("Scale") + .custom(PropertyPresets::Editable("Scale", "Transform", "Local scale")) + .func<&Node::GetName>("GetName") + .func<&Node::GetInstanceId>("GetInstanceId") + .func<&Node::GetComponentByTypeName>("GetComponent"); + } + + Component* Node::GetComponentByTypeName(const std::string& componentType) const + { + for (const auto& component : components_) + { + if (!component) + { + continue; + } + + if (component->GetTypeName() == componentType) + { + return component.get(); + } + } + + return nullptr; + } + std::shared_ptr Node::CreateNode(std::string name, glm::vec3 translation, glm::quat rotation, glm::vec3 scale, uint32_t instanceId) { return std::make_shared(name, translation, rotation, scale, instanceId); diff --git a/src/Assets/Node.h b/src/Assets/Node.h index 0c7aeed8..f7916efc 100644 --- a/src/Assets/Node.h +++ b/src/Assets/Node.h @@ -18,6 +18,7 @@ namespace Assets class Node : public std::enable_shared_from_this { public: + static void RegisterReflection(); using ENodeMobility = Runtime::ENodeMobility; static std::shared_ptr CreateNode(std::string name, glm::vec3 translation, glm::quat rotation, glm::vec3 scale, uint32_t instanceId = 0); @@ -40,6 +41,8 @@ namespace Assets const std::string& GetName() const {return name_; } + Component* GetComponentByTypeName(const std::string& componentType) const; + uint32_t GetInstanceId() const { return instanceId_; } void SetInstanceId(uint32_t id) { instanceId_ = id; } bool TickVelocity(glm::mat4& combinedTS); diff --git a/src/Assets/Scene.cpp b/src/Assets/Scene.cpp index 0ff683e9..3fc5cad6 100644 --- a/src/Assets/Scene.cpp +++ b/src/Assets/Scene.cpp @@ -22,9 +22,47 @@ #include #include +#include namespace Assets { + void Scene::RegisterReflection() + { + using namespace entt::literals; + + entt::meta_factory() + .type("Scene"_hs) + .func<&Assets::Scene::GetIndicesCount>("GetIndicesCount") + .func<&Assets::Scene::FindNodeIdWithComponent>("FindNodeIdWithComponent") + .func<&Assets::Scene::GetNodeById>("GetNodeById"); + } + + int32_t Scene::FindNodeIdWithComponent(const std::string& componentType) const + { + for (const auto& node : nodes_) + { + if (!node) + { + continue; + } + + for (const auto& component : node->GetComponents()) + { + if (component && component->GetTypeName() == componentType) + { + return static_cast(node->GetInstanceId()); + } + } + } + + return -1; + } + + Node* Scene::GetNodeById(uint32_t nodeId) + { + return GetNodeByInstanceId(nodeId); + } + Scene::Scene(Vulkan::CommandPool& commandPool, bool supportRayTracing) { int flags = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; diff --git a/src/Assets/Scene.hpp b/src/Assets/Scene.hpp index 893b9997..b0a1f55a 100644 --- a/src/Assets/Scene.hpp +++ b/src/Assets/Scene.hpp @@ -34,6 +34,7 @@ namespace Assets class Scene final { public: + static void RegisterReflection(); Scene(const Scene&) = delete; Scene(Scene&&) = delete; Scene& operator=(const Scene&) = delete; @@ -81,6 +82,9 @@ namespace Assets const uint32_t GetVerticeCount() const { return verticeCount_; } const uint32_t GetIndirectDrawBatchCount() const { return indirectDrawBatchCount_; } + int32_t FindNodeIdWithComponent(const std::string& componentType) const; + Node* GetNodeById(uint32_t nodeId); + const Assets::GPUDrivenStat& GetGpuDrivenStat() const { return gpuDrivenStat_; } uint32_t GetSelectedId() const { return selectedId_; } diff --git a/src/Runtime/Engine.cpp b/src/Runtime/Engine.cpp index 6806a2f3..13942d99 100644 --- a/src/Runtime/Engine.cpp +++ b/src/Runtime/Engine.cpp @@ -34,6 +34,8 @@ #define _USE_MATH_DEFINES #include +#include + #define BUILDVER(X) std::string buildver(#X); #include "NextAnimation.h" #include "NextPhysics.h" @@ -53,6 +55,18 @@ ENGINE_API Options* GOption = nullptr; +void NextEngine::RegisterReflection() +{ + using namespace entt::literals; + + entt::meta_factory() + .type("NextEngine"_hs) + .func<&NextEngine::GetTotalFrames>("GetTotalFrames") + .func<&NextEngine::GetTestNumber>("GetTestNumber") + .func<&NextEngine::RegisterJSCallback>("RegisterJSCallback") + .func<&NextEngine::GetScenePtr>("GetScenePtr"); +} + namespace { Vulkan::ERendererType ResolveRendererType(Vulkan::ERendererType requestedType, bool supportsRayTracing) diff --git a/src/Runtime/Engine.hpp b/src/Runtime/Engine.hpp index 20ae9ef7..c9aa2a3b 100644 --- a/src/Runtime/Engine.hpp +++ b/src/Runtime/Engine.hpp @@ -119,6 +119,8 @@ class NextEngine final public: VULKAN_NON_COPIABLE(NextEngine) + static void RegisterReflection(); + NextEngine(Options& options, void* userdata = nullptr); ~NextEngine(); diff --git a/src/Runtime/QuickJSEngine.cpp b/src/Runtime/QuickJSEngine.cpp index 3b080b63..30d61a12 100644 --- a/src/Runtime/QuickJSEngine.cpp +++ b/src/Runtime/QuickJSEngine.cpp @@ -196,12 +196,42 @@ namespace return true; } - void Println(qjs::rest args) + std::string JoinArgs(const qjs::rest& args, size_t startIndex) { - for (auto const& arg : args) + std::string result; + for (size_t index = startIndex; index < args.size(); ++index) + { + if (!result.empty()) + { + result += " "; + } + result += args[index]; + } + return result; + } + + spdlog::level::level_enum ParseLogLevel(const std::string& value) + { + if (value == "trace") return spdlog::level::trace; + if (value == "debug") return spdlog::level::debug; + if (value == "info") return spdlog::level::info; + if (value == "warn") return spdlog::level::warn; + if (value == "warning") return spdlog::level::warn; + if (value == "error") return spdlog::level::err; + if (value == "critical") return spdlog::level::critical; + return spdlog::level::info; + } + + void Spdlog(qjs::rest args) + { + if (args.empty()) { - SPDLOG_INFO("{}", arg); + return; } + + const spdlog::level::level_enum level = ParseLogLevel(args[0]); + const std::string message = JoinArgs(args, 1); + spdlog::log(level, "{}", message); } NextEngine* GetEngine() @@ -209,33 +239,33 @@ namespace return NextEngine::GetInstance(); } - Assets::Component* FindComponentByTypeName(Assets::Scene& scene, uint32_t nodeId, const std::string& componentType) + Assets::Component* FindComponentByTypeName(uint32_t nodeId, const std::string& componentType) { - auto* node = scene.GetNodeByInstanceId(nodeId); + auto* node = FindNodeById(nodeId); if (!node) { return nullptr; } - for (const auto& component : node->GetComponents()) - { - if (!component) - { - continue; - } + return node->GetComponentByTypeName(componentType); + } - if (component->GetTypeName() == componentType) - { - return component.get(); - } + Assets::Node* FindNodeById(uint32_t nodeId) + { + auto* engine = NextEngine::GetInstance(); + if (!engine) + { + return nullptr; } - return nullptr; - } + auto* scene = engine->GetScenePtr(); + if (!scene) + { + return nullptr; + } - Assets::Node* FindNodeById(Assets::Scene& scene, uint32_t nodeId) - { - return scene.GetNodeByInstanceId(nodeId); + const auto node = scene->GetNodeSharedByInstanceId(nodeId); + return node ? node.get() : nullptr; } JSValue ComponentPropertyGetter(JSContext* ctx, JSValueConst thisVal, int argc, JSValueConst* argv, @@ -280,7 +310,7 @@ namespace return JS_UNDEFINED; } - Assets::Component* component = FindComponentByTypeName(*scene, nodeId, componentType); + Assets::Component* component = FindComponentByTypeName(nodeId, componentType); if (!component) { JS_FreeCString(ctx, componentType); @@ -346,7 +376,7 @@ namespace return JS_UNDEFINED; } - Assets::Component* component = FindComponentByTypeName(*scene, nodeId, componentType); + Assets::Component* component = FindComponentByTypeName(nodeId, componentType); if (!component) { JS_FreeCString(ctx, componentType); @@ -417,7 +447,7 @@ namespace return JS_UNDEFINED; } - Assets::Component* component = FindComponentByTypeName(*scene, nodeId, componentType); + Assets::Component* component = FindComponentByTypeName(nodeId, componentType); if (!component) { JS_FreeCString(ctx, componentType); @@ -524,25 +554,280 @@ namespace return obj; } - int32_t FindNodeIdWithComponent(Assets::Scene& scene, const std::string& componentType) + JSValue NodePropertyGetter(JSContext* ctx, JSValueConst thisVal, int argc, JSValueConst* argv, + int magic, JSValueConst* data) + { + (void)thisVal; + (void)argc; + (void)argv; + (void)magic; + + uint32_t nodeId = 0; + JS_ToUint32(ctx, &nodeId, data[0]); + + const char* propertyName = JS_ToCString(ctx, data[1]); + if (!propertyName) + { + return JS_UNDEFINED; + } + + Assets::Node* node = FindNodeById(nodeId); + if (!node) + { + JS_FreeCString(ctx, propertyName); + return JS_UNDEFINED; + } + + entt::meta_type metaType = entt::resolve(); + entt::meta_any value = Reflection::PropertyAccessor::GetPropertyValue(metaType, node, propertyName); + JS_FreeCString(ctx, propertyName); + + if (!value) + { + return JS_UNDEFINED; + } + + return Reflection::QuickJSTypeConverter::ToJSValue(ctx, value); + } + + JSValue NodePropertySetter(JSContext* ctx, JSValueConst thisVal, int argc, JSValueConst* argv, + int magic, JSValueConst* data) + { + (void)thisVal; + (void)magic; + + if (argc < 1) + { + return JS_UNDEFINED; + } + + uint32_t nodeId = 0; + JS_ToUint32(ctx, &nodeId, data[0]); + + const char* propertyName = JS_ToCString(ctx, data[1]); + if (!propertyName) + { + return JS_UNDEFINED; + } + + Assets::Node* node = FindNodeById(nodeId); + if (!node) + { + JS_FreeCString(ctx, propertyName); + return JS_UNDEFINED; + } + + entt::meta_type metaType = entt::resolve(); + auto dataEntry = metaType.data(entt::hashed_string::value(propertyName)); + if (!dataEntry) + { + JS_FreeCString(ctx, propertyName); + return JS_UNDEFINED; + } + + entt::meta_type valueType = dataEntry.type(); + entt::meta_any converted = Reflection::QuickJSTypeConverter::FromJSValue(ctx, argv[0], valueType); + JS_FreeCString(ctx, propertyName); + + if (!converted) + { + return JS_UNDEFINED; + } + + Reflection::PropertyAccessor::SetPropertyValue(metaType, node, dataEntry.name(), converted); + return JS_UNDEFINED; + } + + JSValue NodeMethodInvoker(JSContext* ctx, JSValueConst thisVal, int argc, JSValueConst* argv, + int magic, JSValueConst* data) { - for (const auto& node : scene.Nodes()) + (void)thisVal; + (void)magic; + + uint32_t nodeId = 0; + JS_ToUint32(ctx, &nodeId, data[0]); + + const char* functionName = JS_ToCString(ctx, data[1]); + if (!functionName) + { + return JS_UNDEFINED; + } + + Assets::Node* node = FindNodeById(nodeId); + if (!node) + { + JS_FreeCString(ctx, functionName); + return JS_UNDEFINED; + } + + entt::meta_type metaType = entt::resolve(); + auto function = metaType.func(entt::hashed_string::value(functionName)); + JS_FreeCString(ctx, functionName); + + if (!function) + { + return JS_UNDEFINED; + } + + if (function.arity() != static_cast(argc)) { - if (!node) + JS_ThrowTypeError(ctx, "Invalid argument count"); + return JS_EXCEPTION; + } + + entt::meta_any instanceAny = metaType.from_void(node); + entt::meta_any result; + if (argc == 0) + { + result = function.invoke(instanceAny); + } + else + { + std::vector args; + args.reserve(static_cast(argc)); + for (int idx = 0; idx < argc; ++idx) + { + entt::meta_type argType = function.arg(static_cast(idx)); + args.emplace_back(Reflection::QuickJSTypeConverter::FromJSValue(ctx, argv[idx], argType)); + } + + result = function.invoke(instanceAny, args.data(), args.size()); + } + + if (!result) + { + return JS_UNDEFINED; + } + + return Reflection::QuickJSTypeConverter::ToJSValue(ctx, result); + } + + JSValue NodeGetComponent(JSContext* ctx, JSValueConst thisVal, int argc, JSValueConst* argv, + int magic, JSValueConst* data) + { + (void)thisVal; + (void)magic; + + if (argc < 1) + { + return JS_UNDEFINED; + } + + uint32_t nodeId = 0; + JS_ToUint32(ctx, &nodeId, data[0]); + + const char* componentType = JS_ToCString(ctx, argv[0]); + if (!componentType) + { + return JS_UNDEFINED; + } + + Assets::Node* node = FindNodeById(nodeId); + if (!node) + { + JS_FreeCString(ctx, componentType); + return JS_UNDEFINED; + } + + Assets::Component* component = node->GetComponentByTypeName(componentType); + JSValue result = CreateComponentObject(ctx, component, nodeId, componentType); + JS_FreeCString(ctx, componentType); + return result; + } + + JSValue CreateNodeObject(JSContext* ctx, uint32_t nodeId) + { + Assets::Node* node = FindNodeById(nodeId); + if (!node) + { + return JS_UNDEFINED; + } + + entt::meta_type metaType = entt::resolve(); + JSValue obj = JS_NewObject(ctx); + + auto properties = Reflection::PropertyAccessor::GetProperties(metaType); + for (const auto& prop : properties) + { + if (!prop.meta.IsJSExposed()) { continue; } - for (const auto& component : node->GetComponents()) + JSValue data[2]; + data[0] = JS_NewUint32(ctx, nodeId); + data[1] = JS_NewString(ctx, prop.name.c_str()); + + JSValue getter = JS_NewCFunctionData(ctx, NodePropertyGetter, 0, 0, 2, data); + JSValue setter = JS_UNDEFINED; + if (!prop.meta.IsReadOnly()) { - if (component && component->GetTypeName() == componentType) - { - return static_cast(node->GetInstanceId()); - } + setter = JS_NewCFunctionData(ctx, NodePropertySetter, 1, 0, 2, data); } + + JSAtom propAtom = JS_NewAtom(ctx, prop.name.c_str()); + JS_DefinePropertyGetSet(ctx, obj, propAtom, getter, setter, + JS_PROP_ENUMERABLE | JS_PROP_CONFIGURABLE); + JS_FreeAtom(ctx, propAtom); } - return -1; + JSValue componentData[1]; + componentData[0] = JS_NewUint32(ctx, nodeId); + JS_SetPropertyStr(ctx, obj, "GetComponent", + JS_NewCFunctionData(ctx, NodeGetComponent, 1, 0, 1, componentData)); + + for (auto&& [id, func] : metaType.func()) + { + const char* funcName = func.name(); + if (!funcName) + { + continue; + } + + if (std::strcmp(funcName, "GetComponent") == 0) + { + continue; + } + + JSValue data[2]; + data[0] = JS_NewUint32(ctx, nodeId); + data[1] = JS_NewString(ctx, funcName); + + JSValue jsFunc = JS_NewCFunctionData(ctx, NodeMethodInvoker, func.arity(), 0, 2, data); + JS_SetPropertyStr(ctx, obj, funcName, jsFunc); + } + + return obj; + } + + JSValue SceneGetNodeById(JSContext* ctx, JSValueConst thisVal, int argc, JSValueConst* argv) + { + (void)thisVal; + + if (argc < 1) + { + return JS_UNDEFINED; + } + + uint32_t nodeId = 0; + JS_ToUint32(ctx, &nodeId, argv[0]); + return CreateNodeObject(ctx, nodeId); + } + + + void BindScenePrototype(JSContext* ctx) + { + JSValue proto = JS_GetClassProto(ctx, qjs::js_traits>::QJSClassId); + if (!JS_IsObject(proto)) + { + JS_FreeValue(ctx, proto); + return; + } + + JS_SetPropertyStr(ctx, proto, "GetNodeById", + JS_NewCFunction(ctx, SceneGetNodeById, "GetNodeById", 1)); + + JS_FreeValue(ctx, proto); } std::string BuildTypeScriptDefinitions() @@ -553,36 +838,24 @@ namespace result += "export interface Vec4 { x: number; y: number; z: number; w: number; }\n"; result += "export interface Quat { x: number; y: number; z: number; w: number; }\n\n"; - result += "export class NextEngine {\n"; - result += " GetTotalFrames(): number;\n"; - result += " GetTestNumber(): number;\n"; - result += " RegisterJSCallback(callback: (param: number) => void): void;\n"; - result += " GetScenePtr(): Scene;\n"; - result += "}\n\n"; - result += "export class NextComponent {\n"; result += " name_: string;\n"; result += " id_: number;\n"; result += "}\n\n"; - result += "export class Scene {\n"; - result += " GetIndicesCount(): number;\n"; - result += "}\n\n"; + result += Reflection::QuickJSReflectionBridge::GenerateTypeScriptDef("NextEngine"); + result += Reflection::QuickJSReflectionBridge::GenerateTypeScriptDef("Node"); + result += Reflection::QuickJSReflectionBridge::GenerateTypeScriptDef("Scene"); result += Reflection::QuickJSReflectionBridge::GenerateTypeScriptDef("RenderComponent"); result += Reflection::QuickJSReflectionBridge::GenerateTypeScriptDef("PhysicsComponent"); result += Reflection::QuickJSReflectionBridge::GenerateTypeScriptDef("SkinnedMeshComponent"); result += Reflection::QuickJSReflectionBridge::GenerateEnumTypeScriptDef("ENodeMobility"); - result += "\nexport function println(...args: any[]): void;\n"; - result += "export function GetEngine(): NextEngine;\n"; - result += "export function FindNodeIdWithComponent(componentType: string): number;\n"; - result += "export function GetNodeName(nodeId: number): string;\n"; - result += "export function GetNodeTranslation(nodeId: number): Vec3;\n"; - result += "export function GetComponent(nodeId: number, componentType: string): any;\n"; - result += "export function GetComponentProperty(nodeId: number, componentType: string, propertyName: string): any;\n"; - result += "export function SetComponentProperty(nodeId: number, componentType: string, propertyName: string, value: any): boolean;\n"; - result += "export function CallComponentFunction(nodeId: number, componentType: string, functionName: string, ...args: any[]): any;\n"; + result += "\nexport namespace Global {\n"; + result += " function spdlog(level: string, ...args: any[]): void;\n"; + result += " function GetEngine(): NextEngine;\n"; + result += "}\n"; return result; } @@ -646,8 +919,10 @@ void QuickJSEngine::ResetContextAndLoadScript() try { auto& module = context_->addModule("Engine"); - module.function<&Println>("println"); - module.function<&GetEngine>("GetEngine"); + auto globalNamespace = context_->newObject(); + globalNamespace.add<&Spdlog>("spdlog"); + globalNamespace.add<&GetEngine>("GetEngine"); + module.add("Global", std::move(globalNamespace)); module.class_("NextEngine") .fun<&NextEngine::GetTotalFrames>("GetTotalFrames") @@ -655,222 +930,15 @@ void QuickJSEngine::ResetContextAndLoadScript() .fun<&NextEngine::RegisterJSCallback>("RegisterJSCallback") .fun<&NextEngine::GetScenePtr>("GetScenePtr"); module.class_("Scene") - .fun<&Assets::Scene::GetIndicesCount>("GetIndicesCount"); + .fun<&Assets::Scene::GetIndicesCount>("GetIndicesCount") + .fun<&Assets::Scene::FindNodeIdWithComponent>("FindNodeIdWithComponent"); module.class_("NextComponent") .constructor<>() .fun<&NextComponent::name_>("name_") .fun<&NextComponent::id_>("id_"); qjs::Context* jsContext = context_.get(); - module.function("FindNodeIdWithComponent", [](const std::string& componentType) -> int32_t { - auto* engine = NextEngine::GetInstance(); - if (!engine) - { - return -1; - } - - auto* scene = engine->GetScenePtr(); - if (!scene) - { - return -1; - } - - return FindNodeIdWithComponent(*scene, componentType); - }); - - module.function("GetNodeName", [](uint32_t nodeId) -> std::string { - auto* engine = NextEngine::GetInstance(); - if (!engine) - { - return {}; - } - - auto* scene = engine->GetScenePtr(); - if (!scene) - { - return {}; - } - - auto* node = FindNodeById(*scene, nodeId); - if (!node) - { - return {}; - } - - return node->GetName(); - }); - - module.function("GetNodeTranslation", [jsContext](uint32_t nodeId) -> JSValue { - auto* engine = NextEngine::GetInstance(); - if (!engine) - { - return JS_UNDEFINED; - } - - auto* scene = engine->GetScenePtr(); - if (!scene) - { - return JS_UNDEFINED; - } - - auto* node = FindNodeById(*scene, nodeId); - if (!node) - { - return JS_UNDEFINED; - } - - const glm::vec3 translation = node->Translation(); - JSValue obj = JS_NewObject(jsContext->ctx); - JS_SetPropertyStr(jsContext->ctx, obj, "x", JS_NewFloat64(jsContext->ctx, translation.x)); - JS_SetPropertyStr(jsContext->ctx, obj, "y", JS_NewFloat64(jsContext->ctx, translation.y)); - JS_SetPropertyStr(jsContext->ctx, obj, "z", JS_NewFloat64(jsContext->ctx, translation.z)); - return obj; - }); - - module.function("GetComponent", [jsContext](uint32_t nodeId, const std::string& componentType) -> JSValue { - auto* engine = NextEngine::GetInstance(); - if (!engine) - { - return JS_UNDEFINED; - } - - auto* scene = engine->GetScenePtr(); - if (!scene) - { - return JS_UNDEFINED; - } - - Assets::Component* component = FindComponentByTypeName(*scene, nodeId, componentType); - if (!component) - { - return JS_UNDEFINED; - } - - return CreateComponentObject(jsContext->ctx, component, nodeId, componentType); - }); - - module.function("GetComponentProperty", [jsContext](uint32_t nodeId, const std::string& componentType, - const std::string& propertyName) -> JSValue { - auto* engine = NextEngine::GetInstance(); - if (!engine) - { - return JS_UNDEFINED; - } - - auto* scene = engine->GetScenePtr(); - if (!scene) - { - return JS_UNDEFINED; - } - - Assets::Component* component = FindComponentByTypeName(*scene, nodeId, componentType); - if (!component) - { - SPDLOG_WARN("Component '{}' not found on node {}", componentType, nodeId); - return JS_UNDEFINED; - } - - entt::meta_type metaType = component->GetMetaType(); - entt::meta_any value = Reflection::PropertyAccessor::GetPropertyValue(metaType, component, propertyName); - if (!value) - { - SPDLOG_WARN("Property '{}' not found on component '{}'", propertyName, componentType); - return JS_UNDEFINED; - } - - return Reflection::QuickJSTypeConverter::ToJSValue(jsContext->ctx, value); - }); - - module.function("SetComponentProperty", [jsContext](uint32_t nodeId, const std::string& componentType, - const std::string& propertyName, qjs::Value value) -> bool { - auto* engine = NextEngine::GetInstance(); - if (!engine) - { - return false; - } - - auto* scene = engine->GetScenePtr(); - if (!scene) - { - return false; - } - - Assets::Component* component = FindComponentByTypeName(*scene, nodeId, componentType); - if (!component) - { - SPDLOG_WARN("Component '{}' not found on node {}", componentType, nodeId); - return false; - } - - entt::meta_type metaType = component->GetMetaType(); - auto data = metaType.data(entt::hashed_string::value(propertyName.c_str())); - if (!data) - { - SPDLOG_WARN("Property '{}' not found on component '{}'", propertyName, componentType); - return false; - } - - entt::meta_type valueType = data.type(); - if (!Reflection::QuickJSTypeConverter::IsTypeSupported(valueType)) - { - SPDLOG_WARN("Property '{}' on component '{}' is not supported for JS conversion", propertyName, componentType); - return false; - } - entt::meta_any converted = Reflection::QuickJSTypeConverter::FromJSValue(jsContext->ctx, value.v, valueType); - if (!converted) - { - SPDLOG_WARN("Failed to convert value for property '{}' on component '{}'", propertyName, componentType); - return false; - } - - return Reflection::PropertyAccessor::SetPropertyValue(metaType, component, propertyName, converted); - }); - - module.function("CallComponentFunction", [jsContext](uint32_t nodeId, const std::string& componentType, - const std::string& functionName, qjs::rest args) -> JSValue { - auto* engine = NextEngine::GetInstance(); - if (!engine) - { - return JS_UNDEFINED; - } - - auto* scene = engine->GetScenePtr(); - if (!scene) - { - return JS_UNDEFINED; - } - - Assets::Component* component = FindComponentByTypeName(*scene, nodeId, componentType); - if (!component) - { - SPDLOG_WARN("Component '{}' not found on node {}", componentType, nodeId); - return JS_UNDEFINED; - } - - entt::meta_type metaType = component->GetMetaType(); - auto function = metaType.func(entt::hashed_string::value(functionName.c_str())); - if (!function) - { - SPDLOG_WARN("Function '{}' not found on component '{}'", functionName, componentType); - return JS_UNDEFINED; - } - - if (function.arity() != 0 || !args.empty()) - { - SPDLOG_WARN("Function '{}' on component '{}' expects {} args, but {} provided", functionName, - componentType, function.arity(), args.size()); - return JS_UNDEFINED; - } - - entt::meta_any instanceAny = metaType.from_void(component); - entt::meta_any result = function.invoke(instanceAny); - if (!result) - { - return JS_UNDEFINED; - } - - return Reflection::QuickJSTypeConverter::ToJSValue(jsContext->ctx, result); - }); + BindScenePrototype(jsContext->ctx); std::vector scriptBuffer; if (Utilities::Package::FPackageFileSystem::GetInstance().LoadFile("assets/scripts/test.js", scriptBuffer)) diff --git a/src/Runtime/Reflection/QuickJSReflectionBridge.h b/src/Runtime/Reflection/QuickJSReflectionBridge.h index f54b2d66..6b78e810 100644 --- a/src/Runtime/Reflection/QuickJSReflectionBridge.h +++ b/src/Runtime/Reflection/QuickJSReflectionBridge.h @@ -27,6 +27,11 @@ namespace Reflection return "void"; } + if (metaType.is_pointer()) + { + metaType = metaType.remove_pointer(); + } + const PropertyType type = PropertyAccessor::DeducePropertyType(metaType); if (type == PropertyType::Array) { @@ -41,6 +46,15 @@ namespace Reflection return "string"; } + if (type == PropertyType::Unknown) + { + const char* name = metaType.name(); + if (name && *name) + { + return name; + } + } + return QuickJSTypeConverter::ToTypeScriptType(type); } diff --git a/src/Runtime/Reflection/ReflectionRegistry.cpp b/src/Runtime/Reflection/ReflectionRegistry.cpp index d8d745e2..a954ee52 100644 --- a/src/Runtime/Reflection/ReflectionRegistry.cpp +++ b/src/Runtime/Reflection/ReflectionRegistry.cpp @@ -3,6 +3,9 @@ #include "Runtime/Components/RenderComponent.h" #include "Runtime/Components/PhysicsComponent.h" #include "Runtime/Components/SkinnedMeshComponent.h" +#include "Runtime/Engine.hpp" +#include "Assets/Scene.hpp" +#include "Assets/Node.h" namespace Reflection { @@ -25,6 +28,10 @@ namespace Reflection Runtime::RenderComponent::RegisterReflection(); Runtime::PhysicsComponent::RegisterReflection(); Runtime::SkinnedMeshComponent::RegisterReflection(); + Assets::Node::RegisterReflection(); + + NextEngine::RegisterReflection(); + Assets::Scene::RegisterReflection(); sReflectionInitialized = true; } From 4e4355ad6acb58d28837bbb90cb8e980c9e0f7e2 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Mon, 2 Feb 2026 01:10:26 +0800 Subject: [PATCH 26/53] fix on msvc --- src/Runtime/QuickJSEngine.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Runtime/QuickJSEngine.cpp b/src/Runtime/QuickJSEngine.cpp index 30d61a12..4a8727c8 100644 --- a/src/Runtime/QuickJSEngine.cpp +++ b/src/Runtime/QuickJSEngine.cpp @@ -239,17 +239,6 @@ namespace return NextEngine::GetInstance(); } - Assets::Component* FindComponentByTypeName(uint32_t nodeId, const std::string& componentType) - { - auto* node = FindNodeById(nodeId); - if (!node) - { - return nullptr; - } - - return node->GetComponentByTypeName(componentType); - } - Assets::Node* FindNodeById(uint32_t nodeId) { auto* engine = NextEngine::GetInstance(); @@ -267,6 +256,17 @@ namespace const auto node = scene->GetNodeSharedByInstanceId(nodeId); return node ? node.get() : nullptr; } + + Assets::Component* FindComponentByTypeName(uint32_t nodeId, const std::string& componentType) + { + auto* node = FindNodeById(nodeId); + if (!node) + { + return nullptr; + } + + return node->GetComponentByTypeName(componentType); + } JSValue ComponentPropertyGetter(JSContext* ctx, JSValueConst thisVal, int argc, JSValueConst* argv, int magic, JSValueConst* data) From e8a72fc327cf1ead3e3d185855036d810901d5ac Mon Sep 17 00:00:00 2001 From: gameKnife Date: Mon, 2 Feb 2026 10:42:08 +0800 Subject: [PATCH 27/53] refactor(runtime): move config files to Runtime/Config/ - Move UserSettings.hpp to Runtime/Config/ - Move ShowFlags.hpp to Runtime/Config/ - Update all include paths --- CLAUDE.md | 155 ++++++++++++++++++---- assets/typescript/Engine.d.ts | 4 +- src/Runtime/{ => Config}/ShowFlags.hpp | 0 src/Runtime/{ => Config}/UserSettings.hpp | 0 src/Runtime/Engine.cpp | 2 +- src/Runtime/Engine.hpp | 4 +- src/Runtime/UserInterface.cpp | 2 +- src/Utilities/ImGui.hpp | 2 +- 8 files changed, 139 insertions(+), 30 deletions(-) rename src/Runtime/{ => Config}/ShowFlags.hpp (100%) rename src/Runtime/{ => Config}/UserSettings.hpp (100%) diff --git a/CLAUDE.md b/CLAUDE.md index 854fa5b6..d8e13f71 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,7 +11,19 @@ Always communicate with the user in Chinese (中文). gkNextRenderer is a cross-platform 3D game engine built with modern C++20 and Vulkan, featuring hardware/software ray tracing, real-time global illumination, and GPU-driven rendering. Target codebase size is <50k LOC (currently ~15k). -**Subprojects:** gkNextRenderer (main renderer), gkNextEditor (ImGui editor), MagicaLego (voxel prototype), gkNextBenchmark, Packager +**Key Technologies:** +- C++20/C11, Vulkan API, Slang shader language +- ECS architecture (entt library) +- QuickJS TypeScript scripting with hot reload +- Multi-platform: Windows x86_64 / Linux x86_64 / macOS arm64 / Android arm64 / iOS arm64 + +**Subprojects:** +- gkNextRenderer (main renderer) +- gkNextEditor (ImGui editor with node-based material editor) +- MagicaLego (voxel prototype) +- gkNextBenchmark +- gkNextVisualTest (automated visual testing) +- Packager (asset packaging to `.pkg`) ## Build Commands @@ -25,22 +37,33 @@ gkNextRenderer is a cross-platform 3D game engine built with modern C++20 and Vu - Linux: `./build.sh --preset default-linux` - Android: `./build.bat --android` (Windows) or `./build.sh --android` - Clean rebuild: add `--clean` +- List presets: `cmake --list-presets=configure` -**Presets:** `minimal-*` (fewest deps), `default-*` (standard), `full-*` (all features incl. DLSS/OIDN) +**Presets:** +- `minimal-*`: Fewest dependencies (KTX2 only) +- `default-*`: Standard features (KTX2 + Physics + Audio) +- `full-*`: All features including DLSS/OIDN -**List presets:** `cmake --list-presets=configure` +**Optional Features (via build flags or CMake args):** +- `--avif`: AVIF texture loading and screenshots +- `--dlss`: NVIDIA DLSS support (Windows only, downloads Streamline SDK) +- `--oidn`: Intel OpenImageDenoise support (not on macOS, auto-downloads runtime) +- Example: `./build.bat --preset default-windows -- -DENABLE_AVIF=ON` + +**Build output:** `out/build//bin/` ## Run Commands - Windows: `./run.bat --preset ` - macOS/Linux: `./run.sh --preset ` - Specific target: `./run.sh --preset default-macos-arm64 --target gkNextEditor` +- Android: `./run.sh --preset android` **Runtime success indicator:** Log shows `uploaded scene [...] to gpu` ## Testing -**CRITICAL: Tests must be run from the bin directory.** +**CRITICAL: Tests must be run from the bin directory (CWD must be `bin`).** ```bash # Unit tests (Catch2) @@ -52,25 +75,47 @@ cd out/build//bin && ./gkNextUnitTests "[Unit][RenderComponent]" # List available tests/tags cd out/build//bin && ./gkNextUnitTests --list-tests +cd out/build//bin && ./gkNextUnitTests --list-tags # Visual tests (renders scenes, generates screenshots + report) cd out/build//bin && ./gkNextVisualTest ``` +**Visual Test Config:** `assets/configs/visual_test.json` defines scenes, frame counts, output directory. + +## Linting + +**Static Analysis:** +- Config: `.clang-tidy` (naming + include cleaner) +- Generate compile database: `./build.sh --preset -- -DCMAKE_EXPORT_COMPILE_COMMANDS=ON` +- Run clang-tidy: `python3 tools/clang-tools/run-clang-tidy.py -p out/build/` +- Run naming checks: `BUILD_DIR=out/build/ tools/clang-tools/run-naming.sh` + ## Code Style (Summary) -- **First include:** `Common/CoreMinimal.hpp` (includes std, fmt, spdlog, platform detection) -- **Platform abstraction:** Use `PlatformCommon.h`, not direct platform headers; use `#if ANDROID` not `#ifdef` -- **Naming (enforced by .clang-tidy):** - - Types/functions: PascalCase - - Variables/parameters: camelCase - - Private members: camelCase_ (trailing underscore) - - Global variables: PascalCase (e.g., `GOption`) - - Macros: UPPER_CASE -- **Braces:** Allman style (opening brace on new line) -- **Indentation:** 4 spaces, no tabs -- **Shaders:** Slang (`.vert.slang`, `.frag.slang`, `.rgen.slang`); uses ray query, not ray pipeline -- **Vulkan:** Always check VkResult with `VK_CHECK_RESULT`; RAII for resource cleanup +**Naming (enforced by .clang-tidy):** +- Types/functions: PascalCase (e.g., `class RenderContext`, `void RenderFrame()`) +- Variables/parameters: camelCase (e.g., `int frameCounter`) +- Private members: camelCase_ (trailing underscore, e.g., `VkDevice device_`) +- Global variables: PascalCase (e.g., `GOption`) +- Constants/constexpr: camelCase (e.g., `constexpr int maxFrames`) +- Macros: UPPER_CASE (e.g., `VK_CHECK_RESULT`) + +**Formatting:** +- Indentation: 4 spaces, no tabs +- Braces: Allman style (opening brace on new line) +- First include: `Common/CoreMinimal.hpp` (includes std, fmt, spdlog, platform detection) +- Platform abstraction: Use `PlatformCommon.h`, not direct platform headers; use `#if ANDROID` not `#ifdef` + +**Shaders:** +- Use Slang (`.vert.slang`, `.frag.slang`, `.rgen.slang`, `.comp.slang`) +- Uses ray query API, not ray pipeline +- Avoid hard-coded constants; use uniforms/push constants + +**Vulkan:** +- Always check VkResult with `VK_CHECK_RESULT` +- RAII for resource cleanup (pair allocations with destructors) +- Prefer `std::unique_ptr`/`std::shared_ptr` over raw owning pointers ## Architecture Overview @@ -79,23 +124,87 @@ src/ ├── Runtime/ # Core engine runtime │ ├── Platform/ # Platform abstraction (via PlatformCommon.h) │ ├── Components/ # ECS components (entt) -│ ├── Reflection/ # Property reflection (entt::meta) -│ └── Command/ # Command history system +│ ├── Reflection/ # Property reflection (entt::meta) for editor + JS bindings +│ └── Command/ # Command history system (undo/redo) ├── Vulkan/ # Vulkan backend │ └── RayTracing/ # Hardware ray tracing -├── Rendering/ # Render pipelines (PathTracing, SoftwareTracing, SoftwareModern) +├── Rendering/ # Render pipelines +│ ├── PathTracing/ # Full path tracing +│ ├── SoftwareTracing/ # Software ray tracing +│ ├── SoftwareModern/ # Modern rasterization + software GI +│ └── PipelineCommon/ # Shared pipeline utilities ├── Editor/ # ImGui editor +│ ├── Panels/ # Property panel (auto-generated from reflection) +│ ├── Nodes/ # Node-based material editor +│ └── Commands/ # Editor command system (undo/redo) +├── Assets/ # Asset loading (glTF, textures, etc.) ├── Tests/ # Catch2 unit tests -└── Application/ # App entry points (gkNextRenderer, Packager, etc.) +├── Application/ # App entry points +│ ├── gkNextRenderer/ +│ ├── gkNextEditor/ +│ ├── MagicaLego/ +│ ├── gkNextBenchmark/ +│ ├── gkNextVisualTest/ +│ └── Packager/ +└── ThirdParty/ # Third-party code (DO NOT MODIFY) assets/ -├── shaders/ # Slang shaders +├── shaders/ # Slang shaders (.slang) ├── configs/ # Runtime config (visual_test.json) -└── models/ # glTF scenes +├── models/ # glTF scenes +└── typescript/ # TypeScript definitions for QuickJS scripting ``` +## Key Architectural Patterns + +**Reflection System (entt::meta):** +- Provides auto-generated editor UI via PropertyPanel +- Exposes component properties to QuickJS JavaScript bindings +- Supports undo/redo for property modifications +- See `AGENT_GUIDE/ReflectionSystem.md` for detailed documentation +- Register components using `REFLECT_COMPONENT` macro in component's .cpp file +- TypeScript definitions in `assets/typescript/Engine.d.ts` mirror reflected properties + +**QuickJS Scripting:** +- Hot reload support (modify `.js` files at runtime) +- Components reflected via `entt::meta` are auto-exposed to JavaScript +- Global namespace: `Global.GetEngine()`, `Global.spdlog()` +- Scene API: `Scene.FindNodeIdWithComponent()`, `Scene.GetNodeById()` + +**Component System:** +- ECS via entt library +- All components inherit from `Assets::Component` +- Must implement `GetMetaType()` for reflection support +- Common components: RenderComponent, PhysicsComponent, SkinnedMeshComponent + +**Resource Management:** +- Vulkan objects use RAII (destroyed in destructors) +- Always pair allocations with deterministic cleanup +- No silent failures in init/resource loading paths + +## Repository Hygiene + +- DO NOT modify third-party code in `ThirdParty/` or `external/` +- DO NOT commit build artifacts (`out/`, object files) +- DO NOT hard-code absolute paths or add secrets/keys +- When adding dependencies, update `vcpkg.json` + +## Verification After Changes + +1. **Build:** Run build script for target preset +2. **Run:** Verify application starts and logs `uploaded scene [...] to gpu` +3. **Test:** Run unit tests if touching core systems +4. **Visual:** For rendering changes, validate visually in gkNextRenderer or run gkNextVisualTest + ## Key References - **AGENTS.md** - Complete coding standards, build commands, and agent guidelines -- **AGENT_GUIDE/** - Layered documentation (core-patterns, contextual-rules, coding-standards, quick-commands) +- **AGENT_GUIDE/** - Layered documentation: + - `core-patterns.md` - Essential patterns and commands (Layer 1) + - `contextual-rules.md` - Context-specific rules (Layer 2) + - `coding-standards.md` - Detailed code review guidelines + - `quick-commands.md` - Command reference (Layer 3) + - `ReflectionSystem.md` - Reflection system documentation + - `MagicaLego.md` - MagicaLego subproject notes - **README.en.md** - Project overview and quick start +- **.clang-tidy** - Naming conventions (source of truth) diff --git a/assets/typescript/Engine.d.ts b/assets/typescript/Engine.d.ts index e2ae493d..850366c4 100644 --- a/assets/typescript/Engine.d.ts +++ b/assets/typescript/Engine.d.ts @@ -12,7 +12,7 @@ export class NextEngine { GetTotalFrames(): number; GetTestNumber(): number; RegisterJSCallback(arg0: any): void; - GetScenePtr(): Scene; + GetScenePtr(): any; } export class Node { readonly Name: string; @@ -27,7 +27,7 @@ export class Node { export class Scene { GetIndicesCount(): number; FindNodeIdWithComponent(arg0: string): number; - GetNodeById(arg0: number): Node; + GetNodeById(arg0: number): any; } export class RenderComponent { Visible: boolean; diff --git a/src/Runtime/ShowFlags.hpp b/src/Runtime/Config/ShowFlags.hpp similarity index 100% rename from src/Runtime/ShowFlags.hpp rename to src/Runtime/Config/ShowFlags.hpp diff --git a/src/Runtime/UserSettings.hpp b/src/Runtime/Config/UserSettings.hpp similarity index 100% rename from src/Runtime/UserSettings.hpp rename to src/Runtime/Config/UserSettings.hpp diff --git a/src/Runtime/Engine.cpp b/src/Runtime/Engine.cpp index 13942d99..e8607042 100644 --- a/src/Runtime/Engine.cpp +++ b/src/Runtime/Engine.cpp @@ -7,7 +7,7 @@ #include "Runtime/Command/DeleteNodeCommand.hpp" #include "ScreenShot.hpp" #include "UserInterface.hpp" -#include "UserSettings.hpp" +#include "Runtime/Config/UserSettings.hpp" #include "Vulkan/Device.hpp" #include "Vulkan/Instance.hpp" #include "Vulkan/SwapChain.hpp" diff --git a/src/Runtime/Engine.hpp b/src/Runtime/Engine.hpp index c9aa2a3b..0a31395b 100644 --- a/src/Runtime/Engine.hpp +++ b/src/Runtime/Engine.hpp @@ -7,8 +7,8 @@ #include "Rendering/VulkanBaseRenderer.hpp" #include "Runtime/Command/CommandHistory.hpp" #include "SceneList.hpp" -#include "ShowFlags.hpp" -#include "UserSettings.hpp" +#include "Runtime/Config/ShowFlags.hpp" +#include "Runtime/Config/UserSettings.hpp" #include "Utilities/FileHelper.hpp" #include "Vulkan/FrameBuffer.hpp" #include "Vulkan/Window.hpp" diff --git a/src/Runtime/UserInterface.cpp b/src/Runtime/UserInterface.cpp index e2373aa8..a0515e52 100644 --- a/src/Runtime/UserInterface.cpp +++ b/src/Runtime/UserInterface.cpp @@ -2,7 +2,7 @@ #include "Engine.hpp" #include "SceneList.hpp" -#include "UserSettings.hpp" +#include "Runtime/Config/UserSettings.hpp" #include "Utilities/Exception.hpp" #include "Vulkan/DescriptorPool.hpp" #include "Vulkan/Device.hpp" diff --git a/src/Utilities/ImGui.hpp b/src/Utilities/ImGui.hpp index 28f4d717..0d4b3d9b 100644 --- a/src/Utilities/ImGui.hpp +++ b/src/Utilities/ImGui.hpp @@ -3,7 +3,7 @@ #include #include #include -#include "Runtime/ShowFlags.hpp" +#include "Runtime/Config/ShowFlags.hpp" namespace Utilities { From d57c460db383161203e01328fd45180c1fdc2696 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Mon, 2 Feb 2026 10:43:11 +0800 Subject: [PATCH 28/53] refactor(runtime): move TaskCoordinator to Runtime/Utilities/ - Move TaskCoordinator.cpp/hpp to Runtime/Utilities/ - Update all include paths --- src/Assets/CPUAccelerationStructure.cpp | 2 +- src/Assets/Texture.cpp | 2 +- src/Runtime/Engine.cpp | 2 +- src/Runtime/ScreenShot.cpp | 2 +- src/Runtime/UserInterface.cpp | 2 +- src/Runtime/{ => Utilities}/TaskCoordinator.cpp | 2 +- src/Runtime/{ => Utilities}/TaskCoordinator.hpp | 0 7 files changed, 6 insertions(+), 6 deletions(-) rename src/Runtime/{ => Utilities}/TaskCoordinator.cpp (98%) rename src/Runtime/{ => Utilities}/TaskCoordinator.hpp (100%) diff --git a/src/Assets/CPUAccelerationStructure.cpp b/src/Assets/CPUAccelerationStructure.cpp index 40b8ee00..61bc705f 100644 --- a/src/Assets/CPUAccelerationStructure.cpp +++ b/src/Assets/CPUAccelerationStructure.cpp @@ -1,5 +1,5 @@ #include "CPUAccelerationStructure.h" -#include "Runtime/TaskCoordinator.hpp" +#include "Runtime/Utilities/TaskCoordinator.hpp" #include "Vulkan/DeviceMemory.hpp" #include "Assets/Node.h" #include "Runtime/Components/RenderComponent.h" diff --git a/src/Assets/Texture.cpp b/src/Assets/Texture.cpp index 3c796fb6..99fb63d9 100644 --- a/src/Assets/Texture.cpp +++ b/src/Assets/Texture.cpp @@ -4,7 +4,7 @@ #include "Common/CoreMinimal.hpp" #include "Options.hpp" -#include "Runtime/TaskCoordinator.hpp" +#include "Runtime/Utilities/TaskCoordinator.hpp" #include "TextureImage.hpp" #include "Runtime/Engine.hpp" #include "Utilities/FileHelper.hpp" diff --git a/src/Runtime/Engine.cpp b/src/Runtime/Engine.cpp index e8607042..7480ac8b 100644 --- a/src/Runtime/Engine.cpp +++ b/src/Runtime/Engine.cpp @@ -28,7 +28,7 @@ #include "NextAudio.h" #include "Options.hpp" #include "Rendering/RayTraceBaseRenderer.hpp" -#include "TaskCoordinator.hpp" +#include "Runtime/Utilities/TaskCoordinator.hpp" #include "Utilities/Localization.hpp" #define _USE_MATH_DEFINES diff --git a/src/Runtime/ScreenShot.cpp b/src/Runtime/ScreenShot.cpp index d7461250..96c9380c 100644 --- a/src/Runtime/ScreenShot.cpp +++ b/src/Runtime/ScreenShot.cpp @@ -7,7 +7,7 @@ #define _USE_MATH_DEFINES #include -#include "TaskCoordinator.hpp" +#include "Runtime/Utilities/TaskCoordinator.hpp" #include "Vulkan/SwapChain.hpp" #if WITH_AVIF diff --git a/src/Runtime/UserInterface.cpp b/src/Runtime/UserInterface.cpp index a0515e52..86ee799f 100644 --- a/src/Runtime/UserInterface.cpp +++ b/src/Runtime/UserInterface.cpp @@ -32,7 +32,7 @@ #include "Assets/TextureImage.hpp" #include "Options.hpp" #include "Rendering/VulkanBaseRenderer.hpp" -#include "TaskCoordinator.hpp" +#include "Runtime/Utilities/TaskCoordinator.hpp" #include "ThirdParty/fontawesome/IconsFontAwesome6.h" #include "Utilities/FileHelper.hpp" #include "Utilities/ImGui.hpp" diff --git a/src/Runtime/TaskCoordinator.cpp b/src/Runtime/Utilities/TaskCoordinator.cpp similarity index 98% rename from src/Runtime/TaskCoordinator.cpp rename to src/Runtime/Utilities/TaskCoordinator.cpp index 7702c5f2..ab74da2c 100644 --- a/src/Runtime/TaskCoordinator.cpp +++ b/src/Runtime/Utilities/TaskCoordinator.cpp @@ -1,4 +1,4 @@ -#include "TaskCoordinator.hpp" +#include "Runtime/Utilities/TaskCoordinator.hpp" #include diff --git a/src/Runtime/TaskCoordinator.hpp b/src/Runtime/Utilities/TaskCoordinator.hpp similarity index 100% rename from src/Runtime/TaskCoordinator.hpp rename to src/Runtime/Utilities/TaskCoordinator.hpp From b37c0331e25e8541ebd4e05b0ebcc9b55a2ed9d5 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Mon, 2 Feb 2026 10:44:56 +0800 Subject: [PATCH 29/53] refactor(runtime): move scene files to Runtime/Scene/ - Move SceneList.cpp/hpp to Runtime/Scene/ - Move GltfTestRunner.cpp/hpp to Runtime/Scene/ - Update all include paths --- src/DesktopMain.cpp | 2 +- src/Runtime/Engine.hpp | 2 +- src/Runtime/{ => Scene}/GltfTestRunner.cpp | 4 ++-- src/Runtime/{ => Scene}/GltfTestRunner.hpp | 0 src/Runtime/{ => Scene}/SceneList.cpp | 6 +++--- src/Runtime/{ => Scene}/SceneList.hpp | 0 src/Runtime/UserInterface.cpp | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) rename src/Runtime/{ => Scene}/GltfTestRunner.cpp (98%) rename src/Runtime/{ => Scene}/GltfTestRunner.hpp (100%) rename src/Runtime/{ => Scene}/SceneList.cpp (99%) rename src/Runtime/{ => Scene}/SceneList.hpp (100%) diff --git a/src/DesktopMain.cpp b/src/DesktopMain.cpp index 86af8850..d7e0ac89 100644 --- a/src/DesktopMain.cpp +++ b/src/DesktopMain.cpp @@ -1,7 +1,7 @@ #include "Utilities/Exception.hpp" #include "Options.hpp" #include "Runtime/Engine.hpp" -#include "Runtime/GltfTestRunner.hpp" +#include "Runtime/Scene/GltfTestRunner.hpp" #include "Runtime/Platform/PlatformCommon.h" #if WIN32 diff --git a/src/Runtime/Engine.hpp b/src/Runtime/Engine.hpp index 0a31395b..5d6cb2db 100644 --- a/src/Runtime/Engine.hpp +++ b/src/Runtime/Engine.hpp @@ -6,7 +6,7 @@ #include "Options.hpp" #include "Rendering/VulkanBaseRenderer.hpp" #include "Runtime/Command/CommandHistory.hpp" -#include "SceneList.hpp" +#include "Runtime/Scene/SceneList.hpp" #include "Runtime/Config/ShowFlags.hpp" #include "Runtime/Config/UserSettings.hpp" #include "Utilities/FileHelper.hpp" diff --git a/src/Runtime/GltfTestRunner.cpp b/src/Runtime/Scene/GltfTestRunner.cpp similarity index 98% rename from src/Runtime/GltfTestRunner.cpp rename to src/Runtime/Scene/GltfTestRunner.cpp index d48c1d95..1d998626 100644 --- a/src/Runtime/GltfTestRunner.cpp +++ b/src/Runtime/Scene/GltfTestRunner.cpp @@ -1,5 +1,5 @@ -#include "GltfTestRunner.hpp" -#include "Engine.hpp" +#include "Runtime/Scene/GltfTestRunner.hpp" +#include "Runtime/Engine.hpp" #include "Options.hpp" #include "Utilities/FileHelper.hpp" #include diff --git a/src/Runtime/GltfTestRunner.hpp b/src/Runtime/Scene/GltfTestRunner.hpp similarity index 100% rename from src/Runtime/GltfTestRunner.hpp rename to src/Runtime/Scene/GltfTestRunner.hpp diff --git a/src/Runtime/SceneList.cpp b/src/Runtime/Scene/SceneList.cpp similarity index 99% rename from src/Runtime/SceneList.cpp rename to src/Runtime/Scene/SceneList.cpp index f80c4224..f3597f19 100644 --- a/src/Runtime/SceneList.cpp +++ b/src/Runtime/Scene/SceneList.cpp @@ -1,11 +1,11 @@ -#include "SceneList.hpp" +#include "Runtime/Scene/SceneList.hpp" #include "Common/CoreMinimal.hpp" #include "Utilities/FileHelper.hpp" #include "Assets/Material.hpp" #include "Assets/Model.hpp" -#include "Engine.hpp" -#include "NextPhysics.h" +#include "Runtime/Engine.hpp" +#include "Runtime/NextPhysics.h" #include #include diff --git a/src/Runtime/SceneList.hpp b/src/Runtime/Scene/SceneList.hpp similarity index 100% rename from src/Runtime/SceneList.hpp rename to src/Runtime/Scene/SceneList.hpp diff --git a/src/Runtime/UserInterface.cpp b/src/Runtime/UserInterface.cpp index 86ee799f..7ba8795e 100644 --- a/src/Runtime/UserInterface.cpp +++ b/src/Runtime/UserInterface.cpp @@ -1,7 +1,7 @@ #include "UserInterface.hpp" #include "Engine.hpp" -#include "SceneList.hpp" +#include "Runtime/Scene/SceneList.hpp" #include "Runtime/Config/UserSettings.hpp" #include "Utilities/Exception.hpp" #include "Vulkan/DescriptorPool.hpp" From 3241fe08796766fa881ae15a6fd6e22abf8664ff Mon Sep 17 00:00:00 2001 From: gameKnife Date: Mon, 2 Feb 2026 10:51:45 +0800 Subject: [PATCH 30/53] refactor(runtime): move camera files to Runtime/Camera/ - Move ModelViewController.cpp/hpp to Runtime/Camera/ - Move FocusAnimation.cpp/hpp to Runtime/Camera/ - Move NextEngineHelper.cpp/h to Runtime/Camera/ - Update all include paths --- src/Application/MagicaLego/MagicaLegoGameInstance.hpp | 2 +- .../gkNextMotionBenchmark/gkNextMotionBenchmark.hpp | 2 +- src/Application/gkNextRenderer/gkNextRenderer.cpp | 2 +- src/Application/gkNextRenderer/gkNextRenderer.hpp | 2 +- src/Assets/Scene.cpp | 2 +- src/Editor/EditorMain.cpp | 2 +- src/Editor/EditorMain.h | 2 +- src/Editor/Panels/ViewportOverlay.cpp | 2 +- src/Runtime/{ => Camera}/FocusAnimation.cpp | 2 +- src/Runtime/{ => Camera}/FocusAnimation.hpp | 0 src/Runtime/{ => Camera}/ModelViewController.cpp | 2 +- src/Runtime/{ => Camera}/ModelViewController.hpp | 2 +- src/Runtime/{ => Camera}/NextEngineHelper.cpp | 2 +- src/Runtime/{ => Camera}/NextEngineHelper.h | 0 src/Runtime/Components/SkinnedMeshComponent.cpp | 2 +- 15 files changed, 13 insertions(+), 13 deletions(-) rename src/Runtime/{ => Camera}/FocusAnimation.cpp (94%) rename src/Runtime/{ => Camera}/FocusAnimation.hpp (100%) rename src/Runtime/{ => Camera}/ModelViewController.cpp (99%) rename src/Runtime/{ => Camera}/ModelViewController.hpp (98%) rename src/Runtime/{ => Camera}/NextEngineHelper.cpp (99%) rename src/Runtime/{ => Camera}/NextEngineHelper.h (100%) diff --git a/src/Application/MagicaLego/MagicaLegoGameInstance.hpp b/src/Application/MagicaLego/MagicaLegoGameInstance.hpp index c98761e3..af098b4c 100644 --- a/src/Application/MagicaLego/MagicaLegoGameInstance.hpp +++ b/src/Application/MagicaLego/MagicaLegoGameInstance.hpp @@ -2,7 +2,7 @@ // ReSharper disable once CppUnusedIncludeDirective #include "Common/CoreMinimal.hpp" #include "Runtime/Engine.hpp" -#include "Runtime/NextEngineHelper.h" +#include "Runtime/Camera/NextEngineHelper.h" #include "Utilities/FileHelper.hpp" #define MAGICALEGO_SAVE_VERSION 1 diff --git a/src/Application/gkNextBenchmark/gkNextMotionBenchmark/gkNextMotionBenchmark.hpp b/src/Application/gkNextBenchmark/gkNextMotionBenchmark/gkNextMotionBenchmark.hpp index fb76e841..c6bfc0af 100644 --- a/src/Application/gkNextBenchmark/gkNextMotionBenchmark/gkNextMotionBenchmark.hpp +++ b/src/Application/gkNextBenchmark/gkNextMotionBenchmark/gkNextMotionBenchmark.hpp @@ -1,7 +1,7 @@ #pragma once #include "Common/CoreMinimal.hpp" #include "Runtime/Engine.hpp" -#include "Runtime/ModelViewController.hpp" +#include "Runtime/Camera/ModelViewController.hpp" class BenchMarker; diff --git a/src/Application/gkNextRenderer/gkNextRenderer.cpp b/src/Application/gkNextRenderer/gkNextRenderer.cpp index 3768e5bd..24362e87 100644 --- a/src/Application/gkNextRenderer/gkNextRenderer.cpp +++ b/src/Application/gkNextRenderer/gkNextRenderer.cpp @@ -10,7 +10,7 @@ #include "Runtime/Components/RenderComponent.h" #include "Runtime/Components/PhysicsComponent.h" #include "Runtime/Engine.hpp" -#include "Runtime/NextEngineHelper.h" +#include "Runtime/Camera/NextEngineHelper.h" #include "Utilities/Localization.hpp" #include "Utilities/ImGui.hpp" #include "Runtime/Platform/PlatformCommon.h" diff --git a/src/Application/gkNextRenderer/gkNextRenderer.hpp b/src/Application/gkNextRenderer/gkNextRenderer.hpp index c065a652..13504e22 100644 --- a/src/Application/gkNextRenderer/gkNextRenderer.hpp +++ b/src/Application/gkNextRenderer/gkNextRenderer.hpp @@ -2,7 +2,7 @@ #include "Common/CoreMinimal.hpp" #include "Runtime/Engine.hpp" #include "Runtime/GizmoController.hpp" -#include "Runtime/ModelViewController.hpp" +#include "Runtime/Camera/ModelViewController.hpp" class NextRendererGameInstance : public NextGameInstanceBase { diff --git a/src/Assets/Scene.cpp b/src/Assets/Scene.cpp index 3fc5cad6..d3778ab4 100644 --- a/src/Assets/Scene.cpp +++ b/src/Assets/Scene.cpp @@ -18,7 +18,7 @@ #include "Runtime/Components/RenderComponent.h" #include "Runtime/Components/SkinnedMeshComponent.h" #include "Runtime/Engine.hpp" -#include "Runtime/NextEngineHelper.h" +#include "Runtime/Camera/NextEngineHelper.h" #include #include diff --git a/src/Editor/EditorMain.cpp b/src/Editor/EditorMain.cpp index d8685a4a..0c8e1371 100644 --- a/src/Editor/EditorMain.cpp +++ b/src/Editor/EditorMain.cpp @@ -4,7 +4,7 @@ #include "EditorInterface.hpp" #include "Runtime/Components/RenderComponent.h" #include "Runtime/Engine.hpp" -#include "Runtime/NextEngineHelper.h" +#include "Runtime/Camera/NextEngineHelper.h" #include "Editor/EditorActionDispatcher.hpp" #include "Editor/EditorContext.hpp" diff --git a/src/Editor/EditorMain.h b/src/Editor/EditorMain.h index 77a2b5da..16e2757d 100644 --- a/src/Editor/EditorMain.h +++ b/src/Editor/EditorMain.h @@ -3,7 +3,7 @@ #include "Editor/EditorActionDispatcher.hpp" #include "Runtime/Engine.hpp" #include "Runtime/GizmoController.hpp" -#include "Runtime/ModelViewController.hpp" +#include "Runtime/Camera/ModelViewController.hpp" #include #include diff --git a/src/Editor/Panels/ViewportOverlay.cpp b/src/Editor/Panels/ViewportOverlay.cpp index e1d5e412..c242afc2 100644 --- a/src/Editor/Panels/ViewportOverlay.cpp +++ b/src/Editor/Panels/ViewportOverlay.cpp @@ -7,7 +7,7 @@ #include "Editor/EditorActionDispatcher.hpp" #include "Runtime/Components/RenderComponent.h" #include "Runtime/Engine.hpp" -#include "Runtime/NextEngineHelper.h" +#include "Runtime/Camera/NextEngineHelper.h" #include "ThirdParty/fontawesome/IconsFontAwesome6.h" #include "Utilities/ImGui.hpp" #include "Utilities/Math.hpp" diff --git a/src/Runtime/FocusAnimation.cpp b/src/Runtime/Camera/FocusAnimation.cpp similarity index 94% rename from src/Runtime/FocusAnimation.cpp rename to src/Runtime/Camera/FocusAnimation.cpp index 34feb468..0befc5fd 100644 --- a/src/Runtime/FocusAnimation.cpp +++ b/src/Runtime/Camera/FocusAnimation.cpp @@ -1,4 +1,4 @@ -#include "FocusAnimation.hpp" +#include "Runtime/Camera/FocusAnimation.hpp" void FocusAnimation::Start(glm::vec3 startPos, glm::quat startRot, glm::vec3 targetPos, glm::quat targetRot) diff --git a/src/Runtime/FocusAnimation.hpp b/src/Runtime/Camera/FocusAnimation.hpp similarity index 100% rename from src/Runtime/FocusAnimation.hpp rename to src/Runtime/Camera/FocusAnimation.hpp diff --git a/src/Runtime/ModelViewController.cpp b/src/Runtime/Camera/ModelViewController.cpp similarity index 99% rename from src/Runtime/ModelViewController.cpp rename to src/Runtime/Camera/ModelViewController.cpp index b0134b15..1d83c8fa 100644 --- a/src/Runtime/ModelViewController.cpp +++ b/src/Runtime/Camera/ModelViewController.cpp @@ -1,4 +1,4 @@ -#include "ModelViewController.hpp" +#include "Runtime/Camera/ModelViewController.hpp" #include "Assets/Model.hpp" #include "Vulkan/Vulkan.hpp" #include "Platform/PlatformCommon.h" diff --git a/src/Runtime/ModelViewController.hpp b/src/Runtime/Camera/ModelViewController.hpp similarity index 98% rename from src/Runtime/ModelViewController.hpp rename to src/Runtime/Camera/ModelViewController.hpp index 6ed738da..a97a8583 100644 --- a/src/Runtime/ModelViewController.hpp +++ b/src/Runtime/Camera/ModelViewController.hpp @@ -1,6 +1,6 @@ #pragma once -#include "FocusAnimation.hpp" +#include "Runtime/Camera/FocusAnimation.hpp" #include "Utilities/Glm.hpp" #include diff --git a/src/Runtime/NextEngineHelper.cpp b/src/Runtime/Camera/NextEngineHelper.cpp similarity index 99% rename from src/Runtime/NextEngineHelper.cpp rename to src/Runtime/Camera/NextEngineHelper.cpp index 6e5e9faf..49dd1d53 100644 --- a/src/Runtime/NextEngineHelper.cpp +++ b/src/Runtime/Camera/NextEngineHelper.cpp @@ -1,4 +1,4 @@ -#include "NextEngineHelper.h" +#include "Runtime/Camera/NextEngineHelper.h" #include "Engine.hpp" #include "UserInterface.hpp" diff --git a/src/Runtime/NextEngineHelper.h b/src/Runtime/Camera/NextEngineHelper.h similarity index 100% rename from src/Runtime/NextEngineHelper.h rename to src/Runtime/Camera/NextEngineHelper.h diff --git a/src/Runtime/Components/SkinnedMeshComponent.cpp b/src/Runtime/Components/SkinnedMeshComponent.cpp index 824940b9..d0ca3237 100644 --- a/src/Runtime/Components/SkinnedMeshComponent.cpp +++ b/src/Runtime/Components/SkinnedMeshComponent.cpp @@ -1,6 +1,6 @@ #include "SkinnedMeshComponent.h" #include "Runtime/Engine.hpp" -#include "Runtime/NextEngineHelper.h" +#include "Runtime/Camera/NextEngineHelper.h" #include "Runtime/Reflection/PropertyMeta.h" #include #include From fad29bed99d30054c0de8d80deba71c723cb5de4 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Mon, 2 Feb 2026 10:55:26 +0800 Subject: [PATCH 31/53] refactor(runtime): move editor files to Runtime/Editor/ - Move UserInterface.cpp/hpp to Runtime/Editor/ - Move GizmoController.cpp/hpp to Runtime/Editor/ - Move ScreenShot.cpp/hpp to Runtime/Editor/ - Update all include paths --- src/Application/MagicaLego/MagicaLegoUserInterface.cpp | 2 +- src/Application/gkNextBenchmark/Common/BenchMark.cpp | 2 +- src/Application/gkNextRenderer/gkNextRenderer.cpp | 2 +- src/Application/gkNextRenderer/gkNextRenderer.hpp | 2 +- src/Application/gkNextVisualTest/gkNextVisualTest.cpp | 2 +- src/Editor/EditorMain.h | 2 +- src/Editor/Nodes/NodeSetInt.cpp | 2 +- src/Editor/Panels/ContentBrowserPanel.cpp | 2 +- src/Runtime/Camera/ModelViewController.cpp | 2 +- src/Runtime/Camera/NextEngineHelper.cpp | 4 ++-- src/Runtime/{ => Editor}/GizmoController.cpp | 2 +- src/Runtime/{ => Editor}/GizmoController.hpp | 0 src/Runtime/{ => Editor}/ScreenShot.cpp | 2 +- src/Runtime/{ => Editor}/ScreenShot.hpp | 0 src/Runtime/{ => Editor}/UserInterface.cpp | 2 +- src/Runtime/{ => Editor}/UserInterface.hpp | 0 src/Runtime/Engine.cpp | 4 ++-- 17 files changed, 16 insertions(+), 16 deletions(-) rename src/Runtime/{ => Editor}/GizmoController.cpp (99%) rename src/Runtime/{ => Editor}/GizmoController.hpp (100%) rename src/Runtime/{ => Editor}/ScreenShot.cpp (99%) rename src/Runtime/{ => Editor}/ScreenShot.hpp (100%) rename src/Runtime/{ => Editor}/UserInterface.cpp (99%) rename src/Runtime/{ => Editor}/UserInterface.hpp (100%) diff --git a/src/Application/MagicaLego/MagicaLegoUserInterface.cpp b/src/Application/MagicaLego/MagicaLegoUserInterface.cpp index e6e74f48..53c2b260 100644 --- a/src/Application/MagicaLego/MagicaLegoUserInterface.cpp +++ b/src/Application/MagicaLego/MagicaLegoUserInterface.cpp @@ -8,7 +8,7 @@ #include "ThirdParty/fontawesome/IconsFontAwesome6.h" #include "Utilities/FileHelper.hpp" #include "MagicaLegoGameInstance.hpp" -#include "Runtime/UserInterface.hpp" +#include "Runtime/Editor/UserInterface.hpp" #include "Runtime/Platform/PlatformCommon.h" #include "Utilities/ImGui.hpp" #include "Utilities/Localization.hpp" diff --git a/src/Application/gkNextBenchmark/Common/BenchMark.cpp b/src/Application/gkNextBenchmark/Common/BenchMark.cpp index f713a18a..445325ad 100644 --- a/src/Application/gkNextBenchmark/Common/BenchMark.cpp +++ b/src/Application/gkNextBenchmark/Common/BenchMark.cpp @@ -15,7 +15,7 @@ #include "Runtime/Engine.hpp" #include "Utilities/Exception.hpp" #include "Vulkan/Device.hpp" -#include "Runtime/ScreenShot.hpp" +#include "Runtime/Editor/ScreenShot.hpp" //#include diff --git a/src/Application/gkNextRenderer/gkNextRenderer.cpp b/src/Application/gkNextRenderer/gkNextRenderer.cpp index 24362e87..52a26871 100644 --- a/src/Application/gkNextRenderer/gkNextRenderer.cpp +++ b/src/Application/gkNextRenderer/gkNextRenderer.cpp @@ -14,7 +14,7 @@ #include "Utilities/Localization.hpp" #include "Utilities/ImGui.hpp" #include "Runtime/Platform/PlatformCommon.h" -#include "Runtime/ScreenShot.hpp" +#include "Runtime/Editor/ScreenShot.hpp" #include "Utilities/FileHelper.hpp" #include "Runtime/Components/SkinnedMeshComponent.h" #include "Vulkan/SwapChain.hpp" diff --git a/src/Application/gkNextRenderer/gkNextRenderer.hpp b/src/Application/gkNextRenderer/gkNextRenderer.hpp index 13504e22..b43bd172 100644 --- a/src/Application/gkNextRenderer/gkNextRenderer.hpp +++ b/src/Application/gkNextRenderer/gkNextRenderer.hpp @@ -1,7 +1,7 @@ #pragma once #include "Common/CoreMinimal.hpp" #include "Runtime/Engine.hpp" -#include "Runtime/GizmoController.hpp" +#include "Runtime/Editor/GizmoController.hpp" #include "Runtime/Camera/ModelViewController.hpp" class NextRendererGameInstance : public NextGameInstanceBase diff --git a/src/Application/gkNextVisualTest/gkNextVisualTest.cpp b/src/Application/gkNextVisualTest/gkNextVisualTest.cpp index ac6d08a0..9d2f7057 100644 --- a/src/Application/gkNextVisualTest/gkNextVisualTest.cpp +++ b/src/Application/gkNextVisualTest/gkNextVisualTest.cpp @@ -1,6 +1,6 @@ #include "gkNextVisualTest.hpp" #include "Runtime/Engine.hpp" -#include "Runtime/ScreenShot.hpp" +#include "Runtime/Editor/ScreenShot.hpp" #include "Utilities/FileHelper.hpp" #include "Vulkan/Device.hpp" diff --git a/src/Editor/EditorMain.h b/src/Editor/EditorMain.h index 16e2757d..e3f18741 100644 --- a/src/Editor/EditorMain.h +++ b/src/Editor/EditorMain.h @@ -2,7 +2,7 @@ #include "Editor/EditorActionDispatcher.hpp" #include "Runtime/Engine.hpp" -#include "Runtime/GizmoController.hpp" +#include "Runtime/Editor/GizmoController.hpp" #include "Runtime/Camera/ModelViewController.hpp" #include diff --git a/src/Editor/Nodes/NodeSetInt.cpp b/src/Editor/Nodes/NodeSetInt.cpp index f7de2fc0..3da4ae57 100644 --- a/src/Editor/Nodes/NodeSetInt.cpp +++ b/src/Editor/Nodes/NodeSetInt.cpp @@ -3,7 +3,7 @@ #include #include "Editor/EditorContext.hpp" -#include "Runtime/UserInterface.hpp" +#include "Runtime/Editor/UserInterface.hpp" namespace Nodes { diff --git a/src/Editor/Panels/ContentBrowserPanel.cpp b/src/Editor/Panels/ContentBrowserPanel.cpp index 0fb27195..1c5f6c27 100644 --- a/src/Editor/Panels/ContentBrowserPanel.cpp +++ b/src/Editor/Panels/ContentBrowserPanel.cpp @@ -5,7 +5,7 @@ #include "Assets/Scene.hpp" #include "Assets/TextureImage.hpp" #include "Editor/EditorActionDispatcher.hpp" -#include "Runtime/UserInterface.hpp" +#include "Runtime/Editor/UserInterface.hpp" #include "ThirdParty/fontawesome/IconsFontAwesome6.h" #include "Utilities/FileHelper.hpp" diff --git a/src/Runtime/Camera/ModelViewController.cpp b/src/Runtime/Camera/ModelViewController.cpp index 1d83c8fa..efea9881 100644 --- a/src/Runtime/Camera/ModelViewController.cpp +++ b/src/Runtime/Camera/ModelViewController.cpp @@ -1,7 +1,7 @@ #include "Runtime/Camera/ModelViewController.hpp" #include "Assets/Model.hpp" #include "Vulkan/Vulkan.hpp" -#include "Platform/PlatformCommon.h" +#include "Runtime/Platform/PlatformCommon.h" void ModelViewController::Reset(const Assets::Camera& renderCamera) { diff --git a/src/Runtime/Camera/NextEngineHelper.cpp b/src/Runtime/Camera/NextEngineHelper.cpp index 49dd1d53..48d890c3 100644 --- a/src/Runtime/Camera/NextEngineHelper.cpp +++ b/src/Runtime/Camera/NextEngineHelper.cpp @@ -1,7 +1,7 @@ #include "Runtime/Camera/NextEngineHelper.h" -#include "Engine.hpp" -#include "UserInterface.hpp" +#include "Runtime/Engine.hpp" +#include "Runtime/Editor/UserInterface.hpp" #include "Vulkan/SwapChain.hpp" namespace diff --git a/src/Runtime/GizmoController.cpp b/src/Runtime/Editor/GizmoController.cpp similarity index 99% rename from src/Runtime/GizmoController.cpp rename to src/Runtime/Editor/GizmoController.cpp index 7a12a3ae..48d40ef5 100644 --- a/src/Runtime/GizmoController.cpp +++ b/src/Runtime/Editor/GizmoController.cpp @@ -1,4 +1,4 @@ -#include "Runtime/GizmoController.hpp" +#include "Runtime/Editor/GizmoController.hpp" #include "Assets/Node.h" #include "Assets/Scene.hpp" diff --git a/src/Runtime/GizmoController.hpp b/src/Runtime/Editor/GizmoController.hpp similarity index 100% rename from src/Runtime/GizmoController.hpp rename to src/Runtime/Editor/GizmoController.hpp diff --git a/src/Runtime/ScreenShot.cpp b/src/Runtime/Editor/ScreenShot.cpp similarity index 99% rename from src/Runtime/ScreenShot.cpp rename to src/Runtime/Editor/ScreenShot.cpp index 96c9380c..1afa2085 100644 --- a/src/Runtime/ScreenShot.cpp +++ b/src/Runtime/Editor/ScreenShot.cpp @@ -1,4 +1,4 @@ -#include "ScreenShot.hpp" +#include "Runtime/Editor/ScreenShot.hpp" #include "Rendering/VulkanBaseRenderer.hpp" #include "Utilities/Exception.hpp" diff --git a/src/Runtime/ScreenShot.hpp b/src/Runtime/Editor/ScreenShot.hpp similarity index 100% rename from src/Runtime/ScreenShot.hpp rename to src/Runtime/Editor/ScreenShot.hpp diff --git a/src/Runtime/UserInterface.cpp b/src/Runtime/Editor/UserInterface.cpp similarity index 99% rename from src/Runtime/UserInterface.cpp rename to src/Runtime/Editor/UserInterface.cpp index 7ba8795e..d5f3d11c 100644 --- a/src/Runtime/UserInterface.cpp +++ b/src/Runtime/Editor/UserInterface.cpp @@ -1,4 +1,4 @@ -#include "UserInterface.hpp" +#include "Runtime/Editor/UserInterface.hpp" #include "Engine.hpp" #include "Runtime/Scene/SceneList.hpp" diff --git a/src/Runtime/UserInterface.hpp b/src/Runtime/Editor/UserInterface.hpp similarity index 100% rename from src/Runtime/UserInterface.hpp rename to src/Runtime/Editor/UserInterface.hpp diff --git a/src/Runtime/Engine.cpp b/src/Runtime/Engine.cpp index 7480ac8b..80bfb04b 100644 --- a/src/Runtime/Engine.cpp +++ b/src/Runtime/Engine.cpp @@ -5,8 +5,8 @@ #include "Assets/UniformBuffer.hpp" #include "QuickJSEngine.hpp" #include "Runtime/Command/DeleteNodeCommand.hpp" -#include "ScreenShot.hpp" -#include "UserInterface.hpp" +#include "Runtime/Editor/ScreenShot.hpp" +#include "Runtime/Editor/UserInterface.hpp" #include "Runtime/Config/UserSettings.hpp" #include "Vulkan/Device.hpp" #include "Vulkan/Instance.hpp" From 9087f84aebd2564a61b106d599bbbea369f7dcc9 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Mon, 2 Feb 2026 10:58:48 +0800 Subject: [PATCH 32/53] refactor(runtime): move subsystem files to Runtime/Subsystems/ - Move NextAudio.cpp/h to Runtime/Subsystems/ - Move NextPhysics.cpp/h to Runtime/Subsystems/ - Move NextPhysicsTypes.h to Runtime/Subsystems/ - Move NextAnimation.cpp/h to Runtime/Subsystems/ - Move QuickJSEngine.cpp/hpp to Runtime/Subsystems/ - Update all include paths - Update CMakeLists.txt iOS special handling --- src/Assets/Model.cpp | 2 +- src/Assets/Node.h | 2 +- src/Assets/Scene.cpp | 2 +- src/Assets/Scene.hpp | 4 ++-- src/CMakeLists.txt | 2 +- src/Runtime/Components/PhysicsComponent.h | 2 +- src/Runtime/Engine.cpp | 10 +++++----- src/Runtime/Scene/SceneList.cpp | 2 +- src/Runtime/{ => Subsystems}/NextAnimation.cpp | 2 +- src/Runtime/{ => Subsystems}/NextAnimation.h | 0 src/Runtime/{ => Subsystems}/NextAudio.cpp | 2 +- src/Runtime/{ => Subsystems}/NextAudio.h | 0 src/Runtime/{ => Subsystems}/NextPhysics.cpp | 2 +- src/Runtime/{ => Subsystems}/NextPhysics.h | 2 +- src/Runtime/{ => Subsystems}/NextPhysicsTypes.h | 0 src/Runtime/{ => Subsystems}/QuickJSEngine.cpp | 2 +- src/Runtime/{ => Subsystems}/QuickJSEngine.hpp | 0 src/Tests/Test_PhysicsSync.cpp | 2 +- 18 files changed, 19 insertions(+), 19 deletions(-) rename src/Runtime/{ => Subsystems}/NextAnimation.cpp (98%) rename src/Runtime/{ => Subsystems}/NextAnimation.h (100%) rename src/Runtime/{ => Subsystems}/NextAudio.cpp (98%) rename src/Runtime/{ => Subsystems}/NextAudio.h (100%) rename src/Runtime/{ => Subsystems}/NextPhysics.cpp (99%) rename src/Runtime/{ => Subsystems}/NextPhysics.h (97%) rename src/Runtime/{ => Subsystems}/NextPhysicsTypes.h (100%) rename src/Runtime/{ => Subsystems}/QuickJSEngine.cpp (99%) rename src/Runtime/{ => Subsystems}/QuickJSEngine.hpp (100%) diff --git a/src/Assets/Model.cpp b/src/Assets/Model.cpp index 2d78fa81..6ff51e5a 100644 --- a/src/Assets/Model.cpp +++ b/src/Assets/Model.cpp @@ -14,7 +14,7 @@ #include #include "Runtime/Engine.hpp" -#include "Runtime/NextPhysics.h" +#include "Runtime/Subsystems/NextPhysics.h" #define PROVOKING_VERTICE 1 diff --git a/src/Assets/Node.h b/src/Assets/Node.h index f7916efc..4dad423a 100644 --- a/src/Assets/Node.h +++ b/src/Assets/Node.h @@ -1,7 +1,7 @@ #pragma once #include "Common/CoreMinimal.hpp" #include "UniformBuffer.hpp" -#include "Runtime/NextPhysics.h" +#include "Runtime/Subsystems/NextPhysics.h" #include "Component.h" #include "Runtime/Components/PhysicsComponent.h" diff --git a/src/Assets/Scene.cpp b/src/Assets/Scene.cpp index d3778ab4..90f147d7 100644 --- a/src/Assets/Scene.cpp +++ b/src/Assets/Scene.cpp @@ -9,7 +9,7 @@ #include "FSceneSaver.h" #include "Model.hpp" #include "Options.hpp" -#include "Runtime/NextPhysics.h" +#include "Runtime/Subsystems/NextPhysics.h" #include "Scene.hpp" #include "Vulkan/BufferUtil.hpp" diff --git a/src/Assets/Scene.hpp b/src/Assets/Scene.hpp index b0a1f55a..813f7a29 100644 --- a/src/Assets/Scene.hpp +++ b/src/Assets/Scene.hpp @@ -8,8 +8,8 @@ #include "CPUAccelerationStructure.h" #include "Model.hpp" -#include "Runtime/NextPhysics.h" -#include "Runtime/NextPhysicsTypes.h" +#include "Runtime/Subsystems/NextPhysics.h" +#include "Runtime/Subsystems/NextPhysicsTypes.h" #include "Skeleton.hpp" namespace Vulkan diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 81636053..be35c92c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,7 +19,7 @@ else() endif() if (IOS) - set_source_files_properties(Runtime/NextAudio.cpp PROPERTIES LANGUAGE "OBJCXX") + set_source_files_properties(Runtime/Subsystems/NextAudio.cpp PROPERTIES LANGUAGE "OBJCXX") endif() # unit test files diff --git a/src/Runtime/Components/PhysicsComponent.h b/src/Runtime/Components/PhysicsComponent.h index c17cd47c..3d10e8e0 100644 --- a/src/Runtime/Components/PhysicsComponent.h +++ b/src/Runtime/Components/PhysicsComponent.h @@ -1,7 +1,7 @@ #pragma once #include "Assets/Component.h" #include "Runtime/Reflection/ReflectionMacros.h" -#include "Runtime/NextPhysics.h" +#include "Runtime/Subsystems/NextPhysics.h" #include namespace Runtime diff --git a/src/Runtime/Engine.cpp b/src/Runtime/Engine.cpp index 80bfb04b..0fd96252 100644 --- a/src/Runtime/Engine.cpp +++ b/src/Runtime/Engine.cpp @@ -3,7 +3,7 @@ #include "Assets/Scene.hpp" #include "Assets/Texture.hpp" #include "Assets/UniformBuffer.hpp" -#include "QuickJSEngine.hpp" +#include "Runtime/Subsystems/QuickJSEngine.hpp" #include "Runtime/Command/DeleteNodeCommand.hpp" #include "Runtime/Editor/ScreenShot.hpp" #include "Runtime/Editor/UserInterface.hpp" @@ -25,7 +25,7 @@ #include #include -#include "NextAudio.h" +#include "Runtime/Subsystems/NextAudio.h" #include "Options.hpp" #include "Rendering/RayTraceBaseRenderer.hpp" #include "Runtime/Utilities/TaskCoordinator.hpp" @@ -37,9 +37,9 @@ #include #define BUILDVER(X) std::string buildver(#X); -#include "NextAnimation.h" -#include "NextPhysics.h" -#include "Platform/PlatformCommon.h" +#include "Runtime/Subsystems/NextAnimation.h" +#include "Runtime/Subsystems/NextPhysics.h" +#include "Runtime/Platform/PlatformCommon.h" #include "build.version" #include "Common/CoreMinimal.hpp" diff --git a/src/Runtime/Scene/SceneList.cpp b/src/Runtime/Scene/SceneList.cpp index f3597f19..a4f460de 100644 --- a/src/Runtime/Scene/SceneList.cpp +++ b/src/Runtime/Scene/SceneList.cpp @@ -5,7 +5,7 @@ #include "Assets/Model.hpp" #include "Runtime/Engine.hpp" -#include "Runtime/NextPhysics.h" +#include "Runtime/Subsystems/NextPhysics.h" #include #include diff --git a/src/Runtime/NextAnimation.cpp b/src/Runtime/Subsystems/NextAnimation.cpp similarity index 98% rename from src/Runtime/NextAnimation.cpp rename to src/Runtime/Subsystems/NextAnimation.cpp index 7a528d07..29b3297d 100644 --- a/src/Runtime/NextAnimation.cpp +++ b/src/Runtime/Subsystems/NextAnimation.cpp @@ -1,4 +1,4 @@ -#include "NextAnimation.h" +#include "Runtime/Subsystems/NextAnimation.h" #include "Engine.hpp" diff --git a/src/Runtime/NextAnimation.h b/src/Runtime/Subsystems/NextAnimation.h similarity index 100% rename from src/Runtime/NextAnimation.h rename to src/Runtime/Subsystems/NextAnimation.h diff --git a/src/Runtime/NextAudio.cpp b/src/Runtime/Subsystems/NextAudio.cpp similarity index 98% rename from src/Runtime/NextAudio.cpp rename to src/Runtime/Subsystems/NextAudio.cpp index ea055ad9..3b7cd891 100644 --- a/src/Runtime/NextAudio.cpp +++ b/src/Runtime/Subsystems/NextAudio.cpp @@ -1,4 +1,4 @@ -#include "NextAudio.h" +#include "Runtime/Subsystems/NextAudio.h" #include "Utilities/FileHelper.hpp" diff --git a/src/Runtime/NextAudio.h b/src/Runtime/Subsystems/NextAudio.h similarity index 100% rename from src/Runtime/NextAudio.h rename to src/Runtime/Subsystems/NextAudio.h diff --git a/src/Runtime/NextPhysics.cpp b/src/Runtime/Subsystems/NextPhysics.cpp similarity index 99% rename from src/Runtime/NextPhysics.cpp rename to src/Runtime/Subsystems/NextPhysics.cpp index bff1badb..8d765a9f 100644 --- a/src/Runtime/NextPhysics.cpp +++ b/src/Runtime/Subsystems/NextPhysics.cpp @@ -1,4 +1,4 @@ -#include "NextPhysics.h" +#include "Runtime/Subsystems/NextPhysics.h" #if WITH_PHYSIC // The Jolt headers don't include Jolt.h. Always include Jolt.h before including any other Jolt header. diff --git a/src/Runtime/NextPhysics.h b/src/Runtime/Subsystems/NextPhysics.h similarity index 97% rename from src/Runtime/NextPhysics.h rename to src/Runtime/Subsystems/NextPhysics.h index 4904ecbc..a0bb62a3 100644 --- a/src/Runtime/NextPhysics.h +++ b/src/Runtime/Subsystems/NextPhysics.h @@ -5,7 +5,7 @@ #include #include "Vulkan/Vulkan.hpp" -#include "NextPhysicsTypes.h" +#include "Runtime/Subsystems/NextPhysicsTypes.h" struct FNextPhysicsContext; diff --git a/src/Runtime/NextPhysicsTypes.h b/src/Runtime/Subsystems/NextPhysicsTypes.h similarity index 100% rename from src/Runtime/NextPhysicsTypes.h rename to src/Runtime/Subsystems/NextPhysicsTypes.h diff --git a/src/Runtime/QuickJSEngine.cpp b/src/Runtime/Subsystems/QuickJSEngine.cpp similarity index 99% rename from src/Runtime/QuickJSEngine.cpp rename to src/Runtime/Subsystems/QuickJSEngine.cpp index 4a8727c8..324b5b28 100644 --- a/src/Runtime/QuickJSEngine.cpp +++ b/src/Runtime/Subsystems/QuickJSEngine.cpp @@ -1,4 +1,4 @@ -#include "QuickJSEngine.hpp" +#include "Runtime/Subsystems/QuickJSEngine.hpp" #include "Engine.hpp" #include "Assets/Scene.hpp" diff --git a/src/Runtime/QuickJSEngine.hpp b/src/Runtime/Subsystems/QuickJSEngine.hpp similarity index 100% rename from src/Runtime/QuickJSEngine.hpp rename to src/Runtime/Subsystems/QuickJSEngine.hpp diff --git a/src/Tests/Test_PhysicsSync.cpp b/src/Tests/Test_PhysicsSync.cpp index 849900af..aa3202a1 100644 --- a/src/Tests/Test_PhysicsSync.cpp +++ b/src/Tests/Test_PhysicsSync.cpp @@ -1,6 +1,6 @@ #include #include "TestCommon.hpp" -#include "Runtime/NextPhysics.h" +#include "Runtime/Subsystems/NextPhysics.h" #include "Assets/Node.h" #include "Runtime/Components/RenderComponent.h" From b0d267dd240141086e558eb36e95ba2ebae7e6d6 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Mon, 2 Feb 2026 11:45:13 +0800 Subject: [PATCH 33/53] fix(runtime): fix missing include paths in subsystems - Fix Engine.hpp include in QuickJSEngine.cpp - Fix Engine.hpp include in NextAnimation.cpp - Fix Engine.hpp include in NextPhysics.cpp - Fix Engine.hpp include in UserInterface.cpp - Fix PlatformCommon.h include in QuickJSEngine.cpp --- src/Runtime/Editor/UserInterface.cpp | 2 +- src/Runtime/Subsystems/NextAnimation.cpp | 2 +- src/Runtime/Subsystems/NextPhysics.cpp | 2 +- src/Runtime/Subsystems/QuickJSEngine.cpp | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Runtime/Editor/UserInterface.cpp b/src/Runtime/Editor/UserInterface.cpp index d5f3d11c..3bc07254 100644 --- a/src/Runtime/Editor/UserInterface.cpp +++ b/src/Runtime/Editor/UserInterface.cpp @@ -1,6 +1,6 @@ #include "Runtime/Editor/UserInterface.hpp" -#include "Engine.hpp" +#include "Runtime/Engine.hpp" #include "Runtime/Scene/SceneList.hpp" #include "Runtime/Config/UserSettings.hpp" #include "Utilities/Exception.hpp" diff --git a/src/Runtime/Subsystems/NextAnimation.cpp b/src/Runtime/Subsystems/NextAnimation.cpp index 29b3297d..2a1f89c7 100644 --- a/src/Runtime/Subsystems/NextAnimation.cpp +++ b/src/Runtime/Subsystems/NextAnimation.cpp @@ -1,6 +1,6 @@ #include "Runtime/Subsystems/NextAnimation.h" -#include "Engine.hpp" +#include "Runtime/Engine.hpp" #if WITH_OZZ #include "animation/runtime/sampling_job.h" diff --git a/src/Runtime/Subsystems/NextPhysics.cpp b/src/Runtime/Subsystems/NextPhysics.cpp index 8d765a9f..1f6af142 100644 --- a/src/Runtime/Subsystems/NextPhysics.cpp +++ b/src/Runtime/Subsystems/NextPhysics.cpp @@ -28,7 +28,7 @@ #include #include -#include "Engine.hpp" +#include "Runtime/Engine.hpp" #include "Assets/Model.hpp" #if WITH_PHYSIC diff --git a/src/Runtime/Subsystems/QuickJSEngine.cpp b/src/Runtime/Subsystems/QuickJSEngine.cpp index 324b5b28..562d109d 100644 --- a/src/Runtime/Subsystems/QuickJSEngine.cpp +++ b/src/Runtime/Subsystems/QuickJSEngine.cpp @@ -1,6 +1,6 @@ #include "Runtime/Subsystems/QuickJSEngine.hpp" -#include "Engine.hpp" +#include "Runtime/Engine.hpp" #include "Assets/Scene.hpp" #include "Assets/Node.h" #include "Runtime/Components/RenderComponent.h" @@ -10,7 +10,7 @@ #include "Runtime/Reflection/QuickJSReflectionBridge.h" #include "Runtime/Reflection/QuickJSTypeConverter.h" #include "Utilities/FileHelper.hpp" -#include "Platform/PlatformCommon.h" +#include "Runtime/Platform/PlatformCommon.h" #include #include From c84812c874cebc75962cf5615754cd7090514c3b Mon Sep 17 00:00:00 2001 From: gameKnife Date: Mon, 2 Feb 2026 11:49:43 +0800 Subject: [PATCH 34/53] fix(runtime): fix Unity Build compilation issues - Add GLM_ENABLE_EXPERIMENTAL define in GizmoController.cpp - Move imgui.h include before GizmoController.hpp to fix ImGuizmo dependency - Fixes compilation errors caused by Unity Build file ordering changes --- src/Runtime/Editor/GizmoController.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Runtime/Editor/GizmoController.cpp b/src/Runtime/Editor/GizmoController.cpp index 48d40ef5..5422e8b8 100644 --- a/src/Runtime/Editor/GizmoController.cpp +++ b/src/Runtime/Editor/GizmoController.cpp @@ -1,3 +1,5 @@ +#define GLM_ENABLE_EXPERIMENTAL +#include #include "Runtime/Editor/GizmoController.hpp" #include "Assets/Node.h" @@ -6,8 +8,6 @@ #include "Runtime/Command/TransformNodeCommand.hpp" #include "Runtime/Command/DuplicateNodeCommand.hpp" #include "ThirdParty/ImGuizmo/ImGuizmo.h" - -#include #include #include #include From 3e2d5668b1200c94c82a0ffe7a1c4bb545407972 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Mon, 2 Feb 2026 12:12:08 +0800 Subject: [PATCH 35/53] refactor(runtime): reorganize files per user feedback - Move TaskCoordinator from Utilities/ to Subsystems/ (it's a subsystem) - Move NextEngineHelper from Camera/ to Utilities/ (it's a utility function) - Move ScreenShot from Editor/ to Runtime/ root (it's a basic runtime feature) - Add missing Assets/Node.h include in Engine.cpp - Update all include paths accordingly --- src/Application/MagicaLego/MagicaLegoGameInstance.hpp | 2 +- src/Application/gkNextBenchmark/Common/BenchMark.cpp | 2 +- src/Application/gkNextRenderer/gkNextRenderer.cpp | 4 ++-- src/Application/gkNextVisualTest/gkNextVisualTest.cpp | 2 +- src/Assets/CPUAccelerationStructure.cpp | 2 +- src/Assets/Scene.cpp | 2 +- src/Assets/Texture.cpp | 2 +- src/Editor/EditorMain.cpp | 2 +- src/Editor/Panels/ViewportOverlay.cpp | 2 +- src/Runtime/Components/SkinnedMeshComponent.cpp | 2 +- src/Runtime/Editor/UserInterface.cpp | 2 +- src/Runtime/Engine.cpp | 5 +++-- src/Runtime/{Editor => }/ScreenShot.cpp | 4 ++-- src/Runtime/{Editor => }/ScreenShot.hpp | 0 src/Runtime/{Utilities => Subsystems}/TaskCoordinator.cpp | 2 +- src/Runtime/{Utilities => Subsystems}/TaskCoordinator.hpp | 0 src/Runtime/{Camera => Utilities}/NextEngineHelper.cpp | 2 +- src/Runtime/{Camera => Utilities}/NextEngineHelper.h | 0 18 files changed, 19 insertions(+), 18 deletions(-) rename src/Runtime/{Editor => }/ScreenShot.cpp (99%) rename src/Runtime/{Editor => }/ScreenShot.hpp (100%) rename src/Runtime/{Utilities => Subsystems}/TaskCoordinator.cpp (98%) rename src/Runtime/{Utilities => Subsystems}/TaskCoordinator.hpp (100%) rename src/Runtime/{Camera => Utilities}/NextEngineHelper.cpp (99%) rename src/Runtime/{Camera => Utilities}/NextEngineHelper.h (100%) diff --git a/src/Application/MagicaLego/MagicaLegoGameInstance.hpp b/src/Application/MagicaLego/MagicaLegoGameInstance.hpp index af098b4c..421df65e 100644 --- a/src/Application/MagicaLego/MagicaLegoGameInstance.hpp +++ b/src/Application/MagicaLego/MagicaLegoGameInstance.hpp @@ -2,7 +2,7 @@ // ReSharper disable once CppUnusedIncludeDirective #include "Common/CoreMinimal.hpp" #include "Runtime/Engine.hpp" -#include "Runtime/Camera/NextEngineHelper.h" +#include "Runtime/Utilities/NextEngineHelper.h" #include "Utilities/FileHelper.hpp" #define MAGICALEGO_SAVE_VERSION 1 diff --git a/src/Application/gkNextBenchmark/Common/BenchMark.cpp b/src/Application/gkNextBenchmark/Common/BenchMark.cpp index 445325ad..f713a18a 100644 --- a/src/Application/gkNextBenchmark/Common/BenchMark.cpp +++ b/src/Application/gkNextBenchmark/Common/BenchMark.cpp @@ -15,7 +15,7 @@ #include "Runtime/Engine.hpp" #include "Utilities/Exception.hpp" #include "Vulkan/Device.hpp" -#include "Runtime/Editor/ScreenShot.hpp" +#include "Runtime/ScreenShot.hpp" //#include diff --git a/src/Application/gkNextRenderer/gkNextRenderer.cpp b/src/Application/gkNextRenderer/gkNextRenderer.cpp index 52a26871..7edaf349 100644 --- a/src/Application/gkNextRenderer/gkNextRenderer.cpp +++ b/src/Application/gkNextRenderer/gkNextRenderer.cpp @@ -10,11 +10,11 @@ #include "Runtime/Components/RenderComponent.h" #include "Runtime/Components/PhysicsComponent.h" #include "Runtime/Engine.hpp" -#include "Runtime/Camera/NextEngineHelper.h" +#include "Runtime/Utilities/NextEngineHelper.h" #include "Utilities/Localization.hpp" #include "Utilities/ImGui.hpp" #include "Runtime/Platform/PlatformCommon.h" -#include "Runtime/Editor/ScreenShot.hpp" +#include "Runtime/ScreenShot.hpp" #include "Utilities/FileHelper.hpp" #include "Runtime/Components/SkinnedMeshComponent.h" #include "Vulkan/SwapChain.hpp" diff --git a/src/Application/gkNextVisualTest/gkNextVisualTest.cpp b/src/Application/gkNextVisualTest/gkNextVisualTest.cpp index 9d2f7057..ac6d08a0 100644 --- a/src/Application/gkNextVisualTest/gkNextVisualTest.cpp +++ b/src/Application/gkNextVisualTest/gkNextVisualTest.cpp @@ -1,6 +1,6 @@ #include "gkNextVisualTest.hpp" #include "Runtime/Engine.hpp" -#include "Runtime/Editor/ScreenShot.hpp" +#include "Runtime/ScreenShot.hpp" #include "Utilities/FileHelper.hpp" #include "Vulkan/Device.hpp" diff --git a/src/Assets/CPUAccelerationStructure.cpp b/src/Assets/CPUAccelerationStructure.cpp index 61bc705f..f7e7bc61 100644 --- a/src/Assets/CPUAccelerationStructure.cpp +++ b/src/Assets/CPUAccelerationStructure.cpp @@ -1,5 +1,5 @@ #include "CPUAccelerationStructure.h" -#include "Runtime/Utilities/TaskCoordinator.hpp" +#include "Runtime/Subsystems/TaskCoordinator.hpp" #include "Vulkan/DeviceMemory.hpp" #include "Assets/Node.h" #include "Runtime/Components/RenderComponent.h" diff --git a/src/Assets/Scene.cpp b/src/Assets/Scene.cpp index 90f147d7..925ab3f3 100644 --- a/src/Assets/Scene.cpp +++ b/src/Assets/Scene.cpp @@ -18,7 +18,7 @@ #include "Runtime/Components/RenderComponent.h" #include "Runtime/Components/SkinnedMeshComponent.h" #include "Runtime/Engine.hpp" -#include "Runtime/Camera/NextEngineHelper.h" +#include "Runtime/Utilities/NextEngineHelper.h" #include #include diff --git a/src/Assets/Texture.cpp b/src/Assets/Texture.cpp index 99fb63d9..23a3eeb2 100644 --- a/src/Assets/Texture.cpp +++ b/src/Assets/Texture.cpp @@ -4,7 +4,7 @@ #include "Common/CoreMinimal.hpp" #include "Options.hpp" -#include "Runtime/Utilities/TaskCoordinator.hpp" +#include "Runtime/Subsystems/TaskCoordinator.hpp" #include "TextureImage.hpp" #include "Runtime/Engine.hpp" #include "Utilities/FileHelper.hpp" diff --git a/src/Editor/EditorMain.cpp b/src/Editor/EditorMain.cpp index 0c8e1371..0f249a6c 100644 --- a/src/Editor/EditorMain.cpp +++ b/src/Editor/EditorMain.cpp @@ -4,7 +4,7 @@ #include "EditorInterface.hpp" #include "Runtime/Components/RenderComponent.h" #include "Runtime/Engine.hpp" -#include "Runtime/Camera/NextEngineHelper.h" +#include "Runtime/Utilities/NextEngineHelper.h" #include "Editor/EditorActionDispatcher.hpp" #include "Editor/EditorContext.hpp" diff --git a/src/Editor/Panels/ViewportOverlay.cpp b/src/Editor/Panels/ViewportOverlay.cpp index c242afc2..775edf20 100644 --- a/src/Editor/Panels/ViewportOverlay.cpp +++ b/src/Editor/Panels/ViewportOverlay.cpp @@ -7,7 +7,7 @@ #include "Editor/EditorActionDispatcher.hpp" #include "Runtime/Components/RenderComponent.h" #include "Runtime/Engine.hpp" -#include "Runtime/Camera/NextEngineHelper.h" +#include "Runtime/Utilities/NextEngineHelper.h" #include "ThirdParty/fontawesome/IconsFontAwesome6.h" #include "Utilities/ImGui.hpp" #include "Utilities/Math.hpp" diff --git a/src/Runtime/Components/SkinnedMeshComponent.cpp b/src/Runtime/Components/SkinnedMeshComponent.cpp index d0ca3237..c4bae7be 100644 --- a/src/Runtime/Components/SkinnedMeshComponent.cpp +++ b/src/Runtime/Components/SkinnedMeshComponent.cpp @@ -1,6 +1,6 @@ #include "SkinnedMeshComponent.h" #include "Runtime/Engine.hpp" -#include "Runtime/Camera/NextEngineHelper.h" +#include "Runtime/Utilities/NextEngineHelper.h" #include "Runtime/Reflection/PropertyMeta.h" #include #include diff --git a/src/Runtime/Editor/UserInterface.cpp b/src/Runtime/Editor/UserInterface.cpp index 3bc07254..99d191f6 100644 --- a/src/Runtime/Editor/UserInterface.cpp +++ b/src/Runtime/Editor/UserInterface.cpp @@ -32,7 +32,7 @@ #include "Assets/TextureImage.hpp" #include "Options.hpp" #include "Rendering/VulkanBaseRenderer.hpp" -#include "Runtime/Utilities/TaskCoordinator.hpp" +#include "Runtime/Subsystems/TaskCoordinator.hpp" #include "ThirdParty/fontawesome/IconsFontAwesome6.h" #include "Utilities/FileHelper.hpp" #include "Utilities/ImGui.hpp" diff --git a/src/Runtime/Engine.cpp b/src/Runtime/Engine.cpp index 0fd96252..3fcb4899 100644 --- a/src/Runtime/Engine.cpp +++ b/src/Runtime/Engine.cpp @@ -1,11 +1,12 @@ #include "Engine.hpp" #include "Assets/Model.hpp" +#include "Assets/Node.h" #include "Assets/Scene.hpp" #include "Assets/Texture.hpp" #include "Assets/UniformBuffer.hpp" #include "Runtime/Subsystems/QuickJSEngine.hpp" #include "Runtime/Command/DeleteNodeCommand.hpp" -#include "Runtime/Editor/ScreenShot.hpp" +#include "Runtime/ScreenShot.hpp" #include "Runtime/Editor/UserInterface.hpp" #include "Runtime/Config/UserSettings.hpp" #include "Vulkan/Device.hpp" @@ -28,7 +29,7 @@ #include "Runtime/Subsystems/NextAudio.h" #include "Options.hpp" #include "Rendering/RayTraceBaseRenderer.hpp" -#include "Runtime/Utilities/TaskCoordinator.hpp" +#include "Runtime/Subsystems/TaskCoordinator.hpp" #include "Utilities/Localization.hpp" #define _USE_MATH_DEFINES diff --git a/src/Runtime/Editor/ScreenShot.cpp b/src/Runtime/ScreenShot.cpp similarity index 99% rename from src/Runtime/Editor/ScreenShot.cpp rename to src/Runtime/ScreenShot.cpp index 1afa2085..fe6594f9 100644 --- a/src/Runtime/Editor/ScreenShot.cpp +++ b/src/Runtime/ScreenShot.cpp @@ -1,4 +1,4 @@ -#include "Runtime/Editor/ScreenShot.hpp" +#include "Runtime/ScreenShot.hpp" #include "Rendering/VulkanBaseRenderer.hpp" #include "Utilities/Exception.hpp" @@ -7,7 +7,7 @@ #define _USE_MATH_DEFINES #include -#include "Runtime/Utilities/TaskCoordinator.hpp" +#include "Runtime/Subsystems/TaskCoordinator.hpp" #include "Vulkan/SwapChain.hpp" #if WITH_AVIF diff --git a/src/Runtime/Editor/ScreenShot.hpp b/src/Runtime/ScreenShot.hpp similarity index 100% rename from src/Runtime/Editor/ScreenShot.hpp rename to src/Runtime/ScreenShot.hpp diff --git a/src/Runtime/Utilities/TaskCoordinator.cpp b/src/Runtime/Subsystems/TaskCoordinator.cpp similarity index 98% rename from src/Runtime/Utilities/TaskCoordinator.cpp rename to src/Runtime/Subsystems/TaskCoordinator.cpp index ab74da2c..be9d63e4 100644 --- a/src/Runtime/Utilities/TaskCoordinator.cpp +++ b/src/Runtime/Subsystems/TaskCoordinator.cpp @@ -1,4 +1,4 @@ -#include "Runtime/Utilities/TaskCoordinator.hpp" +#include "Runtime/Subsystems/TaskCoordinator.hpp" #include diff --git a/src/Runtime/Utilities/TaskCoordinator.hpp b/src/Runtime/Subsystems/TaskCoordinator.hpp similarity index 100% rename from src/Runtime/Utilities/TaskCoordinator.hpp rename to src/Runtime/Subsystems/TaskCoordinator.hpp diff --git a/src/Runtime/Camera/NextEngineHelper.cpp b/src/Runtime/Utilities/NextEngineHelper.cpp similarity index 99% rename from src/Runtime/Camera/NextEngineHelper.cpp rename to src/Runtime/Utilities/NextEngineHelper.cpp index 48d890c3..8d0c7fce 100644 --- a/src/Runtime/Camera/NextEngineHelper.cpp +++ b/src/Runtime/Utilities/NextEngineHelper.cpp @@ -1,4 +1,4 @@ -#include "Runtime/Camera/NextEngineHelper.h" +#include "Runtime/Utilities/NextEngineHelper.h" #include "Runtime/Engine.hpp" #include "Runtime/Editor/UserInterface.hpp" diff --git a/src/Runtime/Camera/NextEngineHelper.h b/src/Runtime/Utilities/NextEngineHelper.h similarity index 100% rename from src/Runtime/Camera/NextEngineHelper.h rename to src/Runtime/Utilities/NextEngineHelper.h From 2fc74eb10ee9df12d8355bc6ea74fff57facddd3 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Mon, 2 Feb 2026 14:13:10 +0800 Subject: [PATCH 36/53] refactor(assets): move data structures to Assets/Data/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reorganize Assets directory by moving pure data structures into Data/ subdirectory for better code organization. Files moved: - Vertex.hpp → Data/Vertex.hpp - Material.hpp → Data/Material.hpp - Skeleton.hpp → Data/Skeleton.hpp - Animation.hpp/cpp → Data/Animation.hpp/cpp Updated all include paths across the codebase to reflect new structure. Co-Authored-By: Claude Sonnet 4.5 --- src/Assets/CPUAccelerationStructure.h | 2 +- src/Assets/{ => Data}/Animation.cpp | 2 +- src/Assets/{ => Data}/Animation.hpp | 0 src/Assets/{ => Data}/Material.hpp | 0 src/Assets/{ => Data}/Skeleton.hpp | 0 src/Assets/{ => Data}/Vertex.hpp | 0 src/Assets/FProcModel.cpp | 2 +- src/Assets/FSceneLoader.cpp | 2 +- src/Assets/FSceneLoader.h | 2 +- src/Assets/FSceneSaver.cpp | 2 +- src/Assets/Model.hpp | 2 +- src/Assets/Scene.hpp | 2 +- src/Editor/Panels/MaterialEditorPanel.cpp | 2 +- src/Rendering/PipelineCommon/CommonComputePipeline.cpp | 2 +- src/Runtime/Components/SkinnedMeshComponent.h | 4 ++-- src/Runtime/Scene/SceneList.cpp | 4 ++-- src/Runtime/Subsystems/NextAnimation.h | 2 +- src/Tests/Test_GltfSkinning.cpp | 2 +- src/Vulkan/RayTracing/BottomLevelGeometry.cpp | 2 +- 19 files changed, 17 insertions(+), 17 deletions(-) rename src/Assets/{ => Data}/Animation.cpp (97%) rename src/Assets/{ => Data}/Animation.hpp (100%) rename src/Assets/{ => Data}/Material.hpp (100%) rename src/Assets/{ => Data}/Skeleton.hpp (100%) rename src/Assets/{ => Data}/Vertex.hpp (100%) diff --git a/src/Assets/CPUAccelerationStructure.h b/src/Assets/CPUAccelerationStructure.h index 52380ca3..627bf69f 100644 --- a/src/Assets/CPUAccelerationStructure.h +++ b/src/Assets/CPUAccelerationStructure.h @@ -6,7 +6,7 @@ #include #include -#include "Material.hpp" +#include "Data/Material.hpp" namespace std { template <> diff --git a/src/Assets/Animation.cpp b/src/Assets/Data/Animation.cpp similarity index 97% rename from src/Assets/Animation.cpp rename to src/Assets/Data/Animation.cpp index e4e0e766..5efa3c81 100644 --- a/src/Assets/Animation.cpp +++ b/src/Assets/Data/Animation.cpp @@ -1,4 +1,4 @@ -#include "Animation.hpp" +#include "Assets/Data/Animation.hpp" #if WITH_OZZ #include "ozz/animation/runtime/animation.h" diff --git a/src/Assets/Animation.hpp b/src/Assets/Data/Animation.hpp similarity index 100% rename from src/Assets/Animation.hpp rename to src/Assets/Data/Animation.hpp diff --git a/src/Assets/Material.hpp b/src/Assets/Data/Material.hpp similarity index 100% rename from src/Assets/Material.hpp rename to src/Assets/Data/Material.hpp diff --git a/src/Assets/Skeleton.hpp b/src/Assets/Data/Skeleton.hpp similarity index 100% rename from src/Assets/Skeleton.hpp rename to src/Assets/Data/Skeleton.hpp diff --git a/src/Assets/Vertex.hpp b/src/Assets/Data/Vertex.hpp similarity index 100% rename from src/Assets/Vertex.hpp rename to src/Assets/Data/Vertex.hpp diff --git a/src/Assets/FProcModel.cpp b/src/Assets/FProcModel.cpp index e18da3b9..35110730 100644 --- a/src/Assets/FProcModel.cpp +++ b/src/Assets/FProcModel.cpp @@ -1,5 +1,5 @@ #include "FProcModel.h" -#include "Material.hpp" +#include "Data/Material.hpp" #define _USE_MATH_DEFINES #include diff --git a/src/Assets/FSceneLoader.cpp b/src/Assets/FSceneLoader.cpp index 3e09de3d..e9d74446 100644 --- a/src/Assets/FSceneLoader.cpp +++ b/src/Assets/FSceneLoader.cpp @@ -23,7 +23,7 @@ #define STB_IMAGE_WRITE_IMPLEMENTATION #include -#include "Material.hpp" +#include "Data/Material.hpp" #include "Node.h" #include "Runtime/Components/RenderComponent.h" #include "Utilities/FileHelper.hpp" diff --git a/src/Assets/FSceneLoader.h b/src/Assets/FSceneLoader.h index 4c960239..18c09f70 100644 --- a/src/Assets/FSceneLoader.h +++ b/src/Assets/FSceneLoader.h @@ -1,7 +1,7 @@ #pragma once #include "Model.hpp" -#include "Skeleton.hpp" +#include "Data/Skeleton.hpp" namespace Assets { diff --git a/src/Assets/FSceneSaver.cpp b/src/Assets/FSceneSaver.cpp index 9e19556a..16b3ac32 100644 --- a/src/Assets/FSceneSaver.cpp +++ b/src/Assets/FSceneSaver.cpp @@ -8,7 +8,7 @@ #include "Scene.hpp" #include "Node.h" #include "Model.hpp" -#include "Material.hpp" +#include "Data/Material.hpp" #include "Runtime/Components/RenderComponent.h" // Dummy image loader for TinyGLTF (we don't load images when saving) diff --git a/src/Assets/Model.hpp b/src/Assets/Model.hpp index 59b6c1f3..a99bbcfd 100644 --- a/src/Assets/Model.hpp +++ b/src/Assets/Model.hpp @@ -1,5 +1,5 @@ #pragma once -#include "Vertex.hpp" +#include "Data/Vertex.hpp" #include "Texture.hpp" #include "UniformBuffer.hpp" #include diff --git a/src/Assets/Scene.hpp b/src/Assets/Scene.hpp index 813f7a29..2caf3055 100644 --- a/src/Assets/Scene.hpp +++ b/src/Assets/Scene.hpp @@ -10,7 +10,7 @@ #include "Model.hpp" #include "Runtime/Subsystems/NextPhysics.h" #include "Runtime/Subsystems/NextPhysicsTypes.h" -#include "Skeleton.hpp" +#include "Data/Skeleton.hpp" namespace Vulkan { diff --git a/src/Editor/Panels/MaterialEditorPanel.cpp b/src/Editor/Panels/MaterialEditorPanel.cpp index eaa83ad6..dcda47b7 100644 --- a/src/Editor/Panels/MaterialEditorPanel.cpp +++ b/src/Editor/Panels/MaterialEditorPanel.cpp @@ -1,6 +1,6 @@ #include "Editor/EditorUi.hpp" -#include "Assets/Material.hpp" +#include "Assets/Data/Material.hpp" #include "Assets/Scene.hpp" #include "Editor/Nodes/NodeMaterial.hpp" #include "Editor/Nodes/NodeSetFloat.hpp" diff --git a/src/Rendering/PipelineCommon/CommonComputePipeline.cpp b/src/Rendering/PipelineCommon/CommonComputePipeline.cpp index 145a558a..953f5d70 100644 --- a/src/Rendering/PipelineCommon/CommonComputePipeline.cpp +++ b/src/Rendering/PipelineCommon/CommonComputePipeline.cpp @@ -14,7 +14,7 @@ #include "Assets/Scene.hpp" #include "Assets/UniformBuffer.hpp" -#include "Assets/Vertex.hpp" +#include "Assets/Data/Vertex.hpp" namespace Vulkan::PipelineCommon { diff --git a/src/Runtime/Components/SkinnedMeshComponent.h b/src/Runtime/Components/SkinnedMeshComponent.h index 562a2ab6..3884f452 100644 --- a/src/Runtime/Components/SkinnedMeshComponent.h +++ b/src/Runtime/Components/SkinnedMeshComponent.h @@ -1,7 +1,7 @@ #pragma once #include "Common/CoreMinimal.hpp" -#include "Assets/Skeleton.hpp" -#include "Assets/Animation.hpp" +#include "Assets/Data/Skeleton.hpp" +#include "Assets/Data/Animation.hpp" #include "Assets/Model.hpp" #include "Assets/Component.h" #include "Runtime/Reflection/ReflectionMacros.h" diff --git a/src/Runtime/Scene/SceneList.cpp b/src/Runtime/Scene/SceneList.cpp index a4f460de..3ce069fa 100644 --- a/src/Runtime/Scene/SceneList.cpp +++ b/src/Runtime/Scene/SceneList.cpp @@ -1,7 +1,7 @@ #include "Runtime/Scene/SceneList.hpp" #include "Common/CoreMinimal.hpp" #include "Utilities/FileHelper.hpp" -#include "Assets/Material.hpp" +#include "Assets/Data/Material.hpp" #include "Assets/Model.hpp" #include "Runtime/Engine.hpp" @@ -17,7 +17,7 @@ #include "Assets/Node.h" #include "Runtime/Components/RenderComponent.h" #include "Runtime/Components/PhysicsComponent.h" -#include "Assets/Skeleton.hpp" +#include "Assets/Data/Skeleton.hpp" #include "Runtime/Components/SkinnedMeshComponent.h" #include diff --git a/src/Runtime/Subsystems/NextAnimation.h b/src/Runtime/Subsystems/NextAnimation.h index 3fbc6280..59739fcd 100644 --- a/src/Runtime/Subsystems/NextAnimation.h +++ b/src/Runtime/Subsystems/NextAnimation.h @@ -1,6 +1,6 @@ #pragma once #include "Vulkan/Vulkan.hpp" -#include "Assets/Animation.hpp" +#include "Assets/Data/Animation.hpp" class NextAnimation final { diff --git a/src/Tests/Test_GltfSkinning.cpp b/src/Tests/Test_GltfSkinning.cpp index dc4799a4..e25a1007 100644 --- a/src/Tests/Test_GltfSkinning.cpp +++ b/src/Tests/Test_GltfSkinning.cpp @@ -1,7 +1,7 @@ #include #include "Assets/FSceneLoader.h" #include "Assets/Model.hpp" -#include "Assets/Material.hpp" +#include "Assets/Data/Material.hpp" #include #include diff --git a/src/Vulkan/RayTracing/BottomLevelGeometry.cpp b/src/Vulkan/RayTracing/BottomLevelGeometry.cpp index 27d7984f..c6249dea 100644 --- a/src/Vulkan/RayTracing/BottomLevelGeometry.cpp +++ b/src/Vulkan/RayTracing/BottomLevelGeometry.cpp @@ -1,7 +1,7 @@ #include "BottomLevelGeometry.hpp" #include "DeviceProcedures.hpp" #include "Assets/Scene.hpp" -#include "Assets/Vertex.hpp" +#include "Assets/Data/Vertex.hpp" #include "Vulkan/Buffer.hpp" namespace Vulkan::RayTracing { From 5fb7f0e87cc7e387dce2002416702d7f43dd89b8 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Mon, 2 Feb 2026 14:15:16 +0800 Subject: [PATCH 37/53] refactor(assets): move acceleration structures to Assets/Acceleration/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move CPU acceleration structure files to dedicated Acceleration/ subdirectory. Files moved: - CPUAccelerationStructure.h/cpp → Acceleration/CPUAccelerationStructure.h/cpp Updated all include paths to use absolute paths from src root. Co-Authored-By: Claude Sonnet 4.5 --- src/Assets/{ => Acceleration}/CPUAccelerationStructure.cpp | 4 ++-- src/Assets/{ => Acceleration}/CPUAccelerationStructure.h | 2 +- src/Assets/Scene.hpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename src/Assets/{ => Acceleration}/CPUAccelerationStructure.cpp (99%) rename src/Assets/{ => Acceleration}/CPUAccelerationStructure.h (98%) diff --git a/src/Assets/CPUAccelerationStructure.cpp b/src/Assets/Acceleration/CPUAccelerationStructure.cpp similarity index 99% rename from src/Assets/CPUAccelerationStructure.cpp rename to src/Assets/Acceleration/CPUAccelerationStructure.cpp index f7e7bc61..f7f1bf2d 100644 --- a/src/Assets/CPUAccelerationStructure.cpp +++ b/src/Assets/Acceleration/CPUAccelerationStructure.cpp @@ -1,9 +1,9 @@ -#include "CPUAccelerationStructure.h" +#include "Assets/Acceleration/CPUAccelerationStructure.h" #include "Runtime/Subsystems/TaskCoordinator.hpp" #include "Vulkan/DeviceMemory.hpp" #include "Assets/Node.h" #include "Runtime/Components/RenderComponent.h" -#include "TextureImage.hpp" +#include "Assets/TextureImage.hpp" #include "Runtime/Engine.hpp" #include "Assets/Scene.hpp" diff --git a/src/Assets/CPUAccelerationStructure.h b/src/Assets/Acceleration/CPUAccelerationStructure.h similarity index 98% rename from src/Assets/CPUAccelerationStructure.h rename to src/Assets/Acceleration/CPUAccelerationStructure.h index 627bf69f..81ed341a 100644 --- a/src/Assets/CPUAccelerationStructure.h +++ b/src/Assets/Acceleration/CPUAccelerationStructure.h @@ -6,7 +6,7 @@ #include #include -#include "Data/Material.hpp" +#include "Assets/Data/Material.hpp" namespace std { template <> diff --git a/src/Assets/Scene.hpp b/src/Assets/Scene.hpp index 2caf3055..71fcf7c4 100644 --- a/src/Assets/Scene.hpp +++ b/src/Assets/Scene.hpp @@ -6,7 +6,7 @@ #include "Common/CoreMinimal.hpp" #include "Vulkan/Vulkan.hpp" -#include "CPUAccelerationStructure.h" +#include "Acceleration/CPUAccelerationStructure.h" #include "Model.hpp" #include "Runtime/Subsystems/NextPhysics.h" #include "Runtime/Subsystems/NextPhysicsTypes.h" From 1ea9c99161c834417b3cd544771f4daba56a7fcd Mon Sep 17 00:00:00 2001 From: gameKnife Date: Mon, 2 Feb 2026 14:21:46 +0800 Subject: [PATCH 38/53] refactor(assets): move loaders and savers to dedicated subdirectories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move loader and saver files to Loaders/ and Savers/ subdirectories. Files moved: - FSceneLoader.h/cpp → Loaders/FSceneLoader.h/cpp - FProcModel.h/cpp → Loaders/FProcModel.h/cpp - FSceneSaver.h/cpp → Savers/FSceneSaver.h/cpp Updated CMakeLists.txt to reflect new paths for Unity Build exclusions. Fixed all include paths to use absolute paths from src root. Co-Authored-By: Claude Sonnet 4.5 --- src/Application/gkNextRenderer/gkNextRenderer.cpp | 2 +- src/Assets/{ => Loaders}/FProcModel.cpp | 4 ++-- src/Assets/{ => Loaders}/FProcModel.h | 2 +- src/Assets/{ => Loaders}/FSceneLoader.cpp | 8 +++++--- src/Assets/{ => Loaders}/FSceneLoader.h | 4 ++-- src/Assets/Model.cpp | 2 +- src/Assets/Model.hpp | 2 +- src/Assets/{ => Savers}/FSceneSaver.cpp | 10 +++++----- src/Assets/{ => Savers}/FSceneSaver.h | 0 src/Assets/Scene.cpp | 2 +- src/Assets/Scene.hpp | 2 +- src/CMakeLists.txt | 4 ++-- src/Runtime/Scene/SceneList.cpp | 4 ++-- src/Tests/Test_GltfSkinning.cpp | 2 +- 14 files changed, 25 insertions(+), 23 deletions(-) rename src/Assets/{ => Loaders}/FProcModel.cpp (99%) rename src/Assets/{ => Loaders}/FProcModel.h (93%) rename src/Assets/{ => Loaders}/FSceneLoader.cpp (99%) rename src/Assets/{ => Loaders}/FSceneLoader.h (92%) rename src/Assets/{ => Savers}/FSceneSaver.cpp (99%) rename src/Assets/{ => Savers}/FSceneSaver.h (100%) diff --git a/src/Application/gkNextRenderer/gkNextRenderer.cpp b/src/Application/gkNextRenderer/gkNextRenderer.cpp index 7edaf349..40e60d5f 100644 --- a/src/Application/gkNextRenderer/gkNextRenderer.cpp +++ b/src/Application/gkNextRenderer/gkNextRenderer.cpp @@ -5,7 +5,7 @@ #include -#include "Assets/FProcModel.h" +#include "Assets/Loaders/FProcModel.h" #include "Assets/Node.h" #include "Runtime/Components/RenderComponent.h" #include "Runtime/Components/PhysicsComponent.h" diff --git a/src/Assets/FProcModel.cpp b/src/Assets/Loaders/FProcModel.cpp similarity index 99% rename from src/Assets/FProcModel.cpp rename to src/Assets/Loaders/FProcModel.cpp index 35110730..6660326a 100644 --- a/src/Assets/FProcModel.cpp +++ b/src/Assets/Loaders/FProcModel.cpp @@ -1,5 +1,5 @@ -#include "FProcModel.h" -#include "Data/Material.hpp" +#include "Assets/Loaders/FProcModel.h" +#include "Assets/Data/Material.hpp" #define _USE_MATH_DEFINES #include diff --git a/src/Assets/FProcModel.h b/src/Assets/Loaders/FProcModel.h similarity index 93% rename from src/Assets/FProcModel.h rename to src/Assets/Loaders/FProcModel.h index 39ff4957..d9d613fd 100644 --- a/src/Assets/FProcModel.h +++ b/src/Assets/Loaders/FProcModel.h @@ -1,5 +1,5 @@ #pragma once -#include "Model.hpp" +#include "Assets/Model.hpp" namespace Assets { diff --git a/src/Assets/FSceneLoader.cpp b/src/Assets/Loaders/FSceneLoader.cpp similarity index 99% rename from src/Assets/FSceneLoader.cpp rename to src/Assets/Loaders/FSceneLoader.cpp index e9d74446..be1defa3 100644 --- a/src/Assets/FSceneLoader.cpp +++ b/src/Assets/Loaders/FSceneLoader.cpp @@ -1,4 +1,4 @@ -#include "FSceneLoader.h" +#include "Assets/Loaders/FSceneLoader.h" #include "Common/CoreMinimal.hpp" #include @@ -20,11 +20,13 @@ #define TINYGLTF_NO_STB_IMAGE //#define TINYGLTF_NO_STB_IMAGE_WRITE +#ifndef STB_IMAGE_WRITE_IMPLEMENTATION #define STB_IMAGE_WRITE_IMPLEMENTATION +#endif #include -#include "Data/Material.hpp" -#include "Node.h" +#include "Assets/Data/Material.hpp" +#include "Assets/Node.h" #include "Runtime/Components/RenderComponent.h" #include "Utilities/FileHelper.hpp" diff --git a/src/Assets/FSceneLoader.h b/src/Assets/Loaders/FSceneLoader.h similarity index 92% rename from src/Assets/FSceneLoader.h rename to src/Assets/Loaders/FSceneLoader.h index 18c09f70..e5e68d2a 100644 --- a/src/Assets/FSceneLoader.h +++ b/src/Assets/Loaders/FSceneLoader.h @@ -1,7 +1,7 @@ #pragma once -#include "Model.hpp" -#include "Data/Skeleton.hpp" +#include "Assets/Model.hpp" +#include "Assets/Data/Skeleton.hpp" namespace Assets { diff --git a/src/Assets/Model.cpp b/src/Assets/Model.cpp index 6ff51e5a..6b372591 100644 --- a/src/Assets/Model.cpp +++ b/src/Assets/Model.cpp @@ -1,6 +1,6 @@ #include "Model.hpp" #include "Utilities/FileHelper.hpp" -#include "FSceneLoader.h" +#include "Loaders/FSceneLoader.h" #define GLM_ENABLE_EXPERIMENTAL #include diff --git a/src/Assets/Model.hpp b/src/Assets/Model.hpp index a99bbcfd..1e3b1cee 100644 --- a/src/Assets/Model.hpp +++ b/src/Assets/Model.hpp @@ -1,5 +1,5 @@ #pragma once -#include "Data/Vertex.hpp" +#include "Assets/Data/Vertex.hpp" #include "Texture.hpp" #include "UniformBuffer.hpp" #include diff --git a/src/Assets/FSceneSaver.cpp b/src/Assets/Savers/FSceneSaver.cpp similarity index 99% rename from src/Assets/FSceneSaver.cpp rename to src/Assets/Savers/FSceneSaver.cpp index 16b3ac32..9e785442 100644 --- a/src/Assets/FSceneSaver.cpp +++ b/src/Assets/Savers/FSceneSaver.cpp @@ -1,14 +1,14 @@ #include "Common/CoreMinimal.hpp" -#include "FSceneSaver.h" +#include "Assets/Savers/FSceneSaver.h" #include #include #include -#include "Scene.hpp" -#include "Node.h" -#include "Model.hpp" -#include "Data/Material.hpp" +#include "Assets/Scene.hpp" +#include "Assets/Node.h" +#include "Assets/Model.hpp" +#include "Assets/Data/Material.hpp" #include "Runtime/Components/RenderComponent.h" // Dummy image loader for TinyGLTF (we don't load images when saving) diff --git a/src/Assets/FSceneSaver.h b/src/Assets/Savers/FSceneSaver.h similarity index 100% rename from src/Assets/FSceneSaver.h rename to src/Assets/Savers/FSceneSaver.h diff --git a/src/Assets/Scene.cpp b/src/Assets/Scene.cpp index 925ab3f3..9afb16d9 100644 --- a/src/Assets/Scene.cpp +++ b/src/Assets/Scene.cpp @@ -6,7 +6,7 @@ #include #include "Assets/TextureImage.hpp" #include "Common/CoreMinimal.hpp" -#include "FSceneSaver.h" +#include "Savers/FSceneSaver.h" #include "Model.hpp" #include "Options.hpp" #include "Runtime/Subsystems/NextPhysics.h" diff --git a/src/Assets/Scene.hpp b/src/Assets/Scene.hpp index 71fcf7c4..f9280b30 100644 --- a/src/Assets/Scene.hpp +++ b/src/Assets/Scene.hpp @@ -10,7 +10,7 @@ #include "Model.hpp" #include "Runtime/Subsystems/NextPhysics.h" #include "Runtime/Subsystems/NextPhysicsTypes.h" -#include "Data/Skeleton.hpp" +#include "Assets/Data/Skeleton.hpp" namespace Vulkan { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index be35c92c..0817a29a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -193,8 +193,8 @@ foreach(target IN LISTS AllTargets) set_source_files_properties(${src_files_thirdparty} PROPERTIES UNITY_GROUP "thirdparty") # Exclude files with STB implementation from Unity Build to avoid redefinition conflicts - set_source_files_properties(Assets/FSceneLoader.cpp PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) - set_source_files_properties(Assets/FSceneSaver.cpp PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) + set_source_files_properties(Assets/Loaders/FSceneLoader.cpp PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) + set_source_files_properties(Assets/Savers/FSceneSaver.cpp PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) endif() find_package(WebP CONFIG REQUIRED) target_link_libraries(${target} PRIVATE SDL3::SDL3 xxHash::xxhash meshoptimizer::meshoptimizer fmt::fmt CURL::libcurl glm::glm imgui::imgui draco::draco WebP::webp ${extra_libs}) diff --git a/src/Runtime/Scene/SceneList.cpp b/src/Runtime/Scene/SceneList.cpp index 3ce069fa..8f21a833 100644 --- a/src/Runtime/Scene/SceneList.cpp +++ b/src/Runtime/Scene/SceneList.cpp @@ -12,8 +12,8 @@ #include #include -#include "Assets/FProcModel.h" -#include "Assets/FSceneLoader.h" +#include "Assets/Loaders/FProcModel.h" +#include "Assets/Loaders/FSceneLoader.h" #include "Assets/Node.h" #include "Runtime/Components/RenderComponent.h" #include "Runtime/Components/PhysicsComponent.h" diff --git a/src/Tests/Test_GltfSkinning.cpp b/src/Tests/Test_GltfSkinning.cpp index e25a1007..34f4b9e8 100644 --- a/src/Tests/Test_GltfSkinning.cpp +++ b/src/Tests/Test_GltfSkinning.cpp @@ -1,5 +1,5 @@ #include -#include "Assets/FSceneLoader.h" +#include "Assets/Loaders/FSceneLoader.h" #include "Assets/Model.hpp" #include "Assets/Data/Material.hpp" #include From 2d4ef19b3e20bc827b08ad5623564976c4290fe2 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Mon, 2 Feb 2026 15:08:45 +0800 Subject: [PATCH 39/53] refactor(assets): move GPU resources to Assets/GPU/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move GPU resource management files to dedicated GPU/ subdirectory. Files moved: - UniformBuffer.hpp/cpp → GPU/UniformBuffer.hpp/cpp - TextureImage.hpp/cpp → GPU/TextureImage.hpp/cpp - Texture.hpp/cpp → GPU/Texture.hpp/cpp Updated all include paths across Runtime, Rendering, Editor, and Vulkan modules. Co-Authored-By: Claude Sonnet 4.5 --- src/Assets/Acceleration/CPUAccelerationStructure.cpp | 2 +- src/Assets/Acceleration/CPUAccelerationStructure.h | 2 +- src/Assets/{ => GPU}/Texture.cpp | 4 ++-- src/Assets/{ => GPU}/Texture.hpp | 2 +- src/Assets/{ => GPU}/TextureImage.cpp | 4 ++-- src/Assets/{ => GPU}/TextureImage.hpp | 0 src/Assets/{ => GPU}/UniformBuffer.cpp | 2 +- src/Assets/{ => GPU}/UniformBuffer.hpp | 0 src/Assets/Model.hpp | 4 ++-- src/Assets/Node.h | 2 +- src/Assets/Scene.cpp | 2 +- src/Editor/EditorInterface.cpp | 2 +- src/Editor/Panels/ContentBrowserPanel.cpp | 2 +- src/Rendering/PipelineCommon/CommonComputePipeline.cpp | 2 +- src/Rendering/VulkanBaseRenderer.cpp | 4 ++-- src/Rendering/VulkanBaseRenderer.hpp | 2 +- src/Runtime/Config/UserSettings.hpp | 2 +- src/Runtime/Editor/UserInterface.cpp | 2 +- src/Runtime/Engine.cpp | 4 ++-- src/Runtime/Engine.hpp | 2 +- src/Vulkan/PipelineLayout.cpp | 2 +- 21 files changed, 24 insertions(+), 24 deletions(-) rename src/Assets/{ => GPU}/Texture.cpp (99%) rename src/Assets/{ => GPU}/Texture.hpp (98%) rename src/Assets/{ => GPU}/TextureImage.cpp (99%) rename src/Assets/{ => GPU}/TextureImage.hpp (100%) rename src/Assets/{ => GPU}/UniformBuffer.cpp (96%) rename src/Assets/{ => GPU}/UniformBuffer.hpp (100%) diff --git a/src/Assets/Acceleration/CPUAccelerationStructure.cpp b/src/Assets/Acceleration/CPUAccelerationStructure.cpp index f7f1bf2d..db5b01ac 100644 --- a/src/Assets/Acceleration/CPUAccelerationStructure.cpp +++ b/src/Assets/Acceleration/CPUAccelerationStructure.cpp @@ -3,7 +3,7 @@ #include "Vulkan/DeviceMemory.hpp" #include "Assets/Node.h" #include "Runtime/Components/RenderComponent.h" -#include "Assets/TextureImage.hpp" +#include "Assets/GPU/TextureImage.hpp" #include "Runtime/Engine.hpp" #include "Assets/Scene.hpp" diff --git a/src/Assets/Acceleration/CPUAccelerationStructure.h b/src/Assets/Acceleration/CPUAccelerationStructure.h index 81ed341a..3bdad6a2 100644 --- a/src/Assets/Acceleration/CPUAccelerationStructure.h +++ b/src/Assets/Acceleration/CPUAccelerationStructure.h @@ -1,6 +1,6 @@ #pragma once #include "Common/CoreMinimal.hpp" -#include "Assets/UniformBuffer.hpp" +#include "Assets/GPU/UniformBuffer.hpp" #include #include "ThirdParty/tinybvh/tiny_bvh.h" #include diff --git a/src/Assets/Texture.cpp b/src/Assets/GPU/Texture.cpp similarity index 99% rename from src/Assets/Texture.cpp rename to src/Assets/GPU/Texture.cpp index 23a3eeb2..83079272 100644 --- a/src/Assets/Texture.cpp +++ b/src/Assets/GPU/Texture.cpp @@ -1,11 +1,11 @@ -#include "Texture.hpp" +#include "Assets/GPU/Texture.hpp" #include "Utilities/StbImage.hpp" #include "Utilities/Exception.hpp" #include "Common/CoreMinimal.hpp" #include "Options.hpp" #include "Runtime/Subsystems/TaskCoordinator.hpp" -#include "TextureImage.hpp" +#include "Assets/GPU/TextureImage.hpp" #include "Runtime/Engine.hpp" #include "Utilities/FileHelper.hpp" #include "Vulkan/Device.hpp" diff --git a/src/Assets/Texture.hpp b/src/Assets/GPU/Texture.hpp similarity index 98% rename from src/Assets/Texture.hpp rename to src/Assets/GPU/Texture.hpp index d289bdcc..96434829 100644 --- a/src/Assets/Texture.hpp +++ b/src/Assets/GPU/Texture.hpp @@ -7,7 +7,7 @@ #include #include #include -#include "UniformBuffer.hpp" +#include "Assets/GPU/UniformBuffer.hpp" #include "Vulkan/DescriptorSetLayout.hpp" #include "Vulkan/DescriptorSetManager.hpp" #include "Vulkan/DescriptorSets.hpp" diff --git a/src/Assets/TextureImage.cpp b/src/Assets/GPU/TextureImage.cpp similarity index 99% rename from src/Assets/TextureImage.cpp rename to src/Assets/GPU/TextureImage.cpp index b2e9a1bc..1e63206d 100644 --- a/src/Assets/TextureImage.cpp +++ b/src/Assets/GPU/TextureImage.cpp @@ -1,5 +1,5 @@ -#include "TextureImage.hpp" -#include "Texture.hpp" +#include "Assets/GPU/TextureImage.hpp" +#include "Assets/GPU/Texture.hpp" #include "Vulkan/Buffer.hpp" #include "Vulkan/CommandPool.hpp" #include "Vulkan/ImageView.hpp" diff --git a/src/Assets/TextureImage.hpp b/src/Assets/GPU/TextureImage.hpp similarity index 100% rename from src/Assets/TextureImage.hpp rename to src/Assets/GPU/TextureImage.hpp diff --git a/src/Assets/UniformBuffer.cpp b/src/Assets/GPU/UniformBuffer.cpp similarity index 96% rename from src/Assets/UniformBuffer.cpp rename to src/Assets/GPU/UniformBuffer.cpp index 11b6c1ab..8b5b4a34 100644 --- a/src/Assets/UniformBuffer.cpp +++ b/src/Assets/GPU/UniformBuffer.cpp @@ -1,4 +1,4 @@ -#include "UniformBuffer.hpp" +#include "Assets/GPU/UniformBuffer.hpp" #include "Vulkan/Buffer.hpp" #include "Vulkan/CommandPool.hpp" #include diff --git a/src/Assets/UniformBuffer.hpp b/src/Assets/GPU/UniformBuffer.hpp similarity index 100% rename from src/Assets/UniformBuffer.hpp rename to src/Assets/GPU/UniformBuffer.hpp diff --git a/src/Assets/Model.hpp b/src/Assets/Model.hpp index 1e3b1cee..d2b36545 100644 --- a/src/Assets/Model.hpp +++ b/src/Assets/Model.hpp @@ -1,7 +1,7 @@ #pragma once #include "Assets/Data/Vertex.hpp" -#include "Texture.hpp" -#include "UniformBuffer.hpp" +#include "GPU/Texture.hpp" +#include "GPU/UniformBuffer.hpp" #include struct FNextPhysicsBody; diff --git a/src/Assets/Node.h b/src/Assets/Node.h index 4dad423a..5ccad168 100644 --- a/src/Assets/Node.h +++ b/src/Assets/Node.h @@ -1,6 +1,6 @@ #pragma once #include "Common/CoreMinimal.hpp" -#include "UniformBuffer.hpp" +#include "GPU/UniformBuffer.hpp" #include "Runtime/Subsystems/NextPhysics.h" #include "Component.h" #include "Runtime/Components/PhysicsComponent.h" diff --git a/src/Assets/Scene.cpp b/src/Assets/Scene.cpp index 9afb16d9..e06147fe 100644 --- a/src/Assets/Scene.cpp +++ b/src/Assets/Scene.cpp @@ -4,7 +4,7 @@ #include #include #include -#include "Assets/TextureImage.hpp" +#include "Assets/GPU/TextureImage.hpp" #include "Common/CoreMinimal.hpp" #include "Savers/FSceneSaver.h" #include "Model.hpp" diff --git a/src/Editor/EditorInterface.cpp b/src/Editor/EditorInterface.cpp index 1082b4c1..a8a787bf 100644 --- a/src/Editor/EditorInterface.cpp +++ b/src/Editor/EditorInterface.cpp @@ -14,7 +14,7 @@ #include "Editor/EditorUi.hpp" #include "Assets/Scene.hpp" -#include "Assets/TextureImage.hpp" +#include "Assets/GPU/TextureImage.hpp" #include "Common/CoreMinimal.hpp" #include "Editor/EditorActionDispatcher.hpp" #include "Editor/EditorContext.hpp" diff --git a/src/Editor/Panels/ContentBrowserPanel.cpp b/src/Editor/Panels/ContentBrowserPanel.cpp index 1c5f6c27..31c01807 100644 --- a/src/Editor/Panels/ContentBrowserPanel.cpp +++ b/src/Editor/Panels/ContentBrowserPanel.cpp @@ -3,7 +3,7 @@ #include "Editor/EditorDragDrop.hpp" #include "Assets/Scene.hpp" -#include "Assets/TextureImage.hpp" +#include "Assets/GPU/TextureImage.hpp" #include "Editor/EditorActionDispatcher.hpp" #include "Runtime/Editor/UserInterface.hpp" #include "ThirdParty/fontawesome/IconsFontAwesome6.h" diff --git a/src/Rendering/PipelineCommon/CommonComputePipeline.cpp b/src/Rendering/PipelineCommon/CommonComputePipeline.cpp index 953f5d70..d0271a60 100644 --- a/src/Rendering/PipelineCommon/CommonComputePipeline.cpp +++ b/src/Rendering/PipelineCommon/CommonComputePipeline.cpp @@ -13,7 +13,7 @@ #include "Vulkan/RenderPass.hpp" #include "Assets/Scene.hpp" -#include "Assets/UniformBuffer.hpp" +#include "Assets/GPU/UniformBuffer.hpp" #include "Assets/Data/Vertex.hpp" namespace Vulkan::PipelineCommon diff --git a/src/Rendering/VulkanBaseRenderer.cpp b/src/Rendering/VulkanBaseRenderer.cpp index 80580342..84b1e775 100644 --- a/src/Rendering/VulkanBaseRenderer.cpp +++ b/src/Rendering/VulkanBaseRenderer.cpp @@ -23,8 +23,8 @@ #include "Vulkan/Version.hpp" #include "Assets/Scene.hpp" -#include "Assets/UniformBuffer.hpp" -#include "Assets/Texture.hpp" +#include "Assets/GPU/UniformBuffer.hpp" +#include "Assets/GPU/Texture.hpp" #include "Assets/Node.h" #include "Runtime/Components/RenderComponent.h" #include "Runtime/Components/SkinnedMeshComponent.h" diff --git a/src/Rendering/VulkanBaseRenderer.hpp b/src/Rendering/VulkanBaseRenderer.hpp index c99efc6d..395f7819 100644 --- a/src/Rendering/VulkanBaseRenderer.hpp +++ b/src/Rendering/VulkanBaseRenderer.hpp @@ -3,7 +3,7 @@ #include "Vulkan/FrameBuffer.hpp" #include "Vulkan/VulkanGpuTimer.hpp" #include "Vulkan/Image.hpp" -#include "Assets/UniformBuffer.hpp" +#include "Assets/GPU/UniformBuffer.hpp" #include "Assets/Scene.hpp" #include #include diff --git a/src/Runtime/Config/UserSettings.hpp b/src/Runtime/Config/UserSettings.hpp index dc272e1b..18e36a48 100644 --- a/src/Runtime/Config/UserSettings.hpp +++ b/src/Runtime/Config/UserSettings.hpp @@ -1,5 +1,5 @@ #pragma once -#include "Assets/UniformBuffer.hpp" +#include "Assets/GPU/UniformBuffer.hpp" #include "Assets/Model.hpp" diff --git a/src/Runtime/Editor/UserInterface.cpp b/src/Runtime/Editor/UserInterface.cpp index 99d191f6..654f22d3 100644 --- a/src/Runtime/Editor/UserInterface.cpp +++ b/src/Runtime/Editor/UserInterface.cpp @@ -29,7 +29,7 @@ #include #include -#include "Assets/TextureImage.hpp" +#include "Assets/GPU/TextureImage.hpp" #include "Options.hpp" #include "Rendering/VulkanBaseRenderer.hpp" #include "Runtime/Subsystems/TaskCoordinator.hpp" diff --git a/src/Runtime/Engine.cpp b/src/Runtime/Engine.cpp index 3fcb4899..e4af300c 100644 --- a/src/Runtime/Engine.cpp +++ b/src/Runtime/Engine.cpp @@ -2,8 +2,8 @@ #include "Assets/Model.hpp" #include "Assets/Node.h" #include "Assets/Scene.hpp" -#include "Assets/Texture.hpp" -#include "Assets/UniformBuffer.hpp" +#include "Assets/GPU/Texture.hpp" +#include "Assets/GPU/UniformBuffer.hpp" #include "Runtime/Subsystems/QuickJSEngine.hpp" #include "Runtime/Command/DeleteNodeCommand.hpp" #include "Runtime/ScreenShot.hpp" diff --git a/src/Runtime/Engine.hpp b/src/Runtime/Engine.hpp index 5d6cb2db..eacc84fd 100644 --- a/src/Runtime/Engine.hpp +++ b/src/Runtime/Engine.hpp @@ -1,7 +1,7 @@ #pragma once #include "Assets/Model.hpp" -#include "Assets/UniformBuffer.hpp" +#include "Assets/GPU/UniformBuffer.hpp" #include "Common/CoreMinimal.hpp" #include "Options.hpp" #include "Rendering/VulkanBaseRenderer.hpp" diff --git a/src/Vulkan/PipelineLayout.cpp b/src/Vulkan/PipelineLayout.cpp index 3df489c7..551da848 100644 --- a/src/Vulkan/PipelineLayout.cpp +++ b/src/Vulkan/PipelineLayout.cpp @@ -1,7 +1,7 @@ #include "PipelineLayout.hpp" #include "DescriptorSetLayout.hpp" #include "Device.hpp" -#include "Assets/Texture.hpp" +#include "Assets/GPU/Texture.hpp" namespace Vulkan { From 6013e835acc148aa04643a4921b09747fcb66e52 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Mon, 2 Feb 2026 15:14:32 +0800 Subject: [PATCH 40/53] refactor(assets): move core data models to Assets/Core/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move core data model files to dedicated Core/ subdirectory. Files moved: - Component.h → Core/Component.h - Node.h/cpp → Core/Node.h/cpp - Model.hpp/cpp → Core/Model.hpp/cpp - Scene.hpp/cpp → Core/Scene.hpp/cpp Updated all include paths across the entire codebase (Runtime, Rendering, Editor, Application, Tests, and Vulkan modules). Fixed all internal references within Core files to use absolute paths. Co-Authored-By: Claude Sonnet 4.5 --- .../MagicaLego/MagicaLegoGameInstance.cpp | 4 +- .../gkNextRenderer/gkNextRenderer.cpp | 2 +- .../Acceleration/CPUAccelerationStructure.cpp | 4 +- src/Assets/{ => Core}/Component.h | 0 src/Assets/{ => Core}/Model.cpp | 4 +- src/Assets/{ => Core}/Model.hpp | 4 +- src/Assets/{ => Core}/Node.cpp | 2 +- src/Assets/{ => Core}/Node.h | 4 +- src/Assets/{ => Core}/Scene.cpp | 10 +- src/Assets/{ => Core}/Scene.hpp | 4 +- src/Assets/Loaders/FProcModel.h | 2 +- src/Assets/Loaders/FSceneLoader.cpp | 2 +- src/Assets/Loaders/FSceneLoader.h | 2 +- src/Assets/Savers/FSceneSaver.cpp | 6 +- src/Editor/EditorInterface.cpp | 2 +- src/Editor/EditorMain.cpp | 2 +- src/Editor/Overlays/TitleBarOverlay.cpp | 2 +- src/Editor/Panels/ContentBrowserPanel.cpp | 2 +- src/Editor/Panels/MaterialEditorPanel.cpp | 2 +- src/Editor/Panels/OutlinerPanel.cpp | 4 +- src/Editor/Panels/PropertiesPanel.cpp | 4 +- src/Editor/Panels/PropertyWidgets.h | 2 +- src/Editor/Panels/ViewportOverlay.cpp | 4 +- .../PipelineCommon/CommonComputePipeline.cpp | 2 +- src/Rendering/RayTraceBaseRenderer.cpp | 6 +- src/Rendering/VulkanBaseRenderer.cpp | 4 +- src/Rendering/VulkanBaseRenderer.hpp | 2 +- src/Runtime/Camera/ModelViewController.cpp | 2 +- src/Runtime/Command/DeleteNodeCommand.cpp | 4 +- src/Runtime/Command/DeleteNodeCommand.hpp | 2 +- src/Runtime/Command/DuplicateNodeCommand.cpp | 4 +- src/Runtime/Command/PropertyCommand.hpp | 4 +- src/Runtime/Command/TransformNodeCommand.cpp | 4 +- src/Runtime/Components/PhysicsComponent.h | 2 +- src/Runtime/Components/RenderComponent.h | 2 +- src/Runtime/Components/SkinnedMeshComponent.h | 4 +- src/Runtime/Config/UserSettings.hpp | 2 +- src/Runtime/Editor/GizmoController.cpp | 4 +- src/Runtime/Engine.cpp | 6 +- src/Runtime/Engine.hpp | 2 +- src/Runtime/Reflection/ReflectionRegistry.cpp | 4 +- src/Runtime/Scene/SceneList.cpp | 4 +- src/Runtime/Subsystems/NextPhysics.cpp | 2 +- src/Runtime/Subsystems/QuickJSEngine.cpp | 4 +- src/Tests/Test_ComponentSystem.cpp | 4 +- src/Tests/Test_GltfSkinning.cpp | 2 +- src/Tests/Test_PhysicsComponent.cpp | 2 +- src/Tests/Test_PhysicsSync.cpp | 2 +- src/Tests/Test_RenderComponent.cpp | 2 +- src/Utilities/FileHelper.hpp | 314 +++++++++--------- src/Vulkan/RayTracing/BottomLevelGeometry.cpp | 2 +- 51 files changed, 236 insertions(+), 236 deletions(-) rename src/Assets/{ => Core}/Component.h (100%) rename src/Assets/{ => Core}/Model.cpp (98%) rename src/Assets/{ => Core}/Model.hpp (98%) rename src/Assets/{ => Core}/Node.cpp (99%) rename src/Assets/{ => Core}/Node.h (98%) rename src/Assets/{ => Core}/Scene.cpp (99%) rename src/Assets/{ => Core}/Scene.hpp (99%) diff --git a/src/Application/MagicaLego/MagicaLegoGameInstance.cpp b/src/Application/MagicaLego/MagicaLegoGameInstance.cpp index 73150cc3..cfa5e632 100644 --- a/src/Application/MagicaLego/MagicaLegoGameInstance.cpp +++ b/src/Application/MagicaLego/MagicaLegoGameInstance.cpp @@ -1,6 +1,6 @@ #include "MagicaLegoGameInstance.hpp" -#include "Assets/Scene.hpp" -#include "Assets/Node.h" +#include "Assets/Core/Scene.hpp" +#include "Assets/Core/Node.h" #include "Runtime/Components/RenderComponent.h" #include "Runtime/Components/PhysicsComponent.h" #include "Utilities/FileHelper.hpp" diff --git a/src/Application/gkNextRenderer/gkNextRenderer.cpp b/src/Application/gkNextRenderer/gkNextRenderer.cpp index 40e60d5f..a858e06c 100644 --- a/src/Application/gkNextRenderer/gkNextRenderer.cpp +++ b/src/Application/gkNextRenderer/gkNextRenderer.cpp @@ -6,7 +6,7 @@ #include #include "Assets/Loaders/FProcModel.h" -#include "Assets/Node.h" +#include "Assets/Core/Node.h" #include "Runtime/Components/RenderComponent.h" #include "Runtime/Components/PhysicsComponent.h" #include "Runtime/Engine.hpp" diff --git a/src/Assets/Acceleration/CPUAccelerationStructure.cpp b/src/Assets/Acceleration/CPUAccelerationStructure.cpp index db5b01ac..3e521702 100644 --- a/src/Assets/Acceleration/CPUAccelerationStructure.cpp +++ b/src/Assets/Acceleration/CPUAccelerationStructure.cpp @@ -1,11 +1,11 @@ #include "Assets/Acceleration/CPUAccelerationStructure.h" #include "Runtime/Subsystems/TaskCoordinator.hpp" #include "Vulkan/DeviceMemory.hpp" -#include "Assets/Node.h" +#include "Assets/Core/Node.h" #include "Runtime/Components/RenderComponent.h" #include "Assets/GPU/TextureImage.hpp" #include "Runtime/Engine.hpp" -#include "Assets/Scene.hpp" +#include "Assets/Core/Scene.hpp" #include #include diff --git a/src/Assets/Component.h b/src/Assets/Core/Component.h similarity index 100% rename from src/Assets/Component.h rename to src/Assets/Core/Component.h diff --git a/src/Assets/Model.cpp b/src/Assets/Core/Model.cpp similarity index 98% rename from src/Assets/Model.cpp rename to src/Assets/Core/Model.cpp index 6b372591..8ffb929e 100644 --- a/src/Assets/Model.cpp +++ b/src/Assets/Core/Model.cpp @@ -1,6 +1,6 @@ -#include "Model.hpp" +#include "Assets/Core/Model.hpp" #include "Utilities/FileHelper.hpp" -#include "Loaders/FSceneLoader.h" +#include "Assets/Loaders/FSceneLoader.h" #define GLM_ENABLE_EXPERIMENTAL #include diff --git a/src/Assets/Model.hpp b/src/Assets/Core/Model.hpp similarity index 98% rename from src/Assets/Model.hpp rename to src/Assets/Core/Model.hpp index d2b36545..5ad4b4ea 100644 --- a/src/Assets/Model.hpp +++ b/src/Assets/Core/Model.hpp @@ -1,7 +1,7 @@ #pragma once #include "Assets/Data/Vertex.hpp" -#include "GPU/Texture.hpp" -#include "GPU/UniformBuffer.hpp" +#include "Assets/GPU/Texture.hpp" +#include "Assets/GPU/UniformBuffer.hpp" #include struct FNextPhysicsBody; diff --git a/src/Assets/Node.cpp b/src/Assets/Core/Node.cpp similarity index 99% rename from src/Assets/Node.cpp rename to src/Assets/Core/Node.cpp index 7594a0fd..4bc24a6f 100644 --- a/src/Assets/Node.cpp +++ b/src/Assets/Core/Node.cpp @@ -1,4 +1,4 @@ -#include "Node.h" +#include "Assets/Core/Node.h" #include "Runtime/Components/RenderComponent.h" #include "Runtime/Components/PhysicsComponent.h" #include "Runtime/Components/SkinnedMeshComponent.h" diff --git a/src/Assets/Node.h b/src/Assets/Core/Node.h similarity index 98% rename from src/Assets/Node.h rename to src/Assets/Core/Node.h index 5ccad168..99867fda 100644 --- a/src/Assets/Node.h +++ b/src/Assets/Core/Node.h @@ -1,8 +1,8 @@ #pragma once #include "Common/CoreMinimal.hpp" -#include "GPU/UniformBuffer.hpp" +#include "Assets/GPU/UniformBuffer.hpp" #include "Runtime/Subsystems/NextPhysics.h" -#include "Component.h" +#include "Assets/Core/Component.h" #include "Runtime/Components/PhysicsComponent.h" #include "glm/ext.hpp" diff --git a/src/Assets/Scene.cpp b/src/Assets/Core/Scene.cpp similarity index 99% rename from src/Assets/Scene.cpp rename to src/Assets/Core/Scene.cpp index e06147fe..27a2ab03 100644 --- a/src/Assets/Scene.cpp +++ b/src/Assets/Core/Scene.cpp @@ -1,4 +1,4 @@ -#include "Scene.hpp" +#include "Assets/Core/Scene.hpp" #include #include #include @@ -6,14 +6,14 @@ #include #include "Assets/GPU/TextureImage.hpp" #include "Common/CoreMinimal.hpp" -#include "Savers/FSceneSaver.h" -#include "Model.hpp" +#include "Assets/Savers/FSceneSaver.h" +#include "Assets/Core/Model.hpp" #include "Options.hpp" #include "Runtime/Subsystems/NextPhysics.h" -#include "Scene.hpp" +#include "Assets/Core/Scene.hpp" #include "Vulkan/BufferUtil.hpp" -#include "Node.h" +#include "Assets/Core/Node.h" #include "Runtime/Components/PhysicsComponent.h" #include "Runtime/Components/RenderComponent.h" #include "Runtime/Components/SkinnedMeshComponent.h" diff --git a/src/Assets/Scene.hpp b/src/Assets/Core/Scene.hpp similarity index 99% rename from src/Assets/Scene.hpp rename to src/Assets/Core/Scene.hpp index f9280b30..c18a7c72 100644 --- a/src/Assets/Scene.hpp +++ b/src/Assets/Core/Scene.hpp @@ -6,8 +6,8 @@ #include "Common/CoreMinimal.hpp" #include "Vulkan/Vulkan.hpp" -#include "Acceleration/CPUAccelerationStructure.h" -#include "Model.hpp" +#include "Assets/Acceleration/CPUAccelerationStructure.h" +#include "Assets/Core/Model.hpp" #include "Runtime/Subsystems/NextPhysics.h" #include "Runtime/Subsystems/NextPhysicsTypes.h" #include "Assets/Data/Skeleton.hpp" diff --git a/src/Assets/Loaders/FProcModel.h b/src/Assets/Loaders/FProcModel.h index d9d613fd..4c36feeb 100644 --- a/src/Assets/Loaders/FProcModel.h +++ b/src/Assets/Loaders/FProcModel.h @@ -1,5 +1,5 @@ #pragma once -#include "Assets/Model.hpp" +#include "Assets/Core/Model.hpp" namespace Assets { diff --git a/src/Assets/Loaders/FSceneLoader.cpp b/src/Assets/Loaders/FSceneLoader.cpp index be1defa3..2d9a1fd7 100644 --- a/src/Assets/Loaders/FSceneLoader.cpp +++ b/src/Assets/Loaders/FSceneLoader.cpp @@ -26,7 +26,7 @@ #include #include "Assets/Data/Material.hpp" -#include "Assets/Node.h" +#include "Assets/Core/Node.h" #include "Runtime/Components/RenderComponent.h" #include "Utilities/FileHelper.hpp" diff --git a/src/Assets/Loaders/FSceneLoader.h b/src/Assets/Loaders/FSceneLoader.h index e5e68d2a..0721d2c3 100644 --- a/src/Assets/Loaders/FSceneLoader.h +++ b/src/Assets/Loaders/FSceneLoader.h @@ -1,6 +1,6 @@ #pragma once -#include "Assets/Model.hpp" +#include "Assets/Core/Model.hpp" #include "Assets/Data/Skeleton.hpp" namespace Assets diff --git a/src/Assets/Savers/FSceneSaver.cpp b/src/Assets/Savers/FSceneSaver.cpp index 9e785442..25aa8867 100644 --- a/src/Assets/Savers/FSceneSaver.cpp +++ b/src/Assets/Savers/FSceneSaver.cpp @@ -5,9 +5,9 @@ #include #include -#include "Assets/Scene.hpp" -#include "Assets/Node.h" -#include "Assets/Model.hpp" +#include "Assets/Core/Scene.hpp" +#include "Assets/Core/Node.h" +#include "Assets/Core/Model.hpp" #include "Assets/Data/Material.hpp" #include "Runtime/Components/RenderComponent.h" diff --git a/src/Editor/EditorInterface.cpp b/src/Editor/EditorInterface.cpp index a8a787bf..bcd00c8c 100644 --- a/src/Editor/EditorInterface.cpp +++ b/src/Editor/EditorInterface.cpp @@ -13,7 +13,7 @@ #include #include "Editor/EditorUi.hpp" -#include "Assets/Scene.hpp" +#include "Assets/Core/Scene.hpp" #include "Assets/GPU/TextureImage.hpp" #include "Common/CoreMinimal.hpp" #include "Editor/EditorActionDispatcher.hpp" diff --git a/src/Editor/EditorMain.cpp b/src/Editor/EditorMain.cpp index 0f249a6c..32a1ec83 100644 --- a/src/Editor/EditorMain.cpp +++ b/src/Editor/EditorMain.cpp @@ -1,6 +1,6 @@ #include "EditorMain.h" #include -#include "Assets/Node.h" +#include "Assets/Core/Node.h" #include "EditorInterface.hpp" #include "Runtime/Components/RenderComponent.h" #include "Runtime/Engine.hpp" diff --git a/src/Editor/Overlays/TitleBarOverlay.cpp b/src/Editor/Overlays/TitleBarOverlay.cpp index 1e2945bb..30c1cecb 100644 --- a/src/Editor/Overlays/TitleBarOverlay.cpp +++ b/src/Editor/Overlays/TitleBarOverlay.cpp @@ -1,6 +1,6 @@ #include "Editor/EditorUi.hpp" -#include "Assets/Scene.hpp" +#include "Assets/Core/Scene.hpp" #include "Editor/EditorActionDispatcher.hpp" #include "ThirdParty/fontawesome/IconsFontAwesome6.h" diff --git a/src/Editor/Panels/ContentBrowserPanel.cpp b/src/Editor/Panels/ContentBrowserPanel.cpp index 31c01807..aa36a246 100644 --- a/src/Editor/Panels/ContentBrowserPanel.cpp +++ b/src/Editor/Panels/ContentBrowserPanel.cpp @@ -2,7 +2,7 @@ #include "Editor/EditorDragDrop.hpp" -#include "Assets/Scene.hpp" +#include "Assets/Core/Scene.hpp" #include "Assets/GPU/TextureImage.hpp" #include "Editor/EditorActionDispatcher.hpp" #include "Runtime/Editor/UserInterface.hpp" diff --git a/src/Editor/Panels/MaterialEditorPanel.cpp b/src/Editor/Panels/MaterialEditorPanel.cpp index dcda47b7..39f367ec 100644 --- a/src/Editor/Panels/MaterialEditorPanel.cpp +++ b/src/Editor/Panels/MaterialEditorPanel.cpp @@ -1,7 +1,7 @@ #include "Editor/EditorUi.hpp" #include "Assets/Data/Material.hpp" -#include "Assets/Scene.hpp" +#include "Assets/Core/Scene.hpp" #include "Editor/Nodes/NodeMaterial.hpp" #include "Editor/Nodes/NodeSetFloat.hpp" #include "Editor/Nodes/NodeSetInt.hpp" diff --git a/src/Editor/Panels/OutlinerPanel.cpp b/src/Editor/Panels/OutlinerPanel.cpp index 3e9ad84f..9af0c5ef 100644 --- a/src/Editor/Panels/OutlinerPanel.cpp +++ b/src/Editor/Panels/OutlinerPanel.cpp @@ -3,8 +3,8 @@ #include "Editor/EditorActionDispatcher.hpp" #include "Editor/EditorUtils.h" -#include "Assets/Node.h" -#include "Assets/Scene.hpp" +#include "Assets/Core/Node.h" +#include "Assets/Core/Scene.hpp" #include "Runtime/Components/RenderComponent.h" #include "ThirdParty/fontawesome/IconsFontAwesome6.h" diff --git a/src/Editor/Panels/PropertiesPanel.cpp b/src/Editor/Panels/PropertiesPanel.cpp index 679069cc..e4435412 100644 --- a/src/Editor/Panels/PropertiesPanel.cpp +++ b/src/Editor/Panels/PropertiesPanel.cpp @@ -1,8 +1,8 @@ #include "Editor/EditorUi.hpp" #include "Editor/Panels/PropertyWidgets.h" -#include "Assets/Node.h" -#include "Assets/Scene.hpp" +#include "Assets/Core/Node.h" +#include "Assets/Core/Scene.hpp" #include "Runtime/Components/RenderComponent.h" #include "Runtime/Components/PhysicsComponent.h" #include "Runtime/Components/SkinnedMeshComponent.h" diff --git a/src/Editor/Panels/PropertyWidgets.h b/src/Editor/Panels/PropertyWidgets.h index c6a0534f..1f6f709f 100644 --- a/src/Editor/Panels/PropertyWidgets.h +++ b/src/Editor/Panels/PropertyWidgets.h @@ -4,7 +4,7 @@ #include "Runtime/Reflection/PropertyTypes.h" #include "Runtime/Reflection/PropertyAccessor.h" #include "Runtime/Command/CommandHistory.hpp" -#include "Assets/Component.h" +#include "Assets/Core/Component.h" #include #include diff --git a/src/Editor/Panels/ViewportOverlay.cpp b/src/Editor/Panels/ViewportOverlay.cpp index 775edf20..4a0ee7f5 100644 --- a/src/Editor/Panels/ViewportOverlay.cpp +++ b/src/Editor/Panels/ViewportOverlay.cpp @@ -2,8 +2,8 @@ #include "Editor/EditorDragDrop.hpp" -#include "Assets/Node.h" -#include "Assets/Scene.hpp" +#include "Assets/Core/Node.h" +#include "Assets/Core/Scene.hpp" #include "Editor/EditorActionDispatcher.hpp" #include "Runtime/Components/RenderComponent.h" #include "Runtime/Engine.hpp" diff --git a/src/Rendering/PipelineCommon/CommonComputePipeline.cpp b/src/Rendering/PipelineCommon/CommonComputePipeline.cpp index d0271a60..6229964e 100644 --- a/src/Rendering/PipelineCommon/CommonComputePipeline.cpp +++ b/src/Rendering/PipelineCommon/CommonComputePipeline.cpp @@ -12,7 +12,7 @@ #include "Vulkan/SwapChain.hpp" #include "Vulkan/RenderPass.hpp" -#include "Assets/Scene.hpp" +#include "Assets/Core/Scene.hpp" #include "Assets/GPU/UniformBuffer.hpp" #include "Assets/Data/Vertex.hpp" diff --git a/src/Rendering/RayTraceBaseRenderer.cpp b/src/Rendering/RayTraceBaseRenderer.cpp index 6703c4b8..fb38ee0b 100644 --- a/src/Rendering/RayTraceBaseRenderer.cpp +++ b/src/Rendering/RayTraceBaseRenderer.cpp @@ -3,9 +3,9 @@ #include "Vulkan/RayTracing/BottomLevelAccelerationStructure.hpp" #include "Vulkan/RayTracing/DeviceProcedures.hpp" #include "Vulkan/RayTracing/TopLevelAccelerationStructure.hpp" -#include "Assets/Model.hpp" -#include "Assets/Scene.hpp" -#include "Assets/Node.h" +#include "Assets/Core/Model.hpp" +#include "Assets/Core/Scene.hpp" +#include "Assets/Core/Node.h" #include "Runtime/Components/RenderComponent.h" #include "Runtime/Components/SkinnedMeshComponent.h" #include "Vulkan/Buffer.hpp" diff --git a/src/Rendering/VulkanBaseRenderer.cpp b/src/Rendering/VulkanBaseRenderer.cpp index 84b1e775..caee4b6e 100644 --- a/src/Rendering/VulkanBaseRenderer.cpp +++ b/src/Rendering/VulkanBaseRenderer.cpp @@ -22,10 +22,10 @@ #include "Vulkan/Strings.hpp" #include "Vulkan/Version.hpp" -#include "Assets/Scene.hpp" +#include "Assets/Core/Scene.hpp" #include "Assets/GPU/UniformBuffer.hpp" #include "Assets/GPU/Texture.hpp" -#include "Assets/Node.h" +#include "Assets/Core/Node.h" #include "Runtime/Components/RenderComponent.h" #include "Runtime/Components/SkinnedMeshComponent.h" diff --git a/src/Rendering/VulkanBaseRenderer.hpp b/src/Rendering/VulkanBaseRenderer.hpp index 395f7819..f6ab044d 100644 --- a/src/Rendering/VulkanBaseRenderer.hpp +++ b/src/Rendering/VulkanBaseRenderer.hpp @@ -4,7 +4,7 @@ #include "Vulkan/VulkanGpuTimer.hpp" #include "Vulkan/Image.hpp" #include "Assets/GPU/UniformBuffer.hpp" -#include "Assets/Scene.hpp" +#include "Assets/Core/Scene.hpp" #include #include #include diff --git a/src/Runtime/Camera/ModelViewController.cpp b/src/Runtime/Camera/ModelViewController.cpp index efea9881..46ac2b69 100644 --- a/src/Runtime/Camera/ModelViewController.cpp +++ b/src/Runtime/Camera/ModelViewController.cpp @@ -1,5 +1,5 @@ #include "Runtime/Camera/ModelViewController.hpp" -#include "Assets/Model.hpp" +#include "Assets/Core/Model.hpp" #include "Vulkan/Vulkan.hpp" #include "Runtime/Platform/PlatformCommon.h" diff --git a/src/Runtime/Command/DeleteNodeCommand.cpp b/src/Runtime/Command/DeleteNodeCommand.cpp index fcabf522..3be2cc3f 100644 --- a/src/Runtime/Command/DeleteNodeCommand.cpp +++ b/src/Runtime/Command/DeleteNodeCommand.cpp @@ -1,7 +1,7 @@ #include "Runtime/Command/DeleteNodeCommand.hpp" -#include "Assets/Node.h" -#include "Assets/Scene.hpp" +#include "Assets/Core/Node.h" +#include "Assets/Core/Scene.hpp" DeleteNodeCommand::DeleteNodeCommand(Assets::Scene& scene, uint32_t instanceId) : scene_(&scene) diff --git a/src/Runtime/Command/DeleteNodeCommand.hpp b/src/Runtime/Command/DeleteNodeCommand.hpp index f1176a33..3960170c 100644 --- a/src/Runtime/Command/DeleteNodeCommand.hpp +++ b/src/Runtime/Command/DeleteNodeCommand.hpp @@ -4,7 +4,7 @@ #include "Common/CoreMinimal.hpp" #include "Runtime/Command/ICommand.hpp" -#include "Assets/Scene.hpp" +#include "Assets/Core/Scene.hpp" #include #include diff --git a/src/Runtime/Command/DuplicateNodeCommand.cpp b/src/Runtime/Command/DuplicateNodeCommand.cpp index 9e6e017e..899e8d75 100644 --- a/src/Runtime/Command/DuplicateNodeCommand.cpp +++ b/src/Runtime/Command/DuplicateNodeCommand.cpp @@ -1,7 +1,7 @@ #include "Runtime/Command/DuplicateNodeCommand.hpp" -#include "Assets/Node.h" -#include "Assets/Scene.hpp" +#include "Assets/Core/Node.h" +#include "Assets/Core/Scene.hpp" #include "Runtime/Components/PhysicsComponent.h" #include "Runtime/Components/RenderComponent.h" diff --git a/src/Runtime/Command/PropertyCommand.hpp b/src/Runtime/Command/PropertyCommand.hpp index b2d795e9..7d2616e6 100644 --- a/src/Runtime/Command/PropertyCommand.hpp +++ b/src/Runtime/Command/PropertyCommand.hpp @@ -5,8 +5,8 @@ #include "Common/CoreMinimal.hpp" #include "Runtime/Command/ICommand.hpp" #include "Runtime/Reflection/PropertyAccessor.h" -#include "Assets/Component.h" -#include "Assets/Node.h" +#include "Assets/Core/Component.h" +#include "Assets/Core/Node.h" #include #include diff --git a/src/Runtime/Command/TransformNodeCommand.cpp b/src/Runtime/Command/TransformNodeCommand.cpp index af1254a0..1099c2d0 100644 --- a/src/Runtime/Command/TransformNodeCommand.cpp +++ b/src/Runtime/Command/TransformNodeCommand.cpp @@ -1,7 +1,7 @@ #include "Runtime/Command/TransformNodeCommand.hpp" -#include "Assets/Node.h" -#include "Assets/Scene.hpp" +#include "Assets/Core/Node.h" +#include "Assets/Core/Scene.hpp" #include diff --git a/src/Runtime/Components/PhysicsComponent.h b/src/Runtime/Components/PhysicsComponent.h index 3d10e8e0..c19b13aa 100644 --- a/src/Runtime/Components/PhysicsComponent.h +++ b/src/Runtime/Components/PhysicsComponent.h @@ -1,5 +1,5 @@ #pragma once -#include "Assets/Component.h" +#include "Assets/Core/Component.h" #include "Runtime/Reflection/ReflectionMacros.h" #include "Runtime/Subsystems/NextPhysics.h" #include diff --git a/src/Runtime/Components/RenderComponent.h b/src/Runtime/Components/RenderComponent.h index 0fcd884d..f9810af7 100644 --- a/src/Runtime/Components/RenderComponent.h +++ b/src/Runtime/Components/RenderComponent.h @@ -1,5 +1,5 @@ #pragma once -#include "Assets/Component.h" +#include "Assets/Core/Component.h" #include "Runtime/Reflection/ReflectionMacros.h" #include #include diff --git a/src/Runtime/Components/SkinnedMeshComponent.h b/src/Runtime/Components/SkinnedMeshComponent.h index 3884f452..346cc3c3 100644 --- a/src/Runtime/Components/SkinnedMeshComponent.h +++ b/src/Runtime/Components/SkinnedMeshComponent.h @@ -2,8 +2,8 @@ #include "Common/CoreMinimal.hpp" #include "Assets/Data/Skeleton.hpp" #include "Assets/Data/Animation.hpp" -#include "Assets/Model.hpp" -#include "Assets/Component.h" +#include "Assets/Core/Model.hpp" +#include "Assets/Core/Component.h" #include "Runtime/Reflection/ReflectionMacros.h" #include #include diff --git a/src/Runtime/Config/UserSettings.hpp b/src/Runtime/Config/UserSettings.hpp index 18e36a48..d59a66b5 100644 --- a/src/Runtime/Config/UserSettings.hpp +++ b/src/Runtime/Config/UserSettings.hpp @@ -1,7 +1,7 @@ #pragma once #include "Assets/GPU/UniformBuffer.hpp" -#include "Assets/Model.hpp" +#include "Assets/Core/Model.hpp" struct UserSettings final { diff --git a/src/Runtime/Editor/GizmoController.cpp b/src/Runtime/Editor/GizmoController.cpp index 5422e8b8..c2a16532 100644 --- a/src/Runtime/Editor/GizmoController.cpp +++ b/src/Runtime/Editor/GizmoController.cpp @@ -2,8 +2,8 @@ #include #include "Runtime/Editor/GizmoController.hpp" -#include "Assets/Node.h" -#include "Assets/Scene.hpp" +#include "Assets/Core/Node.h" +#include "Assets/Core/Scene.hpp" #include "Runtime/Engine.hpp" #include "Runtime/Command/TransformNodeCommand.hpp" #include "Runtime/Command/DuplicateNodeCommand.hpp" diff --git a/src/Runtime/Engine.cpp b/src/Runtime/Engine.cpp index e4af300c..da1e8552 100644 --- a/src/Runtime/Engine.cpp +++ b/src/Runtime/Engine.cpp @@ -1,7 +1,7 @@ #include "Engine.hpp" -#include "Assets/Model.hpp" -#include "Assets/Node.h" -#include "Assets/Scene.hpp" +#include "Assets/Core/Model.hpp" +#include "Assets/Core/Node.h" +#include "Assets/Core/Scene.hpp" #include "Assets/GPU/Texture.hpp" #include "Assets/GPU/UniformBuffer.hpp" #include "Runtime/Subsystems/QuickJSEngine.hpp" diff --git a/src/Runtime/Engine.hpp b/src/Runtime/Engine.hpp index eacc84fd..efbda574 100644 --- a/src/Runtime/Engine.hpp +++ b/src/Runtime/Engine.hpp @@ -1,6 +1,6 @@ #pragma once -#include "Assets/Model.hpp" +#include "Assets/Core/Model.hpp" #include "Assets/GPU/UniformBuffer.hpp" #include "Common/CoreMinimal.hpp" #include "Options.hpp" diff --git a/src/Runtime/Reflection/ReflectionRegistry.cpp b/src/Runtime/Reflection/ReflectionRegistry.cpp index a954ee52..e8fd3975 100644 --- a/src/Runtime/Reflection/ReflectionRegistry.cpp +++ b/src/Runtime/Reflection/ReflectionRegistry.cpp @@ -4,8 +4,8 @@ #include "Runtime/Components/PhysicsComponent.h" #include "Runtime/Components/SkinnedMeshComponent.h" #include "Runtime/Engine.hpp" -#include "Assets/Scene.hpp" -#include "Assets/Node.h" +#include "Assets/Core/Scene.hpp" +#include "Assets/Core/Node.h" namespace Reflection { diff --git a/src/Runtime/Scene/SceneList.cpp b/src/Runtime/Scene/SceneList.cpp index 8f21a833..51576663 100644 --- a/src/Runtime/Scene/SceneList.cpp +++ b/src/Runtime/Scene/SceneList.cpp @@ -2,7 +2,7 @@ #include "Common/CoreMinimal.hpp" #include "Utilities/FileHelper.hpp" #include "Assets/Data/Material.hpp" -#include "Assets/Model.hpp" +#include "Assets/Core/Model.hpp" #include "Runtime/Engine.hpp" #include "Runtime/Subsystems/NextPhysics.h" @@ -14,7 +14,7 @@ #include "Assets/Loaders/FProcModel.h" #include "Assets/Loaders/FSceneLoader.h" -#include "Assets/Node.h" +#include "Assets/Core/Node.h" #include "Runtime/Components/RenderComponent.h" #include "Runtime/Components/PhysicsComponent.h" #include "Assets/Data/Skeleton.hpp" diff --git a/src/Runtime/Subsystems/NextPhysics.cpp b/src/Runtime/Subsystems/NextPhysics.cpp index 1f6af142..9a9f1220 100644 --- a/src/Runtime/Subsystems/NextPhysics.cpp +++ b/src/Runtime/Subsystems/NextPhysics.cpp @@ -29,7 +29,7 @@ #include #include "Runtime/Engine.hpp" -#include "Assets/Model.hpp" +#include "Assets/Core/Model.hpp" #if WITH_PHYSIC diff --git a/src/Runtime/Subsystems/QuickJSEngine.cpp b/src/Runtime/Subsystems/QuickJSEngine.cpp index 562d109d..d0b98145 100644 --- a/src/Runtime/Subsystems/QuickJSEngine.cpp +++ b/src/Runtime/Subsystems/QuickJSEngine.cpp @@ -1,8 +1,8 @@ #include "Runtime/Subsystems/QuickJSEngine.hpp" #include "Runtime/Engine.hpp" -#include "Assets/Scene.hpp" -#include "Assets/Node.h" +#include "Assets/Core/Scene.hpp" +#include "Assets/Core/Node.h" #include "Runtime/Components/RenderComponent.h" #include "Runtime/Components/PhysicsComponent.h" #include "Runtime/Components/SkinnedMeshComponent.h" diff --git a/src/Tests/Test_ComponentSystem.cpp b/src/Tests/Test_ComponentSystem.cpp index 1dc046f4..7aa4755b 100644 --- a/src/Tests/Test_ComponentSystem.cpp +++ b/src/Tests/Test_ComponentSystem.cpp @@ -1,6 +1,6 @@ #include -#include "Assets/Node.h" -#include "Assets/Component.h" +#include "Assets/Core/Node.h" +#include "Assets/Core/Component.h" #include #include #include diff --git a/src/Tests/Test_GltfSkinning.cpp b/src/Tests/Test_GltfSkinning.cpp index 34f4b9e8..ffb7225f 100644 --- a/src/Tests/Test_GltfSkinning.cpp +++ b/src/Tests/Test_GltfSkinning.cpp @@ -1,6 +1,6 @@ #include #include "Assets/Loaders/FSceneLoader.h" -#include "Assets/Model.hpp" +#include "Assets/Core/Model.hpp" #include "Assets/Data/Material.hpp" #include #include diff --git a/src/Tests/Test_PhysicsComponent.cpp b/src/Tests/Test_PhysicsComponent.cpp index 41a2361c..9fda0857 100644 --- a/src/Tests/Test_PhysicsComponent.cpp +++ b/src/Tests/Test_PhysicsComponent.cpp @@ -1,5 +1,5 @@ #include -#include "Assets/Node.h" +#include "Assets/Core/Node.h" #include "Runtime/Components/PhysicsComponent.h" #include diff --git a/src/Tests/Test_PhysicsSync.cpp b/src/Tests/Test_PhysicsSync.cpp index aa3202a1..a6e4e8a5 100644 --- a/src/Tests/Test_PhysicsSync.cpp +++ b/src/Tests/Test_PhysicsSync.cpp @@ -1,7 +1,7 @@ #include #include "TestCommon.hpp" #include "Runtime/Subsystems/NextPhysics.h" -#include "Assets/Node.h" +#include "Assets/Core/Node.h" #include "Runtime/Components/RenderComponent.h" // Note: TestCommon.hpp provides EngineTestFixture and the necessary CreateGameInstance implementation diff --git a/src/Tests/Test_RenderComponent.cpp b/src/Tests/Test_RenderComponent.cpp index 4cf64c50..c222a72b 100644 --- a/src/Tests/Test_RenderComponent.cpp +++ b/src/Tests/Test_RenderComponent.cpp @@ -1,5 +1,5 @@ #include -#include "Assets/Node.h" +#include "Assets/Core/Node.h" #include "Runtime/Components/RenderComponent.h" #include #include diff --git a/src/Utilities/FileHelper.hpp b/src/Utilities/FileHelper.hpp index 14cc99b8..d021ea87 100644 --- a/src/Utilities/FileHelper.hpp +++ b/src/Utilities/FileHelper.hpp @@ -1,157 +1,157 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include "ThirdParty/lzav/lzav.h" -#include -#include -#include -//#include - -namespace Utilities -{ - namespace FileHelper - { - static void EnsureDirectoryExists(const std::filesystem::path& path) - { - std::filesystem::create_directories(path); - } - - static std::filesystem::path GetAbsolutePath( const std::filesystem::path& srcPath ) - { - return std::filesystem::absolute(srcPath); - } - - static std::string GetPlatformFilePath( const char* srcPath ) - { -#if ANDROID - const char* AndroidExtPath = SDL_GetAndroidExternalStoragePath(); - return std::filesystem::path(AndroidExtPath).append(srcPath).string(); -#elif IOS - return std::filesystem::path(SDL_GetBasePath()).append(srcPath).string(); -#else - return std::filesystem::path("..").append(srcPath).string(); -#endif - } - - static std::string GetNormalizedFilePath( const char* srcPath ) - { - std::string normlizedPath {}; -#if ANDROID - const char* AndroidExtPath = SDL_GetAndroidExternalStoragePath(); - normlizedPath = std::filesystem::path(AndroidExtPath).append(srcPath).string(); -#elif IOS - normlizedPath = std::filesystem::path(SDL_GetBasePath()).append(srcPath).string(); -#else - normlizedPath = std::string("../") + srcPath; -#endif - std::filesystem::path fullPath(normlizedPath); - std::filesystem::path directory = fullPath.parent_path(); - std::string pattern = fullPath.filename().string(); - - if ( std::filesystem::exists(directory) ) - { - for (const auto& entry : std::filesystem::directory_iterator(directory)) { - if (entry.is_regular_file() && entry.path().filename().string() == pattern) { - normlizedPath = std::filesystem::absolute(entry.path()).string(); - break; - } - } - } - - return normlizedPath; - } - } - - namespace NameHelper - { - static std::string RandomName(size_t length) - { - const std::string characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - std::random_device rd; - std::mt19937 generator(rd()); - std::uniform_int_distribution<> distribution(0, static_cast(characters.size()) - 1); - - std::string randomName; - for (size_t i = 0; i < length; ++i) { - randomName += characters[distribution(generator)]; - } - - return randomName; - } - } - - namespace CookHelper - { - static std::string GetCookedFileName(const std::string& filehash, const std::string& cooktype) - { - std::string normlizedPath {}; - #if ANDROID - normlizedPath = std::string(SDL_GetAndroidExternalStoragePath()); - #elif IOS - normlizedPath = std::string(SDL_GetPrefPath("gknext", "renderer")); - #else - normlizedPath = std::string("../"); - #endif - std::filesystem::create_directories(std::filesystem::path(normlizedPath + "/cooked/")); - return normlizedPath + "/cooked/" + cooktype + filehash + ".gncook"; - } - } - - namespace Package - { - enum EPackageRunMode - { - EPM_OsFile, - EPM_PakFile - }; - - struct FPakEntry - { - std::string name; - uint32_t pkgIdx; - uint32_t offset; - uint32_t size; - uint32_t uncompressSize; - }; - - // PackageFileSystem for Mostly User Oriented Resource, like Texture, Model, etc. - // Package mass files to one pak - class FPackageFileSystem - { - public: - // Construct - FPackageFileSystem(EPackageRunMode RunMode); - - void SetRunMode(EPackageRunMode RunMode) { runMode_ = RunMode; } - - // Loading - void Reset(); - void MountPak(const std::string& pakFile); - bool LoadFile(const std::string& entry, std::vector& outData); - - // Recording - //void RecordUsage(const std::string& entry); - //void SaveRecord(const std::string& recordFile); - //void PakFromRecord(const std::string& pakFile, const std::string& recordFile); - - // Paking - void PakAll(const std::string& pakFile, const std::string& srcDir, const std::string& rootPath, const std::string& regex = "", bool enableCompression = true, const std::string& manifestPath = ""); - - static FPackageFileSystem& GetInstance() - { - return *instance_; - } - private: - // pak index - std::map filemaps; - std::vector mountedPaks; - EPackageRunMode runMode_; - - static FPackageFileSystem* instance_; - }; - } -} +#pragma once +#include +#include +#include +#include +#include +#include +#include "ThirdParty/lzav/lzav.h" +#include +#include +#include +//#include + +namespace Utilities +{ + namespace FileHelper + { + static void EnsureDirectoryExists(const std::filesystem::path& path) + { + std::filesystem::create_directories(path); + } + + static std::filesystem::path GetAbsolutePath( const std::filesystem::path& srcPath ) + { + return std::filesystem::absolute(srcPath); + } + + static std::string GetPlatformFilePath( const char* srcPath ) + { +#if ANDROID + const char* AndroidExtPath = SDL_GetAndroidExternalStoragePath(); + return std::filesystem::path(AndroidExtPath).append(srcPath).string(); +#elif IOS + return std::filesystem::path(SDL_GetBasePath()).append(srcPath).string(); +#else + return std::filesystem::path("..").append(srcPath).string(); +#endif + } + + static std::string GetNormalizedFilePath( const char* srcPath ) + { + std::string normlizedPath {}; +#if ANDROID + const char* AndroidExtPath = SDL_GetAndroidExternalStoragePath(); + normlizedPath = std::filesystem::path(AndroidExtPath).append(srcPath).string(); +#elif IOS + normlizedPath = std::filesystem::path(SDL_GetBasePath()).append(srcPath).string(); +#else + normlizedPath = std::string("../") + srcPath; +#endif + std::filesystem::path fullPath(normlizedPath); + std::filesystem::path directory = fullPath.parent_path(); + std::string pattern = fullPath.filename().string(); + + if ( std::filesystem::exists(directory) ) + { + for (const auto& entry : std::filesystem::directory_iterator(directory)) { + if (entry.is_regular_file() && entry.path().filename().string() == pattern) { + normlizedPath = std::filesystem::absolute(entry.path()).string(); + break; + } + } + } + + return normlizedPath; + } + } + + namespace NameHelper + { + static std::string RandomName(size_t length) + { + const std::string characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + std::random_device rd; + std::mt19937 generator(rd()); + std::uniform_int_distribution<> distribution(0, static_cast(characters.size()) - 1); + + std::string randomName; + for (size_t i = 0; i < length; ++i) { + randomName += characters[distribution(generator)]; + } + + return randomName; + } + } + + namespace CookHelper + { + static std::string GetCookedFileName(const std::string& filehash, const std::string& cooktype) + { + std::string normlizedPath {}; + #if ANDROID + normlizedPath = std::string(SDL_GetAndroidExternalStoragePath()); + #elif IOS + normlizedPath = std::string(SDL_GetPrefPath("gknext", "renderer")); + #else + normlizedPath = std::string("../"); + #endif + std::filesystem::create_directories(std::filesystem::path(normlizedPath + "/cooked/")); + return normlizedPath + "/cooked/" + cooktype + filehash + ".gncook"; + } + } + + namespace Package + { + enum EPackageRunMode + { + EPM_OsFile, + EPM_PakFile + }; + + struct FPakEntry + { + std::string name; + uint32_t pkgIdx; + uint32_t offset; + uint32_t size; + uint32_t uncompressSize; + }; + + // PackageFileSystem for Mostly User Oriented Resource, like Texture, Model, etc. + // Package mass files to one pak + class FPackageFileSystem + { + public: + // Construct + FPackageFileSystem(EPackageRunMode RunMode); + + void SetRunMode(EPackageRunMode RunMode) { runMode_ = RunMode; } + + // Loading + void Reset(); + void MountPak(const std::string& pakFile); + bool LoadFile(const std::string& entry, std::vector& outData); + + // Recording + //void RecordUsage(const std::string& entry); + //void SaveRecord(const std::string& recordFile); + //void PakFromRecord(const std::string& pakFile, const std::string& recordFile); + + // Paking + void PakAll(const std::string& pakFile, const std::string& srcDir, const std::string& rootPath, const std::string& regex = "", bool enableCompression = true, const std::string& manifestPath = ""); + + static FPackageFileSystem& GetInstance() + { + return *instance_; + } + private: + // pak index + std::map filemaps; + std::vector mountedPaks; + EPackageRunMode runMode_; + + static FPackageFileSystem* instance_; + }; + } +} diff --git a/src/Vulkan/RayTracing/BottomLevelGeometry.cpp b/src/Vulkan/RayTracing/BottomLevelGeometry.cpp index c6249dea..9f8f9969 100644 --- a/src/Vulkan/RayTracing/BottomLevelGeometry.cpp +++ b/src/Vulkan/RayTracing/BottomLevelGeometry.cpp @@ -1,6 +1,6 @@ #include "BottomLevelGeometry.hpp" #include "DeviceProcedures.hpp" -#include "Assets/Scene.hpp" +#include "Assets/Core/Scene.hpp" #include "Assets/Data/Vertex.hpp" #include "Vulkan/Buffer.hpp" From 4dbed1227649fae5642f1b01c125bfd8ec1666e9 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Mon, 2 Feb 2026 16:35:52 +0800 Subject: [PATCH 41/53] refactor(vulkan): merge Window, Surface, WindowConfig into WindowSurface - Consolidate window system classes into single file - Reduce file count: 42 -> 40 pairs (3 files merged) - Total lines: 309 (Window 238 + Surface 50 + WindowConfig 21) - Update all include paths across codebase Co-Authored-By: Claude Sonnet 4.5 --- .../SoftwareModern/SoftwareModernRenderer.cpp | 2 +- src/Rendering/VulkanBaseRenderer.cpp | 3 +- src/Runtime/Editor/UserInterface.cpp | 3 +- src/Runtime/Engine.cpp | 2 +- src/Runtime/Engine.hpp | 2 +- src/Vulkan/Device.cpp | 2 +- src/Vulkan/Instance.cpp | 2 +- src/Vulkan/Surface.cpp | 22 -- src/Vulkan/Surface.hpp | 28 --- src/Vulkan/SwapChain.cpp | 3 +- src/Vulkan/Window.cpp | 176 --------------- src/Vulkan/Window.hpp | 62 ------ src/Vulkan/WindowConfig.hpp | 21 -- src/Vulkan/WindowSurface.cpp | 209 ++++++++++++++++++ src/Vulkan/WindowSurface.hpp | 110 +++++++++ 15 files changed, 327 insertions(+), 320 deletions(-) delete mode 100644 src/Vulkan/Surface.cpp delete mode 100644 src/Vulkan/Surface.hpp delete mode 100644 src/Vulkan/Window.cpp delete mode 100644 src/Vulkan/Window.hpp delete mode 100644 src/Vulkan/WindowConfig.hpp create mode 100644 src/Vulkan/WindowSurface.cpp create mode 100644 src/Vulkan/WindowSurface.hpp diff --git a/src/Rendering/SoftwareModern/SoftwareModernRenderer.cpp b/src/Rendering/SoftwareModern/SoftwareModernRenderer.cpp index 8a6c691c..42eb3c51 100644 --- a/src/Rendering/SoftwareModern/SoftwareModernRenderer.cpp +++ b/src/Rendering/SoftwareModern/SoftwareModernRenderer.cpp @@ -2,7 +2,7 @@ #include "Runtime/Engine.hpp" #include "Utilities/Math.hpp" #include "Vulkan/SwapChain.hpp" -#include "Vulkan/Window.hpp" +#include "Vulkan/WindowSurface.hpp" #include "Vulkan/RenderImage.hpp" namespace Vulkan::LegacyDeferred { diff --git a/src/Rendering/VulkanBaseRenderer.cpp b/src/Rendering/VulkanBaseRenderer.cpp index caee4b6e..ca0b79a1 100644 --- a/src/Rendering/VulkanBaseRenderer.cpp +++ b/src/Rendering/VulkanBaseRenderer.cpp @@ -12,9 +12,8 @@ #include "Vulkan/PipelineLayout.hpp" #include "Vulkan/RenderPass.hpp" #include "Vulkan/Semaphore.hpp" -#include "Vulkan/Surface.hpp" #include "Vulkan/SwapChain.hpp" -#include "Vulkan/Window.hpp" +#include "Vulkan/WindowSurface.hpp" #include "Vulkan/Enumerate.hpp" #include "Vulkan/ImageMemoryBarrier.hpp" #include "Vulkan/RenderImage.hpp" diff --git a/src/Runtime/Editor/UserInterface.cpp b/src/Runtime/Editor/UserInterface.cpp index 654f22d3..d4ae3c6c 100644 --- a/src/Runtime/Editor/UserInterface.cpp +++ b/src/Runtime/Editor/UserInterface.cpp @@ -9,9 +9,8 @@ #include "Vulkan/Instance.hpp" #include "Vulkan/RenderPass.hpp" #include "Vulkan/SingleTimeCommands.hpp" -#include "Vulkan/Surface.hpp" #include "Vulkan/SwapChain.hpp" -#include "Vulkan/Window.hpp" +#include "Vulkan/WindowSurface.hpp" #include #include diff --git a/src/Runtime/Engine.cpp b/src/Runtime/Engine.cpp index da1e8552..07c856ac 100644 --- a/src/Runtime/Engine.cpp +++ b/src/Runtime/Engine.cpp @@ -12,7 +12,7 @@ #include "Vulkan/Device.hpp" #include "Vulkan/Instance.hpp" #include "Vulkan/SwapChain.hpp" -#include "Vulkan/Window.hpp" +#include "Vulkan/WindowSurface.hpp" #include #include diff --git a/src/Runtime/Engine.hpp b/src/Runtime/Engine.hpp index efbda574..ffec9c47 100644 --- a/src/Runtime/Engine.hpp +++ b/src/Runtime/Engine.hpp @@ -11,7 +11,7 @@ #include "Runtime/Config/UserSettings.hpp" #include "Utilities/FileHelper.hpp" #include "Vulkan/FrameBuffer.hpp" -#include "Vulkan/Window.hpp" +#include "Vulkan/WindowSurface.hpp" class NextPhysics; class QuickJSEngine; diff --git a/src/Vulkan/Device.cpp b/src/Vulkan/Device.cpp index c99fda50..adf223e5 100644 --- a/src/Vulkan/Device.cpp +++ b/src/Vulkan/Device.cpp @@ -1,7 +1,7 @@ #include "Device.hpp" #include "Enumerate.hpp" #include "Instance.hpp" -#include "Surface.hpp" +#include "WindowSurface.hpp" #include "Utilities/Exception.hpp" #include "Vulkan/RayTracing/DeviceProcedures.hpp" #include "Rendering/VulkanBaseRenderer.hpp" diff --git a/src/Vulkan/Instance.cpp b/src/Vulkan/Instance.cpp index 96083136..61287310 100644 --- a/src/Vulkan/Instance.cpp +++ b/src/Vulkan/Instance.cpp @@ -1,7 +1,7 @@ #include "Instance.hpp" #include "Enumerate.hpp" #include "Version.hpp" -#include "Window.hpp" +#include "WindowSurface.hpp" #include "Utilities/Exception.hpp" #include #include diff --git a/src/Vulkan/Surface.cpp b/src/Vulkan/Surface.cpp deleted file mode 100644 index ce5fb124..00000000 --- a/src/Vulkan/Surface.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "Surface.hpp" -#include "Instance.hpp" -#include "Window.hpp" - -namespace Vulkan { - -Surface::Surface(const class Instance& instance) : - instance_(instance) -{ - SDL_Vulkan_CreateSurface(instance.Window().Handle(), instance.Handle(), nullptr, &surface_); -} - -Surface::~Surface() -{ - if (surface_ != nullptr) - { - vkDestroySurfaceKHR(instance_.Handle(), surface_, nullptr); - surface_ = nullptr; - } -} - -} diff --git a/src/Vulkan/Surface.hpp b/src/Vulkan/Surface.hpp deleted file mode 100644 index 5e62947a..00000000 --- a/src/Vulkan/Surface.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include "Vulkan.hpp" - -namespace Vulkan -{ - class Instance; - class Window; - - class Surface final - { - public: - - VULKAN_NON_COPIABLE(Surface) - - explicit Surface(const Instance& instance); - ~Surface(); - - const class Instance& Instance() const { return instance_; } - - private: - - const class Instance& instance_; - - VULKAN_HANDLE(VkSurfaceKHR, surface_) - }; - -} diff --git a/src/Vulkan/SwapChain.cpp b/src/Vulkan/SwapChain.cpp index 422f362b..e3cf2da8 100644 --- a/src/Vulkan/SwapChain.cpp +++ b/src/Vulkan/SwapChain.cpp @@ -3,8 +3,7 @@ #include "Enumerate.hpp" #include "ImageView.hpp" #include "Instance.hpp" -#include "Surface.hpp" -#include "Window.hpp" +#include "WindowSurface.hpp" #include "Utilities/Exception.hpp" #include #include diff --git a/src/Vulkan/Window.cpp b/src/Vulkan/Window.cpp deleted file mode 100644 index fdd68774..00000000 --- a/src/Vulkan/Window.cpp +++ /dev/null @@ -1,176 +0,0 @@ -#include "Window.hpp" -#include "Utilities/Exception.hpp" -#include "Utilities/StbImage.hpp" -#include "Common/CoreMinimal.hpp" -#include "Options.hpp" -#include "Utilities/FileHelper.hpp" -#include - - -namespace Vulkan { - -Window::Window(const WindowConfig& config) : - config_(config) -{ - SDL_WindowFlags flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_VULKAN; -#if WIN32 - flags |= SDL_WINDOW_BORDERLESS; -#endif - window_ = SDL_CreateWindow(config.Title.c_str(), config.Width, config.Height, flags); - if( !window_ ) - { - Throw(std::runtime_error("failed to init SDL Window.")); - } -} - -Window::~Window() -{ - if (window_ != nullptr) - { - SDL_DestroyWindow(window_); - window_ = nullptr; - } -} - -float Window::ContentScale() const -{ - float xscale = 1; - xscale = SDL_GetWindowDisplayScale(window_); - return xscale; -} - -VkExtent2D Window::FramebufferSize() const -{ - int width, height; - SDL_GetWindowSize(window_, &width, &height); - return VkExtent2D{ static_cast(width), static_cast(height) }; -} - -VkExtent2D Window::WindowSize() const -{ - int width, height; - SDL_GetWindowSize(window_, &width, &height); - return VkExtent2D{ static_cast(width), static_cast(height) }; -} - -std::vector Window::GetRequiredInstanceExtensions() const -{ - uint32_t extensionCount = 0; - auto extensionNames = SDL_Vulkan_GetInstanceExtensions(&extensionCount); - return std::vector(extensionNames, extensionNames + extensionCount); -} - -double Window::GetTime() const -{ - return SDL_GetTicks() / 1000.0; -} - -void Window::Close() -{ - SDL_Event e{}; - e.type = SDL_EventType::SDL_EVENT_WINDOW_CLOSE_REQUESTED; - e.window.windowID = SDL_GetWindowID(window_); - SDL_PushEvent(&e); -} - -bool Window::IsMinimized() const -{ - return SDL_GetWindowFlags(window_) & SDL_WINDOW_MINIMIZED; -} - -bool Window::IsMaximumed() const -{ - //return glfwGetWindowAttrib(window_, GLFW_MAXIMIZED); - return SDL_GetWindowFlags(window_) & SDL_WINDOW_MAXIMIZED; -} - -void Window::WaitForEvents() const -{ - //glfwWaitEvents(); - SDL_Event event; - while (SDL_WaitEvent(&event)) - { - - } -} - -void Window::Show() const -{ - SDL_ShowWindow(window_); -} - -void Window::Minimize() { - SDL_MinimizeWindow(window_); -} - -void Window::Maximum() { - SDL_MaximizeWindow(window_); -} - -void Window::Restore() -{ - SDL_RestoreWindow(window_); -} - -constexpr double closeAreaWidth = 0; -constexpr double titleAreaHeight = 55; -void Window::attemptDragWindow() { -#if !ANDROID - float x {}; - float y {}; - SDL_MouseButtonFlags flag = SDL_GetMouseState(&x, &y); - - if (flag == SDL_BUTTON_LEFT && dragState == 0) { - SDL_GetWindowSize(window_, &w_xsiz, &w_ysiz); - - s_xpos = x; - s_ypos = y; - dragState = 1; - } - if (flag == SDL_BUTTON_LEFT && dragState == 1) { - double cXpos, cYpos; - int wXpos, wYpos; - SDL_GetWindowPosition(window_, &wXpos, &wYpos); - - cXpos = x; - cYpos = y; - - if ( - s_xpos >= 0 && s_xpos <= (static_cast(w_xsiz) - closeAreaWidth) && - s_ypos >= 0 && s_ypos <= titleAreaHeight) { - SDL_SetWindowPosition(window_, wXpos + static_cast(cXpos - s_xpos), wYpos + static_cast(cYpos - s_ypos)); - capturedMouse_ = true; - } - if ( - s_xpos >= (static_cast(w_xsiz) - 15) && s_xpos <= (static_cast(w_xsiz)) && - s_ypos >= (static_cast(w_ysiz) - 15) && s_ypos <= (static_cast(w_ysiz))) { - SDL_SetWindowSize(window_, w_xsiz + static_cast(cXpos - s_xpos), w_ysiz + static_cast(cYpos - s_ypos)); - capturedMouse_ = true; - } - } - if (flag != SDL_BUTTON_LEFT && dragState == 1) { - dragState = 0; - capturedMouse_ = false; - } -#endif -} - -void Window::InitGLFW() -{ - SDL_SetHint( SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1" ); - if( !SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD) ) - { - Throw(std::runtime_error("failed to init SDL.")); - } - if( !SDL_Vulkan_LoadLibrary(nullptr) ) - { - Throw(std::runtime_error("failed to init SDL Vulkan.")); - } -} - -void Window::TerminateGLFW() -{ - SDL_Vulkan_UnloadLibrary(); - SDL_Quit(); -} -} diff --git a/src/Vulkan/Window.hpp b/src/Vulkan/Window.hpp deleted file mode 100644 index bc57d3f8..00000000 --- a/src/Vulkan/Window.hpp +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include "WindowConfig.hpp" -#include "Vulkan.hpp" -#include -#include - -namespace Vulkan -{ - - class Window final - { - public: - - VULKAN_NON_COPIABLE(Window) - - explicit Window(const WindowConfig& config); - ~Window(); - - // Window instance properties. - const WindowConfig& Config() const { return config_; } - - Next_Window* Handle() const { return window_; } - - float ContentScale() const; - VkExtent2D FramebufferSize() const; - VkExtent2D WindowSize() const; - - // GLFW instance properties (i.e. not bound to a window handler). - std::vector GetRequiredInstanceExtensions() const; - double GetTime() const; - - // Methods - void Close(); - bool IsMinimized() const; - bool IsMaximumed() const; - void WaitForEvents() const; - void Show() const; - - void Minimize(); - void Maximum(); - void Restore(); - - void attemptDragWindow(); - - // Static methods - static void InitGLFW(); - static void TerminateGLFW(); - - bool IsCapturingMouse() const { return capturedMouse_; } - private: - - const WindowConfig config_; - Next_Window* window_{}; - - bool capturedMouse_ = false; - double s_xpos = 0, s_ypos = 0; - int w_xsiz = 0, w_ysiz = 0; - int dragState = 0; - }; - -} diff --git a/src/Vulkan/WindowConfig.hpp b/src/Vulkan/WindowConfig.hpp deleted file mode 100644 index 7acc8fed..00000000 --- a/src/Vulkan/WindowConfig.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include -#include - -namespace Vulkan -{ - struct WindowConfig final - { - std::string Title; - uint32_t Width; - uint32_t Height; - bool CursorDisabled; - bool Fullscreen; - bool Resizable; - bool NeedScreenShot; - void* AndroidNativeWindow; - bool ForceSDR; - bool HideTitleBar {}; - }; -} diff --git a/src/Vulkan/WindowSurface.cpp b/src/Vulkan/WindowSurface.cpp new file mode 100644 index 00000000..9439ce50 --- /dev/null +++ b/src/Vulkan/WindowSurface.cpp @@ -0,0 +1,209 @@ +#include "WindowSurface.hpp" +#include "Instance.hpp" +#include "Utilities/Exception.hpp" +#include "Utilities/StbImage.hpp" +#include "Common/CoreMinimal.hpp" +#include "Options.hpp" +#include "Utilities/FileHelper.hpp" +#include + +namespace Vulkan +{ + +// ============================================================================ +// Window Implementation +// ============================================================================ + +Window::Window(const WindowConfig& config) : + config_(config) +{ + SDL_WindowFlags flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_VULKAN; +#if WIN32 + flags |= SDL_WINDOW_BORDERLESS; +#endif + window_ = SDL_CreateWindow(config.Title.c_str(), config.Width, config.Height, flags); + if (!window_) + { + Throw(std::runtime_error("failed to init SDL Window.")); + } +} + +Window::~Window() +{ + if (window_ != nullptr) + { + SDL_DestroyWindow(window_); + window_ = nullptr; + } +} + +float Window::ContentScale() const +{ + float xscale = 1; + xscale = SDL_GetWindowDisplayScale(window_); + return xscale; +} + +VkExtent2D Window::FramebufferSize() const +{ + int width, height; + SDL_GetWindowSize(window_, &width, &height); + return VkExtent2D{ static_cast(width), static_cast(height) }; +} + +VkExtent2D Window::WindowSize() const +{ + int width, height; + SDL_GetWindowSize(window_, &width, &height); + return VkExtent2D{ static_cast(width), static_cast(height) }; +} + +std::vector Window::GetRequiredInstanceExtensions() const +{ + uint32_t extensionCount = 0; + auto extensionNames = SDL_Vulkan_GetInstanceExtensions(&extensionCount); + return std::vector(extensionNames, extensionNames + extensionCount); +} + +double Window::GetTime() const +{ + return SDL_GetTicks() / 1000.0; +} + +void Window::Close() +{ + SDL_Event e{}; + e.type = SDL_EventType::SDL_EVENT_WINDOW_CLOSE_REQUESTED; + e.window.windowID = SDL_GetWindowID(window_); + SDL_PushEvent(&e); +} + +bool Window::IsMinimized() const +{ + return SDL_GetWindowFlags(window_) & SDL_WINDOW_MINIMIZED; +} + +bool Window::IsMaximumed() const +{ + //return glfwGetWindowAttrib(window_, GLFW_MAXIMIZED); + return SDL_GetWindowFlags(window_) & SDL_WINDOW_MAXIMIZED; +} + +void Window::WaitForEvents() const +{ + //glfwWaitEvents(); + SDL_Event event; + while (SDL_WaitEvent(&event)) + { + + } +} + +void Window::Show() const +{ + SDL_ShowWindow(window_); +} + +void Window::Minimize() +{ + SDL_MinimizeWindow(window_); +} + +void Window::Maximum() +{ + SDL_MaximizeWindow(window_); +} + +void Window::Restore() +{ + SDL_RestoreWindow(window_); +} + +constexpr double closeAreaWidth = 0; +constexpr double titleAreaHeight = 55; +void Window::attemptDragWindow() +{ +#if !ANDROID + float x{}; + float y{}; + SDL_MouseButtonFlags flag = SDL_GetMouseState(&x, &y); + + if (flag == SDL_BUTTON_LEFT && dragState == 0) + { + SDL_GetWindowSize(window_, &w_xsiz, &w_ysiz); + + s_xpos = x; + s_ypos = y; + dragState = 1; + } + if (flag == SDL_BUTTON_LEFT && dragState == 1) + { + double cXpos, cYpos; + int wXpos, wYpos; + SDL_GetWindowPosition(window_, &wXpos, &wYpos); + + cXpos = x; + cYpos = y; + + if ( + s_xpos >= 0 && s_xpos <= (static_cast(w_xsiz) - closeAreaWidth) && + s_ypos >= 0 && s_ypos <= titleAreaHeight) + { + SDL_SetWindowPosition(window_, wXpos + static_cast(cXpos - s_xpos), wYpos + static_cast(cYpos - s_ypos)); + capturedMouse_ = true; + } + if ( + s_xpos >= (static_cast(w_xsiz) - 15) && s_xpos <= (static_cast(w_xsiz)) && + s_ypos >= (static_cast(w_ysiz) - 15) && s_ypos <= (static_cast(w_ysiz))) + { + SDL_SetWindowSize(window_, w_xsiz + static_cast(cXpos - s_xpos), w_ysiz + static_cast(cYpos - s_ypos)); + capturedMouse_ = true; + } + } + if (flag != SDL_BUTTON_LEFT && dragState == 1) + { + dragState = 0; + capturedMouse_ = false; + } +#endif +} + +void Window::InitGLFW() +{ + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) + { + Throw(std::runtime_error("failed to init SDL.")); + } + if (!SDL_Vulkan_LoadLibrary(nullptr)) + { + Throw(std::runtime_error("failed to init SDL Vulkan.")); + } +} + +void Window::TerminateGLFW() +{ + SDL_Vulkan_UnloadLibrary(); + SDL_Quit(); +} + +// ============================================================================ +// Surface Implementation +// ============================================================================ + +Surface::Surface(const class Instance& instance) : + instance_(instance) +{ + SDL_Vulkan_CreateSurface(instance.Window().Handle(), instance.Handle(), nullptr, &surface_); +} + +Surface::~Surface() +{ + if (surface_ != nullptr) + { + vkDestroySurfaceKHR(instance_.Handle(), surface_, nullptr); + surface_ = nullptr; + } +} + +} diff --git a/src/Vulkan/WindowSurface.hpp b/src/Vulkan/WindowSurface.hpp new file mode 100644 index 00000000..37c1420e --- /dev/null +++ b/src/Vulkan/WindowSurface.hpp @@ -0,0 +1,110 @@ +#pragma once + +#include "Vulkan.hpp" +#include +#include +#include +#include + +namespace Vulkan +{ + +// ============================================================================ +// WindowConfig +// ============================================================================ + +struct WindowConfig final +{ + std::string Title; + uint32_t Width; + uint32_t Height; + bool CursorDisabled; + bool Fullscreen; + bool Resizable; + bool NeedScreenShot; + void* AndroidNativeWindow; + bool ForceSDR; + bool HideTitleBar {}; +}; + +// ============================================================================ +// Window +// ============================================================================ + +class Window final +{ +public: + + VULKAN_NON_COPIABLE(Window) + + explicit Window(const WindowConfig& config); + ~Window(); + + // Window instance properties. + const WindowConfig& Config() const { return config_; } + + Next_Window* Handle() const { return window_; } + + float ContentScale() const; + VkExtent2D FramebufferSize() const; + VkExtent2D WindowSize() const; + + // GLFW instance properties (i.e. not bound to a window handler). + std::vector GetRequiredInstanceExtensions() const; + double GetTime() const; + + // Methods + void Close(); + bool IsMinimized() const; + bool IsMaximumed() const; + void WaitForEvents() const; + void Show() const; + + void Minimize(); + void Maximum(); + void Restore(); + + void attemptDragWindow(); + + // Static methods + static void InitGLFW(); + static void TerminateGLFW(); + + bool IsCapturingMouse() const { return capturedMouse_; } + +private: + + const WindowConfig config_; + Next_Window* window_{}; + + bool capturedMouse_ = false; + double s_xpos = 0, s_ypos = 0; + int w_xsiz = 0, w_ysiz = 0; + int dragState = 0; +}; + +// ============================================================================ +// Surface +// ============================================================================ + +class Instance; + +class Surface final +{ +public: + + VULKAN_NON_COPIABLE(Surface) + + explicit Surface(const Instance& instance); + ~Surface(); + + const class Instance& Instance() const { return instance_; } + +private: + + const class Instance& instance_; + + VULKAN_HANDLE(VkSurfaceKHR, surface_) +}; + +} From 51dabf07622c05aa088745b2072cfe728282dc6e Mon Sep 17 00:00:00 2001 From: gameKnife Date: Mon, 2 Feb 2026 17:08:50 +0800 Subject: [PATCH 42/53] refactor(vulkan): merge Fence, Semaphore, VulkanGpuTimer into SyncAndTiming - Consolidate synchronization primitives and GPU timing into single file - Reduce file count: 40 -> 37 pairs (3 files merged) - Total lines: 436 (Fence 79 + Semaphore 62 + VulkanGpuTimer 295) - Update include paths across codebase Co-Authored-By: Claude Sonnet 4.5 --- src/Rendering/VulkanBaseRenderer.cpp | 3 +- src/Rendering/VulkanBaseRenderer.hpp | 2 +- src/Vulkan/Fence.cpp | 45 ---- src/Vulkan/Fence.hpp | 34 --- src/Vulkan/Semaphore.cpp | 32 --- src/Vulkan/Semaphore.hpp | 30 --- src/Vulkan/SyncAndTiming.cpp | 80 ++++++ src/Vulkan/SyncAndTiming.hpp | 376 +++++++++++++++++++++++++++ src/Vulkan/VulkanGpuTimer.hpp | 295 --------------------- 9 files changed, 458 insertions(+), 439 deletions(-) delete mode 100644 src/Vulkan/Fence.cpp delete mode 100644 src/Vulkan/Fence.hpp delete mode 100644 src/Vulkan/Semaphore.cpp delete mode 100644 src/Vulkan/Semaphore.hpp create mode 100644 src/Vulkan/SyncAndTiming.cpp create mode 100644 src/Vulkan/SyncAndTiming.hpp delete mode 100644 src/Vulkan/VulkanGpuTimer.hpp diff --git a/src/Rendering/VulkanBaseRenderer.cpp b/src/Rendering/VulkanBaseRenderer.cpp index ca0b79a1..e8bd09f9 100644 --- a/src/Rendering/VulkanBaseRenderer.cpp +++ b/src/Rendering/VulkanBaseRenderer.cpp @@ -5,13 +5,12 @@ #include "Vulkan/DebugUtilsMessenger.hpp" #include "Vulkan/DepthBuffer.hpp" #include "Vulkan/Device.hpp" -#include "Vulkan/Fence.hpp" +#include "Vulkan/SyncAndTiming.hpp" #include "Vulkan/BufferUtil.hpp" #include "Vulkan/FrameBuffer.hpp" #include "Vulkan/Instance.hpp" #include "Vulkan/PipelineLayout.hpp" #include "Vulkan/RenderPass.hpp" -#include "Vulkan/Semaphore.hpp" #include "Vulkan/SwapChain.hpp" #include "Vulkan/WindowSurface.hpp" #include "Vulkan/Enumerate.hpp" diff --git a/src/Rendering/VulkanBaseRenderer.hpp b/src/Rendering/VulkanBaseRenderer.hpp index f6ab044d..ea3a8d1d 100644 --- a/src/Rendering/VulkanBaseRenderer.hpp +++ b/src/Rendering/VulkanBaseRenderer.hpp @@ -1,7 +1,7 @@ #pragma once #include "Vulkan/FrameBuffer.hpp" -#include "Vulkan/VulkanGpuTimer.hpp" +#include "Vulkan/SyncAndTiming.hpp" #include "Vulkan/Image.hpp" #include "Assets/GPU/UniformBuffer.hpp" #include "Assets/Core/Scene.hpp" diff --git a/src/Vulkan/Fence.cpp b/src/Vulkan/Fence.cpp deleted file mode 100644 index 4b848f77..00000000 --- a/src/Vulkan/Fence.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "Fence.hpp" -#include "Device.hpp" - -namespace Vulkan { - -Fence::Fence(const class Device& device, const bool signaled) : - device_(device) -{ - VkFenceCreateInfo fenceInfo = {}; - fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; - fenceInfo.flags = signaled ? VK_FENCE_CREATE_SIGNALED_BIT : 0; - - Check(vkCreateFence(device.Handle(), &fenceInfo, nullptr, &fence_), - "create fence"); -} - -Fence::Fence(Fence&& other) noexcept : - device_(other.device_), - fence_(other.fence_) -{ - other.fence_ = nullptr; -} - -Fence::~Fence() -{ - if (fence_ != nullptr) - { - vkDestroyFence(device_.Handle(), fence_, nullptr); - fence_ = nullptr; - } -} - -void Fence::Reset() -{ - Check(vkResetFences(device_.Handle(), 1, &fence_), - "reset fence"); -} - -void Fence::Wait(const uint64_t timeout) const -{ - Check(vkWaitForFences(device_.Handle(), 1, &fence_, VK_TRUE, timeout), - "wait for fence"); -} - -} diff --git a/src/Vulkan/Fence.hpp b/src/Vulkan/Fence.hpp deleted file mode 100644 index 4fed7025..00000000 --- a/src/Vulkan/Fence.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include "Vulkan.hpp" - -namespace Vulkan -{ - class Device; - - class Fence final - { - public: - - Fence(const Fence&) = delete; - Fence& operator = (const Fence&) = delete; - Fence& operator = (Fence&&) = delete; - - explicit Fence(const Device& device, bool signaled); - Fence(Fence&& other) noexcept; - ~Fence(); - - const class Device& Device() const { return device_; } - const VkFence& Handle() const { return fence_; } - - void Reset(); - void Wait(uint64_t timeout) const; - - private: - - const class Device& device_; - - VkFence fence_{}; - }; - -} diff --git a/src/Vulkan/Semaphore.cpp b/src/Vulkan/Semaphore.cpp deleted file mode 100644 index 8695719b..00000000 --- a/src/Vulkan/Semaphore.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "Semaphore.hpp" -#include "Device.hpp" - -namespace Vulkan { - -Semaphore::Semaphore(const class Device& device) : - device_(device) -{ - VkSemaphoreCreateInfo semaphoreInfo = {}; - semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - - Check(vkCreateSemaphore(device.Handle(), &semaphoreInfo, nullptr, &semaphore_), - "create semaphores"); -} - -Semaphore::Semaphore(Semaphore&& other) noexcept : - device_(other.device_), - semaphore_(other.semaphore_) -{ - other.semaphore_ = nullptr; -} - -Semaphore::~Semaphore() -{ - if (semaphore_ != nullptr) - { - vkDestroySemaphore(device_.Handle(), semaphore_, nullptr); - semaphore_ = nullptr; - } -} - -} diff --git a/src/Vulkan/Semaphore.hpp b/src/Vulkan/Semaphore.hpp deleted file mode 100644 index cb27d3d9..00000000 --- a/src/Vulkan/Semaphore.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "Vulkan.hpp" - -namespace Vulkan -{ - class Device; - - class Semaphore final - { - public: - - Semaphore(const Semaphore&) = delete; - Semaphore& operator = (const Semaphore&) = delete; - Semaphore& operator = (Semaphore&&) = delete; - - explicit Semaphore(const Device& device); - Semaphore(Semaphore&& other) noexcept; - ~Semaphore(); - - const class Device& Device() const { return device_; } - - private: - - const class Device& device_; - - VULKAN_HANDLE(VkSemaphore, semaphore_) - }; - -} diff --git a/src/Vulkan/SyncAndTiming.cpp b/src/Vulkan/SyncAndTiming.cpp new file mode 100644 index 00000000..15a8fca2 --- /dev/null +++ b/src/Vulkan/SyncAndTiming.cpp @@ -0,0 +1,80 @@ +#include "SyncAndTiming.hpp" +#include "Device.hpp" + +namespace Vulkan +{ + +// ============================================================================ +// Fence Implementation +// ============================================================================ + +Fence::Fence(const class Device& device, const bool signaled) : + device_(device) +{ + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = signaled ? VK_FENCE_CREATE_SIGNALED_BIT : 0; + + Check(vkCreateFence(device.Handle(), &fenceInfo, nullptr, &fence_), + "create fence"); +} + +Fence::Fence(Fence&& other) noexcept : + device_(other.device_), + fence_(other.fence_) +{ + other.fence_ = nullptr; +} + +Fence::~Fence() +{ + if (fence_ != nullptr) + { + vkDestroyFence(device_.Handle(), fence_, nullptr); + fence_ = nullptr; + } +} + +void Fence::Reset() +{ + Check(vkResetFences(device_.Handle(), 1, &fence_), + "reset fence"); +} + +void Fence::Wait(const uint64_t timeout) const +{ + Check(vkWaitForFences(device_.Handle(), 1, &fence_, VK_TRUE, timeout), + "wait for fence"); +} + +// ============================================================================ +// Semaphore Implementation +// ============================================================================ + +Semaphore::Semaphore(const class Device& device) : + device_(device) +{ + VkSemaphoreCreateInfo semaphoreInfo = {}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + Check(vkCreateSemaphore(device.Handle(), &semaphoreInfo, nullptr, &semaphore_), + "create semaphores"); +} + +Semaphore::Semaphore(Semaphore&& other) noexcept : + device_(other.device_), + semaphore_(other.semaphore_) +{ + other.semaphore_ = nullptr; +} + +Semaphore::~Semaphore() +{ + if (semaphore_ != nullptr) + { + vkDestroySemaphore(device_.Handle(), semaphore_, nullptr); + semaphore_ = nullptr; + } +} + +} diff --git a/src/Vulkan/SyncAndTiming.hpp b/src/Vulkan/SyncAndTiming.hpp new file mode 100644 index 00000000..030efe33 --- /dev/null +++ b/src/Vulkan/SyncAndTiming.hpp @@ -0,0 +1,376 @@ +#pragma once + +#include "Vulkan.hpp" +#include "Device.hpp" +#include "Options.hpp" +#include +#include +#include +#include +#include +#include + +#define SCOPED_GPU_TIMER_FOLDER(name, folder) ScopedGpuTimer scopedGpuTimer(commandBuffer, GpuTimer(), name, folder) +#define SCOPED_GPU_TIMER(name) ScopedGpuTimer scopedGpuTimer(commandBuffer, GpuTimer(), name) +#define SCOPED_CPU_TIMER(name) ScopedCpuTimer scopedCpuTimer(GpuTimer(), name) +#define BENCH_MARK_CHECK() if(!GOption->HardwareQuery || !valid_) return + +namespace Vulkan +{ + +// ============================================================================ +// Fence +// ============================================================================ + +class Fence final +{ +public: + + Fence(const Fence&) = delete; + Fence& operator = (const Fence&) = delete; + Fence& operator = (Fence&&) = delete; + + explicit Fence(const Device& device, bool signaled); + Fence(Fence&& other) noexcept; + ~Fence(); + + const class Device& Device() const { return device_; } + const VkFence& Handle() const { return fence_; } + + void Reset(); + void Wait(uint64_t timeout) const; + +private: + + const class Device& device_; + + VkFence fence_{}; +}; + +// ============================================================================ +// Semaphore +// ============================================================================ + +class Semaphore final +{ +public: + + Semaphore(const Semaphore&) = delete; + Semaphore& operator = (const Semaphore&) = delete; + Semaphore& operator = (Semaphore&&) = delete; + + explicit Semaphore(const Device& device); + Semaphore(Semaphore&& other) noexcept; + ~Semaphore(); + + const class Device& Device() const { return device_; } + +private: + + const class Device& device_; + + VULKAN_HANDLE(VkSemaphore, semaphore_) +}; + +// ============================================================================ +// VulkanGpuTimer +// ============================================================================ + +class VulkanGpuTimer +{ +public: + DEFAULT_NON_COPIABLE(VulkanGpuTimer) + + VulkanGpuTimer(const Device& device, uint32_t totalCount, const VkPhysicalDeviceProperties& prop) : device_(device) + { + time_stamps.resize(totalCount); + timeStampPeriod_ = prop.limits.timestampPeriod; + + if (timeStampPeriod_ == 0) + { + valid_ = false; + return; + } + + VkQueryPoolCreateInfo query_pool_info{}; + query_pool_info.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO; + query_pool_info.queryType = VK_QUERY_TYPE_TIMESTAMP; + query_pool_info.queryCount = static_cast(time_stamps.size()); + + // Use try-catch or noexcept check if possible, but here we just rely on Check not crashing the app if we handle it? + // The project's Check throws runtime_error. + try + { + Check(vkCreateQueryPool(device_.Handle(), &query_pool_info, nullptr, &query_pool_timestamps), "create timestamp pool"); + valid_ = true; + } + catch (...) + { + valid_ = false; + query_pool_timestamps = VK_NULL_HANDLE; + } + } + virtual ~VulkanGpuTimer() + { + if (query_pool_timestamps != VK_NULL_HANDLE) + { + vkDestroyQueryPool(device_.Handle(), query_pool_timestamps, nullptr); + } + } + + void Reset(VkCommandBuffer commandBuffer) + { + BENCH_MARK_CHECK(); + vkCmdResetQueryPool(commandBuffer, query_pool_timestamps, 0, static_cast(time_stamps.size())); + queryIdx = 0; + started_ = true; + + CalculatieGpuStats(); + for (auto& [name, query] : gpu_timer_query_map) + { + std::get<1>(gpu_timer_query_map[name]) = 0; + std::get<0>(gpu_timer_query_map[name]) = 0; + } + } + + void CpuFrameEnd() + { + // iterate the cpu_timer_query_map + for (auto& [name, query] : cpu_timer_query_map) + { + std::get<2>(cpu_timer_query_map[name]) = std::get<1>(cpu_timer_query_map[name]) - std::get<0>(cpu_timer_query_map[name]); + } + } + + void FrameEnd(VkCommandBuffer commandBuffer) + { + BENCH_MARK_CHECK(); + if (started_) + { + started_ = false; + } + else + { + return; + } + vkGetQueryPoolResults( + device_.Handle(), + query_pool_timestamps, + 0, + queryIdx, + time_stamps.size() * sizeof(uint64_t), + time_stamps.data(), + sizeof(uint64_t), + VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); + + for (auto& [name, query] : gpu_timer_query_map) + { + std::get<2>(gpu_timer_query_map[name]) = (time_stamps[std::get<1>(gpu_timer_query_map[name])] - time_stamps[std::get<0>(gpu_timer_query_map[name])]); + } + } + + void Start(VkCommandBuffer commandBuffer, const char* name) + { + BENCH_MARK_CHECK(); + if (gpu_timer_query_map.find(name) == gpu_timer_query_map.end()) + { + gpu_timer_query_map[name] = std::make_tuple(0, 0, 0); + } + vkCmdWriteTimestamp(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, query_pool_timestamps, queryIdx); + device_.DebugUtils().BeginMarker(commandBuffer, name); + std::get<0>(gpu_timer_query_map[name]) = queryIdx; + queryIdx++; + } + void End(VkCommandBuffer commandBuffer, const char* name) + { + BENCH_MARK_CHECK(); + assert(gpu_timer_query_map.find(name) != gpu_timer_query_map.end()); + vkCmdWriteTimestamp(commandBuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, query_pool_timestamps, queryIdx); + device_.DebugUtils().EndMarker(commandBuffer); + std::get<1>(gpu_timer_query_map[name]) = queryIdx; + queryIdx++; + } + void StartCpuTimer(const char* name) + { + if (cpu_timer_query_map.find(name) == cpu_timer_query_map.end()) + { + cpu_timer_query_map[name] = std::make_tuple(0, 0, 0); + } + std::get<0>(cpu_timer_query_map[name]) = std::chrono::high_resolution_clock::now().time_since_epoch().count(); + } + void EndCpuTimer(const char* name) + { + assert(cpu_timer_query_map.find(name) != cpu_timer_query_map.end()); + std::get<1>(cpu_timer_query_map[name]) = std::chrono::high_resolution_clock::now().time_since_epoch().count(); + } + float GetGpuTime(const char* name) + { + if (gpu_timer_query_map.find(name) == gpu_timer_query_map.end()) + { + return 0; + } + return std::get<2>(gpu_timer_query_map[name]) * timeStampPeriod_ * 1e-6f; + } + float GetCpuTime(const char* name) + { + if (cpu_timer_query_map.find(name) == cpu_timer_query_map.end()) + { + return 0; + } + return std::get<2>(cpu_timer_query_map[name]) * 1e-6f; + } + + void CalculatieGpuStats() + { + lastStats.clear(); + std::list > order_list; + for (auto& [name, query] : gpu_timer_query_map) + { + if (std::get<2>(gpu_timer_query_map[name]) == 0) + { + continue; + } + order_list.insert(order_list.begin(), (std::make_tuple(name, GetGpuTime(name.c_str()), std::get<0>(query), std::get<1>(query)))); + } + + order_list.sort([](const std::tuple& a, const std::tuple& b) -> bool + { + return std::get<2>(a) < std::get<2>(b); + }); + + std::vector> activeTimers; + for (auto& [name, time, startIdx, endIdx] : order_list) + { + while (!activeTimers.empty() && std::get<3>(activeTimers.back()) < startIdx) + { + activeTimers.pop_back(); + } + + int stackDepth = static_cast(activeTimers.size()); + activeTimers.push_back(std::make_tuple(name, time, startIdx, endIdx)); + lastStats.push_back(std::make_tuple(name, stackDepth, time)); + } + } + std::vector > FetchAllTimes(int maxStack) + { + std::vector > result; + for (auto& [name, stackDepth, time] : lastStats) + { + if (maxStack > stackDepth) + { + result.push_back(std::make_tuple(name, time, stackDepth)); + } + } + return result; + } + + // Folder management + void PushGpuFolder(const std::string& name) + { + gpuFolderStack_.push_back(currentGpuFolder_.length()); + currentGpuFolder_ += name; + } + void PopGpuFolder() + { + if (!gpuFolderStack_.empty()) + { + currentGpuFolder_.resize(gpuFolderStack_.back()); + gpuFolderStack_.pop_back(); + } + } + const std::string& GetCurrentGpuFolder() const { return currentGpuFolder_; } + + void PushCpuFolder(const std::string& name) + { + cpuFolderStack_.push_back(currentCpuFolder_.length()); + currentCpuFolder_ += name; + } + void PopCpuFolder() + { + if (!cpuFolderStack_.empty()) + { + currentCpuFolder_.resize(cpuFolderStack_.back()); + cpuFolderStack_.pop_back(); + } + } + const std::string& GetCurrentCpuFolder() const { return currentCpuFolder_; } + + std::vector > lastStats; // name, depth, duration seconds + VkQueryPool query_pool_timestamps = VK_NULL_HANDLE; + std::vector time_stamps{}; + std::unordered_map > gpu_timer_query_map{}; + std::unordered_map > cpu_timer_query_map{}; + const Device& device_; + uint32_t queryIdx = 0; + float timeStampPeriod_ = 1; + bool started_ = false; + bool valid_ = false; + + std::string currentGpuFolder_; + std::vector gpuFolderStack_; + std::string currentCpuFolder_; + std::vector cpuFolderStack_; +}; + +// ============================================================================ +// ScopedGpuTimer +// ============================================================================ + +class ScopedGpuTimer +{ +public: + DEFAULT_NON_COPIABLE(ScopedGpuTimer) + + ScopedGpuTimer(VkCommandBuffer commandBuffer, VulkanGpuTimer* timer, const char* name, const char* foldername) : commandBuffer_(commandBuffer), timer_(timer), name_(name) + { + timer_->Start(commandBuffer_, name_.c_str()); + folderTimer = true; + timer_->PushGpuFolder(foldername); + } + ScopedGpuTimer(VkCommandBuffer commandBuffer, VulkanGpuTimer* timer, const char* name) : commandBuffer_(commandBuffer), timer_(timer), name_(name) + { + timer_->Start(commandBuffer_, (timer_->GetCurrentGpuFolder() + name_).c_str()); + } + virtual ~ScopedGpuTimer() + { + if (folderTimer) + { + timer_->PopGpuFolder(); + timer_->End(commandBuffer_, name_.c_str()); + } + else + { + timer_->End(commandBuffer_, (timer_->GetCurrentGpuFolder() + name_).c_str()); + } + } + VkCommandBuffer commandBuffer_; + VulkanGpuTimer* timer_; + std::string name_; + bool folderTimer = false; +}; + +// ============================================================================ +// ScopedCpuTimer +// ============================================================================ + +class ScopedCpuTimer +{ +public: + DEFAULT_NON_COPIABLE(ScopedCpuTimer) + + ScopedCpuTimer(VulkanGpuTimer* timer, const char* name) : timer_(timer), name_(name) + { + timer_->StartCpuTimer((timer_->GetCurrentCpuFolder() + name_).c_str()); + } + virtual ~ScopedCpuTimer() + { + timer_->EndCpuTimer((timer_->GetCurrentCpuFolder() + name_).c_str()); + } + VulkanGpuTimer* timer_; + std::string name_; + + // Static methods removed as per refactoring plan. + // If manual folder management is needed, expose methods on VulkanGpuTimer. +}; + +} diff --git a/src/Vulkan/VulkanGpuTimer.hpp b/src/Vulkan/VulkanGpuTimer.hpp deleted file mode 100644 index f9271110..00000000 --- a/src/Vulkan/VulkanGpuTimer.hpp +++ /dev/null @@ -1,295 +0,0 @@ -#pragma once - -#include "Options.hpp" -#include -#include -#include -#include -#include -#include -#include "Device.hpp" - -#define SCOPED_GPU_TIMER_FOLDER(name, folder) ScopedGpuTimer scopedGpuTimer(commandBuffer, GpuTimer(), name, folder) -#define SCOPED_GPU_TIMER(name) ScopedGpuTimer scopedGpuTimer(commandBuffer, GpuTimer(), name) -#define SCOPED_CPU_TIMER(name) ScopedCpuTimer scopedCpuTimer(GpuTimer(), name) -#define BENCH_MARK_CHECK() if(!GOption->HardwareQuery || !valid_) return - -namespace Vulkan -{ - class VulkanGpuTimer - { - public: - DEFAULT_NON_COPIABLE(VulkanGpuTimer) - - VulkanGpuTimer(const Device& device, uint32_t totalCount, const VkPhysicalDeviceProperties& prop):device_(device) - { - time_stamps.resize(totalCount); - timeStampPeriod_ = prop.limits.timestampPeriod; - - if (timeStampPeriod_ == 0) { - valid_ = false; - return; - } - - VkQueryPoolCreateInfo query_pool_info{}; - query_pool_info.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO; - query_pool_info.queryType = VK_QUERY_TYPE_TIMESTAMP; - query_pool_info.queryCount = static_cast(time_stamps.size()); - - // Use try-catch or noexcept check if possible, but here we just rely on Check not crashing the app if we handle it? - // The project's Check throws runtime_error. - try { - Check(vkCreateQueryPool(device_.Handle(), &query_pool_info, nullptr, &query_pool_timestamps), "create timestamp pool"); - valid_ = true; - } - catch(...) { - valid_ = false; - query_pool_timestamps = VK_NULL_HANDLE; - } - } - virtual ~VulkanGpuTimer() { - if (query_pool_timestamps != VK_NULL_HANDLE) { - vkDestroyQueryPool(device_.Handle(), query_pool_timestamps, nullptr); - } - } - - void Reset(VkCommandBuffer commandBuffer) - { - BENCH_MARK_CHECK(); - vkCmdResetQueryPool(commandBuffer, query_pool_timestamps, 0, static_cast(time_stamps.size())); - queryIdx = 0; - started_ = true; - - CalculatieGpuStats(); - for(auto& [name, query] : gpu_timer_query_map) - { - std::get<1>(gpu_timer_query_map[name]) = 0; - std::get<0>(gpu_timer_query_map[name]) = 0; - } - } - - void CpuFrameEnd() - { - // iterate the cpu_timer_query_map - for(auto& [name, query] : cpu_timer_query_map) - { - std::get<2>(cpu_timer_query_map[name]) = std::get<1>(cpu_timer_query_map[name]) - std::get<0>(cpu_timer_query_map[name]); - } - } - - void FrameEnd(VkCommandBuffer commandBuffer) - { - BENCH_MARK_CHECK(); - if(started_) - { - started_ = false; - } - else - { - return; - } - vkGetQueryPoolResults( - device_.Handle(), - query_pool_timestamps, - 0, - queryIdx, - time_stamps.size() * sizeof(uint64_t), - time_stamps.data(), - sizeof(uint64_t), - VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); - - for(auto& [name, query] : gpu_timer_query_map) - { - std::get<2>(gpu_timer_query_map[name]) = (time_stamps[ std::get<1>(gpu_timer_query_map[name]) ] - time_stamps[ std::get<0>(gpu_timer_query_map[name])]); - } - } - - void Start(VkCommandBuffer commandBuffer, const char* name) - { - BENCH_MARK_CHECK(); - if( gpu_timer_query_map.find(name) == gpu_timer_query_map.end()) - { - gpu_timer_query_map[name] = std::make_tuple(0, 0, 0); - } - vkCmdWriteTimestamp(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, query_pool_timestamps, queryIdx); - device_.DebugUtils().BeginMarker(commandBuffer, name); - std::get<0>(gpu_timer_query_map[name]) = queryIdx; - queryIdx++; - } - void End(VkCommandBuffer commandBuffer, const char* name) - { - BENCH_MARK_CHECK(); - assert( gpu_timer_query_map.find(name) != gpu_timer_query_map.end() ); - vkCmdWriteTimestamp(commandBuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, query_pool_timestamps, queryIdx); - device_.DebugUtils().EndMarker(commandBuffer); - std::get<1>(gpu_timer_query_map[name]) = queryIdx; - queryIdx++; - } - void StartCpuTimer(const char* name) - { - if( cpu_timer_query_map.find(name) == cpu_timer_query_map.end()) - { - cpu_timer_query_map[name] = std::make_tuple(0, 0, 0); - } - std::get<0>(cpu_timer_query_map[name]) = std::chrono::high_resolution_clock::now().time_since_epoch().count(); - } - void EndCpuTimer(const char* name) - { - assert( cpu_timer_query_map.find(name) != cpu_timer_query_map.end() ); - std::get<1>(cpu_timer_query_map[name]) = std::chrono::high_resolution_clock::now().time_since_epoch().count(); - } - float GetGpuTime(const char* name) - { - if(gpu_timer_query_map.find(name) == gpu_timer_query_map.end()) - { - return 0; - } - return std::get<2>(gpu_timer_query_map[name]) * timeStampPeriod_ * 1e-6f; - } - float GetCpuTime(const char* name) - { - if(cpu_timer_query_map.find(name) == cpu_timer_query_map.end()) - { - return 0; - } - return std::get<2>(cpu_timer_query_map[name]) * 1e-6f; - } - - void CalculatieGpuStats() - { - lastStats.clear(); - std::list > order_list; - for(auto& [name, query] : gpu_timer_query_map) - { - if ( std::get<2>(gpu_timer_query_map[name]) == 0) - { - continue; - } - order_list.insert(order_list.begin(), (std::make_tuple(name, GetGpuTime(name.c_str()), std::get<0>(query), std::get<1>(query)))); - } - - order_list.sort([](const std::tuple& a, const std::tuple& b) -> bool - { - return std::get<2>(a) < std::get<2>(b); - }); - - std::vector> activeTimers; - for(auto& [name, time, startIdx, endIdx] : order_list) - { - while (!activeTimers.empty() && std::get<3>(activeTimers.back()) < startIdx) { - activeTimers.pop_back(); - } - - int stackDepth = static_cast(activeTimers.size()); - activeTimers.push_back(std::make_tuple(name, time, startIdx, endIdx)); - lastStats.push_back(std::make_tuple(name, stackDepth, time)); - } - } - std::vector > FetchAllTimes( int maxStack ) - { - std::vector > result; - for(auto& [name, stackDepth, time] : lastStats) - { - if (maxStack > stackDepth) - { - result.push_back(std::make_tuple(name, time, stackDepth)); - } - } - return result; - } - - // Folder management - void PushGpuFolder(const std::string& name) { - gpuFolderStack_.push_back(currentGpuFolder_.length()); - currentGpuFolder_ += name; - } - void PopGpuFolder() { - if (!gpuFolderStack_.empty()) { - currentGpuFolder_.resize(gpuFolderStack_.back()); - gpuFolderStack_.pop_back(); - } - } - const std::string& GetCurrentGpuFolder() const { return currentGpuFolder_; } - - void PushCpuFolder(const std::string& name) { - cpuFolderStack_.push_back(currentCpuFolder_.length()); - currentCpuFolder_ += name; - } - void PopCpuFolder() { - if (!cpuFolderStack_.empty()) { - currentCpuFolder_.resize(cpuFolderStack_.back()); - cpuFolderStack_.pop_back(); - } - } - const std::string& GetCurrentCpuFolder() const { return currentCpuFolder_; } - - std::vector > lastStats; // name, depth, duration seconds - VkQueryPool query_pool_timestamps = VK_NULL_HANDLE; - std::vector time_stamps{}; - std::unordered_map > gpu_timer_query_map{}; - std::unordered_map > cpu_timer_query_map{}; - const Device& device_; - uint32_t queryIdx = 0; - float timeStampPeriod_ = 1; - bool started_ = false; - bool valid_ = false; - - std::string currentGpuFolder_; - std::vector gpuFolderStack_; - std::string currentCpuFolder_; - std::vector cpuFolderStack_; - }; - - class ScopedGpuTimer - { - public: - DEFAULT_NON_COPIABLE(ScopedGpuTimer) - - ScopedGpuTimer(VkCommandBuffer commandBuffer, VulkanGpuTimer* timer, const char* name, const char* foldername ):commandBuffer_(commandBuffer),timer_(timer), name_(name) - { - timer_->Start(commandBuffer_, name_.c_str()); - folderTimer = true; - timer_->PushGpuFolder(foldername); - } - ScopedGpuTimer(VkCommandBuffer commandBuffer, VulkanGpuTimer* timer, const char* name ):commandBuffer_(commandBuffer),timer_(timer), name_(name) - { - timer_->Start(commandBuffer_, (timer_->GetCurrentGpuFolder() + name_).c_str()); - } - virtual ~ScopedGpuTimer() - { - if (folderTimer) - { - timer_->PopGpuFolder(); - timer_->End(commandBuffer_, name_.c_str()); - } - else - { - timer_->End(commandBuffer_, (timer_->GetCurrentGpuFolder() + name_).c_str()); - } - } - VkCommandBuffer commandBuffer_; - VulkanGpuTimer* timer_; - std::string name_; - bool folderTimer = false; - }; - - class ScopedCpuTimer - { - public: - DEFAULT_NON_COPIABLE(ScopedCpuTimer) - - ScopedCpuTimer(VulkanGpuTimer* timer, const char* name ):timer_(timer), name_(name) - { - timer_->StartCpuTimer((timer_->GetCurrentCpuFolder() + name_).c_str()); - } - virtual ~ScopedCpuTimer() - { - timer_->EndCpuTimer( (timer_->GetCurrentCpuFolder() + name_).c_str()); - } - VulkanGpuTimer* timer_; - std::string name_; - - // Static methods removed as per refactoring plan. - // If manual folder management is needed, expose methods on VulkanGpuTimer. - }; -} From 88b1bc219f26997b6979113e280b9bcb95b2b113 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Mon, 2 Feb 2026 17:25:36 +0800 Subject: [PATCH 43/53] refactor(vulkan): merge CommandPool, CommandBuffers, and SingleTimeCommands into CommandExecution - Merged 3 files into CommandExecution.hpp/cpp - CommandPool: manages command pool and queue allocation - CommandBuffers: manages command buffer allocation and recording - SingleTimeCommands: utility for single-time command submission - All classes clearly separated with // ============ markers - Updated all references across codebase Co-Authored-By: Claude Sonnet 4.5 --- src/Assets/GPU/TextureImage.cpp | 4 +- src/Assets/GPU/UniformBuffer.cpp | 2 +- .../PathTracing/PathTracingRenderer.cpp | 2 +- src/Rendering/RayTraceBaseRenderer.cpp | 2 +- src/Rendering/VulkanBaseRenderer.cpp | 6 +- src/Runtime/Editor/UserInterface.cpp | 2 +- src/Vulkan/Buffer.cpp | 2 +- src/Vulkan/BufferUtil.hpp | 2 +- src/Vulkan/CommandBuffers.cpp | 50 ---------- src/Vulkan/CommandBuffers.hpp | 32 ------- src/Vulkan/CommandExecution.cpp | 81 ++++++++++++++++ src/Vulkan/CommandExecution.hpp | 96 +++++++++++++++++++ src/Vulkan/CommandPool.cpp | 29 ------ src/Vulkan/CommandPool.hpp | 30 ------ src/Vulkan/DepthBuffer.cpp | 2 +- src/Vulkan/Image.cpp | 2 +- src/Vulkan/RenderImage.cpp | 2 +- src/Vulkan/SingleTimeCommands.hpp | 41 -------- 18 files changed, 191 insertions(+), 196 deletions(-) delete mode 100644 src/Vulkan/CommandBuffers.cpp delete mode 100644 src/Vulkan/CommandBuffers.hpp create mode 100644 src/Vulkan/CommandExecution.cpp create mode 100644 src/Vulkan/CommandExecution.hpp delete mode 100644 src/Vulkan/CommandPool.cpp delete mode 100644 src/Vulkan/CommandPool.hpp delete mode 100644 src/Vulkan/SingleTimeCommands.hpp diff --git a/src/Assets/GPU/TextureImage.cpp b/src/Assets/GPU/TextureImage.cpp index 1e63206d..d5b66061 100644 --- a/src/Assets/GPU/TextureImage.cpp +++ b/src/Assets/GPU/TextureImage.cpp @@ -1,12 +1,12 @@ #include "Assets/GPU/TextureImage.hpp" #include "Assets/GPU/Texture.hpp" #include "Vulkan/Buffer.hpp" -#include "Vulkan/CommandPool.hpp" +#include "Vulkan/CommandExecution.hpp" #include "Vulkan/ImageView.hpp" #include "Vulkan/Sampler.hpp" #include -#include "Vulkan/SingleTimeCommands.hpp" +#include "Vulkan/CommandExecution.hpp" namespace Assets { diff --git a/src/Assets/GPU/UniformBuffer.cpp b/src/Assets/GPU/UniformBuffer.cpp index 8b5b4a34..ddf12373 100644 --- a/src/Assets/GPU/UniformBuffer.cpp +++ b/src/Assets/GPU/UniformBuffer.cpp @@ -1,6 +1,6 @@ #include "Assets/GPU/UniformBuffer.hpp" #include "Vulkan/Buffer.hpp" -#include "Vulkan/CommandPool.hpp" +#include "Vulkan/CommandExecution.hpp" #include #include "Vulkan/BufferUtil.hpp" diff --git a/src/Rendering/PathTracing/PathTracingRenderer.cpp b/src/Rendering/PathTracing/PathTracingRenderer.cpp index 6fefe8dc..13c039b1 100644 --- a/src/Rendering/PathTracing/PathTracingRenderer.cpp +++ b/src/Rendering/PathTracing/PathTracingRenderer.cpp @@ -2,7 +2,7 @@ #include "Vulkan/BufferUtil.hpp" #include "Vulkan/Image.hpp" #include "Vulkan/ImageMemoryBarrier.hpp" -#include "Vulkan/SingleTimeCommands.hpp" +#include "Vulkan/CommandExecution.hpp" #include "Vulkan/SwapChain.hpp" #include "Utilities/Math.hpp" #include "Vulkan/RenderImage.hpp" diff --git a/src/Rendering/RayTraceBaseRenderer.cpp b/src/Rendering/RayTraceBaseRenderer.cpp index fb38ee0b..344900a4 100644 --- a/src/Rendering/RayTraceBaseRenderer.cpp +++ b/src/Rendering/RayTraceBaseRenderer.cpp @@ -10,7 +10,7 @@ #include "Runtime/Components/SkinnedMeshComponent.h" #include "Vulkan/Buffer.hpp" #include "Vulkan/PipelineLayout.hpp" -#include "Vulkan/SingleTimeCommands.hpp" +#include "Vulkan/CommandExecution.hpp" #include #include #include diff --git a/src/Rendering/VulkanBaseRenderer.cpp b/src/Rendering/VulkanBaseRenderer.cpp index e8bd09f9..f44d3096 100644 --- a/src/Rendering/VulkanBaseRenderer.cpp +++ b/src/Rendering/VulkanBaseRenderer.cpp @@ -1,7 +1,7 @@ #include "VulkanBaseRenderer.hpp" #include "Vulkan/Buffer.hpp" -#include "Vulkan/CommandPool.hpp" -#include "Vulkan/CommandBuffers.hpp" +#include "Vulkan/CommandExecution.hpp" +#include "Vulkan/CommandExecution.hpp" #include "Vulkan/DebugUtilsMessenger.hpp" #include "Vulkan/DepthBuffer.hpp" #include "Vulkan/Device.hpp" @@ -16,7 +16,7 @@ #include "Vulkan/Enumerate.hpp" #include "Vulkan/ImageMemoryBarrier.hpp" #include "Vulkan/RenderImage.hpp" -#include "Vulkan/SingleTimeCommands.hpp" +#include "Vulkan/CommandExecution.hpp" #include "Vulkan/Strings.hpp" #include "Vulkan/Version.hpp" diff --git a/src/Runtime/Editor/UserInterface.cpp b/src/Runtime/Editor/UserInterface.cpp index d4ae3c6c..e830ade7 100644 --- a/src/Runtime/Editor/UserInterface.cpp +++ b/src/Runtime/Editor/UserInterface.cpp @@ -8,7 +8,7 @@ #include "Vulkan/Device.hpp" #include "Vulkan/Instance.hpp" #include "Vulkan/RenderPass.hpp" -#include "Vulkan/SingleTimeCommands.hpp" +#include "Vulkan/CommandExecution.hpp" #include "Vulkan/SwapChain.hpp" #include "Vulkan/WindowSurface.hpp" diff --git a/src/Vulkan/Buffer.cpp b/src/Vulkan/Buffer.cpp index 35160137..66630e45 100644 --- a/src/Vulkan/Buffer.cpp +++ b/src/Vulkan/Buffer.cpp @@ -1,5 +1,5 @@ #include "Buffer.hpp" -#include "SingleTimeCommands.hpp" +#include "CommandExecution.hpp" namespace Vulkan { diff --git a/src/Vulkan/BufferUtil.hpp b/src/Vulkan/BufferUtil.hpp index 698e8950..585b82b3 100644 --- a/src/Vulkan/BufferUtil.hpp +++ b/src/Vulkan/BufferUtil.hpp @@ -1,7 +1,7 @@ #pragma once #include "Buffer.hpp" -#include "CommandPool.hpp" +#include "CommandExecution.hpp" #include "Device.hpp" #include "DeviceMemory.hpp" #include diff --git a/src/Vulkan/CommandBuffers.cpp b/src/Vulkan/CommandBuffers.cpp deleted file mode 100644 index 70d2ec47..00000000 --- a/src/Vulkan/CommandBuffers.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "CommandBuffers.hpp" -#include "CommandPool.hpp" -#include "Device.hpp" - -namespace Vulkan { - -CommandBuffers::CommandBuffers(CommandPool& commandPool, const uint32_t size) : - commandPool_(commandPool) -{ - VkCommandBufferAllocateInfo allocInfo = {}; - allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - allocInfo.commandPool = commandPool.Handle(); - allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - allocInfo.commandBufferCount = size; - - commandBuffers_.resize(size); - - Check(vkAllocateCommandBuffers(commandPool.Device().Handle(), &allocInfo, commandBuffers_.data()), - "allocate command buffers"); -} - -CommandBuffers::~CommandBuffers() -{ - if (!commandBuffers_.empty()) - { - vkFreeCommandBuffers(commandPool_.Device().Handle(), commandPool_.Handle(), static_cast(commandBuffers_.size()), commandBuffers_.data()); - commandBuffers_.clear(); - } -} - -VkCommandBuffer CommandBuffers::Begin(const size_t i) -{ - VkCommandBufferBeginInfo beginInfo = {}; - beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; - beginInfo.pInheritanceInfo = nullptr; // Optional - - Check(vkBeginCommandBuffer(commandBuffers_[i], &beginInfo), - "begin recording command buffer"); - - return commandBuffers_[i]; -} - -void CommandBuffers::End(const size_t i) -{ - Check(vkEndCommandBuffer(commandBuffers_[i]), - "record command buffer"); -} - -} diff --git a/src/Vulkan/CommandBuffers.hpp b/src/Vulkan/CommandBuffers.hpp deleted file mode 100644 index 0e40cd3a..00000000 --- a/src/Vulkan/CommandBuffers.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include "Vulkan.hpp" -#include - -namespace Vulkan -{ - class CommandPool; - - class CommandBuffers final - { - public: - - VULKAN_NON_COPIABLE(CommandBuffers) - - CommandBuffers(CommandPool& commandPool, uint32_t size); - ~CommandBuffers(); - - uint32_t Size() const { return static_cast(commandBuffers_.size()); } - VkCommandBuffer& operator [] (const size_t i) { return commandBuffers_[i]; } - - VkCommandBuffer Begin(size_t i); - void End(size_t); - - private: - - const CommandPool& commandPool_; - - std::vector commandBuffers_; - }; - -} diff --git a/src/Vulkan/CommandExecution.cpp b/src/Vulkan/CommandExecution.cpp new file mode 100644 index 00000000..82857964 --- /dev/null +++ b/src/Vulkan/CommandExecution.cpp @@ -0,0 +1,81 @@ +#include "CommandExecution.hpp" +#include "Device.hpp" + +namespace Vulkan +{ + +// ============================================================================ +// CommandPool +// ============================================================================ + +CommandPool::CommandPool(const class Device& device, const uint32_t queueFamilyIndex, uint32_t queue, const bool allowReset) : + device_(device) +{ + VkCommandPoolCreateInfo poolInfo = {}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.queueFamilyIndex = queueFamilyIndex; + poolInfo.flags = allowReset ? VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT : 0; + + queue_ = queue == 0 ? device.GraphicsQueue() : device.TransferQueue(); + + Check(vkCreateCommandPool(device.Handle(), &poolInfo, nullptr, &commandPool_), + "create command pool"); +} + +CommandPool::~CommandPool() +{ + if (commandPool_ != nullptr) + { + vkDestroyCommandPool(device_.Handle(), commandPool_, nullptr); + commandPool_ = nullptr; + } +} + +// ============================================================================ +// CommandBuffers +// ============================================================================ + +CommandBuffers::CommandBuffers(CommandPool& commandPool, const uint32_t size) : + commandPool_(commandPool) +{ + VkCommandBufferAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool.Handle(); + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = size; + + commandBuffers_.resize(size); + + Check(vkAllocateCommandBuffers(commandPool.Device().Handle(), &allocInfo, commandBuffers_.data()), + "allocate command buffers"); +} + +CommandBuffers::~CommandBuffers() +{ + if (!commandBuffers_.empty()) + { + vkFreeCommandBuffers(commandPool_.Device().Handle(), commandPool_.Handle(), static_cast(commandBuffers_.size()), commandBuffers_.data()); + commandBuffers_.clear(); + } +} + +VkCommandBuffer CommandBuffers::Begin(const size_t i) +{ + VkCommandBufferBeginInfo beginInfo = {}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; + beginInfo.pInheritanceInfo = nullptr; // Optional + + Check(vkBeginCommandBuffer(commandBuffers_[i], &beginInfo), + "begin recording command buffer"); + + return commandBuffers_[i]; +} + +void CommandBuffers::End(const size_t i) +{ + Check(vkEndCommandBuffer(commandBuffers_[i]), + "record command buffer"); +} + +} diff --git a/src/Vulkan/CommandExecution.hpp b/src/Vulkan/CommandExecution.hpp new file mode 100644 index 00000000..10bdc053 --- /dev/null +++ b/src/Vulkan/CommandExecution.hpp @@ -0,0 +1,96 @@ +#pragma once + +#include "Vulkan.hpp" +#include +#include + +namespace Vulkan +{ + class Device; + + // ============================================================================ + // CommandPool + // ============================================================================ + + class CommandPool final + { + public: + + VULKAN_NON_COPIABLE(CommandPool) + + CommandPool(const Device& device, uint32_t queueFamilyIndex, uint32_t queue, bool allowReset); + ~CommandPool(); + + const class Device& Device() const { return device_; } + VkQueue Queue() const { return queue_; } + + private: + + const class Device& device_; + + VULKAN_HANDLE(VkCommandPool, commandPool_) + + VkQueue queue_; + }; + + // ============================================================================ + // CommandBuffers + // ============================================================================ + + class CommandBuffers final + { + public: + + VULKAN_NON_COPIABLE(CommandBuffers) + + CommandBuffers(CommandPool& commandPool, uint32_t size); + ~CommandBuffers(); + + uint32_t Size() const { return static_cast(commandBuffers_.size()); } + VkCommandBuffer& operator [] (const size_t i) { return commandBuffers_[i]; } + + VkCommandBuffer Begin(size_t i); + void End(size_t); + + private: + + const CommandPool& commandPool_; + + std::vector commandBuffers_; + }; + + // ============================================================================ + // SingleTimeCommands + // ============================================================================ + + class SingleTimeCommands final + { + public: + + static void Submit(CommandPool& commandPool, const std::function& action) + { + CommandBuffers commandBuffers(commandPool, 1); + + VkCommandBufferBeginInfo beginInfo = {}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffers[0], &beginInfo); + + action(commandBuffers[0]); + + vkEndCommandBuffer(commandBuffers[0]); + + VkSubmitInfo submitInfo = {}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffers[0]; + + const auto graphicsQueue = commandPool.Queue(); + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, nullptr); + vkQueueWaitIdle(graphicsQueue); + } + }; + +} diff --git a/src/Vulkan/CommandPool.cpp b/src/Vulkan/CommandPool.cpp deleted file mode 100644 index be83de39..00000000 --- a/src/Vulkan/CommandPool.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "CommandPool.hpp" -#include "Device.hpp" - -namespace Vulkan { - -CommandPool::CommandPool(const class Device& device, const uint32_t queueFamilyIndex, uint32_t queue, const bool allowReset) : - device_(device) -{ - VkCommandPoolCreateInfo poolInfo = {}; - poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - poolInfo.queueFamilyIndex = queueFamilyIndex; - poolInfo.flags = allowReset ? VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT : 0; - - queue_ = queue == 0 ? device.GraphicsQueue() : device.TransferQueue(); - - Check(vkCreateCommandPool(device.Handle(), &poolInfo, nullptr, &commandPool_), - "create command pool"); -} - -CommandPool::~CommandPool() -{ - if (commandPool_ != nullptr) - { - vkDestroyCommandPool(device_.Handle(), commandPool_, nullptr); - commandPool_ = nullptr; - } -} - -} diff --git a/src/Vulkan/CommandPool.hpp b/src/Vulkan/CommandPool.hpp deleted file mode 100644 index e5df34e0..00000000 --- a/src/Vulkan/CommandPool.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "Vulkan.hpp" - -namespace Vulkan -{ - class Device; - - class CommandPool final - { - public: - - VULKAN_NON_COPIABLE(CommandPool) - - CommandPool(const Device& device, uint32_t queueFamilyIndex, uint32_t queue, bool allowReset); - ~CommandPool(); - - const class Device& Device() const { return device_; } - VkQueue Queue() const { return queue_; } - - private: - - const class Device& device_; - - VULKAN_HANDLE(VkCommandPool, commandPool_) - - VkQueue queue_; - }; - -} diff --git a/src/Vulkan/DepthBuffer.cpp b/src/Vulkan/DepthBuffer.cpp index c51bf593..7b3cf796 100644 --- a/src/Vulkan/DepthBuffer.cpp +++ b/src/Vulkan/DepthBuffer.cpp @@ -1,5 +1,5 @@ #include "DepthBuffer.hpp" -#include "CommandPool.hpp" +#include "CommandExecution.hpp" #include "Device.hpp" #include "DeviceMemory.hpp" #include "Image.hpp" diff --git a/src/Vulkan/Image.cpp b/src/Vulkan/Image.cpp index d6d476cd..9eb03d65 100644 --- a/src/Vulkan/Image.cpp +++ b/src/Vulkan/Image.cpp @@ -2,7 +2,7 @@ #include "Buffer.hpp" #include "DepthBuffer.hpp" #include "Device.hpp" -#include "SingleTimeCommands.hpp" +#include "CommandExecution.hpp" #include "Utilities/Exception.hpp" namespace Vulkan { diff --git a/src/Vulkan/RenderImage.cpp b/src/Vulkan/RenderImage.cpp index 4d17b2d5..af83370b 100644 --- a/src/Vulkan/RenderImage.cpp +++ b/src/Vulkan/RenderImage.cpp @@ -5,7 +5,7 @@ #include "Image.hpp" #include "ImageMemoryBarrier.hpp" #include "ImageView.hpp" -#include "SingleTimeCommands.hpp" +#include "CommandExecution.hpp" #include "Utilities/Exception.hpp" #include "Vulkan/RayTracing/DeviceProcedures.hpp" diff --git a/src/Vulkan/SingleTimeCommands.hpp b/src/Vulkan/SingleTimeCommands.hpp deleted file mode 100644 index 7670b604..00000000 --- a/src/Vulkan/SingleTimeCommands.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include "Vulkan.hpp" -#include "CommandBuffers.hpp" -#include "CommandPool.hpp" -#include "Device.hpp" -#include - -namespace Vulkan -{ - class SingleTimeCommands final - { - public: - - static void Submit(CommandPool& commandPool, const std::function& action) - { - CommandBuffers commandBuffers(commandPool, 1); - - VkCommandBufferBeginInfo beginInfo = {}; - beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - - vkBeginCommandBuffer(commandBuffers[0], &beginInfo); - - action(commandBuffers[0]); - - vkEndCommandBuffer(commandBuffers[0]); - - VkSubmitInfo submitInfo = {}; - submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &commandBuffers[0]; - - const auto graphicsQueue = commandPool.Queue(); - - vkQueueSubmit(graphicsQueue, 1, &submitInfo, nullptr); - vkQueueWaitIdle(graphicsQueue); - } - }; - -} From 0c4f36f3186bf1526a8e3c5dd777a509ff35ec9a Mon Sep 17 00:00:00 2001 From: gameKnife Date: Mon, 2 Feb 2026 17:28:40 +0800 Subject: [PATCH 44/53] refactor(vulkan): merge descriptor system into DescriptorSystem - Merged 5 files into DescriptorSystem.hpp/cpp - DescriptorBinding: struct defining descriptor binding parameters - DescriptorPool: manages descriptor pool allocation - DescriptorSetLayout: manages descriptor set layout - DescriptorSets: manages descriptor set allocation and updates - DescriptorSetManager: high-level manager coordinating pool, layout, and sets - All classes clearly separated with // ============ markers - Updated all references across codebase Co-Authored-By: Claude Sonnet 4.5 --- src/Assets/GPU/Texture.cpp | 4 +- src/Assets/GPU/Texture.hpp | 4 +- src/Editor/Nodes/NodeSetInt.hpp | 2 +- .../PipelineCommon/CommonComputePipeline.cpp | 4 +- src/Runtime/Editor/UserInterface.cpp | 2 +- src/Vulkan/DescriptorBinding.hpp | 14 - src/Vulkan/DescriptorPool.cpp | 36 --- src/Vulkan/DescriptorPool.hpp | 28 -- src/Vulkan/DescriptorSetLayout.cpp | 62 ----- src/Vulkan/DescriptorSetLayout.hpp | 26 -- src/Vulkan/DescriptorSetManager.cpp | 36 --- src/Vulkan/DescriptorSetManager.hpp | 33 --- src/Vulkan/DescriptorSets.cpp | 118 --------- src/Vulkan/DescriptorSets.hpp | 53 ---- src/Vulkan/DescriptorSystem.cpp | 245 ++++++++++++++++++ src/Vulkan/DescriptorSystem.hpp | 134 ++++++++++ src/Vulkan/PipelineBase.hpp | 3 +- src/Vulkan/PipelineLayout.cpp | 2 +- 18 files changed, 386 insertions(+), 420 deletions(-) delete mode 100644 src/Vulkan/DescriptorBinding.hpp delete mode 100644 src/Vulkan/DescriptorPool.cpp delete mode 100644 src/Vulkan/DescriptorPool.hpp delete mode 100644 src/Vulkan/DescriptorSetLayout.cpp delete mode 100644 src/Vulkan/DescriptorSetLayout.hpp delete mode 100644 src/Vulkan/DescriptorSetManager.cpp delete mode 100644 src/Vulkan/DescriptorSetManager.hpp delete mode 100644 src/Vulkan/DescriptorSets.cpp delete mode 100644 src/Vulkan/DescriptorSets.hpp create mode 100644 src/Vulkan/DescriptorSystem.cpp create mode 100644 src/Vulkan/DescriptorSystem.hpp diff --git a/src/Assets/GPU/Texture.cpp b/src/Assets/GPU/Texture.cpp index 83079272..0c19a060 100644 --- a/src/Assets/GPU/Texture.cpp +++ b/src/Assets/GPU/Texture.cpp @@ -10,9 +10,7 @@ #include "Utilities/FileHelper.hpp" #include "Vulkan/Device.hpp" #include "Vulkan/ImageView.hpp" -#include "Vulkan/DescriptorBinding.hpp" -#include "Vulkan/DescriptorSetManager.hpp" -#include "Vulkan/DescriptorSets.hpp" +#include "Vulkan/DescriptorSystem.hpp" #include "ThirdParty/lzav/lzav.h" #include diff --git a/src/Assets/GPU/Texture.hpp b/src/Assets/GPU/Texture.hpp index 96434829..48669051 100644 --- a/src/Assets/GPU/Texture.hpp +++ b/src/Assets/GPU/Texture.hpp @@ -8,9 +8,7 @@ #include #include #include "Assets/GPU/UniformBuffer.hpp" -#include "Vulkan/DescriptorSetLayout.hpp" -#include "Vulkan/DescriptorSetManager.hpp" -#include "Vulkan/DescriptorSets.hpp" +#include "Vulkan/DescriptorSystem.hpp" namespace Vulkan { class CommandPool; diff --git a/src/Editor/Nodes/NodeSetInt.hpp b/src/Editor/Nodes/NodeSetInt.hpp index 735da0d3..d4b29a6a 100644 --- a/src/Editor/Nodes/NodeSetInt.hpp +++ b/src/Editor/Nodes/NodeSetInt.hpp @@ -3,7 +3,7 @@ #define NODES_SET_INT_H #include "../../ImNodeFlow/include/ImNodeFlow.h" -#include "Vulkan/DescriptorSetLayout.hpp" +#include "Vulkan/DescriptorSystem.hpp" namespace Nodes { diff --git a/src/Rendering/PipelineCommon/CommonComputePipeline.cpp b/src/Rendering/PipelineCommon/CommonComputePipeline.cpp index 6229964e..40ad6176 100644 --- a/src/Rendering/PipelineCommon/CommonComputePipeline.cpp +++ b/src/Rendering/PipelineCommon/CommonComputePipeline.cpp @@ -3,9 +3,7 @@ #include "Runtime/Engine.hpp" #include "Vulkan/Buffer.hpp" -#include "Vulkan/DescriptorSetManager.hpp" -#include "Vulkan/DescriptorPool.hpp" -#include "Vulkan/DescriptorSets.hpp" +#include "Vulkan/DescriptorSystem.hpp" #include "Vulkan/Device.hpp" #include "Vulkan/PipelineLayout.hpp" #include "Vulkan/ShaderModule.hpp" diff --git a/src/Runtime/Editor/UserInterface.cpp b/src/Runtime/Editor/UserInterface.cpp index e830ade7..b6a4e522 100644 --- a/src/Runtime/Editor/UserInterface.cpp +++ b/src/Runtime/Editor/UserInterface.cpp @@ -4,7 +4,7 @@ #include "Runtime/Scene/SceneList.hpp" #include "Runtime/Config/UserSettings.hpp" #include "Utilities/Exception.hpp" -#include "Vulkan/DescriptorPool.hpp" +#include "Vulkan/DescriptorSystem.hpp" #include "Vulkan/Device.hpp" #include "Vulkan/Instance.hpp" #include "Vulkan/RenderPass.hpp" diff --git a/src/Vulkan/DescriptorBinding.hpp b/src/Vulkan/DescriptorBinding.hpp deleted file mode 100644 index c625d2cf..00000000 --- a/src/Vulkan/DescriptorBinding.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include "Vulkan.hpp" - -namespace Vulkan -{ - struct DescriptorBinding - { - uint32_t Binding; // Slot to which the descriptor will be bound, corresponding to the layout index in the shader. - uint32_t DescriptorCount; // Number of descriptors to bind - VkDescriptorType Type; // Type of the bound descriptor(s) - VkShaderStageFlags Stage; // Shader stage at which the bound resources will be available - }; -} \ No newline at end of file diff --git a/src/Vulkan/DescriptorPool.cpp b/src/Vulkan/DescriptorPool.cpp deleted file mode 100644 index 7f7c4eb3..00000000 --- a/src/Vulkan/DescriptorPool.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "DescriptorPool.hpp" -#include "Device.hpp" - -namespace Vulkan { - -DescriptorPool::DescriptorPool(const Vulkan::Device& device, const std::vector& descriptorBindings, const size_t maxSets, bool bindless) : - device_(device) -{ - std::vector poolSizes; - - for (const auto& binding : descriptorBindings) - { - poolSizes.push_back(VkDescriptorPoolSize{ binding.Type, static_cast(binding.DescriptorCount * maxSets )}); - } - - VkDescriptorPoolCreateInfo poolInfo = {}; - poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; - poolInfo.flags = bindless ? VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT_EXT : VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; - poolInfo.poolSizeCount = static_cast(poolSizes.size()); - poolInfo.pPoolSizes = poolSizes.data(); - poolInfo.maxSets = static_cast(maxSets); - - Check(vkCreateDescriptorPool(device.Handle(), &poolInfo, nullptr, &descriptorPool_), - "create descriptor pool"); -} - -DescriptorPool::~DescriptorPool() -{ - if (descriptorPool_ != nullptr) - { - vkDestroyDescriptorPool(device_.Handle(), descriptorPool_, nullptr); - descriptorPool_ = nullptr; - } -} - -} diff --git a/src/Vulkan/DescriptorPool.hpp b/src/Vulkan/DescriptorPool.hpp deleted file mode 100644 index c2d58caf..00000000 --- a/src/Vulkan/DescriptorPool.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include "DescriptorBinding.hpp" -#include - -namespace Vulkan -{ - class Device; - - class DescriptorPool final - { - public: - - VULKAN_NON_COPIABLE(DescriptorPool) - - DescriptorPool(const Device& device, const std::vector& descriptorBindings, size_t maxSets, bool bindless = false); - ~DescriptorPool(); - - const class Device& Device() const { return device_; } - - private: - - const class Device& device_; - - VULKAN_HANDLE(VkDescriptorPool, descriptorPool_) - }; - -} diff --git a/src/Vulkan/DescriptorSetLayout.cpp b/src/Vulkan/DescriptorSetLayout.cpp deleted file mode 100644 index b7715d01..00000000 --- a/src/Vulkan/DescriptorSetLayout.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include "DescriptorSetLayout.hpp" -#include "Device.hpp" - -namespace Vulkan { - -DescriptorSetLayout::DescriptorSetLayout(const Device& device, const std::vector& descriptorBindings, bool bindless) : - device_(device) -{ - std::vector layoutBindings; - std::vector bindlessBindingFlags; - - for (const auto& binding : descriptorBindings) - { - VkDescriptorSetLayoutBinding b = {}; - b.binding = binding.Binding; - b.descriptorCount = binding.DescriptorCount; - b.descriptorType = binding.Type; - b.stageFlags = binding.Stage; - - layoutBindings.push_back(b); - } - - for ( int i = 0; i < layoutBindings.size() - 1; ++i ) - { - bindlessBindingFlags.push_back( VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT_EXT | VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT_EXT ); - } - bindlessBindingFlags.push_back(VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT_EXT | - VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT_EXT | VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT_EXT); - - VkDescriptorSetLayoutCreateInfo layoutInfo = {}; - layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - layoutInfo.bindingCount = static_cast(layoutBindings.size()); - layoutInfo.pBindings = layoutBindings.data(); - - // bindless stuff - VkDescriptorSetLayoutBindingFlagsCreateInfoEXT extendedInfo{ - VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO_EXT, nullptr - }; - - extendedInfo.bindingCount = layoutInfo.bindingCount; - extendedInfo.pBindingFlags = bindlessBindingFlags.data(); - - if( bindless ) - { - layoutInfo.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT_EXT; - layoutInfo.pNext = &extendedInfo; - } - - Check(vkCreateDescriptorSetLayout(device.Handle(), &layoutInfo, nullptr, &layout_), - "create descriptor set layout"); -} - -DescriptorSetLayout::~DescriptorSetLayout() -{ - if (layout_ != nullptr) - { - vkDestroyDescriptorSetLayout(device_.Handle(), layout_, nullptr); - layout_ = nullptr; - } -} - -} diff --git a/src/Vulkan/DescriptorSetLayout.hpp b/src/Vulkan/DescriptorSetLayout.hpp deleted file mode 100644 index f1adad9c..00000000 --- a/src/Vulkan/DescriptorSetLayout.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include "DescriptorBinding.hpp" -#include - -namespace Vulkan -{ - class Device; - - class DescriptorSetLayout final - { - public: - - VULKAN_NON_COPIABLE(DescriptorSetLayout) - - DescriptorSetLayout(const Device& device, const std::vector& descriptorBindings, bool bindless = false); - ~DescriptorSetLayout(); - - private: - - const Device& device_; - - VULKAN_HANDLE(VkDescriptorSetLayout, layout_) - }; - -} diff --git a/src/Vulkan/DescriptorSetManager.cpp b/src/Vulkan/DescriptorSetManager.cpp deleted file mode 100644 index b4e58946..00000000 --- a/src/Vulkan/DescriptorSetManager.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "DescriptorSetManager.hpp" -#include "DescriptorPool.hpp" -#include "DescriptorSetLayout.hpp" -#include "DescriptorSets.hpp" -#include "Device.hpp" -#include "Utilities/Exception.hpp" -#include - -namespace Vulkan { - -DescriptorSetManager::DescriptorSetManager(const Device& device, const std::vector& descriptorBindings, const size_t maxSets, bool bindless) -{ - // Sanity check to avoid binding different resources to the same binding point. - std::map bindingTypes; - - for (const auto& binding : descriptorBindings) - { - if (!bindingTypes.insert(std::make_pair(binding.Binding, binding.Type)).second) - { - Throw(std::invalid_argument("binding collision")); - } - } - - descriptorPool_.reset(new DescriptorPool(device, descriptorBindings, maxSets, bindless)); - descriptorSetLayout_.reset(new class DescriptorSetLayout(device, descriptorBindings, bindless)); - descriptorSets_.reset(new class DescriptorSets(*descriptorPool_, *descriptorSetLayout_, bindingTypes, maxSets, bindless)); -} - -DescriptorSetManager::~DescriptorSetManager() -{ - descriptorSets_.reset(); - descriptorSetLayout_.reset(); - descriptorPool_.reset(); -} - -} diff --git a/src/Vulkan/DescriptorSetManager.hpp b/src/Vulkan/DescriptorSetManager.hpp deleted file mode 100644 index f5655aba..00000000 --- a/src/Vulkan/DescriptorSetManager.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include "DescriptorBinding.hpp" -#include -#include - -namespace Vulkan -{ - class Device; - class DescriptorPool; - class DescriptorSetLayout; - class DescriptorSets; - - class DescriptorSetManager final - { - public: - - VULKAN_NON_COPIABLE(DescriptorSetManager) - - explicit DescriptorSetManager(const Device& device, const std::vector& descriptorBindings, size_t maxSets, bool bindless = false); - ~DescriptorSetManager(); - - const class DescriptorSetLayout& DescriptorSetLayout() const { return *descriptorSetLayout_; } - class DescriptorSets& DescriptorSets() { return *descriptorSets_; } - - private: - - std::unique_ptr descriptorPool_; - std::unique_ptr descriptorSetLayout_; - std::unique_ptr descriptorSets_; - }; - -} diff --git a/src/Vulkan/DescriptorSets.cpp b/src/Vulkan/DescriptorSets.cpp deleted file mode 100644 index b6fe7da1..00000000 --- a/src/Vulkan/DescriptorSets.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include "DescriptorSets.hpp" -#include "DescriptorPool.hpp" -#include "DescriptorSetLayout.hpp" -#include "Device.hpp" -#include "Utilities/Exception.hpp" -#include -#include - -namespace Vulkan { - -DescriptorSets::DescriptorSets( - const DescriptorPool& descriptorPool, - const DescriptorSetLayout& layout, - std::map bindingTypes, - const size_t size, - bool bindless) : - descriptorPool_(descriptorPool), - bindingTypes_(std::move(bindingTypes)) -{ - std::vector layouts(size, layout.Handle()); - - VkDescriptorSetAllocateInfo allocInfo = {}; - allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; - allocInfo.descriptorPool = descriptorPool.Handle(); - allocInfo.descriptorSetCount = static_cast(size); - allocInfo.pSetLayouts = layouts.data(); - - // bindless stuff - VkDescriptorSetVariableDescriptorCountAllocateInfoEXT countInfo{ - VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO_EXT - }; - uint32_t maxBinding = 65535u - 1; - countInfo.descriptorSetCount = static_cast(size); - countInfo.pDescriptorCounts = &maxBinding; - if (bindless) allocInfo.pNext = &countInfo; - - descriptorSets_.resize(size); - - Check(vkAllocateDescriptorSets(descriptorPool.Device().Handle(), &allocInfo, descriptorSets_.data()), - "allocate descriptor sets"); -} - -DescriptorSets::~DescriptorSets() -{ - //if (!descriptorSets_.empty()) - //{ - // vkFreeDescriptorSets( - // descriptorPool_.Device().Handle(), - // descriptorPool_.Handle(), - // static_cast(descriptorSets_.size()), - // descriptorSets_.data()); - - // descriptorSets_.clear(); - //} -} - -VkWriteDescriptorSet DescriptorSets::Bind(const uint32_t index, const uint32_t binding, const VkDescriptorBufferInfo& bufferInfo, uint32_t arrayElement, const uint32_t count) const -{ - VkWriteDescriptorSet descriptorWrite = {}; - descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - descriptorWrite.dstSet = descriptorSets_[index]; - descriptorWrite.dstBinding = binding; - descriptorWrite.dstArrayElement = arrayElement; - descriptorWrite.descriptorType = GetBindingType(binding); - descriptorWrite.descriptorCount = count; - descriptorWrite.pBufferInfo = &bufferInfo; - - return descriptorWrite; -} - -VkWriteDescriptorSet DescriptorSets::Bind(const uint32_t index, const uint32_t binding, const VkDescriptorImageInfo& imageInfo, uint32_t arrayElement, const uint32_t count) const -{ - VkWriteDescriptorSet descriptorWrite = {}; - descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - descriptorWrite.dstSet = descriptorSets_[index]; - descriptorWrite.dstBinding = binding; - descriptorWrite.dstArrayElement = arrayElement; - descriptorWrite.descriptorType = GetBindingType(binding); - descriptorWrite.descriptorCount = count; - descriptorWrite.pImageInfo = &imageInfo; - - return descriptorWrite; -} - -VkWriteDescriptorSet DescriptorSets::Bind(uint32_t index, uint32_t binding, const VkWriteDescriptorSetAccelerationStructureKHR& structureInfo, uint32_t arrayElement, const uint32_t count) const -{ - VkWriteDescriptorSet descriptorWrite = {}; - descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - descriptorWrite.dstSet = descriptorSets_[index]; - descriptorWrite.dstBinding = binding; - descriptorWrite.dstArrayElement = arrayElement; - descriptorWrite.descriptorType = GetBindingType(binding); - descriptorWrite.descriptorCount = count; - descriptorWrite.pNext = &structureInfo; - - return descriptorWrite; -} - -void DescriptorSets::UpdateDescriptors(uint32_t index, const std::vector& descriptorWrites) -{ - vkUpdateDescriptorSets( - descriptorPool_.Device().Handle(), - static_cast(descriptorWrites.size()), - descriptorWrites.data(), 0, nullptr); -} - -VkDescriptorType DescriptorSets::GetBindingType(uint32_t binding) const -{ - const auto it = bindingTypes_.find(binding); - if (it == bindingTypes_.end()) - { - Throw(std::invalid_argument("binding not found")); - } - - return it->second; -} - -} diff --git a/src/Vulkan/DescriptorSets.hpp b/src/Vulkan/DescriptorSets.hpp deleted file mode 100644 index 19865c65..00000000 --- a/src/Vulkan/DescriptorSets.hpp +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include "Vulkan.hpp" -#include -#include -#include - -namespace Vulkan -{ - class Buffer; - class DescriptorPool; - class DescriptorSetLayout; - class ImageView; - - class DescriptorSets final - { - public: - - VULKAN_NON_COPIABLE(DescriptorSets) - - DescriptorSets( - const DescriptorPool& descriptorPool, - const DescriptorSetLayout& layout, - std::map bindingTypes, - size_t size, - bool bindless); - - ~DescriptorSets(); - - VkDescriptorSet Handle(uint32_t index) const - { - // always return available - index = glm::min(index, static_cast(descriptorSets_.size() - 1)); - return descriptorSets_[index]; - } - - VkWriteDescriptorSet Bind(uint32_t index, uint32_t binding, const VkDescriptorBufferInfo& bufferInfo, uint32_t arrayElement = 0,uint32_t count = 1) const; - VkWriteDescriptorSet Bind(uint32_t index, uint32_t binding, const VkDescriptorImageInfo& imageInfo, uint32_t arrayElement = 0, uint32_t count = 1) const; - VkWriteDescriptorSet Bind(uint32_t index, uint32_t binding, const VkWriteDescriptorSetAccelerationStructureKHR& structureInfo, uint32_t arrayElement = 0, uint32_t count = 1) const; - - void UpdateDescriptors(uint32_t index, const std::vector& descriptorWrites); - - private: - - VkDescriptorType GetBindingType(uint32_t binding) const; - - const DescriptorPool& descriptorPool_; - const std::map bindingTypes_; - - std::vector descriptorSets_; - }; - -} diff --git a/src/Vulkan/DescriptorSystem.cpp b/src/Vulkan/DescriptorSystem.cpp new file mode 100644 index 00000000..84b74608 --- /dev/null +++ b/src/Vulkan/DescriptorSystem.cpp @@ -0,0 +1,245 @@ +#include "DescriptorSystem.hpp" +#include "Device.hpp" +#include "Utilities/Exception.hpp" +#include +#include +#include + +namespace Vulkan +{ + +// ============================================================================ +// DescriptorPool +// ============================================================================ + +DescriptorPool::DescriptorPool(const Vulkan::Device& device, const std::vector& descriptorBindings, const size_t maxSets, bool bindless) : + device_(device) +{ + std::vector poolSizes; + + for (const auto& binding : descriptorBindings) + { + poolSizes.push_back(VkDescriptorPoolSize{ binding.Type, static_cast(binding.DescriptorCount * maxSets )}); + } + + VkDescriptorPoolCreateInfo poolInfo = {}; + poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolInfo.flags = bindless ? VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT_EXT : VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; + poolInfo.poolSizeCount = static_cast(poolSizes.size()); + poolInfo.pPoolSizes = poolSizes.data(); + poolInfo.maxSets = static_cast(maxSets); + + Check(vkCreateDescriptorPool(device.Handle(), &poolInfo, nullptr, &descriptorPool_), + "create descriptor pool"); +} + +DescriptorPool::~DescriptorPool() +{ + if (descriptorPool_ != nullptr) + { + vkDestroyDescriptorPool(device_.Handle(), descriptorPool_, nullptr); + descriptorPool_ = nullptr; + } +} + +// ============================================================================ +// DescriptorSetLayout +// ============================================================================ + +DescriptorSetLayout::DescriptorSetLayout(const Device& device, const std::vector& descriptorBindings, bool bindless) : + device_(device) +{ + std::vector layoutBindings; + std::vector bindlessBindingFlags; + + for (const auto& binding : descriptorBindings) + { + VkDescriptorSetLayoutBinding b = {}; + b.binding = binding.Binding; + b.descriptorCount = binding.DescriptorCount; + b.descriptorType = binding.Type; + b.stageFlags = binding.Stage; + + layoutBindings.push_back(b); + } + + for ( int i = 0; i < layoutBindings.size() - 1; ++i ) + { + bindlessBindingFlags.push_back( VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT_EXT | VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT_EXT ); + } + bindlessBindingFlags.push_back(VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT_EXT | + VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT_EXT | VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT_EXT); + + VkDescriptorSetLayoutCreateInfo layoutInfo = {}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = static_cast(layoutBindings.size()); + layoutInfo.pBindings = layoutBindings.data(); + + // bindless stuff + VkDescriptorSetLayoutBindingFlagsCreateInfoEXT extendedInfo{ + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO_EXT, nullptr + }; + + extendedInfo.bindingCount = layoutInfo.bindingCount; + extendedInfo.pBindingFlags = bindlessBindingFlags.data(); + + if( bindless ) + { + layoutInfo.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT_EXT; + layoutInfo.pNext = &extendedInfo; + } + + Check(vkCreateDescriptorSetLayout(device.Handle(), &layoutInfo, nullptr, &layout_), + "create descriptor set layout"); +} + +DescriptorSetLayout::~DescriptorSetLayout() +{ + if (layout_ != nullptr) + { + vkDestroyDescriptorSetLayout(device_.Handle(), layout_, nullptr); + layout_ = nullptr; + } +} + +// ============================================================================ +// DescriptorSets +// ============================================================================ + +DescriptorSets::DescriptorSets( + const DescriptorPool& descriptorPool, + const DescriptorSetLayout& layout, + std::map bindingTypes, + const size_t size, + bool bindless) : + descriptorPool_(descriptorPool), + bindingTypes_(std::move(bindingTypes)) +{ + std::vector layouts(size, layout.Handle()); + + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool.Handle(); + allocInfo.descriptorSetCount = static_cast(size); + allocInfo.pSetLayouts = layouts.data(); + + // bindless stuff + VkDescriptorSetVariableDescriptorCountAllocateInfoEXT countInfo{ + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO_EXT + }; + uint32_t maxBinding = 65535u - 1; + countInfo.descriptorSetCount = static_cast(size); + countInfo.pDescriptorCounts = &maxBinding; + if (bindless) allocInfo.pNext = &countInfo; + + descriptorSets_.resize(size); + + Check(vkAllocateDescriptorSets(descriptorPool.Device().Handle(), &allocInfo, descriptorSets_.data()), + "allocate descriptor sets"); +} + +DescriptorSets::~DescriptorSets() +{ + //if (!descriptorSets_.empty()) + //{ + // vkFreeDescriptorSets( + // descriptorPool_.Device().Handle(), + // descriptorPool_.Handle(), + // static_cast(descriptorSets_.size()), + // descriptorSets_.data()); + + // descriptorSets_.clear(); + //} +} + +VkWriteDescriptorSet DescriptorSets::Bind(const uint32_t index, const uint32_t binding, const VkDescriptorBufferInfo& bufferInfo, uint32_t arrayElement, const uint32_t count) const +{ + VkWriteDescriptorSet descriptorWrite = {}; + descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrite.dstSet = descriptorSets_[index]; + descriptorWrite.dstBinding = binding; + descriptorWrite.dstArrayElement = arrayElement; + descriptorWrite.descriptorType = GetBindingType(binding); + descriptorWrite.descriptorCount = count; + descriptorWrite.pBufferInfo = &bufferInfo; + + return descriptorWrite; +} + +VkWriteDescriptorSet DescriptorSets::Bind(const uint32_t index, const uint32_t binding, const VkDescriptorImageInfo& imageInfo, uint32_t arrayElement, const uint32_t count) const +{ + VkWriteDescriptorSet descriptorWrite = {}; + descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrite.dstSet = descriptorSets_[index]; + descriptorWrite.dstBinding = binding; + descriptorWrite.dstArrayElement = arrayElement; + descriptorWrite.descriptorType = GetBindingType(binding); + descriptorWrite.descriptorCount = count; + descriptorWrite.pImageInfo = &imageInfo; + + return descriptorWrite; +} + +VkWriteDescriptorSet DescriptorSets::Bind(uint32_t index, uint32_t binding, const VkWriteDescriptorSetAccelerationStructureKHR& structureInfo, uint32_t arrayElement, const uint32_t count) const +{ + VkWriteDescriptorSet descriptorWrite = {}; + descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrite.dstSet = descriptorSets_[index]; + descriptorWrite.dstBinding = binding; + descriptorWrite.dstArrayElement = arrayElement; + descriptorWrite.descriptorType = GetBindingType(binding); + descriptorWrite.descriptorCount = count; + descriptorWrite.pNext = &structureInfo; + + return descriptorWrite; +} + +void DescriptorSets::UpdateDescriptors(uint32_t index, const std::vector& descriptorWrites) +{ + vkUpdateDescriptorSets( + descriptorPool_.Device().Handle(), + static_cast(descriptorWrites.size()), + descriptorWrites.data(), 0, nullptr); +} + +VkDescriptorType DescriptorSets::GetBindingType(uint32_t binding) const +{ + const auto it = bindingTypes_.find(binding); + if (it == bindingTypes_.end()) + { + Throw(std::invalid_argument("binding not found")); + } + + return it->second; +} + +// ============================================================================ +// DescriptorSetManager +// ============================================================================ + +DescriptorSetManager::DescriptorSetManager(const Device& device, const std::vector& descriptorBindings, const size_t maxSets, bool bindless) +{ + // Sanity check to avoid binding different resources to the same binding point. + std::map bindingTypes; + + for (const auto& binding : descriptorBindings) + { + if (!bindingTypes.insert(std::make_pair(binding.Binding, binding.Type)).second) + { + Throw(std::invalid_argument("binding collision")); + } + } + + descriptorPool_.reset(new DescriptorPool(device, descriptorBindings, maxSets, bindless)); + descriptorSetLayout_.reset(new class DescriptorSetLayout(device, descriptorBindings, bindless)); + descriptorSets_.reset(new class DescriptorSets(*descriptorPool_, *descriptorSetLayout_, bindingTypes, maxSets, bindless)); +} + +DescriptorSetManager::~DescriptorSetManager() +{ + descriptorSets_.reset(); + descriptorSetLayout_.reset(); + descriptorPool_.reset(); +} + +} diff --git a/src/Vulkan/DescriptorSystem.hpp b/src/Vulkan/DescriptorSystem.hpp new file mode 100644 index 00000000..8f093514 --- /dev/null +++ b/src/Vulkan/DescriptorSystem.hpp @@ -0,0 +1,134 @@ +#pragma once + +#include "Vulkan.hpp" +#include +#include +#include +#include + +namespace Vulkan +{ + class Device; + class Buffer; + class ImageView; + + // ============================================================================ + // DescriptorBinding + // ============================================================================ + + struct DescriptorBinding + { + uint32_t Binding; // Slot to which the descriptor will be bound, corresponding to the layout index in the shader. + uint32_t DescriptorCount; // Number of descriptors to bind + VkDescriptorType Type; // Type of the bound descriptor(s) + VkShaderStageFlags Stage; // Shader stage at which the bound resources will be available + }; + + // ============================================================================ + // DescriptorPool + // ============================================================================ + + class DescriptorPool final + { + public: + + VULKAN_NON_COPIABLE(DescriptorPool) + + DescriptorPool(const Device& device, const std::vector& descriptorBindings, size_t maxSets, bool bindless = false); + ~DescriptorPool(); + + const class Device& Device() const { return device_; } + + private: + + const class Device& device_; + + VULKAN_HANDLE(VkDescriptorPool, descriptorPool_) + }; + + // ============================================================================ + // DescriptorSetLayout + // ============================================================================ + + class DescriptorSetLayout final + { + public: + + VULKAN_NON_COPIABLE(DescriptorSetLayout) + + DescriptorSetLayout(const Device& device, const std::vector& descriptorBindings, bool bindless = false); + ~DescriptorSetLayout(); + + private: + + const Device& device_; + + VULKAN_HANDLE(VkDescriptorSetLayout, layout_) + }; + + // ============================================================================ + // DescriptorSets + // ============================================================================ + + class DescriptorSets final + { + public: + + VULKAN_NON_COPIABLE(DescriptorSets) + + DescriptorSets( + const DescriptorPool& descriptorPool, + const DescriptorSetLayout& layout, + std::map bindingTypes, + size_t size, + bool bindless); + + ~DescriptorSets(); + + VkDescriptorSet Handle(uint32_t index) const + { + // always return available + index = glm::min(index, static_cast(descriptorSets_.size() - 1)); + return descriptorSets_[index]; + } + + VkWriteDescriptorSet Bind(uint32_t index, uint32_t binding, const VkDescriptorBufferInfo& bufferInfo, uint32_t arrayElement = 0,uint32_t count = 1) const; + VkWriteDescriptorSet Bind(uint32_t index, uint32_t binding, const VkDescriptorImageInfo& imageInfo, uint32_t arrayElement = 0, uint32_t count = 1) const; + VkWriteDescriptorSet Bind(uint32_t index, uint32_t binding, const VkWriteDescriptorSetAccelerationStructureKHR& structureInfo, uint32_t arrayElement = 0, uint32_t count = 1) const; + + void UpdateDescriptors(uint32_t index, const std::vector& descriptorWrites); + + private: + + VkDescriptorType GetBindingType(uint32_t binding) const; + + const DescriptorPool& descriptorPool_; + const std::map bindingTypes_; + + std::vector descriptorSets_; + }; + + // ============================================================================ + // DescriptorSetManager + // ============================================================================ + + class DescriptorSetManager final + { + public: + + VULKAN_NON_COPIABLE(DescriptorSetManager) + + explicit DescriptorSetManager(const Device& device, const std::vector& descriptorBindings, size_t maxSets, bool bindless = false); + ~DescriptorSetManager(); + + const class DescriptorSetLayout& DescriptorSetLayout() const { return *descriptorSetLayout_; } + class DescriptorSets& DescriptorSets() { return *descriptorSets_; } + + private: + + std::unique_ptr descriptorPool_; + std::unique_ptr descriptorSetLayout_; + std::unique_ptr descriptorSets_; + }; + +} diff --git a/src/Vulkan/PipelineBase.hpp b/src/Vulkan/PipelineBase.hpp index 87e1421c..a9278057 100644 --- a/src/Vulkan/PipelineBase.hpp +++ b/src/Vulkan/PipelineBase.hpp @@ -4,8 +4,7 @@ #include "Vulkan/Device.hpp" #include "Vulkan/SwapChain.hpp" #include "Vulkan/PipelineLayout.hpp" -#include "Vulkan/DescriptorSetManager.hpp" -#include "Vulkan/DescriptorSets.hpp" +#include "Vulkan/DescriptorSystem.hpp" namespace Vulkan { diff --git a/src/Vulkan/PipelineLayout.cpp b/src/Vulkan/PipelineLayout.cpp index 551da848..f65651b5 100644 --- a/src/Vulkan/PipelineLayout.cpp +++ b/src/Vulkan/PipelineLayout.cpp @@ -1,5 +1,5 @@ #include "PipelineLayout.hpp" -#include "DescriptorSetLayout.hpp" +#include "DescriptorSystem.hpp" #include "Device.hpp" #include "Assets/GPU/Texture.hpp" From 778aae301993ee79cfc70546846ddb12e829c9a2 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Mon, 2 Feb 2026 17:33:48 +0800 Subject: [PATCH 45/53] refactor(vulkan): merge RenderPass, FrameBuffer, PipelineLayout, PipelineBase into RenderingPipeline - Consolidate rendering pipeline components into single file - Reduce file count: 8 -> 2 pairs (6 files merged) - Total lines: 564 (RenderPass: 291, FrameBuffer: 74, PipelineLayout: 83, PipelineBase: 34) - Update include paths across codebase - Move Device, SwapChain, DescriptorSystem includes to header for PipelineBase Co-Authored-By: Claude Sonnet 4.5 --- src/Vulkan/FrameBuffer.cpp | 73 ----- src/Vulkan/FrameBuffer.hpp | 34 --- src/Vulkan/PipelineBase.hpp | 33 --- src/Vulkan/PipelineLayout.cpp | 82 ------ src/Vulkan/PipelineLayout.hpp | 34 --- src/Vulkan/RenderPass.cpp | 290 -------------------- src/Vulkan/RenderPass.hpp | 35 --- src/Vulkan/RenderingPipeline.cpp | 444 +++++++++++++++++++++++++++++++ src/Vulkan/RenderingPipeline.hpp | 120 +++++++++ 9 files changed, 564 insertions(+), 581 deletions(-) delete mode 100644 src/Vulkan/FrameBuffer.cpp delete mode 100644 src/Vulkan/FrameBuffer.hpp delete mode 100644 src/Vulkan/PipelineBase.hpp delete mode 100644 src/Vulkan/PipelineLayout.cpp delete mode 100644 src/Vulkan/PipelineLayout.hpp delete mode 100644 src/Vulkan/RenderPass.cpp delete mode 100644 src/Vulkan/RenderPass.hpp create mode 100644 src/Vulkan/RenderingPipeline.cpp create mode 100644 src/Vulkan/RenderingPipeline.hpp diff --git a/src/Vulkan/FrameBuffer.cpp b/src/Vulkan/FrameBuffer.cpp deleted file mode 100644 index d4d1224f..00000000 --- a/src/Vulkan/FrameBuffer.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include "FrameBuffer.hpp" -#include "DepthBuffer.hpp" -#include "Device.hpp" -#include "ImageView.hpp" -#include "RenderPass.hpp" -#include "SwapChain.hpp" -#include - -namespace Vulkan { - -FrameBuffer::FrameBuffer(const VkExtent2D& extent, const class ImageView& imageView, const class RenderPass& renderPass, bool withDS ) : device_(imageView.Device()) -{ - std::vector attachments; - attachments.push_back(imageView.Handle()); - if(withDS) - { - attachments.push_back( renderPass.DepthBuffer().ImageView().Handle() ); - } - - VkFramebufferCreateInfo framebufferInfo = {}; - framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; - framebufferInfo.renderPass = renderPass.Handle(); - framebufferInfo.attachmentCount = static_cast(attachments.size()); - framebufferInfo.pAttachments = attachments.data(); - framebufferInfo.width = extent.width; - framebufferInfo.height = extent.height; - framebufferInfo.layers = 1; - - Check(vkCreateFramebuffer(imageView.Device().Handle(), &framebufferInfo, nullptr, &framebuffer_), - "create framebuffer"); -} - -FrameBuffer::FrameBuffer(const VkExtent2D& extent, const Vulkan::ImageView& imageView, const Vulkan::ImageView& imageView1, -const Vulkan::ImageView& imageView2, const Vulkan::RenderPass& renderPass): device_(imageView.Device()) -{ - std::array attachments = - { - imageView.Handle(), - imageView1.Handle(), - imageView2.Handle(), - renderPass.DepthBuffer().ImageView().Handle() - }; - - VkFramebufferCreateInfo framebufferInfo = {}; - framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; - framebufferInfo.renderPass = renderPass.Handle(); - framebufferInfo.attachmentCount = static_cast(attachments.size()); - framebufferInfo.pAttachments = attachments.data(); - framebufferInfo.width = extent.width; - framebufferInfo.height = extent.height; - framebufferInfo.layers = 1; - - Check(vkCreateFramebuffer(imageView.Device().Handle(), &framebufferInfo, nullptr, &framebuffer_), - "create framebuffer"); -} - -FrameBuffer::FrameBuffer(FrameBuffer&& other) noexcept : - device_(other.device_), - framebuffer_(other.framebuffer_) -{ - other.framebuffer_ = nullptr; -} - -FrameBuffer::~FrameBuffer() -{ - if (framebuffer_ != nullptr) - { - vkDestroyFramebuffer(device_.Handle(), framebuffer_, nullptr); - framebuffer_ = nullptr; - } -} - -} diff --git a/src/Vulkan/FrameBuffer.hpp b/src/Vulkan/FrameBuffer.hpp deleted file mode 100644 index e779d478..00000000 --- a/src/Vulkan/FrameBuffer.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include "Vulkan.hpp" - -namespace Vulkan -{ - class ImageView; - class RenderPass; - - class FrameBuffer final - { - public: - - FrameBuffer(const FrameBuffer&) = delete; - FrameBuffer& operator = (const FrameBuffer&) = delete; - FrameBuffer& operator = (FrameBuffer&&) = delete; - - explicit FrameBuffer(const VkExtent2D& extent, const ImageView& imageView, const RenderPass& renderPass, bool withDS = true); - explicit FrameBuffer(const VkExtent2D& extent, const ImageView& imageView, const ImageView& imageView1, const ImageView& imageView2,const RenderPass& renderPass); - FrameBuffer(FrameBuffer&& other) noexcept; - ~FrameBuffer(); - - //const class ImageView& ImageView() const { return imageView_; } - //const class RenderPass& RenderPass() const { return renderPass_; } - - private: - - //const class ImageView& imageView_; - //const class RenderPass& renderPass_; - const class Device& device_; - VULKAN_HANDLE(VkFramebuffer, framebuffer_) - }; - -} diff --git a/src/Vulkan/PipelineBase.hpp b/src/Vulkan/PipelineBase.hpp deleted file mode 100644 index a9278057..00000000 --- a/src/Vulkan/PipelineBase.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include "Vulkan.hpp" -#include "Vulkan/Device.hpp" -#include "Vulkan/SwapChain.hpp" -#include "Vulkan/PipelineLayout.hpp" -#include "Vulkan/DescriptorSystem.hpp" - -namespace Vulkan -{ - class PipelineBase - { - public: - PipelineBase(const Vulkan::SwapChain& swapChain):swapChain_(swapChain) {} - virtual ~PipelineBase() - { - if (pipeline_ != nullptr) - { - vkDestroyPipeline(swapChain_.Device().Handle(), pipeline_, nullptr); - pipeline_ = nullptr; - } - pipelineLayout_.reset(); - descriptorSetManager_.reset(); - } - VkDescriptorSet DescriptorSet(uint32_t index) const {return descriptorSetManager_->DescriptorSets().Handle(index);} - const Vulkan::PipelineLayout& PipelineLayout() const { return *pipelineLayout_; } - - VULKAN_HANDLE(VkPipeline, pipeline_) - const Vulkan::SwapChain& swapChain_; - std::unique_ptr descriptorSetManager_; - std::unique_ptr pipelineLayout_; - }; -} diff --git a/src/Vulkan/PipelineLayout.cpp b/src/Vulkan/PipelineLayout.cpp deleted file mode 100644 index f65651b5..00000000 --- a/src/Vulkan/PipelineLayout.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "PipelineLayout.hpp" -#include "DescriptorSystem.hpp" -#include "Device.hpp" -#include "Assets/GPU/Texture.hpp" - -namespace Vulkan { - -PipelineLayout::PipelineLayout(const Device& device, const std::vector managers, uint32_t maxSets, const VkPushConstantRange* pushConstantRanges, - uint32_t pushConstantRangeCount) : device_(device) -{ - for ( DescriptorSetManager* manager : managers ) - { - cachedDescriptorSetLayouts_.push_back(manager->DescriptorSetLayout().Handle()); - } - - cachedDescriptorSets_.resize(maxSets); - for( uint32_t i = 0; i < maxSets; ++i ) - { - for ( DescriptorSetManager* manager : managers ) - { - cachedDescriptorSets_[i].push_back(manager->DescriptorSets().Handle(i)); - } - } - VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; - pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - pipelineLayoutInfo.setLayoutCount = static_cast(cachedDescriptorSetLayouts_.size()); - pipelineLayoutInfo.pSetLayouts = cachedDescriptorSetLayouts_.data(); - pipelineLayoutInfo.pushConstantRangeCount = pushConstantRangeCount; - pipelineLayoutInfo.pPushConstantRanges = pushConstantRanges; - - Check(vkCreatePipelineLayout(device_.Handle(), &pipelineLayoutInfo, nullptr, &pipelineLayout_), - "create pipeline layout"); -} - -PipelineLayout::PipelineLayout(const Device & device, const DescriptorSetLayout& descriptorSetLayout, const VkPushConstantRange* pushConstantRanges, uint32_t pushConstantRangeCount) : - device_(device) -{ - // add the global texture set with set = 1, currently an ugly impl - Assets::GlobalTexturePool* gPool = Assets::GlobalTexturePool::GetInstance(); - cachedDescriptorSetLayouts_ = { descriptorSetLayout.Handle(), gPool->Layout() }; - - VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; - pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - pipelineLayoutInfo.setLayoutCount = 2; - pipelineLayoutInfo.pSetLayouts = cachedDescriptorSetLayouts_.data(); - pipelineLayoutInfo.pushConstantRangeCount = pushConstantRangeCount; - pipelineLayoutInfo.pPushConstantRanges = pushConstantRanges; - - Check(vkCreatePipelineLayout(device_.Handle(), &pipelineLayoutInfo, nullptr, &pipelineLayout_), - "create pipeline layout"); -} - -PipelineLayout::PipelineLayout(const Device& device, const VkPushConstantRange* pushConstantRanges, - uint32_t pushConstantRangeCount) : device_(device) -{ - VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; - pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - pipelineLayoutInfo.setLayoutCount = 0; - pipelineLayoutInfo.pSetLayouts = nullptr; - pipelineLayoutInfo.pushConstantRangeCount = pushConstantRangeCount; - pipelineLayoutInfo.pPushConstantRanges = pushConstantRanges; - - Check(vkCreatePipelineLayout(device_.Handle(), &pipelineLayoutInfo, nullptr, &pipelineLayout_), - "create pipeline layout"); -} - -PipelineLayout::~PipelineLayout() -{ - if (pipelineLayout_ != nullptr) - { - vkDestroyPipelineLayout(device_.Handle(), pipelineLayout_, nullptr); - pipelineLayout_ = nullptr; - } -} - -void PipelineLayout::BindDescriptorSets(VkCommandBuffer commandBuffer, uint32_t idx) const -{ - vkCmdBindDescriptorSets( commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE,Handle(), 0, - static_cast(cachedDescriptorSets_[idx].size()), cachedDescriptorSets_[idx].data(), 0, nullptr ); - -} -} diff --git a/src/Vulkan/PipelineLayout.hpp b/src/Vulkan/PipelineLayout.hpp deleted file mode 100644 index 4dc87b27..00000000 --- a/src/Vulkan/PipelineLayout.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include "Vulkan.hpp" -#include - -namespace Vulkan -{ - class DescriptorSetLayout; - class Device; - class DescriptorSetManager; - - class PipelineLayout final - { - public: - - VULKAN_NON_COPIABLE(PipelineLayout) - - PipelineLayout(const Device& device, const std::vector managers, uint32_t maxSets, const VkPushConstantRange* pushConstantRanges = nullptr, uint32_t pushConstantRangeCount = 0); - PipelineLayout(const Device& device, const DescriptorSetLayout& descriptorSetLayout, const VkPushConstantRange* pushConstantRanges = nullptr, uint32_t pushConstantRangeCount = 0); - PipelineLayout(const Device& device, const VkPushConstantRange* pushConstantRanges = nullptr, uint32_t pushConstantRangeCount = 0); - ~PipelineLayout(); - - void BindDescriptorSets(VkCommandBuffer commandBuffer, uint32_t idx) const; - private: - - const Device& device_; - - VULKAN_HANDLE(VkPipelineLayout, pipelineLayout_) - - std::vector cachedDescriptorSetLayouts_; - std::vector< std::vector > cachedDescriptorSets_; - }; - -} diff --git a/src/Vulkan/RenderPass.cpp b/src/Vulkan/RenderPass.cpp deleted file mode 100644 index b3574104..00000000 --- a/src/Vulkan/RenderPass.cpp +++ /dev/null @@ -1,290 +0,0 @@ -#include "RenderPass.hpp" -#include "DepthBuffer.hpp" -#include "Device.hpp" -#include "SwapChain.hpp" -#include - -namespace Vulkan -{ - RenderPass::RenderPass(const Vulkan::SwapChain& swapChain, const class DepthBuffer& depthBuffer, VkAttachmentLoadOp colorBufferLoadOp) : - swapChain_(swapChain), - depthBuffer_(depthBuffer) - { - VkAttachmentDescription colorAttachment = {}; - colorAttachment.format = swapChain.Format(); - colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - colorAttachment.loadOp = colorBufferLoadOp; - colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - colorAttachment.initialLayout = colorBufferLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - - VkAttachmentReference colorAttachmentRef = {}; - colorAttachmentRef.attachment = 0; - colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - - VkSubpassDescription subpass = {}; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorAttachmentRef; - - VkSubpassDependency dependency = {}; - dependency.srcSubpass = VK_SUBPASS_EXTERNAL; - dependency.dstSubpass = 0; - dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependency.srcAccessMask = 0; - dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - - std::array attachments = - { - colorAttachment, - }; - - VkRenderPassCreateInfo renderPassInfo = {}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassInfo.attachmentCount = static_cast(attachments.size()); - renderPassInfo.pAttachments = attachments.data(); - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpass; - renderPassInfo.dependencyCount = 1; - renderPassInfo.pDependencies = &dependency; - - Check(vkCreateRenderPass(swapChain_.Device().Handle(), &renderPassInfo, nullptr, &renderPass_), - "create render pass"); - } - - RenderPass::RenderPass( - const class SwapChain& swapChain, - const class DepthBuffer& depthBuffer, - const VkAttachmentLoadOp colorBufferLoadOp, - const VkAttachmentLoadOp depthBufferLoadOp) : - swapChain_(swapChain), - depthBuffer_(depthBuffer) - { - VkAttachmentDescription colorAttachment = {}; - colorAttachment.format = swapChain.Format(); - colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - colorAttachment.loadOp = colorBufferLoadOp; - colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - colorAttachment.initialLayout = colorBufferLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - - VkAttachmentDescription depthAttachment = {}; - depthAttachment.format = depthBuffer.Format(); - depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - depthAttachment.loadOp = depthBufferLoadOp; - depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - depthAttachment.initialLayout = depthBufferLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - VkAttachmentReference colorAttachmentRef = {}; - colorAttachmentRef.attachment = 0; - colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - - VkAttachmentReference depthAttachmentRef = {}; - depthAttachmentRef.attachment = 1; - depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - VkSubpassDescription subpass = {}; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorAttachmentRef; - subpass.pDepthStencilAttachment = &depthAttachmentRef; - - VkSubpassDependency dependency = {}; - dependency.srcSubpass = VK_SUBPASS_EXTERNAL; - dependency.dstSubpass = 0; - dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependency.srcAccessMask = 0; - dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - - std::array attachments = - { - colorAttachment, - depthAttachment - }; - - VkRenderPassCreateInfo renderPassInfo = {}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassInfo.attachmentCount = static_cast(attachments.size()); - renderPassInfo.pAttachments = attachments.data(); - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpass; - renderPassInfo.dependencyCount = 1; - renderPassInfo.pDependencies = &dependency; - - Check(vkCreateRenderPass(swapChain_.Device().Handle(), &renderPassInfo, nullptr, &renderPass_), - "create render pass"); - } - - RenderPass::RenderPass(const Vulkan::SwapChain& swapChain, VkFormat format, const Vulkan::DepthBuffer& depthBuffer, - VkAttachmentLoadOp colorBufferLoadOp, VkAttachmentLoadOp depthBufferLoadOp) : swapChain_(swapChain), depthBuffer_(depthBuffer) - { - VkAttachmentDescription colorAttachment = {}; - colorAttachment.format = format; - colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - colorAttachment.loadOp = colorBufferLoadOp; - colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - colorAttachment.initialLayout = colorBufferLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - - VkAttachmentDescription depthAttachment = {}; - depthAttachment.format = depthBuffer.Format(); - depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - depthAttachment.loadOp = depthBufferLoadOp; - depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - depthAttachment.initialLayout = depthBufferLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - VkAttachmentReference colorAttachmentRef = {}; - colorAttachmentRef.attachment = 0; - colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - - VkAttachmentReference depthAttachmentRef = {}; - depthAttachmentRef.attachment = 1; - depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - VkSubpassDescription subpass = {}; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorAttachmentRef; - subpass.pDepthStencilAttachment = &depthAttachmentRef; - - VkSubpassDependency dependency = {}; - dependency.srcSubpass = VK_SUBPASS_EXTERNAL; - dependency.dstSubpass = 0; - dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependency.srcAccessMask = 0; - dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - - std::array attachments = - { - colorAttachment, - depthAttachment - }; - - VkRenderPassCreateInfo renderPassInfo = {}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassInfo.attachmentCount = static_cast(attachments.size()); - renderPassInfo.pAttachments = attachments.data(); - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpass; - renderPassInfo.dependencyCount = 1; - renderPassInfo.pDependencies = &dependency; - - Check(vkCreateRenderPass(swapChain_.Device().Handle(), &renderPassInfo, nullptr, &renderPass_), - "create render pass"); - } - - RenderPass::RenderPass(const Vulkan::SwapChain& swapChain, VkFormat format, VkFormat format1, VkFormat format2, - const Vulkan::DepthBuffer& depthBuffer, VkAttachmentLoadOp colorBufferLoadOp, VkAttachmentLoadOp depthBufferLoadOp) : swapChain_(swapChain), depthBuffer_(depthBuffer) - { - VkAttachmentDescription colorAttachment = {}; - colorAttachment.format = format; - colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - colorAttachment.loadOp = colorBufferLoadOp; - colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - colorAttachment.initialLayout = colorBufferLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - - VkAttachmentDescription colorAttachment1 = {}; - colorAttachment1.format = format1; - colorAttachment1.samples = VK_SAMPLE_COUNT_1_BIT; - colorAttachment1.loadOp = colorBufferLoadOp; - colorAttachment1.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - colorAttachment1.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - colorAttachment1.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - colorAttachment1.initialLayout = colorBufferLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - colorAttachment1.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - - VkAttachmentDescription colorAttachment2 = {}; - colorAttachment2.format = format2; - colorAttachment2.samples = VK_SAMPLE_COUNT_1_BIT; - colorAttachment2.loadOp = colorBufferLoadOp; - colorAttachment2.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - colorAttachment2.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - colorAttachment2.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - colorAttachment2.initialLayout = colorBufferLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - colorAttachment2.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - - VkAttachmentDescription depthAttachment = {}; - depthAttachment.format = depthBuffer.Format(); - depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - depthAttachment.loadOp = depthBufferLoadOp; - depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - depthAttachment.initialLayout = depthBufferLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - std::vector colorAttachmentRefs; - colorAttachmentRefs.push_back({0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}); - colorAttachmentRefs.push_back({1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}); - colorAttachmentRefs.push_back({2, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}); - - VkAttachmentReference depthAttachmentRef = {}; - depthAttachmentRef.attachment = 3; - depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - VkSubpassDescription subpass = {}; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = static_cast(colorAttachmentRefs.size()); - subpass.pColorAttachments = colorAttachmentRefs.data(); - subpass.pDepthStencilAttachment = &depthAttachmentRef; - - VkSubpassDependency dependency = {}; - dependency.srcSubpass = VK_SUBPASS_EXTERNAL; - dependency.dstSubpass = 0; - dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependency.srcAccessMask = 0; - dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - - std::vector attachments; - attachments.push_back(colorAttachment); - attachments.push_back(colorAttachment1); - attachments.push_back(colorAttachment2); - attachments.push_back(depthAttachment); - - VkRenderPassCreateInfo renderPassInfo = {}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassInfo.attachmentCount = static_cast(attachments.size()); - renderPassInfo.pAttachments = attachments.data(); - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpass; - renderPassInfo.dependencyCount = 1; - renderPassInfo.pDependencies = &dependency; - - Check(vkCreateRenderPass(swapChain_.Device().Handle(), &renderPassInfo, nullptr, &renderPass_), - "create render pass"); - } - - RenderPass::~RenderPass() - { - if (renderPass_ != nullptr) - { - vkDestroyRenderPass(swapChain_.Device().Handle(), renderPass_, nullptr); - renderPass_ = nullptr; - } - } - - void RenderPass::SetDebugName(const std::string& name) - { - const auto& debugUtils = swapChain_.Device().DebugUtils(); - debugUtils.SetObjectName(renderPass_, name.c_str()); - } -} diff --git a/src/Vulkan/RenderPass.hpp b/src/Vulkan/RenderPass.hpp deleted file mode 100644 index d0cfce99..00000000 --- a/src/Vulkan/RenderPass.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include -#include "Vulkan.hpp" - -namespace Vulkan -{ - class DepthBuffer; - class SwapChain; - - class RenderPass final - { - public: - - VULKAN_NON_COPIABLE(RenderPass) - - RenderPass(const SwapChain& swapChain, const class DepthBuffer& depthBuffer, VkAttachmentLoadOp colorBufferLoadOp); - RenderPass(const SwapChain& swapChain, const DepthBuffer& depthBuffer, VkAttachmentLoadOp colorBufferLoadOp, VkAttachmentLoadOp depthBufferLoadOp); - RenderPass(const SwapChain& swapChain, VkFormat format, const DepthBuffer& depthBuffer, VkAttachmentLoadOp colorBufferLoadOp, VkAttachmentLoadOp depthBufferLoadOp); - RenderPass(const SwapChain& swapChain, VkFormat format, VkFormat format1, VkFormat format2, const DepthBuffer& depthBuffer, VkAttachmentLoadOp colorBufferLoadOp, VkAttachmentLoadOp depthBufferLoadOp); - ~RenderPass(); - - const class SwapChain& SwapChain() const { return swapChain_; } - const class DepthBuffer& DepthBuffer() const { return depthBuffer_; } - - void SetDebugName(const std::string& name); - private: - - const class SwapChain& swapChain_; - const class DepthBuffer& depthBuffer_; - - VULKAN_HANDLE(VkRenderPass, renderPass_) - }; - -} diff --git a/src/Vulkan/RenderingPipeline.cpp b/src/Vulkan/RenderingPipeline.cpp new file mode 100644 index 00000000..3c007a8d --- /dev/null +++ b/src/Vulkan/RenderingPipeline.cpp @@ -0,0 +1,444 @@ +#include "RenderingPipeline.hpp" +#include "DepthBuffer.hpp" +#include "Device.hpp" +#include "SwapChain.hpp" +#include "ImageView.hpp" +#include "DescriptorSystem.hpp" +#include "Assets/GPU/Texture.hpp" +#include + +namespace Vulkan +{ + +// ============================================================================ +// RenderPass +// ============================================================================ + +RenderPass::RenderPass(const Vulkan::SwapChain& swapChain, const class DepthBuffer& depthBuffer, VkAttachmentLoadOp colorBufferLoadOp) : + swapChain_(swapChain), + depthBuffer_(depthBuffer) +{ + VkAttachmentDescription colorAttachment = {}; + colorAttachment.format = swapChain.Format(); + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = colorBufferLoadOp; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = colorBufferLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef = {}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass = {}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkSubpassDependency dependency = {}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + std::array attachments = + { + colorAttachment, + }; + + VkRenderPassCreateInfo renderPassInfo = {}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = static_cast(attachments.size()); + renderPassInfo.pAttachments = attachments.data(); + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + Check(vkCreateRenderPass(swapChain_.Device().Handle(), &renderPassInfo, nullptr, &renderPass_), + "create render pass"); +} + +RenderPass::RenderPass( + const class SwapChain& swapChain, + const class DepthBuffer& depthBuffer, + const VkAttachmentLoadOp colorBufferLoadOp, + const VkAttachmentLoadOp depthBufferLoadOp) : + swapChain_(swapChain), + depthBuffer_(depthBuffer) +{ + VkAttachmentDescription colorAttachment = {}; + colorAttachment.format = swapChain.Format(); + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = colorBufferLoadOp; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = colorBufferLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentDescription depthAttachment = {}; + depthAttachment.format = depthBuffer.Format(); + depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + depthAttachment.loadOp = depthBufferLoadOp; + depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.initialLayout = depthBufferLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkAttachmentReference colorAttachmentRef = {}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkAttachmentReference depthAttachmentRef = {}; + depthAttachmentRef.attachment = 1; + depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass = {}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + subpass.pDepthStencilAttachment = &depthAttachmentRef; + + VkSubpassDependency dependency = {}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + std::array attachments = + { + colorAttachment, + depthAttachment + }; + + VkRenderPassCreateInfo renderPassInfo = {}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = static_cast(attachments.size()); + renderPassInfo.pAttachments = attachments.data(); + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + Check(vkCreateRenderPass(swapChain_.Device().Handle(), &renderPassInfo, nullptr, &renderPass_), + "create render pass"); +} + +RenderPass::RenderPass(const Vulkan::SwapChain& swapChain, VkFormat format, const Vulkan::DepthBuffer& depthBuffer, + VkAttachmentLoadOp colorBufferLoadOp, VkAttachmentLoadOp depthBufferLoadOp) : swapChain_(swapChain), depthBuffer_(depthBuffer) +{ + VkAttachmentDescription colorAttachment = {}; + colorAttachment.format = format; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = colorBufferLoadOp; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = colorBufferLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentDescription depthAttachment = {}; + depthAttachment.format = depthBuffer.Format(); + depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + depthAttachment.loadOp = depthBufferLoadOp; + depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.initialLayout = depthBufferLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkAttachmentReference colorAttachmentRef = {}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkAttachmentReference depthAttachmentRef = {}; + depthAttachmentRef.attachment = 1; + depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass = {}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + subpass.pDepthStencilAttachment = &depthAttachmentRef; + + VkSubpassDependency dependency = {}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + std::array attachments = + { + colorAttachment, + depthAttachment + }; + + VkRenderPassCreateInfo renderPassInfo = {}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = static_cast(attachments.size()); + renderPassInfo.pAttachments = attachments.data(); + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + Check(vkCreateRenderPass(swapChain_.Device().Handle(), &renderPassInfo, nullptr, &renderPass_), + "create render pass"); +} + +RenderPass::RenderPass(const Vulkan::SwapChain& swapChain, VkFormat format, VkFormat format1, VkFormat format2, + const Vulkan::DepthBuffer& depthBuffer, VkAttachmentLoadOp colorBufferLoadOp, VkAttachmentLoadOp depthBufferLoadOp) : swapChain_(swapChain), depthBuffer_(depthBuffer) +{ + VkAttachmentDescription colorAttachment = {}; + colorAttachment.format = format; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = colorBufferLoadOp; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = colorBufferLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentDescription colorAttachment1 = {}; + colorAttachment1.format = format1; + colorAttachment1.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment1.loadOp = colorBufferLoadOp; + colorAttachment1.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment1.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment1.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment1.initialLayout = colorBufferLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + colorAttachment1.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentDescription colorAttachment2 = {}; + colorAttachment2.format = format2; + colorAttachment2.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment2.loadOp = colorBufferLoadOp; + colorAttachment2.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment2.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment2.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment2.initialLayout = colorBufferLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + colorAttachment2.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentDescription depthAttachment = {}; + depthAttachment.format = depthBuffer.Format(); + depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + depthAttachment.loadOp = depthBufferLoadOp; + depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.initialLayout = depthBufferLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + std::vector colorAttachmentRefs; + colorAttachmentRefs.push_back({0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}); + colorAttachmentRefs.push_back({1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}); + colorAttachmentRefs.push_back({2, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}); + + VkAttachmentReference depthAttachmentRef = {}; + depthAttachmentRef.attachment = 3; + depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass = {}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = static_cast(colorAttachmentRefs.size()); + subpass.pColorAttachments = colorAttachmentRefs.data(); + subpass.pDepthStencilAttachment = &depthAttachmentRef; + + VkSubpassDependency dependency = {}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + std::vector attachments; + attachments.push_back(colorAttachment); + attachments.push_back(colorAttachment1); + attachments.push_back(colorAttachment2); + attachments.push_back(depthAttachment); + + VkRenderPassCreateInfo renderPassInfo = {}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = static_cast(attachments.size()); + renderPassInfo.pAttachments = attachments.data(); + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + Check(vkCreateRenderPass(swapChain_.Device().Handle(), &renderPassInfo, nullptr, &renderPass_), + "create render pass"); +} + +RenderPass::~RenderPass() +{ + if (renderPass_ != nullptr) + { + vkDestroyRenderPass(swapChain_.Device().Handle(), renderPass_, nullptr); + renderPass_ = nullptr; + } +} + +void RenderPass::SetDebugName(const std::string& name) +{ + const auto& debugUtils = swapChain_.Device().DebugUtils(); + debugUtils.SetObjectName(renderPass_, name.c_str()); +} + +// ============================================================================ +// FrameBuffer +// ============================================================================ + +FrameBuffer::FrameBuffer(const VkExtent2D& extent, const class ImageView& imageView, const class RenderPass& renderPass, bool withDS ) : device_(imageView.Device()) +{ + std::vector attachments; + attachments.push_back(imageView.Handle()); + if(withDS) + { + attachments.push_back( renderPass.DepthBuffer().ImageView().Handle() ); + } + + VkFramebufferCreateInfo framebufferInfo = {}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass.Handle(); + framebufferInfo.attachmentCount = static_cast(attachments.size()); + framebufferInfo.pAttachments = attachments.data(); + framebufferInfo.width = extent.width; + framebufferInfo.height = extent.height; + framebufferInfo.layers = 1; + + Check(vkCreateFramebuffer(imageView.Device().Handle(), &framebufferInfo, nullptr, &framebuffer_), + "create framebuffer"); +} + +FrameBuffer::FrameBuffer(const VkExtent2D& extent, const Vulkan::ImageView& imageView, const Vulkan::ImageView& imageView1, +const Vulkan::ImageView& imageView2, const Vulkan::RenderPass& renderPass): device_(imageView.Device()) +{ + std::array attachments = + { + imageView.Handle(), + imageView1.Handle(), + imageView2.Handle(), + renderPass.DepthBuffer().ImageView().Handle() + }; + + VkFramebufferCreateInfo framebufferInfo = {}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass.Handle(); + framebufferInfo.attachmentCount = static_cast(attachments.size()); + framebufferInfo.pAttachments = attachments.data(); + framebufferInfo.width = extent.width; + framebufferInfo.height = extent.height; + framebufferInfo.layers = 1; + + Check(vkCreateFramebuffer(imageView.Device().Handle(), &framebufferInfo, nullptr, &framebuffer_), + "create framebuffer"); +} + +FrameBuffer::FrameBuffer(FrameBuffer&& other) noexcept : + device_(other.device_), + framebuffer_(other.framebuffer_) +{ + other.framebuffer_ = nullptr; +} + +FrameBuffer::~FrameBuffer() +{ + if (framebuffer_ != nullptr) + { + vkDestroyFramebuffer(device_.Handle(), framebuffer_, nullptr); + framebuffer_ = nullptr; + } +} + +// ============================================================================ +// PipelineLayout +// ============================================================================ + +PipelineLayout::PipelineLayout(const Device& device, const std::vector managers, uint32_t maxSets, const VkPushConstantRange* pushConstantRanges, + uint32_t pushConstantRangeCount) : device_(device) +{ + for ( DescriptorSetManager* manager : managers ) + { + cachedDescriptorSetLayouts_.push_back(manager->DescriptorSetLayout().Handle()); + } + + cachedDescriptorSets_.resize(maxSets); + for( uint32_t i = 0; i < maxSets; ++i ) + { + for ( DescriptorSetManager* manager : managers ) + { + cachedDescriptorSets_[i].push_back(manager->DescriptorSets().Handle(i)); + } + } + VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = static_cast(cachedDescriptorSetLayouts_.size()); + pipelineLayoutInfo.pSetLayouts = cachedDescriptorSetLayouts_.data(); + pipelineLayoutInfo.pushConstantRangeCount = pushConstantRangeCount; + pipelineLayoutInfo.pPushConstantRanges = pushConstantRanges; + + Check(vkCreatePipelineLayout(device_.Handle(), &pipelineLayoutInfo, nullptr, &pipelineLayout_), + "create pipeline layout"); +} + +PipelineLayout::PipelineLayout(const Device & device, const DescriptorSetLayout& descriptorSetLayout, const VkPushConstantRange* pushConstantRanges, uint32_t pushConstantRangeCount) : + device_(device) +{ + // add the global texture set with set = 1, currently an ugly impl + Assets::GlobalTexturePool* gPool = Assets::GlobalTexturePool::GetInstance(); + cachedDescriptorSetLayouts_ = { descriptorSetLayout.Handle(), gPool->Layout() }; + + VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 2; + pipelineLayoutInfo.pSetLayouts = cachedDescriptorSetLayouts_.data(); + pipelineLayoutInfo.pushConstantRangeCount = pushConstantRangeCount; + pipelineLayoutInfo.pPushConstantRanges = pushConstantRanges; + + Check(vkCreatePipelineLayout(device_.Handle(), &pipelineLayoutInfo, nullptr, &pipelineLayout_), + "create pipeline layout"); +} + +PipelineLayout::PipelineLayout(const Device& device, const VkPushConstantRange* pushConstantRanges, + uint32_t pushConstantRangeCount) : device_(device) +{ + VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 0; + pipelineLayoutInfo.pSetLayouts = nullptr; + pipelineLayoutInfo.pushConstantRangeCount = pushConstantRangeCount; + pipelineLayoutInfo.pPushConstantRanges = pushConstantRanges; + + Check(vkCreatePipelineLayout(device_.Handle(), &pipelineLayoutInfo, nullptr, &pipelineLayout_), + "create pipeline layout"); +} + +PipelineLayout::~PipelineLayout() +{ + if (pipelineLayout_ != nullptr) + { + vkDestroyPipelineLayout(device_.Handle(), pipelineLayout_, nullptr); + pipelineLayout_ = nullptr; + } +} + +void PipelineLayout::BindDescriptorSets(VkCommandBuffer commandBuffer, uint32_t idx) const +{ + vkCmdBindDescriptorSets( commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE,Handle(), 0, + static_cast(cachedDescriptorSets_[idx].size()), cachedDescriptorSets_[idx].data(), 0, nullptr ); + +} + +} diff --git a/src/Vulkan/RenderingPipeline.hpp b/src/Vulkan/RenderingPipeline.hpp new file mode 100644 index 00000000..dd3d8089 --- /dev/null +++ b/src/Vulkan/RenderingPipeline.hpp @@ -0,0 +1,120 @@ +#pragma once + +#include "Vulkan.hpp" +#include "Device.hpp" +#include "SwapChain.hpp" +#include "DescriptorSystem.hpp" +#include +#include +#include + +namespace Vulkan +{ + class DepthBuffer; + class ImageView; + + // ============================================================================ + // RenderPass + // ============================================================================ + + class RenderPass final + { + public: + + VULKAN_NON_COPIABLE(RenderPass) + + RenderPass(const SwapChain& swapChain, const class DepthBuffer& depthBuffer, VkAttachmentLoadOp colorBufferLoadOp); + RenderPass(const SwapChain& swapChain, const DepthBuffer& depthBuffer, VkAttachmentLoadOp colorBufferLoadOp, VkAttachmentLoadOp depthBufferLoadOp); + RenderPass(const SwapChain& swapChain, VkFormat format, const DepthBuffer& depthBuffer, VkAttachmentLoadOp colorBufferLoadOp, VkAttachmentLoadOp depthBufferLoadOp); + RenderPass(const SwapChain& swapChain, VkFormat format, VkFormat format1, VkFormat format2, const DepthBuffer& depthBuffer, VkAttachmentLoadOp colorBufferLoadOp, VkAttachmentLoadOp depthBufferLoadOp); + ~RenderPass(); + + const class SwapChain& SwapChain() const { return swapChain_; } + const class DepthBuffer& DepthBuffer() const { return depthBuffer_; } + + void SetDebugName(const std::string& name); + private: + + const class SwapChain& swapChain_; + const class DepthBuffer& depthBuffer_; + + VULKAN_HANDLE(VkRenderPass, renderPass_) + }; + + // ============================================================================ + // FrameBuffer + // ============================================================================ + + class FrameBuffer final + { + public: + + FrameBuffer(const FrameBuffer&) = delete; + FrameBuffer& operator = (const FrameBuffer&) = delete; + FrameBuffer& operator = (FrameBuffer&&) = delete; + + explicit FrameBuffer(const VkExtent2D& extent, const ImageView& imageView, const RenderPass& renderPass, bool withDS = true); + explicit FrameBuffer(const VkExtent2D& extent, const ImageView& imageView, const ImageView& imageView1, const ImageView& imageView2,const RenderPass& renderPass); + FrameBuffer(FrameBuffer&& other) noexcept; + ~FrameBuffer(); + + private: + + const class Device& device_; + VULKAN_HANDLE(VkFramebuffer, framebuffer_) + }; + + // ============================================================================ + // PipelineLayout + // ============================================================================ + + class PipelineLayout final + { + public: + + VULKAN_NON_COPIABLE(PipelineLayout) + + PipelineLayout(const Device& device, const std::vector managers, uint32_t maxSets, const VkPushConstantRange* pushConstantRanges = nullptr, uint32_t pushConstantRangeCount = 0); + PipelineLayout(const Device& device, const DescriptorSetLayout& descriptorSetLayout, const VkPushConstantRange* pushConstantRanges = nullptr, uint32_t pushConstantRangeCount = 0); + PipelineLayout(const Device& device, const VkPushConstantRange* pushConstantRanges = nullptr, uint32_t pushConstantRangeCount = 0); + ~PipelineLayout(); + + void BindDescriptorSets(VkCommandBuffer commandBuffer, uint32_t idx) const; + private: + + const Device& device_; + + VULKAN_HANDLE(VkPipelineLayout, pipelineLayout_) + + std::vector cachedDescriptorSetLayouts_; + std::vector< std::vector > cachedDescriptorSets_; + }; + + // ============================================================================ + // PipelineBase + // ============================================================================ + + class PipelineBase + { + public: + PipelineBase(const Vulkan::SwapChain& swapChain):swapChain_(swapChain) {} + virtual ~PipelineBase() + { + if (pipeline_ != nullptr) + { + vkDestroyPipeline(swapChain_.Device().Handle(), pipeline_, nullptr); + pipeline_ = nullptr; + } + pipelineLayout_.reset(); + descriptorSetManager_.reset(); + } + VkDescriptorSet DescriptorSet(uint32_t index) const {return descriptorSetManager_->DescriptorSets().Handle(index);} + const Vulkan::PipelineLayout& PipelineLayout() const { return *pipelineLayout_; } + + VULKAN_HANDLE(VkPipeline, pipeline_) + const Vulkan::SwapChain& swapChain_; + std::unique_ptr descriptorSetManager_; + std::unique_ptr pipelineLayout_; + }; + +} From 878bdf4b820940fdf2c312a8b04b9b6238cdf26b Mon Sep 17 00:00:00 2001 From: gameKnife Date: Mon, 2 Feb 2026 17:42:53 +0800 Subject: [PATCH 46/53] refactor(vulkan): merge Image, ImageView, Buffer, DepthBuffer, RenderImage into GpuResources - Consolidate GPU resource management into single file - Reduce file count: 10 -> 2 pairs (8 files merged) - Total lines: 664 (Image: 237, ImageView: 37, Buffer: 86, DepthBuffer: 71, RenderImage: 74) - Update include paths across 25+ files in codebase - Move helper functions (FindSupportedFormat, FindDepthFormat) to anonymous namespace Co-Authored-By: Claude Sonnet 4.5 --- src/Vulkan/Buffer.cpp | 85 ------- src/Vulkan/Buffer.hpp | 37 --- src/Vulkan/DepthBuffer.cpp | 70 ------ src/Vulkan/DepthBuffer.hpp | 41 --- src/Vulkan/GpuResources.cpp | 487 ++++++++++++++++++++++++++++++++++++ src/Vulkan/GpuResources.hpp | 177 +++++++++++++ src/Vulkan/Image.cpp | 236 ----------------- src/Vulkan/Image.hpp | 53 ---- src/Vulkan/ImageView.cpp | 37 --- src/Vulkan/ImageView.hpp | 28 --- src/Vulkan/RenderImage.cpp | 73 ------ src/Vulkan/RenderImage.hpp | 47 ---- 12 files changed, 664 insertions(+), 707 deletions(-) delete mode 100644 src/Vulkan/Buffer.cpp delete mode 100644 src/Vulkan/Buffer.hpp delete mode 100644 src/Vulkan/DepthBuffer.cpp delete mode 100644 src/Vulkan/DepthBuffer.hpp create mode 100644 src/Vulkan/GpuResources.cpp create mode 100644 src/Vulkan/GpuResources.hpp delete mode 100644 src/Vulkan/Image.cpp delete mode 100644 src/Vulkan/Image.hpp delete mode 100644 src/Vulkan/ImageView.cpp delete mode 100644 src/Vulkan/ImageView.hpp delete mode 100644 src/Vulkan/RenderImage.cpp delete mode 100644 src/Vulkan/RenderImage.hpp diff --git a/src/Vulkan/Buffer.cpp b/src/Vulkan/Buffer.cpp deleted file mode 100644 index 66630e45..00000000 --- a/src/Vulkan/Buffer.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "Buffer.hpp" -#include "CommandExecution.hpp" - -namespace Vulkan { - -Buffer::Buffer(const class Device& device, const size_t size, const VkBufferUsageFlags usage) : - device_(device) -{ - VkBufferCreateInfo bufferInfo = {}; - bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - bufferInfo.size = size; - bufferInfo.usage = usage; - bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - - Check(vkCreateBuffer(device.Handle(), &bufferInfo, nullptr, &buffer_), - "create buffer"); -} - -Buffer::~Buffer() -{ - if (buffer_ != nullptr) - { - vkDestroyBuffer(device_.Handle(), buffer_, nullptr); - buffer_ = nullptr; - } -} - -DeviceMemory Buffer::AllocateMemory(const VkMemoryPropertyFlags propertyFlags) -{ - return AllocateMemory(0, propertyFlags); -} - -DeviceMemory Buffer::AllocateMemory(const VkMemoryAllocateFlags allocateFlags, const VkMemoryPropertyFlags propertyFlags) -{ - const auto requirements = GetMemoryRequirements(); - DeviceMemory memory(device_, requirements.size, requirements.memoryTypeBits, allocateFlags, propertyFlags); - - Check(vkBindBufferMemory(device_.Handle(), buffer_, memory.Handle(), 0), - "bind buffer memory"); - - return memory; -} - -VkMemoryRequirements Buffer::GetMemoryRequirements() const -{ - VkMemoryRequirements requirements; - vkGetBufferMemoryRequirements(device_.Handle(), buffer_, &requirements); - return requirements; -} - -VkDeviceAddress Buffer::GetDeviceAddress() const -{ - VkBufferDeviceAddressInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; - info.pNext = nullptr; - info.buffer = Handle(); - return vkGetBufferDeviceAddress(device_.Handle(), &info); -} - -void Buffer::CopyFrom(CommandPool& commandPool, const Buffer& src, VkDeviceSize size) -{ - SingleTimeCommands::Submit(commandPool, [&](VkCommandBuffer commandBuffer) - { - VkBufferCopy copyRegion = {}; - copyRegion.srcOffset = 0; // Optional - copyRegion.dstOffset = 0; // Optional - copyRegion.size = size; - - vkCmdCopyBuffer(commandBuffer, src.Handle(), Handle(), 1, ©Region); - }); -} - -void Buffer::CopyTo(CommandPool& commandPool, const Buffer& dst, VkDeviceSize size) -{ - SingleTimeCommands::Submit(commandPool, [&](VkCommandBuffer commandBuffer) - { - VkBufferCopy copyRegion = {}; - copyRegion.srcOffset = 0; // Optional - copyRegion.dstOffset = 0; // Optional - copyRegion.size = size; - - vkCmdCopyBuffer(commandBuffer, Handle(), dst.Handle(), 1, ©Region); - }); -} -} diff --git a/src/Vulkan/Buffer.hpp b/src/Vulkan/Buffer.hpp deleted file mode 100644 index dc224d87..00000000 --- a/src/Vulkan/Buffer.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include "Vulkan.hpp" -#include "DeviceMemory.hpp" - - -namespace Vulkan -{ - class CommandPool; - class Device; - - class Buffer final - { - public: - - VULKAN_NON_COPIABLE(Buffer) - - Buffer(const Device& device, size_t size, VkBufferUsageFlags usage); - ~Buffer(); - - const class Device& Device() const { return device_; } - - DeviceMemory AllocateMemory(VkMemoryPropertyFlags propertyFlags); - DeviceMemory AllocateMemory(VkMemoryAllocateFlags allocateFlags, VkMemoryPropertyFlags propertyFlags); - VkMemoryRequirements GetMemoryRequirements() const; - VkDeviceAddress GetDeviceAddress() const; - - void CopyFrom(CommandPool& commandPool, const Buffer& src, VkDeviceSize size); - void CopyTo(CommandPool& commandPool, const Buffer& dst, VkDeviceSize size); - - private: - - const class Device& device_; - - VULKAN_HANDLE(VkBuffer, buffer_) - }; -} diff --git a/src/Vulkan/DepthBuffer.cpp b/src/Vulkan/DepthBuffer.cpp deleted file mode 100644 index 7b3cf796..00000000 --- a/src/Vulkan/DepthBuffer.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "DepthBuffer.hpp" -#include "CommandExecution.hpp" -#include "Device.hpp" -#include "DeviceMemory.hpp" -#include "Image.hpp" -#include "ImageView.hpp" -#include "Utilities/Exception.hpp" - -namespace Vulkan { - - namespace - { - VkFormat FindSupportedFormat(const Device& device, const std::vector& candidates, const VkImageTiling tiling, const VkFormatFeatureFlags features) - { - for (auto format : candidates) - { - VkFormatProperties props; - vkGetPhysicalDeviceFormatProperties(device.PhysicalDevice(), format, &props); - - if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) - { - return format; - } - - if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) - { - return format; - } - } - - Throw(std::runtime_error("failed to find supported format")); - } - - VkFormat FindDepthFormat(const Device& device) - { - return FindSupportedFormat( - device, - { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT }, - VK_IMAGE_TILING_OPTIMAL, - VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT - ); - } - } - - DepthBuffer::DepthBuffer(CommandPool& commandPool, const VkExtent2D extent) : - format_(FindDepthFormat(commandPool.Device())) - { - const auto& device = commandPool.Device(); - - image_.reset(new Image(device, extent, 1, format_, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)); - imageMemory_.reset(new DeviceMemory(image_->AllocateMemory(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT))); - imageView_.reset(new class ImageView(device, image_->Handle(), format_, VK_IMAGE_ASPECT_DEPTH_BIT)); - - image_->TransitionImageLayout(commandPool, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); - - const auto& debugUtils = device.DebugUtils(); - - debugUtils.SetObjectName(image_->Handle(), "Depth Buffer Image"); - debugUtils.SetObjectName(imageMemory_->Handle(), "Depth Buffer Image Memory"); - debugUtils.SetObjectName(imageView_->Handle(), "Depth Buffer ImageView"); - } - - DepthBuffer::~DepthBuffer() - { - imageView_.reset(); - image_.reset(); - imageMemory_.reset(); // release memory after bound image has been destroyed - } - -} diff --git a/src/Vulkan/DepthBuffer.hpp b/src/Vulkan/DepthBuffer.hpp deleted file mode 100644 index f530225e..00000000 --- a/src/Vulkan/DepthBuffer.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include "Vulkan.hpp" -#include - -namespace Vulkan -{ - class CommandPool; - class Device; - class DeviceMemory; - class Image; - class ImageView; - - class DepthBuffer final - { - public: - - VULKAN_NON_COPIABLE(DepthBuffer) - - DepthBuffer(CommandPool& commandPool, VkExtent2D extent); - ~DepthBuffer(); - - VkFormat Format() const { return format_; } - const class Image& GetImage() const { return *image_; } - const class DeviceMemory& GetImageMemory() const { return *imageMemory_; } - const class ImageView& ImageView() const { return *imageView_; } - - static bool HasStencilComponent(const VkFormat format) - { - return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; - } - - private: - - const VkFormat format_; - std::unique_ptr image_; - std::unique_ptr imageMemory_; - std::unique_ptr imageView_; - }; - -} diff --git a/src/Vulkan/GpuResources.cpp b/src/Vulkan/GpuResources.cpp new file mode 100644 index 00000000..7e4b5912 --- /dev/null +++ b/src/Vulkan/GpuResources.cpp @@ -0,0 +1,487 @@ +#include "GpuResources.hpp" +#include "Device.hpp" +#include "CommandExecution.hpp" +#include "ImageMemoryBarrier.hpp" +#include "Utilities/Exception.hpp" +#include "Vulkan/RayTracing/DeviceProcedures.hpp" + +namespace Vulkan +{ + +// ============================================================================ +// Image +// ============================================================================ + +Image::Image(const class Device& device, const VkExtent2D extent, uint32_t miplevel, const VkFormat format) : + Image(device, extent, miplevel, format, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, false) +{ +} + +Image::Image( + const class Device& device, + const VkExtent2D extent, + const uint32_t miplevel, + const VkFormat format, + const VkImageTiling tiling, + const VkImageUsageFlags usage, + bool useForExternal) : + device_(device), + extent_(extent), + format_(format), + imageLayout_(VK_IMAGE_LAYOUT_UNDEFINED), + mipLevel_(miplevel), + external_(useForExternal) +{ + VkImageCreateInfo imageInfo = {}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = extent.width; + imageInfo.extent.height = extent.height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = miplevel; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = tiling; + imageInfo.initialLayout = imageLayout_; + imageInfo.usage = usage; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.flags = 0; // Optional + + VkExternalMemoryImageCreateInfo externalMemoryImageInfo{}; + externalMemoryImageInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO; +#if WIN32 && !defined(__MINGW32__) + externalMemoryImageInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT; +#else + externalMemoryImageInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT; +#endif + +#if !ANDROID + if(external_) + { + imageInfo.pNext = &externalMemoryImageInfo; + } +#endif + + Check(vkCreateImage(device.Handle(), &imageInfo, nullptr, &image_), + "create image"); +} + +Image::Image(Image&& other) noexcept : + device_(other.device_), + extent_(other.extent_), + format_(other.format_), + imageLayout_(other.imageLayout_), + image_(other.image_) +{ + other.image_ = nullptr; +} + +Image::~Image() +{ + if (image_ != nullptr) + { + vkDestroyImage(device_.Handle(), image_, nullptr); + image_ = nullptr; + } +} + +DeviceMemory Image::AllocateMemory(const VkMemoryPropertyFlags properties, bool external) const +{ + const auto requirements = GetMemoryRequirements(); + DeviceMemory memory(device_, requirements.size, requirements.memoryTypeBits, 0, properties, external); + + Check(vkBindImageMemory(device_.Handle(), image_, memory.Handle(), 0), + "bind image memory"); + + return memory; +} + +VkMemoryRequirements Image::GetMemoryRequirements() const +{ + VkMemoryRequirements requirements; + vkGetImageMemoryRequirements(device_.Handle(), image_, &requirements); + return requirements; +} + +void Image::TransitionImageLayout(CommandPool& commandPool, VkImageLayout newLayout) +{ + SingleTimeCommands::Submit(commandPool, [&](VkCommandBuffer commandBuffer) + { + VkImageMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = imageLayout_; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image_; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = mipLevel_; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + if (newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) + { + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + + if (DepthBuffer::HasStencilComponent(format_)) + { + barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; + } + } + else + { + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + } + + VkPipelineStageFlags sourceStage; + VkPipelineStageFlags destinationStage; + + if (imageLayout_ == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) + { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } + else if (imageLayout_ == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) + { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } + else if (imageLayout_ == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) + { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + } + else if (newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) + { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } + else if (newLayout == VK_IMAGE_LAYOUT_GENERAL) + { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } + else + { + Throw(std::invalid_argument("unsupported layout transition")); + } + + vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0, nullptr, 0, nullptr, 1, &barrier); + }); + + imageLayout_ = newLayout; +} + +void Image::CopyFrom(CommandPool& commandPool, const Buffer& buffer) +{ + SingleTimeCommands::Submit(commandPool, [&](VkCommandBuffer commandBuffer) + { + VkBufferImageCopy region = {}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = { 0, 0, 0 }; + region.imageExtent = { extent_.width, extent_.height, 1 }; + + vkCmdCopyBufferToImage(commandBuffer, buffer.Handle(), image_, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + }); +} + +void Image::CopyFromToMipLevel( + CommandPool& commandPool, + const Buffer& buffer, + uint32_t mipLevel, + uint32_t mipWidth, + uint32_t mipHeight) +{ + SingleTimeCommands::Submit(commandPool, [&](VkCommandBuffer commandBuffer) + { + VkBufferImageCopy region = {}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = mipLevel; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + + region.imageOffset = {0, 0, 0}; + region.imageExtent = {mipWidth, mipHeight, 1}; + + vkCmdCopyBufferToImage( + commandBuffer, + buffer.Handle(), + image_, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ®ion); + }); +} + +// ============================================================================ +// ImageView +// ============================================================================ + +ImageView::ImageView(const class Device& device, const VkImage image, const VkFormat format, const VkImageAspectFlags aspectFlags, const uint32_t miplevel) : + device_(device) +{ + VkImageViewCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = image; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = format; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = aspectFlags; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = miplevel; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + Check(vkCreateImageView(device_.Handle(), &createInfo, nullptr, &imageView_), + "create image view"); +} + +ImageView::~ImageView() +{ + if (imageView_ != nullptr) + { + vkDestroyImageView(device_.Handle(), imageView_, nullptr); + imageView_ = nullptr; + } +} + +// ============================================================================ +// Buffer +// ============================================================================ + +Buffer::Buffer(const class Device& device, const size_t size, const VkBufferUsageFlags usage) : + device_(device) +{ + VkBufferCreateInfo bufferInfo = {}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + Check(vkCreateBuffer(device.Handle(), &bufferInfo, nullptr, &buffer_), + "create buffer"); +} + +Buffer::~Buffer() +{ + if (buffer_ != nullptr) + { + vkDestroyBuffer(device_.Handle(), buffer_, nullptr); + buffer_ = nullptr; + } +} + +DeviceMemory Buffer::AllocateMemory(const VkMemoryPropertyFlags propertyFlags) +{ + return AllocateMemory(0, propertyFlags); +} + +DeviceMemory Buffer::AllocateMemory(const VkMemoryAllocateFlags allocateFlags, const VkMemoryPropertyFlags propertyFlags) +{ + const auto requirements = GetMemoryRequirements(); + DeviceMemory memory(device_, requirements.size, requirements.memoryTypeBits, allocateFlags, propertyFlags); + + Check(vkBindBufferMemory(device_.Handle(), buffer_, memory.Handle(), 0), + "bind buffer memory"); + + return memory; +} + +VkMemoryRequirements Buffer::GetMemoryRequirements() const +{ + VkMemoryRequirements requirements; + vkGetBufferMemoryRequirements(device_.Handle(), buffer_, &requirements); + return requirements; +} + +VkDeviceAddress Buffer::GetDeviceAddress() const +{ + VkBufferDeviceAddressInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + info.pNext = nullptr; + info.buffer = Handle(); + return vkGetBufferDeviceAddress(device_.Handle(), &info); +} + +void Buffer::CopyFrom(CommandPool& commandPool, const Buffer& src, VkDeviceSize size) +{ + SingleTimeCommands::Submit(commandPool, [&](VkCommandBuffer commandBuffer) + { + VkBufferCopy copyRegion = {}; + copyRegion.srcOffset = 0; // Optional + copyRegion.dstOffset = 0; // Optional + copyRegion.size = size; + + vkCmdCopyBuffer(commandBuffer, src.Handle(), Handle(), 1, ©Region); + }); +} + +void Buffer::CopyTo(CommandPool& commandPool, const Buffer& dst, VkDeviceSize size) +{ + SingleTimeCommands::Submit(commandPool, [&](VkCommandBuffer commandBuffer) + { + VkBufferCopy copyRegion = {}; + copyRegion.srcOffset = 0; // Optional + copyRegion.dstOffset = 0; // Optional + copyRegion.size = size; + + vkCmdCopyBuffer(commandBuffer, Handle(), dst.Handle(), 1, ©Region); + }); +} + +// ============================================================================ +// DepthBuffer +// ============================================================================ + +namespace +{ + VkFormat FindSupportedFormat(const Device& device, const std::vector& candidates, const VkImageTiling tiling, const VkFormatFeatureFlags features) + { + for (auto format : candidates) + { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(device.PhysicalDevice(), format, &props); + + if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) + { + return format; + } + + if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) + { + return format; + } + } + + Throw(std::runtime_error("failed to find supported format")); + } + + VkFormat FindDepthFormat(const Device& device) + { + return FindSupportedFormat( + device, + { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT }, + VK_IMAGE_TILING_OPTIMAL, + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT + ); + } +} + +DepthBuffer::DepthBuffer(CommandPool& commandPool, const VkExtent2D extent) : + format_(FindDepthFormat(commandPool.Device())) +{ + const auto& device = commandPool.Device(); + + image_.reset(new Image(device, extent, 1, format_, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)); + imageMemory_.reset(new DeviceMemory(image_->AllocateMemory(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT))); + imageView_.reset(new class ImageView(device, image_->Handle(), format_, VK_IMAGE_ASPECT_DEPTH_BIT)); + + image_->TransitionImageLayout(commandPool, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + + const auto& debugUtils = device.DebugUtils(); + + debugUtils.SetObjectName(image_->Handle(), "Depth Buffer Image"); + debugUtils.SetObjectName(imageMemory_->Handle(), "Depth Buffer Image Memory"); + debugUtils.SetObjectName(imageView_->Handle(), "Depth Buffer ImageView"); +} + +DepthBuffer::~DepthBuffer() +{ + imageView_.reset(); + image_.reset(); + imageMemory_.reset(); // release memory after bound image has been destroyed +} + +// ============================================================================ +// RenderImage +// ============================================================================ + +RenderImage::RenderImage(const Device& device, + VkExtent2D extent, + VkFormat format, + VkImageTiling tiling, + VkImageUsageFlags usage, + bool external, + const char* debugName) +{ + image_.reset(new Image(device, extent, 1, format, tiling, usage, external)); + imageMemory_.reset( + new DeviceMemory(image_->AllocateMemory(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, external))); + imageView_.reset(new ImageView(device, image_->Handle(), format, VK_IMAGE_ASPECT_COLOR_BIT)); + + if(debugName) + { + const auto& debugUtils = device.DebugUtils(); + debugUtils.SetObjectName(image_->Handle(), debugName); + } + + sampler_.reset(new Vulkan::Sampler(device, Vulkan::SamplerConfig())); +} + +RenderImage::~RenderImage() +{ + sampler_.reset(); + image_.reset(); + imageMemory_.reset(); + imageView_.reset(); +} + +void RenderImage::InsertBarrier(VkCommandBuffer commandBuffer, VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask, VkImageLayout oldLayout, VkImageLayout newLayout) const +{ + VkImageSubresourceRange subresourceRange = {}; + subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + subresourceRange.baseMipLevel = 0; + subresourceRange.levelCount = 1; + subresourceRange.baseArrayLayer = 0; + subresourceRange.layerCount = 1; + + ImageMemoryBarrier::Insert(commandBuffer, GetImage().Handle(), subresourceRange, + srcAccessMask, dstAccessMask, oldLayout, + newLayout); +} + +ExtHandle RenderImage::GetExternalHandle() const +{ + ExtHandle handle{}; + #if WIN32 && !defined(__MINGW32__) + VkMemoryGetWin32HandleInfoKHR handleInfo = { VK_STRUCTURE_TYPE_MEMORY_GET_WIN32_HANDLE_INFO_KHR }; + handleInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT; + handleInfo.memory = imageMemory_->Handle(); + image_->Device().GetDeviceProcedures().vkGetMemoryWin32HandleKHR(image_->Device().Handle(), &handleInfo, &handle); +#elif __linux__ + VkMemoryGetFdInfoKHR fdInfo = { VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR }; + fdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT; + fdInfo.memory = imageMemory_->Handle(); + image_->Device().GetDeviceProcedures().vkGetMemoryFdKHR(image_->Device().Handle(), &fdInfo, &handle); +#endif + return handle; +} + +} diff --git a/src/Vulkan/GpuResources.hpp b/src/Vulkan/GpuResources.hpp new file mode 100644 index 00000000..2bff5cbe --- /dev/null +++ b/src/Vulkan/GpuResources.hpp @@ -0,0 +1,177 @@ +#pragma once + +#include "Vulkan.hpp" +#include "DeviceMemory.hpp" +#include "Sampler.hpp" +#include + +#if WIN32 && !defined(__MINGW32__) +#define ExtHandle HANDLE +#else +#define ExtHandle int +#endif + +namespace Vulkan +{ + class CommandPool; + class Device; + + // ============================================================================ + // Image + // ============================================================================ + + class Image final + { + public: + + Image(const Image&) = delete; + Image& operator = (const Image&) = delete; + Image& operator = (Image&&) = delete; + + Image(const Device& device, VkExtent2D extent, uint32_t miplevel, VkFormat format); + Image(const Device& device, VkExtent2D extent, uint32_t miplevel, VkFormat format,VkImageTiling tiling, VkImageUsageFlags usage, bool useForExternal = false); + Image(Image&& other) noexcept; + ~Image(); + + const class Device& Device() const { return device_; } + VkExtent2D Extent() const { return extent_; } + VkFormat Format() const { return format_; } + + DeviceMemory AllocateMemory(VkMemoryPropertyFlags properties, bool external = false) const; + VkMemoryRequirements GetMemoryRequirements() const; + + void TransitionImageLayout(CommandPool& commandPool, VkImageLayout newLayout); + void CopyFrom(CommandPool& commandPool, const class Buffer& buffer); + + void CopyFromToMipLevel( + Vulkan::CommandPool& commandPool, + const class Buffer& buffer, + uint32_t mipLevel, + uint32_t mipWidth, + uint32_t mipHeight); + + private: + + const class Device& device_; + const VkExtent2D extent_; + const VkFormat format_; + VkImageLayout imageLayout_; + uint32_t mipLevel_; + bool external_; + VULKAN_HANDLE(VkImage, image_) + }; + + // ============================================================================ + // ImageView + // ============================================================================ + + class ImageView final + { + public: + + VULKAN_NON_COPIABLE(ImageView) + + explicit ImageView(const Device& device, VkImage image, VkFormat format, VkImageAspectFlags aspectFlags, const uint32_t miplevel = 1); + ~ImageView(); + + const class Device& Device() const { return device_; } + + + private: + + const class Device& device_; + + VULKAN_HANDLE(VkImageView, imageView_) + }; + + // ============================================================================ + // Buffer + // ============================================================================ + + class Buffer final + { + public: + + VULKAN_NON_COPIABLE(Buffer) + + Buffer(const Device& device, size_t size, VkBufferUsageFlags usage); + ~Buffer(); + + const class Device& Device() const { return device_; } + + DeviceMemory AllocateMemory(VkMemoryPropertyFlags propertyFlags); + DeviceMemory AllocateMemory(VkMemoryAllocateFlags allocateFlags, VkMemoryPropertyFlags propertyFlags); + VkMemoryRequirements GetMemoryRequirements() const; + VkDeviceAddress GetDeviceAddress() const; + + void CopyFrom(CommandPool& commandPool, const Buffer& src, VkDeviceSize size); + void CopyTo(CommandPool& commandPool, const Buffer& dst, VkDeviceSize size); + + private: + + const class Device& device_; + + VULKAN_HANDLE(VkBuffer, buffer_) + }; + + // ============================================================================ + // DepthBuffer + // ============================================================================ + + class DepthBuffer final + { + public: + + VULKAN_NON_COPIABLE(DepthBuffer) + + DepthBuffer(CommandPool& commandPool, VkExtent2D extent); + ~DepthBuffer(); + + VkFormat Format() const { return format_; } + const class Image& GetImage() const { return *image_; } + const class DeviceMemory& GetImageMemory() const { return *imageMemory_; } + const class ImageView& ImageView() const { return *imageView_; } + + static bool HasStencilComponent(const VkFormat format) + { + return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; + } + + private: + + const VkFormat format_; + std::unique_ptr image_; + std::unique_ptr imageMemory_; + std::unique_ptr imageView_; + }; + + // ============================================================================ + // RenderImage + // ============================================================================ + + class RenderImage final + { + public: + + RenderImage(const RenderImage&) = delete; + RenderImage& operator = (const RenderImage&) = delete; + RenderImage& operator = (RenderImage&&) = delete; + + RenderImage(const Device& device,VkExtent2D extent, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, bool external = false, const char* debugName = nullptr); + ~RenderImage(); + + const Image& GetImage() const { return *image_; } + const DeviceMemory& GetImageMemory() const { return *imageMemory_; } + const ImageView& GetImageView() const { return *imageView_; } + const Vulkan::Sampler& Sampler() const { return *sampler_; } + void InsertBarrier(VkCommandBuffer commandBuffer, VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask, VkImageLayout oldLayout, VkImageLayout newLayout) const; + ExtHandle GetExternalHandle() const; + private: + std::unique_ptr image_; + std::unique_ptr imageMemory_; + std::unique_ptr imageView_; + std::unique_ptr sampler_; + + }; + +} diff --git a/src/Vulkan/Image.cpp b/src/Vulkan/Image.cpp deleted file mode 100644 index 9eb03d65..00000000 --- a/src/Vulkan/Image.cpp +++ /dev/null @@ -1,236 +0,0 @@ -#include "Image.hpp" -#include "Buffer.hpp" -#include "DepthBuffer.hpp" -#include "Device.hpp" -#include "CommandExecution.hpp" -#include "Utilities/Exception.hpp" - -namespace Vulkan { - -Image::Image(const class Device& device, const VkExtent2D extent, uint32_t miplevel, const VkFormat format) : - Image(device, extent, miplevel, format, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, false) -{ -} - -Image::Image( - const class Device& device, - const VkExtent2D extent, - const uint32_t miplevel, - const VkFormat format, - const VkImageTiling tiling, - const VkImageUsageFlags usage, - bool useForExternal) : - device_(device), - extent_(extent), - format_(format), - imageLayout_(VK_IMAGE_LAYOUT_UNDEFINED), - mipLevel_(miplevel), - external_(useForExternal) -{ - VkImageCreateInfo imageInfo = {}; - imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - imageInfo.imageType = VK_IMAGE_TYPE_2D; - imageInfo.extent.width = extent.width; - imageInfo.extent.height = extent.height; - imageInfo.extent.depth = 1; - imageInfo.mipLevels = miplevel; - imageInfo.arrayLayers = 1; - imageInfo.format = format; - imageInfo.tiling = tiling; - imageInfo.initialLayout = imageLayout_; - imageInfo.usage = usage; - imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; - imageInfo.flags = 0; // Optional - - VkExternalMemoryImageCreateInfo externalMemoryImageInfo{}; - externalMemoryImageInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO; -#if WIN32 && !defined(__MINGW32__) - externalMemoryImageInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT; -#else - externalMemoryImageInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT; -#endif - -#if !ANDROID - if(external_) - { - imageInfo.pNext = &externalMemoryImageInfo; - } -#endif - - Check(vkCreateImage(device.Handle(), &imageInfo, nullptr, &image_), - "create image"); -} - -Image::Image(Image&& other) noexcept : - device_(other.device_), - extent_(other.extent_), - format_(other.format_), - imageLayout_(other.imageLayout_), - image_(other.image_) -{ - other.image_ = nullptr; -} - -Image::~Image() -{ - if (image_ != nullptr) - { - vkDestroyImage(device_.Handle(), image_, nullptr); - image_ = nullptr; - } -} - -DeviceMemory Image::AllocateMemory(const VkMemoryPropertyFlags properties, bool external) const -{ - const auto requirements = GetMemoryRequirements(); - DeviceMemory memory(device_, requirements.size, requirements.memoryTypeBits, 0, properties, external); - - Check(vkBindImageMemory(device_.Handle(), image_, memory.Handle(), 0), - "bind image memory"); - - return memory; -} - -VkMemoryRequirements Image::GetMemoryRequirements() const -{ - VkMemoryRequirements requirements; - vkGetImageMemoryRequirements(device_.Handle(), image_, &requirements); - return requirements; -} - -void Image::TransitionImageLayout(CommandPool& commandPool, VkImageLayout newLayout) -{ - SingleTimeCommands::Submit(commandPool, [&](VkCommandBuffer commandBuffer) - { - VkImageMemoryBarrier barrier = {}; - barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - barrier.oldLayout = imageLayout_; - barrier.newLayout = newLayout; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.image = image_; - barrier.subresourceRange.baseMipLevel = 0; - barrier.subresourceRange.levelCount = mipLevel_; - barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = 1; - - if (newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) - { - barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - - if (DepthBuffer::HasStencilComponent(format_)) - { - barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; - } - } - else - { - barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - } - - VkPipelineStageFlags sourceStage; - VkPipelineStageFlags destinationStage; - - if (imageLayout_ == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) - { - barrier.srcAccessMask = 0; - barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - - sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; - } - else if (imageLayout_ == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) - { - barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - - sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; - destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - } - else if (imageLayout_ == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) - { - barrier.srcAccessMask = 0; - barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - - sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; - } - else if (newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) - { - barrier.srcAccessMask = 0; - barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; - } - else if (newLayout == VK_IMAGE_LAYOUT_GENERAL) - { - barrier.srcAccessMask = 0; - barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - } - else - { - Throw(std::invalid_argument("unsupported layout transition")); - } - - vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0, nullptr, 0, nullptr, 1, &barrier); - }); - - imageLayout_ = newLayout; -} - -void Image::CopyFrom(CommandPool& commandPool, const Buffer& buffer) -{ - SingleTimeCommands::Submit(commandPool, [&](VkCommandBuffer commandBuffer) - { - VkBufferImageCopy region = {}; - region.bufferOffset = 0; - region.bufferRowLength = 0; - region.bufferImageHeight = 0; - region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - region.imageSubresource.mipLevel = 0; - region.imageSubresource.baseArrayLayer = 0; - region.imageSubresource.layerCount = 1; - region.imageOffset = { 0, 0, 0 }; - region.imageExtent = { extent_.width, extent_.height, 1 }; - - vkCmdCopyBufferToImage(commandBuffer, buffer.Handle(), image_, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); - }); -} - - // Implement in Image.cpp - void Image::CopyFromToMipLevel( - CommandPool& commandPool, - const Buffer& buffer, - uint32_t mipLevel, - uint32_t mipWidth, - uint32_t mipHeight) - { - SingleTimeCommands::Submit(commandPool, [&](VkCommandBuffer commandBuffer) - { - VkBufferImageCopy region = {}; - region.bufferOffset = 0; - region.bufferRowLength = 0; - region.bufferImageHeight = 0; - - region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - region.imageSubresource.mipLevel = mipLevel; - region.imageSubresource.baseArrayLayer = 0; - region.imageSubresource.layerCount = 1; - - region.imageOffset = {0, 0, 0}; - region.imageExtent = {mipWidth, mipHeight, 1}; - - vkCmdCopyBufferToImage( - commandBuffer, - buffer.Handle(), - image_, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, - ®ion); - }); - } - -} diff --git a/src/Vulkan/Image.hpp b/src/Vulkan/Image.hpp deleted file mode 100644 index 8881b5cb..00000000 --- a/src/Vulkan/Image.hpp +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include "Vulkan.hpp" -#include "DeviceMemory.hpp" - -namespace Vulkan -{ - class Buffer; - class CommandPool; - class Device; - - class Image final - { - public: - - Image(const Image&) = delete; - Image& operator = (const Image&) = delete; - Image& operator = (Image&&) = delete; - - Image(const Device& device, VkExtent2D extent, uint32_t miplevel, VkFormat format); - Image(const Device& device, VkExtent2D extent, uint32_t miplevel, VkFormat format,VkImageTiling tiling, VkImageUsageFlags usage, bool useForExternal = false); - Image(Image&& other) noexcept; - ~Image(); - - const class Device& Device() const { return device_; } - VkExtent2D Extent() const { return extent_; } - VkFormat Format() const { return format_; } - - DeviceMemory AllocateMemory(VkMemoryPropertyFlags properties, bool external = false) const; - VkMemoryRequirements GetMemoryRequirements() const; - - void TransitionImageLayout(CommandPool& commandPool, VkImageLayout newLayout); - void CopyFrom(CommandPool& commandPool, const Buffer& buffer); - - void CopyFromToMipLevel( - Vulkan::CommandPool& commandPool, - const Vulkan::Buffer& buffer, - uint32_t mipLevel, - uint32_t mipWidth, - uint32_t mipHeight); - - private: - - const class Device& device_; - const VkExtent2D extent_; - const VkFormat format_; - VkImageLayout imageLayout_; - uint32_t mipLevel_; - bool external_; - VULKAN_HANDLE(VkImage, image_) - }; - -} diff --git a/src/Vulkan/ImageView.cpp b/src/Vulkan/ImageView.cpp deleted file mode 100644 index f6982617..00000000 --- a/src/Vulkan/ImageView.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "ImageView.hpp" -#include "Device.hpp" - -namespace Vulkan { - -ImageView::ImageView(const class Device& device, const VkImage image, const VkFormat format, const VkImageAspectFlags aspectFlags, const uint32_t miplevel) : - device_(device) -{ - VkImageViewCreateInfo createInfo = {}; - createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - createInfo.image = image; - createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - createInfo.format = format; - createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.subresourceRange.aspectMask = aspectFlags; - createInfo.subresourceRange.baseMipLevel = 0; - createInfo.subresourceRange.levelCount = miplevel; - createInfo.subresourceRange.baseArrayLayer = 0; - createInfo.subresourceRange.layerCount = 1; - - Check(vkCreateImageView(device_.Handle(), &createInfo, nullptr, &imageView_), - "create image view"); -} - -ImageView::~ImageView() -{ - if (imageView_ != nullptr) - { - vkDestroyImageView(device_.Handle(), imageView_, nullptr); - imageView_ = nullptr; - } -} - -} \ No newline at end of file diff --git a/src/Vulkan/ImageView.hpp b/src/Vulkan/ImageView.hpp deleted file mode 100644 index 67702b51..00000000 --- a/src/Vulkan/ImageView.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include "Vulkan.hpp" - -namespace Vulkan -{ - class Device; - - class ImageView final - { - public: - - VULKAN_NON_COPIABLE(ImageView) - - explicit ImageView(const Device& device, VkImage image, VkFormat format, VkImageAspectFlags aspectFlags, const uint32_t miplevel = 1); - ~ImageView(); - - const class Device& Device() const { return device_; } - - - private: - - const class Device& device_; - - VULKAN_HANDLE(VkImageView, imageView_) - }; - -} diff --git a/src/Vulkan/RenderImage.cpp b/src/Vulkan/RenderImage.cpp deleted file mode 100644 index af83370b..00000000 --- a/src/Vulkan/RenderImage.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include "RenderImage.hpp" -#include "Buffer.hpp" -#include "DepthBuffer.hpp" -#include "Device.hpp" -#include "Image.hpp" -#include "ImageMemoryBarrier.hpp" -#include "ImageView.hpp" -#include "CommandExecution.hpp" -#include "Utilities/Exception.hpp" -#include "Vulkan/RayTracing/DeviceProcedures.hpp" - -namespace Vulkan { - RenderImage::RenderImage(const Device& device, - VkExtent2D extent, - VkFormat format, - VkImageTiling tiling, - VkImageUsageFlags usage, - bool external, - const char* debugName) - { - image_.reset(new Image(device, extent, 1, format, tiling, usage, external)); - imageMemory_.reset( - new DeviceMemory(image_->AllocateMemory(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, external))); - imageView_.reset(new ImageView(device, image_->Handle(), format, VK_IMAGE_ASPECT_COLOR_BIT)); - - if(debugName) - { - const auto& debugUtils = device.DebugUtils(); - debugUtils.SetObjectName(image_->Handle(), debugName); - } - - sampler_.reset(new Vulkan::Sampler(device, Vulkan::SamplerConfig())); - } - - RenderImage::~RenderImage() - { - sampler_.reset(); - image_.reset(); - imageMemory_.reset(); - imageView_.reset(); - } - - void RenderImage::InsertBarrier(VkCommandBuffer commandBuffer, VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask, VkImageLayout oldLayout, VkImageLayout newLayout) const - { - VkImageSubresourceRange subresourceRange = {}; - subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - subresourceRange.baseMipLevel = 0; - subresourceRange.levelCount = 1; - subresourceRange.baseArrayLayer = 0; - subresourceRange.layerCount = 1; - - ImageMemoryBarrier::Insert(commandBuffer, GetImage().Handle(), subresourceRange, - srcAccessMask, dstAccessMask, oldLayout, - newLayout); - } - - ExtHandle RenderImage::GetExternalHandle() const - { - ExtHandle handle{}; - #if WIN32 && !defined(__MINGW32__) - VkMemoryGetWin32HandleInfoKHR handleInfo = { VK_STRUCTURE_TYPE_MEMORY_GET_WIN32_HANDLE_INFO_KHR }; - handleInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT; - handleInfo.memory = imageMemory_->Handle(); - image_->Device().GetDeviceProcedures().vkGetMemoryWin32HandleKHR(image_->Device().Handle(), &handleInfo, &handle); -#elif __linux__ - VkMemoryGetFdInfoKHR fdInfo = { VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR }; - fdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT; - fdInfo.memory = imageMemory_->Handle(); - image_->Device().GetDeviceProcedures().vkGetMemoryFdKHR(image_->Device().Handle(), &fdInfo, &handle); -#endif - return handle; - } -} diff --git a/src/Vulkan/RenderImage.hpp b/src/Vulkan/RenderImage.hpp deleted file mode 100644 index 2964ee8e..00000000 --- a/src/Vulkan/RenderImage.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include - -#include "Vulkan.hpp" -#include "DeviceMemory.hpp" -#include "Sampler.hpp" - -#if WIN32 && !defined(__MINGW32__) -#define ExtHandle HANDLE -#else -#define ExtHandle int -#endif -namespace Vulkan -{ - class ImageView; - class Image; - class Buffer; - class CommandPool; - class Device; - - class RenderImage final - { - public: - - RenderImage(const RenderImage&) = delete; - RenderImage& operator = (const RenderImage&) = delete; - RenderImage& operator = (RenderImage&&) = delete; - - RenderImage(const Device& device,VkExtent2D extent, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, bool external = false, const char* debugName = nullptr); - ~RenderImage(); - - const Image& GetImage() const { return *image_; } - const DeviceMemory& GetImageMemory() const { return *imageMemory_; } - const ImageView& GetImageView() const { return *imageView_; } - const Vulkan::Sampler& Sampler() const { return *sampler_; } - void InsertBarrier(VkCommandBuffer commandBuffer, VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask, VkImageLayout oldLayout, VkImageLayout newLayout) const; - ExtHandle GetExternalHandle() const; - private: - std::unique_ptr image_; - std::unique_ptr imageMemory_; - std::unique_ptr imageView_; - std::unique_ptr sampler_; - - }; - -} From a8eeb3e51942a75538dd92ef2aef0308f4713dac Mon Sep 17 00:00:00 2001 From: gameKnife Date: Mon, 2 Feb 2026 18:27:28 +0800 Subject: [PATCH 47/53] refactor(vulkan): merge DeviceMemory, Sampler, ShaderModule into MemoryAndShader - Merged 3 file pairs into MemoryAndShader.hpp/cpp - Preserved BufferUtil.hpp as independent file (template dependencies) - Updated all include references - Phase 7 complete Co-Authored-By: Claude Sonnet 4.5 --- .../Acceleration/CPUAccelerationStructure.cpp | 2 +- src/Assets/GPU/Texture.cpp | 2 +- src/Assets/GPU/Texture.hpp | 2 +- src/Assets/GPU/TextureImage.cpp | 6 +- src/Assets/GPU/TextureImage.hpp | 2 +- src/Assets/GPU/UniformBuffer.cpp | 2 +- .../PathTracing/PathTracingRenderer.cpp | 4 +- .../PipelineCommon/CommonComputePipeline.cpp | 8 +- .../PipelineCommon/CommonComputePipeline.hpp | 4 +- src/Rendering/RayTraceBaseRenderer.cpp | 4 +- .../SoftwareModern/SoftwareModernRenderer.cpp | 2 +- .../SoftwareModern/SoftwareModernRenderer.hpp | 2 +- .../SoftwareTracingRenderer.cpp | 2 +- src/Rendering/VulkanBaseRenderer.cpp | 12 +- src/Rendering/VulkanBaseRenderer.hpp | 4 +- src/Runtime/Editor/UserInterface.cpp | 4 +- src/Runtime/Editor/UserInterface.hpp | 2 +- src/Runtime/Engine.hpp | 2 +- src/Vulkan/BufferUtil.hpp | 4 +- src/Vulkan/DeviceMemory.hpp | 35 ------ src/Vulkan/GpuResources.hpp | 4 +- .../{DeviceMemory.cpp => MemoryAndShader.cpp} | 105 ++++++++++++++++- src/Vulkan/MemoryAndShader.hpp | 109 ++++++++++++++++++ .../RayTracing/AccelerationStructure.cpp | 2 +- .../BottomLevelAccelerationStructure.cpp | 2 +- src/Vulkan/RayTracing/BottomLevelGeometry.cpp | 2 +- .../TopLevelAccelerationStructure.cpp | 2 +- src/Vulkan/RenderingPipeline.cpp | 4 +- src/Vulkan/Sampler.cpp | 43 ------- src/Vulkan/Sampler.hpp | 46 -------- src/Vulkan/ShaderModule.cpp | 51 -------- src/Vulkan/ShaderModule.hpp | 34 ------ src/Vulkan/SwapChain.cpp | 2 +- 33 files changed, 252 insertions(+), 259 deletions(-) delete mode 100644 src/Vulkan/DeviceMemory.hpp rename src/Vulkan/{DeviceMemory.cpp => MemoryAndShader.cpp} (62%) create mode 100644 src/Vulkan/MemoryAndShader.hpp delete mode 100644 src/Vulkan/Sampler.cpp delete mode 100644 src/Vulkan/Sampler.hpp delete mode 100644 src/Vulkan/ShaderModule.cpp delete mode 100644 src/Vulkan/ShaderModule.hpp diff --git a/src/Assets/Acceleration/CPUAccelerationStructure.cpp b/src/Assets/Acceleration/CPUAccelerationStructure.cpp index 3e521702..f887035b 100644 --- a/src/Assets/Acceleration/CPUAccelerationStructure.cpp +++ b/src/Assets/Acceleration/CPUAccelerationStructure.cpp @@ -1,6 +1,6 @@ #include "Assets/Acceleration/CPUAccelerationStructure.h" #include "Runtime/Subsystems/TaskCoordinator.hpp" -#include "Vulkan/DeviceMemory.hpp" +#include "Vulkan/MemoryAndShader.hpp" #include "Assets/Core/Node.h" #include "Runtime/Components/RenderComponent.h" #include "Assets/GPU/TextureImage.hpp" diff --git a/src/Assets/GPU/Texture.cpp b/src/Assets/GPU/Texture.cpp index 0c19a060..457a44c7 100644 --- a/src/Assets/GPU/Texture.cpp +++ b/src/Assets/GPU/Texture.cpp @@ -9,7 +9,7 @@ #include "Runtime/Engine.hpp" #include "Utilities/FileHelper.hpp" #include "Vulkan/Device.hpp" -#include "Vulkan/ImageView.hpp" +#include "Vulkan/GpuResources.hpp" #include "Vulkan/DescriptorSystem.hpp" #include "ThirdParty/lzav/lzav.h" diff --git a/src/Assets/GPU/Texture.hpp b/src/Assets/GPU/Texture.hpp index 48669051..69a7d1f1 100644 --- a/src/Assets/GPU/Texture.hpp +++ b/src/Assets/GPU/Texture.hpp @@ -2,7 +2,7 @@ #include "Common/CoreMinimal.hpp" #include "Vulkan/Vulkan.hpp" -#include "Vulkan/Sampler.hpp" +#include "Vulkan/MemoryAndShader.hpp" #include #include #include diff --git a/src/Assets/GPU/TextureImage.cpp b/src/Assets/GPU/TextureImage.cpp index d5b66061..ac29b15d 100644 --- a/src/Assets/GPU/TextureImage.cpp +++ b/src/Assets/GPU/TextureImage.cpp @@ -1,9 +1,9 @@ #include "Assets/GPU/TextureImage.hpp" #include "Assets/GPU/Texture.hpp" -#include "Vulkan/Buffer.hpp" +#include "Vulkan/GpuResources.hpp" #include "Vulkan/CommandExecution.hpp" -#include "Vulkan/ImageView.hpp" -#include "Vulkan/Sampler.hpp" +#include "Vulkan/GpuResources.hpp" +#include "Vulkan/MemoryAndShader.hpp" #include #include "Vulkan/CommandExecution.hpp" diff --git a/src/Assets/GPU/TextureImage.hpp b/src/Assets/GPU/TextureImage.hpp index 31b1a426..cde3f63c 100644 --- a/src/Assets/GPU/TextureImage.hpp +++ b/src/Assets/GPU/TextureImage.hpp @@ -1,6 +1,6 @@ #pragma once -#include "Vulkan/Image.hpp" +#include "Vulkan/GpuResources.hpp" #include #include #include diff --git a/src/Assets/GPU/UniformBuffer.cpp b/src/Assets/GPU/UniformBuffer.cpp index ddf12373..457be67c 100644 --- a/src/Assets/GPU/UniformBuffer.cpp +++ b/src/Assets/GPU/UniformBuffer.cpp @@ -1,5 +1,5 @@ #include "Assets/GPU/UniformBuffer.hpp" -#include "Vulkan/Buffer.hpp" +#include "Vulkan/GpuResources.hpp" #include "Vulkan/CommandExecution.hpp" #include diff --git a/src/Rendering/PathTracing/PathTracingRenderer.cpp b/src/Rendering/PathTracing/PathTracingRenderer.cpp index 13c039b1..6151e659 100644 --- a/src/Rendering/PathTracing/PathTracingRenderer.cpp +++ b/src/Rendering/PathTracing/PathTracingRenderer.cpp @@ -1,11 +1,11 @@ #include "PathTracingRenderer.hpp" #include "Vulkan/BufferUtil.hpp" -#include "Vulkan/Image.hpp" +#include "Vulkan/GpuResources.hpp" #include "Vulkan/ImageMemoryBarrier.hpp" #include "Vulkan/CommandExecution.hpp" #include "Vulkan/SwapChain.hpp" #include "Utilities/Math.hpp" -#include "Vulkan/RenderImage.hpp" +#include "Vulkan/GpuResources.hpp" #include "Rendering/PipelineCommon/CommonComputePipeline.hpp" #include diff --git a/src/Rendering/PipelineCommon/CommonComputePipeline.cpp b/src/Rendering/PipelineCommon/CommonComputePipeline.cpp index 40ad6176..a68918a8 100644 --- a/src/Rendering/PipelineCommon/CommonComputePipeline.cpp +++ b/src/Rendering/PipelineCommon/CommonComputePipeline.cpp @@ -2,13 +2,13 @@ #include "Runtime/Engine.hpp" -#include "Vulkan/Buffer.hpp" +#include "Vulkan/GpuResources.hpp" #include "Vulkan/DescriptorSystem.hpp" #include "Vulkan/Device.hpp" -#include "Vulkan/PipelineLayout.hpp" -#include "Vulkan/ShaderModule.hpp" +#include "Vulkan/RenderingPipeline.hpp" +#include "Vulkan/MemoryAndShader.hpp" #include "Vulkan/SwapChain.hpp" -#include "Vulkan/RenderPass.hpp" +#include "Vulkan/RenderingPipeline.hpp" #include "Assets/Core/Scene.hpp" #include "Assets/GPU/UniformBuffer.hpp" diff --git a/src/Rendering/PipelineCommon/CommonComputePipeline.hpp b/src/Rendering/PipelineCommon/CommonComputePipeline.hpp index d958f235..691c639a 100644 --- a/src/Rendering/PipelineCommon/CommonComputePipeline.hpp +++ b/src/Rendering/PipelineCommon/CommonComputePipeline.hpp @@ -1,8 +1,8 @@ #pragma once #include "Vulkan/Vulkan.hpp" -#include "Vulkan/ImageView.hpp" -#include "Vulkan/PipelineBase.hpp" +#include "Vulkan/GpuResources.hpp" +#include "Vulkan/RenderingPipeline.hpp" #include #include diff --git a/src/Rendering/RayTraceBaseRenderer.cpp b/src/Rendering/RayTraceBaseRenderer.cpp index 344900a4..57cc87c9 100644 --- a/src/Rendering/RayTraceBaseRenderer.cpp +++ b/src/Rendering/RayTraceBaseRenderer.cpp @@ -8,8 +8,8 @@ #include "Assets/Core/Node.h" #include "Runtime/Components/RenderComponent.h" #include "Runtime/Components/SkinnedMeshComponent.h" -#include "Vulkan/Buffer.hpp" -#include "Vulkan/PipelineLayout.hpp" +#include "Vulkan/GpuResources.hpp" +#include "Vulkan/RenderingPipeline.hpp" #include "Vulkan/CommandExecution.hpp" #include #include diff --git a/src/Rendering/SoftwareModern/SoftwareModernRenderer.cpp b/src/Rendering/SoftwareModern/SoftwareModernRenderer.cpp index 42eb3c51..86d3ed61 100644 --- a/src/Rendering/SoftwareModern/SoftwareModernRenderer.cpp +++ b/src/Rendering/SoftwareModern/SoftwareModernRenderer.cpp @@ -3,7 +3,7 @@ #include "Utilities/Math.hpp" #include "Vulkan/SwapChain.hpp" #include "Vulkan/WindowSurface.hpp" -#include "Vulkan/RenderImage.hpp" +#include "Vulkan/GpuResources.hpp" namespace Vulkan::LegacyDeferred { diff --git a/src/Rendering/SoftwareModern/SoftwareModernRenderer.hpp b/src/Rendering/SoftwareModern/SoftwareModernRenderer.hpp index 8acd7af4..f5bd4314 100644 --- a/src/Rendering/SoftwareModern/SoftwareModernRenderer.hpp +++ b/src/Rendering/SoftwareModern/SoftwareModernRenderer.hpp @@ -1,6 +1,6 @@ #pragma once -#include "Vulkan/FrameBuffer.hpp" +#include "Vulkan/RenderingPipeline.hpp" #include "Rendering/VulkanBaseRenderer.hpp" #include "Rendering/RayTraceBaseRenderer.hpp" diff --git a/src/Rendering/SoftwareTracing/SoftwareTracingRenderer.cpp b/src/Rendering/SoftwareTracing/SoftwareTracingRenderer.cpp index 37a10fce..6e26ddb6 100644 --- a/src/Rendering/SoftwareTracing/SoftwareTracingRenderer.cpp +++ b/src/Rendering/SoftwareTracing/SoftwareTracingRenderer.cpp @@ -3,7 +3,7 @@ #include "Runtime/Engine.hpp" #include "Vulkan/SwapChain.hpp" -#include "Vulkan/RenderImage.hpp" +#include "Vulkan/GpuResources.hpp" #include "Utilities/Math.hpp" diff --git a/src/Rendering/VulkanBaseRenderer.cpp b/src/Rendering/VulkanBaseRenderer.cpp index f44d3096..85e822a6 100644 --- a/src/Rendering/VulkanBaseRenderer.cpp +++ b/src/Rendering/VulkanBaseRenderer.cpp @@ -1,21 +1,21 @@ #include "VulkanBaseRenderer.hpp" -#include "Vulkan/Buffer.hpp" +#include "Vulkan/GpuResources.hpp" #include "Vulkan/CommandExecution.hpp" #include "Vulkan/CommandExecution.hpp" #include "Vulkan/DebugUtilsMessenger.hpp" -#include "Vulkan/DepthBuffer.hpp" +#include "Vulkan/GpuResources.hpp" #include "Vulkan/Device.hpp" #include "Vulkan/SyncAndTiming.hpp" #include "Vulkan/BufferUtil.hpp" -#include "Vulkan/FrameBuffer.hpp" +#include "Vulkan/RenderingPipeline.hpp" #include "Vulkan/Instance.hpp" -#include "Vulkan/PipelineLayout.hpp" -#include "Vulkan/RenderPass.hpp" +#include "Vulkan/RenderingPipeline.hpp" +#include "Vulkan/RenderingPipeline.hpp" #include "Vulkan/SwapChain.hpp" #include "Vulkan/WindowSurface.hpp" #include "Vulkan/Enumerate.hpp" #include "Vulkan/ImageMemoryBarrier.hpp" -#include "Vulkan/RenderImage.hpp" +#include "Vulkan/GpuResources.hpp" #include "Vulkan/CommandExecution.hpp" #include "Vulkan/Strings.hpp" #include "Vulkan/Version.hpp" diff --git a/src/Rendering/VulkanBaseRenderer.hpp b/src/Rendering/VulkanBaseRenderer.hpp index ea3a8d1d..19aa3110 100644 --- a/src/Rendering/VulkanBaseRenderer.hpp +++ b/src/Rendering/VulkanBaseRenderer.hpp @@ -1,8 +1,8 @@ #pragma once -#include "Vulkan/FrameBuffer.hpp" +#include "Vulkan/RenderingPipeline.hpp" #include "Vulkan/SyncAndTiming.hpp" -#include "Vulkan/Image.hpp" +#include "Vulkan/GpuResources.hpp" #include "Assets/GPU/UniformBuffer.hpp" #include "Assets/Core/Scene.hpp" #include diff --git a/src/Runtime/Editor/UserInterface.cpp b/src/Runtime/Editor/UserInterface.cpp index b6a4e522..572ccf95 100644 --- a/src/Runtime/Editor/UserInterface.cpp +++ b/src/Runtime/Editor/UserInterface.cpp @@ -7,7 +7,7 @@ #include "Vulkan/DescriptorSystem.hpp" #include "Vulkan/Device.hpp" #include "Vulkan/Instance.hpp" -#include "Vulkan/RenderPass.hpp" +#include "Vulkan/RenderingPipeline.hpp" #include "Vulkan/CommandExecution.hpp" #include "Vulkan/SwapChain.hpp" #include "Vulkan/WindowSurface.hpp" @@ -36,7 +36,7 @@ #include "Utilities/FileHelper.hpp" #include "Utilities/ImGui.hpp" #include "Utilities/Math.hpp" -#include "Vulkan/ImageView.hpp" +#include "Vulkan/GpuResources.hpp" extern float GAndroidMagicScale; extern std::unique_ptr GApplication; diff --git a/src/Runtime/Editor/UserInterface.hpp b/src/Runtime/Editor/UserInterface.hpp index 025c0edc..790616c0 100644 --- a/src/Runtime/Editor/UserInterface.hpp +++ b/src/Runtime/Editor/UserInterface.hpp @@ -3,7 +3,7 @@ #include #include #include "Vulkan/Vulkan.hpp" -#include "Vulkan/FrameBuffer.hpp" +#include "Vulkan/RenderingPipeline.hpp" #include #include #include diff --git a/src/Runtime/Engine.hpp b/src/Runtime/Engine.hpp index ffec9c47..4293e09a 100644 --- a/src/Runtime/Engine.hpp +++ b/src/Runtime/Engine.hpp @@ -10,7 +10,7 @@ #include "Runtime/Config/ShowFlags.hpp" #include "Runtime/Config/UserSettings.hpp" #include "Utilities/FileHelper.hpp" -#include "Vulkan/FrameBuffer.hpp" +#include "Vulkan/RenderingPipeline.hpp" #include "Vulkan/WindowSurface.hpp" class NextPhysics; diff --git a/src/Vulkan/BufferUtil.hpp b/src/Vulkan/BufferUtil.hpp index 585b82b3..56370adf 100644 --- a/src/Vulkan/BufferUtil.hpp +++ b/src/Vulkan/BufferUtil.hpp @@ -1,9 +1,9 @@ #pragma once -#include "Buffer.hpp" +#include "GpuResources.hpp" #include "CommandExecution.hpp" #include "Device.hpp" -#include "DeviceMemory.hpp" +#include "MemoryAndShader.hpp" #include #include #include diff --git a/src/Vulkan/DeviceMemory.hpp b/src/Vulkan/DeviceMemory.hpp deleted file mode 100644 index ed9e223a..00000000 --- a/src/Vulkan/DeviceMemory.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include "Vulkan.hpp" - -namespace Vulkan -{ - class Device; - - class DeviceMemory final - { - public: - - DeviceMemory(const DeviceMemory&) = delete; - DeviceMemory& operator = (const DeviceMemory&) = delete; - DeviceMemory& operator = (DeviceMemory&&) = delete; - - DeviceMemory(const Device& device, size_t size, uint32_t memoryTypeBits, VkMemoryAllocateFlags allocateFLags, VkMemoryPropertyFlags propertyFlags, bool external = false); - DeviceMemory(DeviceMemory&& other) noexcept; - ~DeviceMemory(); - - const class Device& Device() const { return device_; } - - void* Map(size_t offset, size_t size); - void Unmap(); - - private: - - uint32_t FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) const; - - const class Device& device_; - - VULKAN_HANDLE(VkDeviceMemory, memory_) - }; - -} diff --git a/src/Vulkan/GpuResources.hpp b/src/Vulkan/GpuResources.hpp index 2bff5cbe..ee387a0d 100644 --- a/src/Vulkan/GpuResources.hpp +++ b/src/Vulkan/GpuResources.hpp @@ -1,8 +1,8 @@ #pragma once #include "Vulkan.hpp" -#include "DeviceMemory.hpp" -#include "Sampler.hpp" +#include "MemoryAndShader.hpp" +#include "MemoryAndShader.hpp" #include #if WIN32 && !defined(__MINGW32__) diff --git a/src/Vulkan/DeviceMemory.cpp b/src/Vulkan/MemoryAndShader.cpp similarity index 62% rename from src/Vulkan/DeviceMemory.cpp rename to src/Vulkan/MemoryAndShader.cpp index 256fb101..9bb0f4bc 100644 --- a/src/Vulkan/DeviceMemory.cpp +++ b/src/Vulkan/MemoryAndShader.cpp @@ -1,6 +1,7 @@ -#include "DeviceMemory.hpp" +#include "MemoryAndShader.hpp" #include "Device.hpp" #include "Utilities/Exception.hpp" +#include "Utilities/FileHelper.hpp" #ifdef VK_USE_PLATFORM_WIN32_KHR # include @@ -77,10 +78,14 @@ WinSecurityAttributes::~WinSecurityAttributes() #endif namespace Vulkan { - + +// ============================================================================ +// DeviceMemory +// ============================================================================ + DeviceMemory::DeviceMemory( - const class Device& device, - const size_t size, + const class Device& device, + const size_t size, const uint32_t memoryTypeBits, const VkMemoryAllocateFlags allocateFLags, const VkMemoryPropertyFlags propertyFlags, @@ -91,7 +96,7 @@ DeviceMemory::DeviceMemory( flagsInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO; flagsInfo.pNext = nullptr; flagsInfo.flags = allocateFLags; - + VkMemoryAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfo.pNext = &flagsInfo; @@ -122,7 +127,7 @@ DeviceMemory::DeviceMemory( allocInfo.pNext = &exportMemoryAllocateInfo; } #endif - + Check(vkAllocateMemory(device.Handle(), &allocInfo, nullptr, &memory_), "allocate memory"); } @@ -173,4 +178,92 @@ uint32_t DeviceMemory::FindMemoryType(const uint32_t typeFilter, const VkMemoryP Throw(std::runtime_error("failed to find suitable memory type")); } +// ============================================================================ +// Sampler +// ============================================================================ + +Sampler::Sampler(const class Device& device, const SamplerConfig& config) : + device_(device) +{ + VkSamplerCreateInfo samplerInfo = {}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = config.MagFilter; + samplerInfo.minFilter = config.MinFilter; + samplerInfo.addressModeU = config.AddressModeU; + samplerInfo.addressModeV = config.AddressModeV; + samplerInfo.addressModeW = config.AddressModeW; + samplerInfo.anisotropyEnable = config.AnisotropyEnable; + samplerInfo.maxAnisotropy = config.MaxAnisotropy; + samplerInfo.borderColor = config.BorderColor; + samplerInfo.unnormalizedCoordinates = config.UnnormalizedCoordinates; + samplerInfo.compareEnable = config.CompareEnable; + samplerInfo.compareOp = config.CompareOp; + samplerInfo.mipmapMode = config.MipmapMode; + samplerInfo.mipLodBias = config.MipLodBias; + samplerInfo.minLod = config.MinLod; + samplerInfo.maxLod = config.MaxLod; + + if (vkCreateSampler(device.Handle(), &samplerInfo, nullptr, &sampler_) != VK_SUCCESS) + { + Throw(std::runtime_error("failed to create sampler")); + } +} + +Sampler::~Sampler() +{ + if (sampler_ != nullptr) + { + vkDestroySampler(device_.Handle(), sampler_, nullptr); + sampler_ = nullptr; + } +} + +// ============================================================================ +// ShaderModule +// ============================================================================ + +ShaderModule::ShaderModule(const class Device& device, const std::string& filename) : + ShaderModule(device, ReadFile(filename)) +{ +} + +ShaderModule::ShaderModule(const class Device& device, const std::vector& code) : + device_(device) +{ + VkShaderModuleCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + Check(vkCreateShaderModule(device.Handle(), &createInfo, nullptr, &shaderModule_), + "create shader module"); +} + +ShaderModule::~ShaderModule() +{ + if (shaderModule_ != nullptr) + { + vkDestroyShaderModule(device_.Handle(), shaderModule_, nullptr); + shaderModule_ = nullptr; + } +} + +VkPipelineShaderStageCreateInfo ShaderModule::CreateShaderStage(VkShaderStageFlagBits stage) const +{ + VkPipelineShaderStageCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + createInfo.stage = stage; + createInfo.module = shaderModule_; + createInfo.pName = "main"; + + return createInfo; +} + +std::vector ShaderModule::ReadFile(const std::string& filename) +{ + std::vector buffer; + Utilities::Package::FPackageFileSystem::GetInstance().LoadFile(filename, buffer); + return buffer; +} + } diff --git a/src/Vulkan/MemoryAndShader.hpp b/src/Vulkan/MemoryAndShader.hpp new file mode 100644 index 00000000..38de08d2 --- /dev/null +++ b/src/Vulkan/MemoryAndShader.hpp @@ -0,0 +1,109 @@ +#pragma once + +#include "Vulkan.hpp" +#include +#include + +namespace Vulkan +{ + class Device; + + // ============================================================================ + // DeviceMemory + // ============================================================================ + + class DeviceMemory final + { + public: + + DeviceMemory(const DeviceMemory&) = delete; + DeviceMemory& operator = (const DeviceMemory&) = delete; + DeviceMemory& operator = (DeviceMemory&&) = delete; + + DeviceMemory(const Device& device, size_t size, uint32_t memoryTypeBits, VkMemoryAllocateFlags allocateFLags, VkMemoryPropertyFlags propertyFlags, bool external = false); + DeviceMemory(DeviceMemory&& other) noexcept; + ~DeviceMemory(); + + const class Device& Device() const { return device_; } + + void* Map(size_t offset, size_t size); + void Unmap(); + + private: + + uint32_t FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) const; + + const class Device& device_; + + VULKAN_HANDLE(VkDeviceMemory, memory_) + }; + + // ============================================================================ + // Sampler + // ============================================================================ + + struct SamplerConfig final + { + VkFilter MagFilter = VK_FILTER_LINEAR; + VkFilter MinFilter = VK_FILTER_LINEAR; + VkSamplerAddressMode AddressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + VkSamplerAddressMode AddressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + VkSamplerAddressMode AddressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + bool AnisotropyEnable = true; + float MaxAnisotropy = 16; + VkBorderColor BorderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + bool UnnormalizedCoordinates = false; + bool CompareEnable = false; + VkCompareOp CompareOp = VK_COMPARE_OP_ALWAYS; + VkSamplerMipmapMode MipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + float MipLodBias = 0.0f; + float MinLod = 0.0f; + float MaxLod = 0.0f; + }; + + class Sampler final + { + public: + + VULKAN_NON_COPIABLE(Sampler) + + Sampler(const Device& device, const SamplerConfig& config); + ~Sampler(); + + const class Device& Device() const { return device_; } + + private: + + const class Device& device_; + + VULKAN_HANDLE(VkSampler, sampler_) + }; + + // ============================================================================ + // ShaderModule + // ============================================================================ + + class ShaderModule final + { + public: + + VULKAN_NON_COPIABLE(ShaderModule) + + ShaderModule(const Device& device, const std::string& filename); + ShaderModule(const Device& device, const std::vector& code); + ~ShaderModule(); + + const class Device& Device() const { return device_; } + + VkPipelineShaderStageCreateInfo CreateShaderStage(VkShaderStageFlagBits stage) const; + + private: + + static std::vector ReadFile(const std::string& filename); + + const class Device& device_; + + VULKAN_HANDLE(VkShaderModule, shaderModule_) + }; + +} diff --git a/src/Vulkan/RayTracing/AccelerationStructure.cpp b/src/Vulkan/RayTracing/AccelerationStructure.cpp index 8d6c6c50..94e894ae 100644 --- a/src/Vulkan/RayTracing/AccelerationStructure.cpp +++ b/src/Vulkan/RayTracing/AccelerationStructure.cpp @@ -1,7 +1,7 @@ #include "AccelerationStructure.hpp" #include "DeviceProcedures.hpp" #include "RayTracingProperties.hpp" -#include "Vulkan/Buffer.hpp" +#include "Vulkan/GpuResources.hpp" #include "Vulkan/Device.hpp" #undef MemoryBarrier diff --git a/src/Vulkan/RayTracing/BottomLevelAccelerationStructure.cpp b/src/Vulkan/RayTracing/BottomLevelAccelerationStructure.cpp index 928441d5..440de2b1 100644 --- a/src/Vulkan/RayTracing/BottomLevelAccelerationStructure.cpp +++ b/src/Vulkan/RayTracing/BottomLevelAccelerationStructure.cpp @@ -1,6 +1,6 @@ #include "BottomLevelAccelerationStructure.hpp" #include "DeviceProcedures.hpp" -#include "Vulkan/Buffer.hpp" +#include "Vulkan/GpuResources.hpp" namespace Vulkan::RayTracing { diff --git a/src/Vulkan/RayTracing/BottomLevelGeometry.cpp b/src/Vulkan/RayTracing/BottomLevelGeometry.cpp index 9f8f9969..c8c762da 100644 --- a/src/Vulkan/RayTracing/BottomLevelGeometry.cpp +++ b/src/Vulkan/RayTracing/BottomLevelGeometry.cpp @@ -2,7 +2,7 @@ #include "DeviceProcedures.hpp" #include "Assets/Core/Scene.hpp" #include "Assets/Data/Vertex.hpp" -#include "Vulkan/Buffer.hpp" +#include "Vulkan/GpuResources.hpp" namespace Vulkan::RayTracing { diff --git a/src/Vulkan/RayTracing/TopLevelAccelerationStructure.cpp b/src/Vulkan/RayTracing/TopLevelAccelerationStructure.cpp index 27bd87c9..05b1ec6f 100644 --- a/src/Vulkan/RayTracing/TopLevelAccelerationStructure.cpp +++ b/src/Vulkan/RayTracing/TopLevelAccelerationStructure.cpp @@ -2,7 +2,7 @@ #include "BottomLevelAccelerationStructure.hpp" #include "DeviceProcedures.hpp" #include "Utilities/Exception.hpp" -#include "Vulkan/Buffer.hpp" +#include "Vulkan/GpuResources.hpp" #include "Vulkan/Device.hpp" namespace Vulkan::RayTracing { diff --git a/src/Vulkan/RenderingPipeline.cpp b/src/Vulkan/RenderingPipeline.cpp index 3c007a8d..edacd88b 100644 --- a/src/Vulkan/RenderingPipeline.cpp +++ b/src/Vulkan/RenderingPipeline.cpp @@ -1,8 +1,8 @@ #include "RenderingPipeline.hpp" -#include "DepthBuffer.hpp" +#include "GpuResources.hpp" #include "Device.hpp" #include "SwapChain.hpp" -#include "ImageView.hpp" +#include "GpuResources.hpp" #include "DescriptorSystem.hpp" #include "Assets/GPU/Texture.hpp" #include diff --git a/src/Vulkan/Sampler.cpp b/src/Vulkan/Sampler.cpp deleted file mode 100644 index e296e485..00000000 --- a/src/Vulkan/Sampler.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "Sampler.hpp" -#include "Device.hpp" -#include "Utilities/Exception.hpp" - -namespace Vulkan { - -Sampler::Sampler(const class Device& device, const SamplerConfig& config) : - device_(device) -{ - VkSamplerCreateInfo samplerInfo = {}; - samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; - samplerInfo.magFilter = config.MagFilter; - samplerInfo.minFilter = config.MinFilter; - samplerInfo.addressModeU = config.AddressModeU; - samplerInfo.addressModeV = config.AddressModeV; - samplerInfo.addressModeW = config.AddressModeW; - samplerInfo.anisotropyEnable = config.AnisotropyEnable; - samplerInfo.maxAnisotropy = config.MaxAnisotropy; - samplerInfo.borderColor = config.BorderColor; - samplerInfo.unnormalizedCoordinates = config.UnnormalizedCoordinates; - samplerInfo.compareEnable = config.CompareEnable; - samplerInfo.compareOp = config.CompareOp; - samplerInfo.mipmapMode = config.MipmapMode; - samplerInfo.mipLodBias = config.MipLodBias; - samplerInfo.minLod = config.MinLod; - samplerInfo.maxLod = config.MaxLod; - - if (vkCreateSampler(device.Handle(), &samplerInfo, nullptr, &sampler_) != VK_SUCCESS) - { - Throw(std::runtime_error("failed to create sampler")); - } -} - -Sampler::~Sampler() -{ - if (sampler_ != nullptr) - { - vkDestroySampler(device_.Handle(), sampler_, nullptr); - sampler_ = nullptr; - } -} - -} diff --git a/src/Vulkan/Sampler.hpp b/src/Vulkan/Sampler.hpp deleted file mode 100644 index f29ea89f..00000000 --- a/src/Vulkan/Sampler.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include "Vulkan.hpp" - -namespace Vulkan -{ - class Device; - - struct SamplerConfig final - { - VkFilter MagFilter = VK_FILTER_LINEAR; - VkFilter MinFilter = VK_FILTER_LINEAR; - VkSamplerAddressMode AddressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; - VkSamplerAddressMode AddressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; - VkSamplerAddressMode AddressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - bool AnisotropyEnable = true; - float MaxAnisotropy = 16; - VkBorderColor BorderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; - bool UnnormalizedCoordinates = false; - bool CompareEnable = false; - VkCompareOp CompareOp = VK_COMPARE_OP_ALWAYS; - VkSamplerMipmapMode MipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; - float MipLodBias = 0.0f; - float MinLod = 0.0f; - float MaxLod = 0.0f; - }; - - class Sampler final - { - public: - - VULKAN_NON_COPIABLE(Sampler) - - Sampler(const Device& device, const SamplerConfig& config); - ~Sampler(); - - const class Device& Device() const { return device_; } - - private: - - const class Device& device_; - - VULKAN_HANDLE(VkSampler, sampler_) - }; - -} diff --git a/src/Vulkan/ShaderModule.cpp b/src/Vulkan/ShaderModule.cpp deleted file mode 100644 index 955433cc..00000000 --- a/src/Vulkan/ShaderModule.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "ShaderModule.hpp" -#include "Device.hpp" -#include "Utilities/FileHelper.hpp" - -namespace Vulkan { - -ShaderModule::ShaderModule(const class Device& device, const std::string& filename) : - ShaderModule(device, ReadFile(filename)) -{ -} - -ShaderModule::ShaderModule(const class Device& device, const std::vector& code) : - device_(device) -{ - VkShaderModuleCreateInfo createInfo = {}; - createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - createInfo.codeSize = code.size(); - createInfo.pCode = reinterpret_cast(code.data()); - - Check(vkCreateShaderModule(device.Handle(), &createInfo, nullptr, &shaderModule_), - "create shader module"); -} - -ShaderModule::~ShaderModule() -{ - if (shaderModule_ != nullptr) - { - vkDestroyShaderModule(device_.Handle(), shaderModule_, nullptr); - shaderModule_ = nullptr; - } -} - -VkPipelineShaderStageCreateInfo ShaderModule::CreateShaderStage(VkShaderStageFlagBits stage) const -{ - VkPipelineShaderStageCreateInfo createInfo = {}; - createInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - createInfo.stage = stage; - createInfo.module = shaderModule_; - createInfo.pName = "main"; - - return createInfo; -} - -std::vector ShaderModule::ReadFile(const std::string& filename) -{ - std::vector buffer; - Utilities::Package::FPackageFileSystem::GetInstance().LoadFile(filename, buffer); - return buffer; -} - -} diff --git a/src/Vulkan/ShaderModule.hpp b/src/Vulkan/ShaderModule.hpp deleted file mode 100644 index baa59220..00000000 --- a/src/Vulkan/ShaderModule.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include "Vulkan.hpp" -#include -#include - -namespace Vulkan -{ - class Device; - - class ShaderModule final - { - public: - - VULKAN_NON_COPIABLE(ShaderModule) - - ShaderModule(const Device& device, const std::string& filename); - ShaderModule(const Device& device, const std::vector& code); - ~ShaderModule(); - - const class Device& Device() const { return device_; } - - VkPipelineShaderStageCreateInfo CreateShaderStage(VkShaderStageFlagBits stage) const; - - private: - - static std::vector ReadFile(const std::string& filename); - - const class Device& device_; - - VULKAN_HANDLE(VkShaderModule, shaderModule_) - }; - -} diff --git a/src/Vulkan/SwapChain.cpp b/src/Vulkan/SwapChain.cpp index e3cf2da8..15d82b06 100644 --- a/src/Vulkan/SwapChain.cpp +++ b/src/Vulkan/SwapChain.cpp @@ -1,7 +1,7 @@ #include "SwapChain.hpp" #include "Device.hpp" #include "Enumerate.hpp" -#include "ImageView.hpp" +#include "GpuResources.hpp" #include "Instance.hpp" #include "WindowSurface.hpp" #include "Utilities/Exception.hpp" From b78220e5a23355d144685648578541bbed7f7e6c Mon Sep 17 00:00:00 2001 From: gameKnife Date: Mon, 2 Feb 2026 22:27:58 +0800 Subject: [PATCH 48/53] refactor(vulkan): merge debug utilities into DebugUtilities Merged the following files into DebugUtilities.hpp/cpp: - DebugUtils, DebugUtilsMessenger - Strings (utility class for Vulkan enums) - Enumerate (template utilities) - ImageMemoryBarrier, BufferMemoryBarrier - Version (driver version handling) - Vulkan.hpp/cpp (core Check and ToString functions) This creates a single comprehensive utilities file containing: - Core Vulkan error checking and result string conversion - Debug naming and markers for Vulkan objects - Validation layer callback setup - Memory barrier helper classes - Enumerate template functions for Vulkan queries - String conversion utilities for device types and vendors - Version parsing utilities Phase 8 complete - reduced 14 files (7 pairs + 3 header-only) to 1 pair Co-Authored-By: Claude Sonnet 4.5 --- src/Assets/Core/Scene.hpp | 2 +- src/Assets/Data/Vertex.hpp | 2 +- src/Assets/GPU/Texture.hpp | 2 +- src/Editor/EditorInterface.hpp | 2 +- .../PathTracing/PathTracingRenderer.cpp | 2 +- .../PipelineCommon/CommonComputePipeline.hpp | 2 +- src/Rendering/VulkanBaseRenderer.cpp | 10 +- src/Runtime/Camera/ModelViewController.cpp | 2 +- src/Runtime/Editor/UserInterface.hpp | 2 +- src/Runtime/Subsystems/NextPhysics.h | 2 +- src/Vulkan/CommandExecution.hpp | 2 +- src/Vulkan/DebugUtilities.cpp | 358 +++++++++++++++++ src/Vulkan/DebugUtilities.hpp | 367 ++++++++++++++++++ src/Vulkan/DebugUtils.cpp | 27 -- src/Vulkan/DebugUtils.hpp | 83 ---- src/Vulkan/DebugUtilsMessenger.cpp | 220 ----------- src/Vulkan/DebugUtilsMessenger.hpp | 28 -- src/Vulkan/DescriptorSystem.hpp | 2 +- src/Vulkan/Device.cpp | 2 +- src/Vulkan/Device.hpp | 5 +- src/Vulkan/Enumerate.hpp | 86 ---- src/Vulkan/GpuResources.cpp | 2 +- src/Vulkan/GpuResources.hpp | 2 +- src/Vulkan/ImageMemoryBarrier.hpp | 82 ---- src/Vulkan/Instance.cpp | 4 +- src/Vulkan/Instance.hpp | 2 +- src/Vulkan/MemoryAndShader.hpp | 2 +- .../RayTracing/AccelerationStructure.hpp | 2 +- src/Vulkan/RayTracing/BottomLevelGeometry.hpp | 2 +- src/Vulkan/RayTracing/DeviceProcedures.hpp | 2 +- .../RayTracing/RayTracingProperties.hpp | 2 +- src/Vulkan/RenderingPipeline.hpp | 2 +- src/Vulkan/Strings.cpp | 45 --- src/Vulkan/Strings.hpp | 21 - src/Vulkan/SwapChain.cpp | 4 +- src/Vulkan/SwapChain.hpp | 2 +- src/Vulkan/SyncAndTiming.hpp | 2 +- src/Vulkan/Version.hpp | 43 -- src/Vulkan/Vulkan.cpp | 64 --- src/Vulkan/Vulkan.hpp | 41 -- src/Vulkan/WindowSurface.hpp | 2 +- 41 files changed, 760 insertions(+), 776 deletions(-) create mode 100644 src/Vulkan/DebugUtilities.cpp create mode 100644 src/Vulkan/DebugUtilities.hpp delete mode 100644 src/Vulkan/DebugUtils.cpp delete mode 100644 src/Vulkan/DebugUtils.hpp delete mode 100644 src/Vulkan/DebugUtilsMessenger.cpp delete mode 100644 src/Vulkan/DebugUtilsMessenger.hpp delete mode 100644 src/Vulkan/Enumerate.hpp delete mode 100644 src/Vulkan/ImageMemoryBarrier.hpp delete mode 100644 src/Vulkan/Strings.cpp delete mode 100644 src/Vulkan/Strings.hpp delete mode 100644 src/Vulkan/Version.hpp delete mode 100644 src/Vulkan/Vulkan.cpp delete mode 100644 src/Vulkan/Vulkan.hpp diff --git a/src/Assets/Core/Scene.hpp b/src/Assets/Core/Scene.hpp index c18a7c72..02c6800f 100644 --- a/src/Assets/Core/Scene.hpp +++ b/src/Assets/Core/Scene.hpp @@ -4,7 +4,7 @@ #include #include #include "Common/CoreMinimal.hpp" -#include "Vulkan/Vulkan.hpp" +#include "Vulkan/DebugUtilities.hpp" #include "Assets/Acceleration/CPUAccelerationStructure.h" #include "Assets/Core/Model.hpp" diff --git a/src/Assets/Data/Vertex.hpp b/src/Assets/Data/Vertex.hpp index 14766570..58283654 100644 --- a/src/Assets/Data/Vertex.hpp +++ b/src/Assets/Data/Vertex.hpp @@ -1,7 +1,7 @@ #pragma once #include "Utilities/Glm.hpp" -#include "Vulkan/Vulkan.hpp" +#include "Vulkan/DebugUtilities.hpp" #include namespace Assets diff --git a/src/Assets/GPU/Texture.hpp b/src/Assets/GPU/Texture.hpp index 69a7d1f1..1efbe50e 100644 --- a/src/Assets/GPU/Texture.hpp +++ b/src/Assets/GPU/Texture.hpp @@ -1,7 +1,7 @@ #pragma once #include "Common/CoreMinimal.hpp" -#include "Vulkan/Vulkan.hpp" +#include "Vulkan/DebugUtilities.hpp" #include "Vulkan/MemoryAndShader.hpp" #include #include diff --git a/src/Editor/EditorInterface.hpp b/src/Editor/EditorInterface.hpp index 8b860789..f8bcc729 100644 --- a/src/Editor/EditorInterface.hpp +++ b/src/Editor/EditorInterface.hpp @@ -2,7 +2,7 @@ #include #include "Editor/Core/EditorUiState.hpp" -#include "Vulkan/Vulkan.hpp" +#include "Vulkan/DebugUtilities.hpp" #include diff --git a/src/Rendering/PathTracing/PathTracingRenderer.cpp b/src/Rendering/PathTracing/PathTracingRenderer.cpp index 6151e659..b39a697d 100644 --- a/src/Rendering/PathTracing/PathTracingRenderer.cpp +++ b/src/Rendering/PathTracing/PathTracingRenderer.cpp @@ -1,7 +1,7 @@ #include "PathTracingRenderer.hpp" #include "Vulkan/BufferUtil.hpp" #include "Vulkan/GpuResources.hpp" -#include "Vulkan/ImageMemoryBarrier.hpp" +#include "Vulkan/DebugUtilities.hpp" #include "Vulkan/CommandExecution.hpp" #include "Vulkan/SwapChain.hpp" #include "Utilities/Math.hpp" diff --git a/src/Rendering/PipelineCommon/CommonComputePipeline.hpp b/src/Rendering/PipelineCommon/CommonComputePipeline.hpp index 691c639a..d6e468e3 100644 --- a/src/Rendering/PipelineCommon/CommonComputePipeline.hpp +++ b/src/Rendering/PipelineCommon/CommonComputePipeline.hpp @@ -1,6 +1,6 @@ #pragma once -#include "Vulkan/Vulkan.hpp" +#include "Vulkan/DebugUtilities.hpp" #include "Vulkan/GpuResources.hpp" #include "Vulkan/RenderingPipeline.hpp" diff --git a/src/Rendering/VulkanBaseRenderer.cpp b/src/Rendering/VulkanBaseRenderer.cpp index 85e822a6..0f41782e 100644 --- a/src/Rendering/VulkanBaseRenderer.cpp +++ b/src/Rendering/VulkanBaseRenderer.cpp @@ -2,7 +2,7 @@ #include "Vulkan/GpuResources.hpp" #include "Vulkan/CommandExecution.hpp" #include "Vulkan/CommandExecution.hpp" -#include "Vulkan/DebugUtilsMessenger.hpp" +#include "Vulkan/DebugUtilities.hpp" #include "Vulkan/GpuResources.hpp" #include "Vulkan/Device.hpp" #include "Vulkan/SyncAndTiming.hpp" @@ -13,12 +13,12 @@ #include "Vulkan/RenderingPipeline.hpp" #include "Vulkan/SwapChain.hpp" #include "Vulkan/WindowSurface.hpp" -#include "Vulkan/Enumerate.hpp" -#include "Vulkan/ImageMemoryBarrier.hpp" +#include "Vulkan/DebugUtilities.hpp" +#include "Vulkan/DebugUtilities.hpp" #include "Vulkan/GpuResources.hpp" #include "Vulkan/CommandExecution.hpp" -#include "Vulkan/Strings.hpp" -#include "Vulkan/Version.hpp" +#include "Vulkan/DebugUtilities.hpp" +#include "Vulkan/DebugUtilities.hpp" #include "Assets/Core/Scene.hpp" #include "Assets/GPU/UniformBuffer.hpp" diff --git a/src/Runtime/Camera/ModelViewController.cpp b/src/Runtime/Camera/ModelViewController.cpp index 46ac2b69..ff72e1e0 100644 --- a/src/Runtime/Camera/ModelViewController.cpp +++ b/src/Runtime/Camera/ModelViewController.cpp @@ -1,6 +1,6 @@ #include "Runtime/Camera/ModelViewController.hpp" #include "Assets/Core/Model.hpp" -#include "Vulkan/Vulkan.hpp" +#include "Vulkan/DebugUtilities.hpp" #include "Runtime/Platform/PlatformCommon.h" void ModelViewController::Reset(const Assets::Camera& renderCamera) diff --git a/src/Runtime/Editor/UserInterface.hpp b/src/Runtime/Editor/UserInterface.hpp index 790616c0..befef336 100644 --- a/src/Runtime/Editor/UserInterface.hpp +++ b/src/Runtime/Editor/UserInterface.hpp @@ -2,7 +2,7 @@ #include #include #include -#include "Vulkan/Vulkan.hpp" +#include "Vulkan/DebugUtilities.hpp" #include "Vulkan/RenderingPipeline.hpp" #include #include diff --git a/src/Runtime/Subsystems/NextPhysics.h b/src/Runtime/Subsystems/NextPhysics.h index a0bb62a3..0acf3873 100644 --- a/src/Runtime/Subsystems/NextPhysics.h +++ b/src/Runtime/Subsystems/NextPhysics.h @@ -4,7 +4,7 @@ #include #include -#include "Vulkan/Vulkan.hpp" +#include "Vulkan/DebugUtilities.hpp" #include "Runtime/Subsystems/NextPhysicsTypes.h" struct FNextPhysicsContext; diff --git a/src/Vulkan/CommandExecution.hpp b/src/Vulkan/CommandExecution.hpp index 10bdc053..7d4138e0 100644 --- a/src/Vulkan/CommandExecution.hpp +++ b/src/Vulkan/CommandExecution.hpp @@ -1,6 +1,6 @@ #pragma once -#include "Vulkan.hpp" +#include "DebugUtilities.hpp" #include #include diff --git a/src/Vulkan/DebugUtilities.cpp b/src/Vulkan/DebugUtilities.cpp new file mode 100644 index 00000000..898fcb2b --- /dev/null +++ b/src/Vulkan/DebugUtilities.cpp @@ -0,0 +1,358 @@ +#include "DebugUtilities.hpp" +#include "Instance.hpp" +#include "Utilities/Exception.hpp" +#include "Common/CoreMinimal.hpp" +#include + +#ifdef VK_USE_PLATFORM_WIN32_KHR +# include +# include +#endif + +namespace Vulkan { + +// ============================================================================ +// Core Vulkan Utilities +// ============================================================================ + +void Check(const VkResult result, const char* const operation) +{ + if (result != VK_SUCCESS) + { + Throw(std::runtime_error(std::string("failed to ") + operation + " (" + ToString(result) + ")")); + } +} + +const char* ToString(const VkResult result) +{ + switch (result) + { +#define STR(r) case VK_ ##r: return #r + STR(SUCCESS); + STR(NOT_READY); + STR(TIMEOUT); + STR(EVENT_SET); + STR(EVENT_RESET); + STR(INCOMPLETE); + STR(ERROR_OUT_OF_HOST_MEMORY); + STR(ERROR_OUT_OF_DEVICE_MEMORY); + STR(ERROR_INITIALIZATION_FAILED); + STR(ERROR_DEVICE_LOST); + STR(ERROR_MEMORY_MAP_FAILED); + STR(ERROR_LAYER_NOT_PRESENT); + STR(ERROR_EXTENSION_NOT_PRESENT); + STR(ERROR_FEATURE_NOT_PRESENT); + STR(ERROR_INCOMPATIBLE_DRIVER); + STR(ERROR_TOO_MANY_OBJECTS); + STR(ERROR_FORMAT_NOT_SUPPORTED); + STR(ERROR_FRAGMENTED_POOL); + STR(ERROR_UNKNOWN); + STR(ERROR_OUT_OF_POOL_MEMORY); + STR(ERROR_INVALID_EXTERNAL_HANDLE); + STR(ERROR_FRAGMENTATION); + STR(ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS); + STR(ERROR_SURFACE_LOST_KHR); + STR(ERROR_NATIVE_WINDOW_IN_USE_KHR); + STR(SUBOPTIMAL_KHR); + STR(ERROR_OUT_OF_DATE_KHR); + STR(ERROR_INCOMPATIBLE_DISPLAY_KHR); + STR(ERROR_VALIDATION_FAILED_EXT); + STR(ERROR_INVALID_SHADER_NV); + STR(ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT); + STR(ERROR_NOT_PERMITTED_EXT); + STR(ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT); + STR(THREAD_IDLE_KHR); + STR(THREAD_DONE_KHR); + STR(OPERATION_DEFERRED_KHR); + STR(OPERATION_NOT_DEFERRED_KHR); + STR(PIPELINE_COMPILE_REQUIRED_EXT); +#undef STR + default: + return "UNKNOWN_ERROR"; + } +} + +// ============================================================================ +// Strings +// ============================================================================ + +const char* Strings::DeviceType(const VkPhysicalDeviceType deviceType) +{ + switch (deviceType) + { + case VK_PHYSICAL_DEVICE_TYPE_OTHER: + return "Other"; + case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: + return "Integrated GPU"; + case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: + return "Discrete GPU"; + case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: + return "Virtual GPU"; + case VK_PHYSICAL_DEVICE_TYPE_CPU: + return "CPU"; + default: + return "UnknownDeviceType"; + } +} + +const char* Strings::VendorId(const uint32_t vendorId) +{ + switch (vendorId) + { + case 0x1002: + return "AMD"; + case 0x1010: + return "ImgTec"; + case 0x10DE: + return "NVIDIA"; + case 0x13B5: + return "ARM"; + case 0x5143: + return "Qualcomm"; + case 0x8086: + return "INTEL"; + default: + return "UnknownVendor"; + } +} + +// ============================================================================ +// DebugUtils +// ============================================================================ + +DebugUtils::DebugUtils(VkInstance instance) + : vkSetDebugUtilsObjectNameEXT_(reinterpret_cast(vkGetInstanceProcAddr(instance, "vkSetDebugUtilsObjectNameEXT"))) + , vkCmdBeginDebugUtilsLabelEXT_(reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdBeginDebugUtilsLabelEXT"))) + , vkCmdEndDebugUtilsLabelEXT_(reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdEndDebugUtilsLabelEXT"))) +{ +#if !ANDROID + if (vkSetDebugUtilsObjectNameEXT_ == nullptr) + { + Throw(std::runtime_error("failed to get address of 'vkSetDebugUtilsObjectNameEXT'")); + } + if (vkCmdBeginDebugUtilsLabelEXT_ == nullptr) + { + Throw(std::runtime_error("failed to get address of 'vkCmdBeginDebugUtilsLabelEXT'")); + } + if (vkCmdEndDebugUtilsLabelEXT_ == nullptr) + { + Throw(std::runtime_error("failed to get address of 'vkCmdEndDebugUtilsLabelEXT'")); + } +#endif +} + +// ============================================================================ +// DebugUtilsMessenger +// ============================================================================ + +namespace +{ + + const char* ObjectTypeToString(const VkObjectType objectType) + { + switch (objectType) + { +#define STR(e) case VK_OBJECT_TYPE_ ## e: return # e + STR(UNKNOWN); + STR(INSTANCE); + STR(PHYSICAL_DEVICE); + STR(DEVICE); + STR(QUEUE); + STR(SEMAPHORE); + STR(COMMAND_BUFFER); + STR(FENCE); + STR(DEVICE_MEMORY); + STR(BUFFER); + STR(IMAGE); + STR(EVENT); + STR(QUERY_POOL); + STR(BUFFER_VIEW); + STR(IMAGE_VIEW); + STR(SHADER_MODULE); + STR(PIPELINE_CACHE); + STR(PIPELINE_LAYOUT); + STR(RENDER_PASS); + STR(PIPELINE); + STR(DESCRIPTOR_SET_LAYOUT); + STR(SAMPLER); + STR(DESCRIPTOR_POOL); + STR(DESCRIPTOR_SET); + STR(FRAMEBUFFER); + STR(COMMAND_POOL); + STR(SAMPLER_YCBCR_CONVERSION); + STR(DESCRIPTOR_UPDATE_TEMPLATE); + STR(SURFACE_KHR); + STR(SWAPCHAIN_KHR); + STR(DISPLAY_KHR); + STR(DISPLAY_MODE_KHR); + STR(DEBUG_REPORT_CALLBACK_EXT); + STR(DEBUG_UTILS_MESSENGER_EXT); + STR(ACCELERATION_STRUCTURE_KHR); + STR(VALIDATION_CACHE_EXT); + STR(PERFORMANCE_CONFIGURATION_INTEL); + STR(DEFERRED_OPERATION_KHR); + STR(INDIRECT_COMMANDS_LAYOUT_NV); +#undef STR + default: return "unknown"; + } + } + + VKAPI_ATTR VkBool32 VKAPI_CALL VulkanDebugCallback( + const VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + const VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* const pCallbackData, + void* const pUserData) + { + (void)pUserData; + + // Build complete message in one go + std::string message; + + // Add severity prefix + switch (messageSeverity) + { + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: + message += "VERBOSE: "; + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: + message += "INFO: "; + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: + message += "WARNING: "; + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: + message += "ERROR: "; + break; + default: + message += "UNKNOWN: "; + } + + // Add message type + switch (messageType) + { + case VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT: + message += "GENERAL: "; + break; + case VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT: + message += "VALIDATION: "; + break; + case VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT: + message += "PERFORMANCE: "; + break; + default: + message += "UNKNOWN: "; + } + + // Add main message + message += pCallbackData->pMessage; + + // Add object information if present and severity is high enough + if (pCallbackData->objectCount > 0 && messageSeverity > VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) + { + message += "\n\n Objects ("; + message += std::to_string(pCallbackData->objectCount); + message += "):"; + + for (uint32_t i = 0; i != pCallbackData->objectCount; ++i) + { + const auto object = pCallbackData->pObjects[i]; + message += "\n - Object: Type: "; + message += ObjectTypeToString(object.objectType); + message += ", Handle: "; + // Convert handle to hex string for better readability + char handleStr[20]; + snprintf(handleStr, sizeof(handleStr), "%p", reinterpret_cast(object.objectHandle)); + message += handleStr; + message += ", Name: '"; + message += (object.pObjectName ? object.pObjectName : ""); + message += "'"; + } + } + + // Log the complete message based on severity + switch (messageSeverity) + { + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: + SPDLOG_TRACE("{}", message); + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: + SPDLOG_INFO("{}", message); + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: + SPDLOG_WARN("{}", message); + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: + SPDLOG_ERROR("{}", message); + break; + default: + SPDLOG_WARN("{}", message); + } + + + return VK_FALSE; + } + + VkResult CreateDebugUtilsMessengerExt(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pCallback) + { + const auto func = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT")); + return func != nullptr + ? func(instance, pCreateInfo, pAllocator, pCallback) + : VK_ERROR_EXTENSION_NOT_PRESENT; + } + + void DestroyDebugUtilsMessengerExt(VkInstance instance, VkDebugUtilsMessengerEXT callback, const VkAllocationCallbacks* pAllocator) + { + const auto func = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT")); + if (func != nullptr) { + func(instance, callback, pAllocator); + } + } +} + +DebugUtilsMessenger::DebugUtilsMessenger(const Instance& instance, VkDebugUtilsMessageSeverityFlagBitsEXT threshold) : + instance_(instance), + threshold_(threshold) +{ + if (instance.ValidationLayers().empty()) + { + return; + } + + VkDebugUtilsMessageSeverityFlagsEXT severity = 0; + + switch (threshold) + { + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: + severity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: + severity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: + severity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: + severity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + break; + default: + Throw(std::invalid_argument("invalid threshold")); + } + + VkDebugUtilsMessengerCreateInfoEXT createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = severity; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = VulkanDebugCallback; + createInfo.pUserData = nullptr; + + Check(CreateDebugUtilsMessengerExt(instance_.Handle(), &createInfo, nullptr, &messenger_), + "set up Vulkan debug callback"); +} + +DebugUtilsMessenger::~DebugUtilsMessenger() +{ + if (messenger_ != nullptr) + { + DestroyDebugUtilsMessengerExt(instance_.Handle(), messenger_, nullptr); + messenger_ = nullptr; + } +} + +} diff --git a/src/Vulkan/DebugUtilities.hpp b/src/Vulkan/DebugUtilities.hpp new file mode 100644 index 00000000..b5dddf43 --- /dev/null +++ b/src/Vulkan/DebugUtilities.hpp @@ -0,0 +1,367 @@ +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include +#include +typedef SDL_Window Next_Window; +#include +#if ANDROID +#include +#endif +#undef APIENTRY + +#include +#include +#include + +#define DEFAULT_NON_COPIABLE(ClassName) \ + ClassName(const ClassName&) = delete; \ + ClassName(ClassName&&) = delete; \ + ClassName& operator = (const ClassName&) = delete; \ + ClassName& operator = (ClassName&&) = delete; \ + +#define VULKAN_NON_COPIABLE(ClassName) \ + ClassName(const ClassName&) = delete; \ + ClassName(ClassName&&) = delete; \ + ClassName& operator = (const ClassName&) = delete; \ + ClassName& operator = (ClassName&&) = delete; \ + static const char* StaticClass() {return #ClassName;} \ + virtual const char* GetActualClassName() const {return #ClassName;} + +#define VULKAN_HANDLE(VulkanHandleType, name) \ +public: \ + VulkanHandleType Handle() const { return name; } \ +protected: \ + VulkanHandleType name{}; + +namespace Vulkan +{ + // ============================================================================ + // Core Vulkan Utilities + // ============================================================================ + + void Check(VkResult result, const char* operation); + const char* ToString(VkResult result); + + // ============================================================================ + // Version + // ============================================================================ + + class Version final + { + public: + + explicit Version(const uint32_t version) : + Major(VK_VERSION_MAJOR(version)), + Minor(VK_VERSION_MINOR(version)), + Patch(VK_VERSION_PATCH(version)) + { + } + + Version(const uint32_t version, const uint32_t vendorId) : + Major(VK_VERSION_MAJOR(version)), + Minor(VK_VERSION_MINOR(version) >> (vendorId == 0x10DE ? 2 : 0)), + Patch(VK_VERSION_PATCH(version) >> (vendorId == 0x10DE ? 4 : 0)) + { + // NVIDIA specific driver versioning. + // https://github.com/SaschaWillems/VulkanCapsViewer/blob/master/vulkanDeviceInfo.cpp + // 10 bits = major version (up to 1023) + // 8 bits = minor version (up to 255) + // 8 bits = secondary branch version/build version (up to 255) + // 6 bits = tertiary branch/build version (up to 63) + } + + const unsigned Major; + const unsigned Minor; + const unsigned Patch; + + friend const std::string to_string(const Version& version) + { + return fmt::format("{}.{}.{}", version.Major, version.Minor, version.Patch); + } + }; + + // ============================================================================ + // Strings + // ============================================================================ + + class Strings final + { + public: + + VULKAN_NON_COPIABLE(Strings) + + Strings() = delete; + ~Strings() = delete; + + static const char* DeviceType(VkPhysicalDeviceType deviceType); + static const char* VendorId(uint32_t vendorId); + }; + + // ============================================================================ + // Enumerate Templates + // ============================================================================ + + template + inline void GetEnumerateVector(VkResult(enumerate) (uint32_t*, TValue*), std::vector& vector) + { + uint32_t count = 0; + Check(enumerate(&count, nullptr), + "enumerate"); + + vector.resize(count); + Check(enumerate(&count, vector.data()), + "enumerate"); + } + + template + inline void GetEnumerateVector(THandle handle, void(enumerate) (THandle, uint32_t*, TValue*), std::vector& vector) + { + uint32_t count = 0; + enumerate(handle, &count, nullptr); + + vector.resize(count); + enumerate(handle, &count, vector.data()); + } + + template + inline void GetEnumerateVector(THandle handle, VkResult(enumerate) (THandle, uint32_t*, TValue*), std::vector& vector) + { + uint32_t count = 0; + Check(enumerate(handle, &count, nullptr), + "enumerate"); + + vector.resize(count); + Check(enumerate(handle, &count, vector.data()), + "enumerate"); + } + + template + inline void GetEnumerateVector(THandle1 handle1, THandle2 handle2, VkResult(enumerate) (THandle1, THandle2, uint32_t*, TValue*), std::vector& vector) + { + uint32_t count = 0; + Check(enumerate(handle1, handle2, &count, nullptr), + "enumerate"); + + vector.resize(count); + Check(enumerate(handle1, handle2, &count, vector.data()), + "enumerate"); + } + + template + inline std::vector GetEnumerateVector(VkResult(enumerate) (uint32_t*, TValue*)) + { + std::vector initial; + GetEnumerateVector(enumerate, initial); + return initial; + } + + template + inline std::vector GetEnumerateVector(THandle handle, void(enumerate) (THandle, uint32_t*, TValue*)) + { + std::vector initial; + GetEnumerateVector(handle, enumerate, initial); + return initial; + } + + template + inline std::vector GetEnumerateVector(THandle handle, VkResult(enumerate) (THandle, uint32_t*, TValue*)) + { + std::vector initial; + GetEnumerateVector(handle, enumerate, initial); + return initial; + } + + template + inline std::vector GetEnumerateVector(THandle1 handle1, THandle2 handle2, VkResult(enumerate) (THandle1, THandle2, uint32_t*, TValue*)) + { + std::vector initial; + GetEnumerateVector(handle1, handle2, enumerate, initial); + return initial; + } + + // ============================================================================ + // ImageMemoryBarrier + // ============================================================================ + + class ImageMemoryBarrier final + { + public: + + static void Insert( + const VkCommandBuffer commandBuffer, + const VkImage image, + const VkImageSubresourceRange subresourceRange, + const VkAccessFlags srcAccessMask, + const VkAccessFlags dstAccessMask, + const VkImageLayout oldLayout, + const VkImageLayout newLayout) + { + VkImageMemoryBarrier barrier; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.pNext = nullptr; + barrier.srcAccessMask = srcAccessMask; + barrier.dstAccessMask = dstAccessMask; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange = subresourceRange; + + vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, + &barrier); + } + + static void FullInsert( + const VkCommandBuffer commandBuffer, + const VkImage image, + const VkAccessFlags srcAccessMask, + const VkAccessFlags dstAccessMask, + const VkImageLayout oldLayout, + const VkImageLayout newLayout) + { + VkImageSubresourceRange subresourceRange = {}; + subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + subresourceRange.baseMipLevel = 0; + subresourceRange.levelCount = 1; + subresourceRange.baseArrayLayer = 0; + subresourceRange.layerCount = 1; + Insert(commandBuffer, image, subresourceRange, srcAccessMask, dstAccessMask, oldLayout, newLayout); + } + }; + + class BufferMemoryBarrier final + { + public: + static void Insert(const VkCommandBuffer commandBuffer, const VkBuffer buffer) + { + VkBufferMemoryBarrier bufferBarrier = {}; + bufferBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; + bufferBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + bufferBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + bufferBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufferBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufferBarrier.buffer = buffer; + bufferBarrier.offset = 0; + bufferBarrier.size = VK_WHOLE_SIZE; + + vkCmdPipelineBarrier( + commandBuffer, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + 0, + 0, nullptr, + 1, &bufferBarrier, + 0, nullptr + ); + } + }; + + // ============================================================================ + // DebugUtils + // ============================================================================ + + class DebugUtils final + { + public: + + VULKAN_NON_COPIABLE(DebugUtils) + + explicit DebugUtils(VkInstance instance); + ~DebugUtils() = default; + + void SetDevice(VkDevice device) + { + device_ = device; + } + + void SetObjectName(const VkAccelerationStructureKHR& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR); } + void SetObjectName(const VkBuffer& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_BUFFER); } + void SetObjectName(const VkCommandBuffer& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_COMMAND_BUFFER); } + void SetObjectName(const VkDescriptorSet& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_DESCRIPTOR_SET); } + void SetObjectName(const VkDescriptorSetLayout& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT); } + void SetObjectName(const VkDeviceMemory& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_DEVICE_MEMORY); } + void SetObjectName(const VkFramebuffer& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_FRAMEBUFFER); } + void SetObjectName(const VkImage& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_IMAGE); } + void SetObjectName(const VkImageView& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_IMAGE_VIEW); } + void SetObjectName(const VkPipeline& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_PIPELINE); } + void SetObjectName(const VkQueue& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_QUEUE); } + void SetObjectName(const VkRenderPass& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_RENDER_PASS); } + void SetObjectName(const VkSemaphore& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_SEMAPHORE); } + void SetObjectName(const VkShaderModule& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_SHADER_MODULE); } + void SetObjectName(const VkSwapchainKHR& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_SWAPCHAIN_KHR); } + void SetObjectName(const VkPipelineLayout& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_PIPELINE_LAYOUT); } + + void BeginMarker(VkCommandBuffer commandBuffer, const char* name) const + { +#if !ANDROID + VkDebugUtilsLabelEXT label = {}; + label.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; + label.pLabelName = name; + + vkCmdBeginDebugUtilsLabelEXT_(commandBuffer, &label); +#endif + } + + void EndMarker(VkCommandBuffer commandBuffer) const + { +#if !ANDROID + vkCmdEndDebugUtilsLabelEXT_(commandBuffer); +#endif + } + + private: + + template + void SetObjectName(const T& object, const char* name, VkObjectType type) const + { +#if !ANDROID + VkDebugUtilsObjectNameInfoEXT info = {}; + info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT; + info.pNext = nullptr; + info.objectHandle = reinterpret_cast(object); + info.objectType = type; + info.pObjectName = name; + + Check(vkSetDebugUtilsObjectNameEXT_(device_, &info), "set object name"); +#endif + } + + const PFN_vkSetDebugUtilsObjectNameEXT vkSetDebugUtilsObjectNameEXT_; + const PFN_vkCmdBeginDebugUtilsLabelEXT vkCmdBeginDebugUtilsLabelEXT_; + const PFN_vkCmdEndDebugUtilsLabelEXT vkCmdEndDebugUtilsLabelEXT_; + + VkDevice device_{}; + }; + + // ============================================================================ + // DebugUtilsMessenger + // ============================================================================ + + class Instance; + + class DebugUtilsMessenger final + { + public: + + VULKAN_NON_COPIABLE(DebugUtilsMessenger) + + DebugUtilsMessenger(const Instance& instance, VkDebugUtilsMessageSeverityFlagBitsEXT threshold); + ~DebugUtilsMessenger(); + + VkDebugUtilsMessageSeverityFlagBitsEXT Threshold() const { return threshold_; } + + private: + + const Instance& instance_; + const VkDebugUtilsMessageSeverityFlagBitsEXT threshold_; + + VULKAN_HANDLE(VkDebugUtilsMessengerEXT, messenger_) + }; + +} diff --git a/src/Vulkan/DebugUtils.cpp b/src/Vulkan/DebugUtils.cpp deleted file mode 100644 index 5cfffc72..00000000 --- a/src/Vulkan/DebugUtils.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "DebugUtils.hpp" -#include "Utilities/Exception.hpp" - -namespace Vulkan { - -DebugUtils::DebugUtils(VkInstance instance) - : vkSetDebugUtilsObjectNameEXT_(reinterpret_cast(vkGetInstanceProcAddr(instance, "vkSetDebugUtilsObjectNameEXT"))) - , vkCmdBeginDebugUtilsLabelEXT_(reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdBeginDebugUtilsLabelEXT"))) - , vkCmdEndDebugUtilsLabelEXT_(reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdEndDebugUtilsLabelEXT"))) -{ -#if !ANDROID - if (vkSetDebugUtilsObjectNameEXT_ == nullptr) - { - Throw(std::runtime_error("failed to get address of 'vkSetDebugUtilsObjectNameEXT'")); - } - if (vkCmdBeginDebugUtilsLabelEXT_ == nullptr) - { - Throw(std::runtime_error("failed to get address of 'vkCmdBeginDebugUtilsLabelEXT'")); - } - if (vkCmdEndDebugUtilsLabelEXT_ == nullptr) - { - Throw(std::runtime_error("failed to get address of 'vkCmdEndDebugUtilsLabelEXT'")); - } -#endif -} - -} \ No newline at end of file diff --git a/src/Vulkan/DebugUtils.hpp b/src/Vulkan/DebugUtils.hpp deleted file mode 100644 index 0cbc5b0a..00000000 --- a/src/Vulkan/DebugUtils.hpp +++ /dev/null @@ -1,83 +0,0 @@ -#pragma once - -#include "Vulkan/Vulkan.hpp" - -namespace Vulkan -{ - class DebugUtils final - { - public: - - VULKAN_NON_COPIABLE(DebugUtils) - - explicit DebugUtils(VkInstance instance); - ~DebugUtils() = default; - - - void SetDevice(VkDevice device) - { - device_ = device; - } - - void SetObjectName(const VkAccelerationStructureKHR& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR); } - void SetObjectName(const VkBuffer& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_BUFFER); } - void SetObjectName(const VkCommandBuffer& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_COMMAND_BUFFER); } - void SetObjectName(const VkDescriptorSet& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_DESCRIPTOR_SET); } - void SetObjectName(const VkDescriptorSetLayout& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT); } - void SetObjectName(const VkDeviceMemory& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_DEVICE_MEMORY); } - void SetObjectName(const VkFramebuffer& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_FRAMEBUFFER); } - void SetObjectName(const VkImage& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_IMAGE); } - void SetObjectName(const VkImageView& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_IMAGE_VIEW); } - void SetObjectName(const VkPipeline& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_PIPELINE); } - void SetObjectName(const VkQueue& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_QUEUE); } - void SetObjectName(const VkRenderPass& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_RENDER_PASS); } - void SetObjectName(const VkSemaphore& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_SEMAPHORE); } - void SetObjectName(const VkShaderModule& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_SHADER_MODULE); } - void SetObjectName(const VkSwapchainKHR& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_SWAPCHAIN_KHR); } - void SetObjectName(const VkPipelineLayout& object, const char* name) const { SetObjectName(object, name, VK_OBJECT_TYPE_PIPELINE_LAYOUT); } - - void BeginMarker(VkCommandBuffer commandBuffer, const char* name) const - { -#if !ANDROID - VkDebugUtilsLabelEXT label = {}; - label.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; - label.pLabelName = name; - - vkCmdBeginDebugUtilsLabelEXT_(commandBuffer, &label); -#endif - } - - void EndMarker(VkCommandBuffer commandBuffer) const - { -#if !ANDROID - vkCmdEndDebugUtilsLabelEXT_(commandBuffer); -#endif - } - - private: - - template - void SetObjectName(const T& object, const char* name, VkObjectType type) const - { -#if !ANDROID - VkDebugUtilsObjectNameInfoEXT info = {}; - info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT; - info.pNext = nullptr; - info.objectHandle = reinterpret_cast(object); - info.objectType = type; - info.pObjectName = name; - - Check(vkSetDebugUtilsObjectNameEXT_(device_, &info), "set object name"); -#endif - } - - - - const PFN_vkSetDebugUtilsObjectNameEXT vkSetDebugUtilsObjectNameEXT_; - const PFN_vkCmdBeginDebugUtilsLabelEXT vkCmdBeginDebugUtilsLabelEXT_; - const PFN_vkCmdEndDebugUtilsLabelEXT vkCmdEndDebugUtilsLabelEXT_; - - VkDevice device_{}; - }; - -} diff --git a/src/Vulkan/DebugUtilsMessenger.cpp b/src/Vulkan/DebugUtilsMessenger.cpp deleted file mode 100644 index 69f62d4c..00000000 --- a/src/Vulkan/DebugUtilsMessenger.cpp +++ /dev/null @@ -1,220 +0,0 @@ -#include "DebugUtilsMessenger.hpp" - -#include "Instance.hpp" -#include "Utilities/Exception.hpp" -#include "Common/CoreMinimal.hpp" -#include - -namespace Vulkan { - - namespace - { - - const char* ObjectTypeToString(const VkObjectType objectType) - { - switch (objectType) - { -#define STR(e) case VK_OBJECT_TYPE_ ## e: return # e - STR(UNKNOWN); - STR(INSTANCE); - STR(PHYSICAL_DEVICE); - STR(DEVICE); - STR(QUEUE); - STR(SEMAPHORE); - STR(COMMAND_BUFFER); - STR(FENCE); - STR(DEVICE_MEMORY); - STR(BUFFER); - STR(IMAGE); - STR(EVENT); - STR(QUERY_POOL); - STR(BUFFER_VIEW); - STR(IMAGE_VIEW); - STR(SHADER_MODULE); - STR(PIPELINE_CACHE); - STR(PIPELINE_LAYOUT); - STR(RENDER_PASS); - STR(PIPELINE); - STR(DESCRIPTOR_SET_LAYOUT); - STR(SAMPLER); - STR(DESCRIPTOR_POOL); - STR(DESCRIPTOR_SET); - STR(FRAMEBUFFER); - STR(COMMAND_POOL); - STR(SAMPLER_YCBCR_CONVERSION); - STR(DESCRIPTOR_UPDATE_TEMPLATE); - STR(SURFACE_KHR); - STR(SWAPCHAIN_KHR); - STR(DISPLAY_KHR); - STR(DISPLAY_MODE_KHR); - STR(DEBUG_REPORT_CALLBACK_EXT); - STR(DEBUG_UTILS_MESSENGER_EXT); - STR(ACCELERATION_STRUCTURE_KHR); - STR(VALIDATION_CACHE_EXT); - STR(PERFORMANCE_CONFIGURATION_INTEL); - STR(DEFERRED_OPERATION_KHR); - STR(INDIRECT_COMMANDS_LAYOUT_NV); -#undef STR - default: return "unknown"; - } - } - - VKAPI_ATTR VkBool32 VKAPI_CALL VulkanDebugCallback( - const VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, - const VkDebugUtilsMessageTypeFlagsEXT messageType, - const VkDebugUtilsMessengerCallbackDataEXT* const pCallbackData, - void* const pUserData) - { - (void)pUserData; - - // Build complete message in one go - std::string message; - - // Add severity prefix - switch (messageSeverity) - { - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: - message += "VERBOSE: "; - break; - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: - message += "INFO: "; - break; - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: - message += "WARNING: "; - break; - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: - message += "ERROR: "; - break; - default: - message += "UNKNOWN: "; - } - - // Add message type - switch (messageType) - { - case VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT: - message += "GENERAL: "; - break; - case VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT: - message += "VALIDATION: "; - break; - case VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT: - message += "PERFORMANCE: "; - break; - default: - message += "UNKNOWN: "; - } - - // Add main message - message += pCallbackData->pMessage; - - // Add object information if present and severity is high enough - if (pCallbackData->objectCount > 0 && messageSeverity > VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) - { - message += "\n\n Objects ("; - message += std::to_string(pCallbackData->objectCount); - message += "):"; - - for (uint32_t i = 0; i != pCallbackData->objectCount; ++i) - { - const auto object = pCallbackData->pObjects[i]; - message += "\n - Object: Type: "; - message += ObjectTypeToString(object.objectType); - message += ", Handle: "; - // Convert handle to hex string for better readability - char handleStr[20]; - snprintf(handleStr, sizeof(handleStr), "%p", reinterpret_cast(object.objectHandle)); - message += handleStr; - message += ", Name: '"; - message += (object.pObjectName ? object.pObjectName : ""); - message += "'"; - } - } - - // Log the complete message based on severity - switch (messageSeverity) - { - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: - SPDLOG_TRACE("{}", message); - break; - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: - SPDLOG_INFO("{}", message); - break; - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: - SPDLOG_WARN("{}", message); - break; - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: - SPDLOG_ERROR("{}", message); - break; - default: - SPDLOG_WARN("{}", message); - } - - - return VK_FALSE; - } - - VkResult CreateDebugUtilsMessengerExt(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pCallback) - { - const auto func = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT")); - return func != nullptr - ? func(instance, pCreateInfo, pAllocator, pCallback) - : VK_ERROR_EXTENSION_NOT_PRESENT; - } - - void DestroyDebugUtilsMessengerExt(VkInstance instance, VkDebugUtilsMessengerEXT callback, const VkAllocationCallbacks* pAllocator) - { - const auto func = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT")); - if (func != nullptr) { - func(instance, callback, pAllocator); - } - } - } - -DebugUtilsMessenger::DebugUtilsMessenger(const Instance& instance, VkDebugUtilsMessageSeverityFlagBitsEXT threshold) : - instance_(instance), - threshold_(threshold) -{ - if (instance.ValidationLayers().empty()) - { - return; - } - - VkDebugUtilsMessageSeverityFlagsEXT severity = 0; - - switch (threshold) - { - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: - severity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT; - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: - severity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT; - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: - severity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT; - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: - severity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; - break; - default: - Throw(std::invalid_argument("invalid threshold")); - } - - VkDebugUtilsMessengerCreateInfoEXT createInfo = {}; - createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; - createInfo.messageSeverity = severity; - createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; - createInfo.pfnUserCallback = VulkanDebugCallback; - createInfo.pUserData = nullptr; - - Check(CreateDebugUtilsMessengerExt(instance_.Handle(), &createInfo, nullptr, &messenger_), - "set up Vulkan debug callback"); -} - -DebugUtilsMessenger::~DebugUtilsMessenger() -{ - if (messenger_ != nullptr) - { - DestroyDebugUtilsMessengerExt(instance_.Handle(), messenger_, nullptr); - messenger_ = nullptr; - } -} - -} diff --git a/src/Vulkan/DebugUtilsMessenger.hpp b/src/Vulkan/DebugUtilsMessenger.hpp deleted file mode 100644 index 798e068c..00000000 --- a/src/Vulkan/DebugUtilsMessenger.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include "Vulkan.hpp" - -namespace Vulkan -{ - class Instance; - - class DebugUtilsMessenger final - { - public: - - VULKAN_NON_COPIABLE(DebugUtilsMessenger) - - DebugUtilsMessenger(const Instance& instance, VkDebugUtilsMessageSeverityFlagBitsEXT threshold); - ~DebugUtilsMessenger(); - - VkDebugUtilsMessageSeverityFlagBitsEXT Threshold() const { return threshold_; } - - private: - - const Instance& instance_; - const VkDebugUtilsMessageSeverityFlagBitsEXT threshold_; - - VULKAN_HANDLE(VkDebugUtilsMessengerEXT, messenger_) - }; - -} diff --git a/src/Vulkan/DescriptorSystem.hpp b/src/Vulkan/DescriptorSystem.hpp index 8f093514..b6ba7238 100644 --- a/src/Vulkan/DescriptorSystem.hpp +++ b/src/Vulkan/DescriptorSystem.hpp @@ -1,6 +1,6 @@ #pragma once -#include "Vulkan.hpp" +#include "DebugUtilities.hpp" #include #include #include diff --git a/src/Vulkan/Device.cpp b/src/Vulkan/Device.cpp index adf223e5..a8aed9bb 100644 --- a/src/Vulkan/Device.cpp +++ b/src/Vulkan/Device.cpp @@ -1,5 +1,5 @@ #include "Device.hpp" -#include "Enumerate.hpp" +#include "DebugUtilities.hpp" #include "Instance.hpp" #include "WindowSurface.hpp" #include "Utilities/Exception.hpp" diff --git a/src/Vulkan/Device.hpp b/src/Vulkan/Device.hpp index b7767b93..927a845b 100644 --- a/src/Vulkan/Device.hpp +++ b/src/Vulkan/Device.hpp @@ -1,11 +1,10 @@ #pragma once #include - -#include "DebugUtils.hpp" -#include "Vulkan.hpp" #include +#include "DebugUtilities.hpp" + namespace Vulkan { class Surface; diff --git a/src/Vulkan/Enumerate.hpp b/src/Vulkan/Enumerate.hpp deleted file mode 100644 index 760cefb2..00000000 --- a/src/Vulkan/Enumerate.hpp +++ /dev/null @@ -1,86 +0,0 @@ -#pragma once - -#include "Vulkan.hpp" -#include - -namespace Vulkan { - -template -inline void GetEnumerateVector(VkResult(enumerate) (uint32_t*, TValue*), std::vector& vector) -{ - uint32_t count = 0; - Check(enumerate(&count, nullptr), - "enumerate"); - - vector.resize(count); - Check(enumerate(&count, vector.data()), - "enumerate"); -} - -template -inline void GetEnumerateVector(THandle handle, void(enumerate) (THandle, uint32_t*, TValue*), std::vector& vector) -{ - uint32_t count = 0; - enumerate(handle, &count, nullptr); - - vector.resize(count); - enumerate(handle, &count, vector.data()); -} - -template -inline void GetEnumerateVector(THandle handle, VkResult(enumerate) (THandle, uint32_t*, TValue*), std::vector& vector) -{ - uint32_t count = 0; - Check(enumerate(handle, &count, nullptr), - "enumerate"); - - vector.resize(count); - Check(enumerate(handle, &count, vector.data()), - "enumerate"); -} - -template -inline void GetEnumerateVector(THandle1 handle1, THandle2 handle2, VkResult(enumerate) (THandle1, THandle2, uint32_t*, TValue*), std::vector& vector) -{ - uint32_t count = 0; - Check(enumerate(handle1, handle2, &count, nullptr), - "enumerate"); - - vector.resize(count); - Check(enumerate(handle1, handle2, &count, vector.data()), - "enumerate"); -} - -template -inline std::vector GetEnumerateVector(VkResult(enumerate) (uint32_t*, TValue*)) -{ - std::vector initial; - GetEnumerateVector(enumerate, initial); - return initial; -} - -template -inline std::vector GetEnumerateVector(THandle handle, void(enumerate) (THandle, uint32_t*, TValue*)) -{ - std::vector initial; - GetEnumerateVector(handle, enumerate, initial); - return initial; -} - -template -inline std::vector GetEnumerateVector(THandle handle, VkResult(enumerate) (THandle, uint32_t*, TValue*)) -{ - std::vector initial; - GetEnumerateVector(handle, enumerate, initial); - return initial; -} - -template -inline std::vector GetEnumerateVector(THandle1 handle1, THandle2 handle2, VkResult(enumerate) (THandle1, THandle2, uint32_t*, TValue*)) -{ - std::vector initial; - GetEnumerateVector(handle1, handle2, enumerate, initial); - return initial; -} - -} diff --git a/src/Vulkan/GpuResources.cpp b/src/Vulkan/GpuResources.cpp index 7e4b5912..d68707bc 100644 --- a/src/Vulkan/GpuResources.cpp +++ b/src/Vulkan/GpuResources.cpp @@ -1,7 +1,7 @@ #include "GpuResources.hpp" #include "Device.hpp" #include "CommandExecution.hpp" -#include "ImageMemoryBarrier.hpp" +#include "DebugUtilities.hpp" #include "Utilities/Exception.hpp" #include "Vulkan/RayTracing/DeviceProcedures.hpp" diff --git a/src/Vulkan/GpuResources.hpp b/src/Vulkan/GpuResources.hpp index ee387a0d..c152d64f 100644 --- a/src/Vulkan/GpuResources.hpp +++ b/src/Vulkan/GpuResources.hpp @@ -1,6 +1,6 @@ #pragma once -#include "Vulkan.hpp" +#include "DebugUtilities.hpp" #include "MemoryAndShader.hpp" #include "MemoryAndShader.hpp" #include diff --git a/src/Vulkan/ImageMemoryBarrier.hpp b/src/Vulkan/ImageMemoryBarrier.hpp deleted file mode 100644 index 520ad9bb..00000000 --- a/src/Vulkan/ImageMemoryBarrier.hpp +++ /dev/null @@ -1,82 +0,0 @@ -#pragma once - -#include "Vulkan.hpp" - -namespace Vulkan -{ - class ImageMemoryBarrier final - { - public: - - static void Insert( - const VkCommandBuffer commandBuffer, - const VkImage image, - const VkImageSubresourceRange subresourceRange, - const VkAccessFlags srcAccessMask, - const VkAccessFlags dstAccessMask, - const VkImageLayout oldLayout, - const VkImageLayout newLayout) - { - VkImageMemoryBarrier barrier; - barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - barrier.pNext = nullptr; - barrier.srcAccessMask = srcAccessMask; - barrier.dstAccessMask = dstAccessMask; - barrier.oldLayout = oldLayout; - barrier.newLayout = newLayout; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.image = image; - barrier.subresourceRange = subresourceRange; - - vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, - &barrier); - } - - static void FullInsert( - const VkCommandBuffer commandBuffer, - const VkImage image, - const VkAccessFlags srcAccessMask, - const VkAccessFlags dstAccessMask, - const VkImageLayout oldLayout, - const VkImageLayout newLayout) - { - VkImageSubresourceRange subresourceRange = {}; - subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - subresourceRange.baseMipLevel = 0; - subresourceRange.levelCount = 1; - subresourceRange.baseArrayLayer = 0; - subresourceRange.layerCount = 1; - Insert(commandBuffer, image, subresourceRange, srcAccessMask, dstAccessMask, oldLayout, newLayout); - } - }; - - class BufferMemoryBarrier final - { - public: - static void Insert(const VkCommandBuffer commandBuffer, const VkBuffer buffer) - { - VkBufferMemoryBarrier bufferBarrier = {}; - bufferBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; - bufferBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - bufferBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - bufferBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - bufferBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - bufferBarrier.buffer = buffer; - bufferBarrier.offset = 0; - bufferBarrier.size = VK_WHOLE_SIZE; - - vkCmdPipelineBarrier( - commandBuffer, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - 0, - 0, nullptr, - 1, &bufferBarrier, - 0, nullptr - ); - } - }; - -} diff --git a/src/Vulkan/Instance.cpp b/src/Vulkan/Instance.cpp index 61287310..0dca66b8 100644 --- a/src/Vulkan/Instance.cpp +++ b/src/Vulkan/Instance.cpp @@ -1,6 +1,6 @@ #include "Instance.hpp" -#include "Enumerate.hpp" -#include "Version.hpp" +#include "DebugUtilities.hpp" +#include "DebugUtilities.hpp" #include "WindowSurface.hpp" #include "Utilities/Exception.hpp" #include diff --git a/src/Vulkan/Instance.hpp b/src/Vulkan/Instance.hpp index 2bcb91e3..ce3a90da 100644 --- a/src/Vulkan/Instance.hpp +++ b/src/Vulkan/Instance.hpp @@ -1,6 +1,6 @@ #pragma once -#include "Vulkan.hpp" +#include "DebugUtilities.hpp" #include namespace Vulkan diff --git a/src/Vulkan/MemoryAndShader.hpp b/src/Vulkan/MemoryAndShader.hpp index 38de08d2..af589ace 100644 --- a/src/Vulkan/MemoryAndShader.hpp +++ b/src/Vulkan/MemoryAndShader.hpp @@ -1,6 +1,6 @@ #pragma once -#include "Vulkan.hpp" +#include "DebugUtilities.hpp" #include #include diff --git a/src/Vulkan/RayTracing/AccelerationStructure.hpp b/src/Vulkan/RayTracing/AccelerationStructure.hpp index 52df59bf..3f35ee89 100644 --- a/src/Vulkan/RayTracing/AccelerationStructure.hpp +++ b/src/Vulkan/RayTracing/AccelerationStructure.hpp @@ -1,6 +1,6 @@ #pragma once -#include "Vulkan/Vulkan.hpp" +#include "Vulkan/DebugUtilities.hpp" namespace Vulkan { diff --git a/src/Vulkan/RayTracing/BottomLevelGeometry.hpp b/src/Vulkan/RayTracing/BottomLevelGeometry.hpp index dff6fe26..95da0345 100644 --- a/src/Vulkan/RayTracing/BottomLevelGeometry.hpp +++ b/src/Vulkan/RayTracing/BottomLevelGeometry.hpp @@ -1,6 +1,6 @@ #pragma once -#include "Vulkan/Vulkan.hpp" +#include "Vulkan/DebugUtilities.hpp" #include namespace Assets diff --git a/src/Vulkan/RayTracing/DeviceProcedures.hpp b/src/Vulkan/RayTracing/DeviceProcedures.hpp index 4c4bec12..6cb29f07 100644 --- a/src/Vulkan/RayTracing/DeviceProcedures.hpp +++ b/src/Vulkan/RayTracing/DeviceProcedures.hpp @@ -1,6 +1,6 @@ #pragma once -#include "Vulkan/Vulkan.hpp" +#include "Vulkan/DebugUtilities.hpp" #include namespace Vulkan diff --git a/src/Vulkan/RayTracing/RayTracingProperties.hpp b/src/Vulkan/RayTracing/RayTracingProperties.hpp index 07af95ba..ccef8b98 100644 --- a/src/Vulkan/RayTracing/RayTracingProperties.hpp +++ b/src/Vulkan/RayTracing/RayTracingProperties.hpp @@ -1,6 +1,6 @@ #pragma once -#include "Vulkan/Vulkan.hpp" +#include "Vulkan/DebugUtilities.hpp" namespace Vulkan { diff --git a/src/Vulkan/RenderingPipeline.hpp b/src/Vulkan/RenderingPipeline.hpp index dd3d8089..3753f8f7 100644 --- a/src/Vulkan/RenderingPipeline.hpp +++ b/src/Vulkan/RenderingPipeline.hpp @@ -1,6 +1,6 @@ #pragma once -#include "Vulkan.hpp" +#include "DebugUtilities.hpp" #include "Device.hpp" #include "SwapChain.hpp" #include "DescriptorSystem.hpp" diff --git a/src/Vulkan/Strings.cpp b/src/Vulkan/Strings.cpp deleted file mode 100644 index 955ed0b0..00000000 --- a/src/Vulkan/Strings.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "Strings.hpp" - -namespace Vulkan { - -const char* Strings::DeviceType(const VkPhysicalDeviceType deviceType) -{ - switch (deviceType) - { - case VK_PHYSICAL_DEVICE_TYPE_OTHER: - return "Other"; - case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: - return "Integrated GPU"; - case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: - return "Discrete GPU"; - case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: - return "Virtual GPU"; - case VK_PHYSICAL_DEVICE_TYPE_CPU: - return "CPU"; - default: - return "UnknownDeviceType"; - } -} - -const char* Strings::VendorId(const uint32_t vendorId) -{ - switch (vendorId) - { - case 0x1002: - return "AMD"; - case 0x1010: - return "ImgTec"; - case 0x10DE: - return "NVIDIA"; - case 0x13B5: - return "ARM"; - case 0x5143: - return "Qualcomm"; - case 0x8086: - return "INTEL"; - default: - return "UnknownVendor"; - } -} - -} diff --git a/src/Vulkan/Strings.hpp b/src/Vulkan/Strings.hpp deleted file mode 100644 index 45db2aa4..00000000 --- a/src/Vulkan/Strings.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "Vulkan.hpp" - -namespace Vulkan -{ - - class Strings final - { - public: - - VULKAN_NON_COPIABLE(Strings) - - Strings() = delete; - ~Strings() = delete; - - static const char* DeviceType(VkPhysicalDeviceType deviceType); - static const char* VendorId(uint32_t vendorId); - }; - -} diff --git a/src/Vulkan/SwapChain.cpp b/src/Vulkan/SwapChain.cpp index 15d82b06..e11ae122 100644 --- a/src/Vulkan/SwapChain.cpp +++ b/src/Vulkan/SwapChain.cpp @@ -1,6 +1,6 @@ #include "SwapChain.hpp" #include "Device.hpp" -#include "Enumerate.hpp" +#include "DebugUtilities.hpp" #include "GpuResources.hpp" #include "Instance.hpp" #include "WindowSurface.hpp" @@ -9,7 +9,7 @@ #include #include -#include "ImageMemoryBarrier.hpp" +#include "DebugUtilities.hpp" float GAndroidMagicScale = 1.0f; diff --git a/src/Vulkan/SwapChain.hpp b/src/Vulkan/SwapChain.hpp index 7eb52539..163e13c1 100644 --- a/src/Vulkan/SwapChain.hpp +++ b/src/Vulkan/SwapChain.hpp @@ -1,6 +1,6 @@ #pragma once -#include "Vulkan.hpp" +#include "DebugUtilities.hpp" #include #include diff --git a/src/Vulkan/SyncAndTiming.hpp b/src/Vulkan/SyncAndTiming.hpp index 030efe33..3a9f0952 100644 --- a/src/Vulkan/SyncAndTiming.hpp +++ b/src/Vulkan/SyncAndTiming.hpp @@ -1,6 +1,6 @@ #pragma once -#include "Vulkan.hpp" +#include "DebugUtilities.hpp" #include "Device.hpp" #include "Options.hpp" #include diff --git a/src/Vulkan/Version.hpp b/src/Vulkan/Version.hpp deleted file mode 100644 index a043b5df..00000000 --- a/src/Vulkan/Version.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include "Vulkan.hpp" -#include - -namespace Vulkan -{ - - class Version final - { - public: - - explicit Version(const uint32_t version) : - Major(VK_VERSION_MAJOR(version)), - Minor(VK_VERSION_MINOR(version)), - Patch(VK_VERSION_PATCH(version)) - { - } - - Version(const uint32_t version, const uint32_t vendorId) : - Major(VK_VERSION_MAJOR(version)), - Minor(VK_VERSION_MINOR(version) >> (vendorId == 0x10DE ? 2 : 0)), - Patch(VK_VERSION_PATCH(version) >> (vendorId == 0x10DE ? 4 : 0)) - { - // NVIDIA specific driver versioning. - // https://github.com/SaschaWillems/VulkanCapsViewer/blob/master/vulkanDeviceInfo.cpp - // 10 bits = major version (up to 1023) - // 8 bits = minor version (up to 255) - // 8 bits = secondary branch version/build version (up to 255) - // 6 bits = tertiary branch/build version (up to 63) - } - - const unsigned Major; - const unsigned Minor; - const unsigned Patch; - - friend const std::string to_string(const Version& version) - { - return fmt::format("{}.{}.{}", version.Major, version.Minor, version.Patch); - } - }; - -} diff --git a/src/Vulkan/Vulkan.cpp b/src/Vulkan/Vulkan.cpp deleted file mode 100644 index 8c0f39be..00000000 --- a/src/Vulkan/Vulkan.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include "Vulkan.hpp" -#include "Utilities/Exception.hpp" -#include - -namespace Vulkan { - -void Check(const VkResult result, const char* const operation) -{ - if (result != VK_SUCCESS) - { - Throw(std::runtime_error(std::string("failed to ") + operation + " (" + ToString(result) + ")")); - } -} - -const char* ToString(const VkResult result) -{ - switch (result) - { -#define STR(r) case VK_ ##r: return #r - STR(SUCCESS); - STR(NOT_READY); - STR(TIMEOUT); - STR(EVENT_SET); - STR(EVENT_RESET); - STR(INCOMPLETE); - STR(ERROR_OUT_OF_HOST_MEMORY); - STR(ERROR_OUT_OF_DEVICE_MEMORY); - STR(ERROR_INITIALIZATION_FAILED); - STR(ERROR_DEVICE_LOST); - STR(ERROR_MEMORY_MAP_FAILED); - STR(ERROR_LAYER_NOT_PRESENT); - STR(ERROR_EXTENSION_NOT_PRESENT); - STR(ERROR_FEATURE_NOT_PRESENT); - STR(ERROR_INCOMPATIBLE_DRIVER); - STR(ERROR_TOO_MANY_OBJECTS); - STR(ERROR_FORMAT_NOT_SUPPORTED); - STR(ERROR_FRAGMENTED_POOL); - STR(ERROR_UNKNOWN); - STR(ERROR_OUT_OF_POOL_MEMORY); - STR(ERROR_INVALID_EXTERNAL_HANDLE); - STR(ERROR_FRAGMENTATION); - STR(ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS); - STR(ERROR_SURFACE_LOST_KHR); - STR(ERROR_NATIVE_WINDOW_IN_USE_KHR); - STR(SUBOPTIMAL_KHR); - STR(ERROR_OUT_OF_DATE_KHR); - STR(ERROR_INCOMPATIBLE_DISPLAY_KHR); - STR(ERROR_VALIDATION_FAILED_EXT); - STR(ERROR_INVALID_SHADER_NV); - STR(ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT); - STR(ERROR_NOT_PERMITTED_EXT); - STR(ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT); - STR(THREAD_IDLE_KHR); - STR(THREAD_DONE_KHR); - STR(OPERATION_DEFERRED_KHR); - STR(OPERATION_NOT_DEFERRED_KHR); - STR(PIPELINE_COMPILE_REQUIRED_EXT); -#undef STR - default: - return "UNKNOWN_ERROR"; - } -} - -} diff --git a/src/Vulkan/Vulkan.hpp b/src/Vulkan/Vulkan.hpp deleted file mode 100644 index b63d2826..00000000 --- a/src/Vulkan/Vulkan.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#ifndef NOMINMAX -#define NOMINMAX -#endif - -#include -#include -typedef SDL_Window Next_Window; -#include -#if ANDROID -#include -//#include -#endif -#undef APIENTRY - -#define DEFAULT_NON_COPIABLE(ClassName) \ -ClassName(const ClassName&) = delete; \ -ClassName(ClassName&&) = delete; \ -ClassName& operator = (const ClassName&) = delete; \ -ClassName& operator = (ClassName&&) = delete; \ - -#define VULKAN_NON_COPIABLE(ClassName) \ - ClassName(const ClassName&) = delete; \ - ClassName(ClassName&&) = delete; \ - ClassName& operator = (const ClassName&) = delete; \ - ClassName& operator = (ClassName&&) = delete; \ - static const char* StaticClass() {return #ClassName;} \ - virtual const char* GetActualClassName() const {return #ClassName;} - -#define VULKAN_HANDLE(VulkanHandleType, name) \ -public: \ - VulkanHandleType Handle() const { return name; } \ -protected: \ - VulkanHandleType name{}; - -namespace Vulkan -{ - void Check(VkResult result, const char* operation); - const char* ToString(VkResult result); -} diff --git a/src/Vulkan/WindowSurface.hpp b/src/Vulkan/WindowSurface.hpp index 37c1420e..c2ec6549 100644 --- a/src/Vulkan/WindowSurface.hpp +++ b/src/Vulkan/WindowSurface.hpp @@ -1,6 +1,6 @@ #pragma once -#include "Vulkan.hpp" +#include "DebugUtilities.hpp" #include #include #include From 2cb206261ee4b47641bcab4dea6c148bd8ac41f1 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Tue, 3 Feb 2026 01:23:30 +0800 Subject: [PATCH 49/53] build: restore MinGW presets and tooling Align MinGW defaults and platform handling to keep MSYS2 builds working.\n\nCo-authored-by: gpt-5.2-codex --- CMakePresets.json | 52 ++++++++++++++++++++++++++-- README.en.md | 4 +-- README.md | 4 +-- build.sh | 5 ++- cmake/ProjectOptions.cmake | 1 + src/CMakeLists.txt | 19 ++++++++-- src/DesktopMain.cpp | 4 +++ src/Rendering/VulkanBaseRenderer.cpp | 2 +- src/Vulkan/GpuResources.cpp | 4 +-- 9 files changed, 83 insertions(+), 12 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index 53dd819d..3ab57bb3 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -57,7 +57,7 @@ "ENABLE_KTX2": "ON", "ENABLE_PHYSIC": "ON", "ENABLE_AUDIO": "ON", - "ENABLE_OIDN": "ON", + "ENABLE_OIDN": "OFF", "ENABLE_QUICKJS": "ON", "ENABLE_UNITY_BUILD": "ON" } @@ -140,6 +140,39 @@ "CMAKE_BUILD_TYPE": "RelWithDebInfo" } }, + { + "name": "mingw-base", + "hidden": true, + "inherits": "base", + "generator": "Ninja", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + }, + "cacheVariables": { + "VCPKG_TARGET_TRIPLET": "x64-mingw-static", + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } + }, + { + "name": "default-mingw", + "inherits": ["mingw-base", "features-default"], + "displayName": "Default MinGW", + "description": "Default MinGW configuration for development" + }, + { + "name": "minimal-mingw", + "inherits": ["mingw-base", "features-minimal"], + "displayName": "Minimal MinGW", + "description": "Minimal MinGW configuration for development" + }, + { + "name": "full-mingw", + "inherits": ["mingw-base", "features-full"], + "displayName": "Full MinGW", + "description": "Full MinGW configuration for development" + }, { "name": "macos-arm64-base", "inherits": "macos-base", @@ -254,6 +287,21 @@ "name": "full-ios", "configurePreset": "full-ios", "configuration": "RelWithDebInfo" + }, + { + "name": "default-mingw", + "configurePreset": "default-mingw", + "configuration": "RelWithDebInfo" + }, + { + "name": "minimal-mingw", + "configurePreset": "minimal-mingw", + "configuration": "RelWithDebInfo" + }, + { + "name": "full-mingw", + "configurePreset": "full-mingw", + "configuration": "RelWithDebInfo" } ] -} \ No newline at end of file +} diff --git a/README.en.md b/README.en.md index 5ba82449..362bba15 100644 --- a/README.en.md +++ b/README.en.md @@ -60,8 +60,8 @@ Windows (MSYS2 MinGW): ``` shell pacman -S --needed git mingw-w64-x86_64-ninja mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain ./vcpkg.sh -./build.sh --preset mingw -./run.sh --preset mingw +./build.sh --preset default-mingw +./run.sh --preset default-mingw ``` Linux (example: Ubuntu): diff --git a/README.md b/README.md index 5b987d0b..260b8634 100644 --- a/README.md +++ b/README.md @@ -65,8 +65,8 @@ Windows(MSYS2 MinGW): # 无需前置安装 pacman -S --needed git mingw-w64-x86_64-ninja mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain ./vcpkg.sh -./build.sh --preset mingw -./run.sh --preset mingw +./build.sh --preset default-mingw +./run.sh --preset default-mingw ``` Linux(示例:Ubuntu): diff --git a/build.sh b/build.sh index ce310c26..0f8efa30 100755 --- a/build.sh +++ b/build.sh @@ -21,6 +21,7 @@ set -euo pipefail # ./build.sh --preset default-linux -- -DENABLE_AVIF=ON # ./build.sh --preset default-linux --clean # ./build.sh --preset default-linux --reconfigure +# ./build.sh --preset default-mingw # ============================================================================== # ### HELP_END ### @@ -50,7 +51,7 @@ detect_default_preset() { Darwin*) if [ "$(uname -m)" = "arm64" ]; then echo "default-macos-arm64"; else echo "default-macos-x64"; fi ;; Linux*) echo "default-linux" ;; - MINGW*|MSYS*) echo "default-windows" ;; + MINGW*|MSYS*) echo "default-mingw" ;; *) echo "unknown" ;; esac } @@ -62,6 +63,8 @@ ensure_vcpkg() { platform=$(detect_default_preset) if [[ "$platform" == *"macos"* ]]; then "$PROJECT_ROOT/vcpkg.sh" "macos" + elif [[ "$platform" == "default-mingw" ]]; then + "$PROJECT_ROOT/vcpkg.sh" else "$PROJECT_ROOT/vcpkg.sh" "$(echo "$platform" | cut -d- -f2)" fi diff --git a/cmake/ProjectOptions.cmake b/cmake/ProjectOptions.cmake index 032c3f63..f470e8ce 100644 --- a/cmake/ProjectOptions.cmake +++ b/cmake/ProjectOptions.cmake @@ -11,6 +11,7 @@ endif () if (MINGW) target_link_options(gk_project_options INTERFACE "-municode") target_compile_definitions(gk_project_options INTERFACE MINGW=1) + target_compile_options(gk_project_options INTERFACE "-Wno-deprecated-declarations") endif() if (MSVC) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0817a29a..b5df740a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -103,10 +103,14 @@ add_executable(MagicaLego add_executable(Packager Application/Packager/PackagerMain.cpp ) + +if( NOT MINGW ) find_package(Catch2 3 REQUIRED) add_executable(gkNextUnitTests ${UNIT_TEST_SOURCES}) endif() +endif() + if (UNIX AND NOT APPLE AND NOT ANDROID) set(extra_libs -lstdc++fs ${Backtrace_LIBRARIES}) endif() @@ -134,6 +138,17 @@ set(AllTargets gkNextEngine gkNextRenderer ) +elseif( MINGW ) +set(AllTargets +gkNextEngine +gkNextRenderer +gkNextStillBenchmark +gkNextMotionBenchmark +gkNextVisualTest +gkNextEditor +Packager +MagicaLego +) else() set(AllTargets gkNextEngine @@ -306,7 +321,7 @@ foreach(target IN LISTS AllTargets) endif() # specific for targets - if (UNIX AND NOT APPLE AND NOT ANDROID) + if ( (UNIX OR MINGW) AND NOT APPLE AND NOT ANDROID) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native -mavx") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -mavx") endif() @@ -347,7 +362,7 @@ foreach(target IN LISTS AllTargets) endforeach() # Test Target -if ( NOT ANDROID AND NOT IOS ) +if ( NOT ANDROID AND NOT IOS AND NOT MINGW ) target_link_libraries(gkNextUnitTests PRIVATE gkNextEngine Catch2::Catch2WithMain gk_project_options) set_target_properties(gkNextUnitTests PROPERTIES FOLDER "Tests") endif() diff --git a/src/DesktopMain.cpp b/src/DesktopMain.cpp index d7e0ac89..00c88f97 100644 --- a/src/DesktopMain.cpp +++ b/src/DesktopMain.cpp @@ -99,7 +99,11 @@ void SDL_AppQuit(void *appstate, SDL_AppResult result) if (GOption->FastExit) { +#if __MINGW32__ + std::exit(0); +#else std::quick_exit(0); +#endif } GTestRunner.reset(); diff --git a/src/Rendering/VulkanBaseRenderer.cpp b/src/Rendering/VulkanBaseRenderer.cpp index 0f41782e..5b2d7c09 100644 --- a/src/Rendering/VulkanBaseRenderer.cpp +++ b/src/Rendering/VulkanBaseRenderer.cpp @@ -1665,7 +1665,7 @@ namespace Vulkan size_t SrcImageW8 = 4 * 2 * extent.width; size_t SrcImage8 = 4 * 2; -#if __linux__ || __APPLE__ +#if __linux__ || __APPLE__ || __MINGW32__ oidn::BufferRef colorBuf = oidnDevice.newBuffer(oidn::ExternalMemoryTypeFlag::OpaqueFD, rtDenoise0_->GetExternalHandle(), SrcImageSize); oidn::BufferRef outBuf = oidnDevice.newBuffer(oidn::ExternalMemoryTypeFlag::OpaqueFD,rtDenoise1_->GetExternalHandle(), SrcImageSize); oidn::BufferRef albedoBuf = oidnDevice.newBuffer(oidn::ExternalMemoryTypeFlag::OpaqueFD, rtAlbedo_->GetExternalHandle(), SrcImageSize); diff --git a/src/Vulkan/GpuResources.cpp b/src/Vulkan/GpuResources.cpp index d68707bc..4174461f 100644 --- a/src/Vulkan/GpuResources.cpp +++ b/src/Vulkan/GpuResources.cpp @@ -470,12 +470,12 @@ void RenderImage::InsertBarrier(VkCommandBuffer commandBuffer, VkAccessFlags src ExtHandle RenderImage::GetExternalHandle() const { ExtHandle handle{}; - #if WIN32 && !defined(__MINGW32__) +#if WIN32 && !defined(__MINGW32__) VkMemoryGetWin32HandleInfoKHR handleInfo = { VK_STRUCTURE_TYPE_MEMORY_GET_WIN32_HANDLE_INFO_KHR }; handleInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT; handleInfo.memory = imageMemory_->Handle(); image_->Device().GetDeviceProcedures().vkGetMemoryWin32HandleKHR(image_->Device().Handle(), &handleInfo, &handle); -#elif __linux__ +#else VkMemoryGetFdInfoKHR fdInfo = { VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR }; fdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT; fdInfo.memory = imageMemory_->Handle(); From 6f760d6cc44af77969e1e3f1ed1ae31a923da7d9 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Tue, 3 Feb 2026 08:46:23 +0800 Subject: [PATCH 50/53] fix ci issue - fix on non-unity build - use right vulkansdk ver for macos & ios --- .github/workflows/ios.yml | 2 +- .github/workflows/macos.yml | 2 +- CMakePresets.json | 2 +- src/Assets/GPU/TextureImage.cpp | 1 + src/Runtime/Subsystems/NextAnimation.h | 2 +- src/Runtime/Subsystems/NextAudio.h | 2 +- 6 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index e23873fe..514abc99 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -22,7 +22,7 @@ jobs: runs-on: macos-latest env: - SDK_VERSION: 1.4.313.2 + SDK_VERSION: 1.4.313.0 steps: - uses: actions/checkout@v4 - name: Install Vulkan SDK diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index ba8b76e3..b2f318ba 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -22,7 +22,7 @@ jobs: runs-on: macos-latest env: - SDK_VERSION: 1.4.313.2 + SDK_VERSION: 1.4.313.0 steps: - uses: actions/checkout@v4 - name: Install Vulkan SDK diff --git a/CMakePresets.json b/CMakePresets.json index 3ab57bb3..b5dc4f00 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -59,7 +59,7 @@ "ENABLE_AUDIO": "ON", "ENABLE_OIDN": "OFF", "ENABLE_QUICKJS": "ON", - "ENABLE_UNITY_BUILD": "ON" + "ENABLE_UNITY_BUILD": "OFF" } }, { diff --git a/src/Assets/GPU/TextureImage.cpp b/src/Assets/GPU/TextureImage.cpp index ac29b15d..27e74adf 100644 --- a/src/Assets/GPU/TextureImage.cpp +++ b/src/Assets/GPU/TextureImage.cpp @@ -4,6 +4,7 @@ #include "Vulkan/CommandExecution.hpp" #include "Vulkan/GpuResources.hpp" #include "Vulkan/MemoryAndShader.hpp" +#include "Vulkan/Device.hpp" #include #include "Vulkan/CommandExecution.hpp" diff --git a/src/Runtime/Subsystems/NextAnimation.h b/src/Runtime/Subsystems/NextAnimation.h index 59739fcd..c66152da 100644 --- a/src/Runtime/Subsystems/NextAnimation.h +++ b/src/Runtime/Subsystems/NextAnimation.h @@ -1,5 +1,5 @@ #pragma once -#include "Vulkan/Vulkan.hpp" +#include "Vulkan/DebugUtilities.hpp" #include "Assets/Data/Animation.hpp" class NextAnimation final diff --git a/src/Runtime/Subsystems/NextAudio.h b/src/Runtime/Subsystems/NextAudio.h index 0c74ddf3..090df7dc 100644 --- a/src/Runtime/Subsystems/NextAudio.h +++ b/src/Runtime/Subsystems/NextAudio.h @@ -1,7 +1,7 @@ #pragma once #include "Common/CoreMinimal.hpp" -#include "Vulkan/Vulkan.hpp" +#include "Vulkan/DebugUtilities.hpp" #if WITH_AUDIO struct ma_engine; From f1195062957af8c77f634e5ac10d74e064052656 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Tue, 3 Feb 2026 09:00:38 +0800 Subject: [PATCH 51/53] fix - review issues - quickjs default disabled, for android build --- CMakeLists.txt | 4 ++-- .../MagicaLego/MagicaLegoUserInterface.cpp | 15 ++++++++++----- src/Assets/Loaders/FSceneLoader.cpp | 4 ++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 77905c65..09537b43 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,7 @@ if (ENABLE_PHYSIC) set(WITH_PHYSIC ON CACHE BOOL "Enable Physics" FORCE) endif() -option(ENABLE_AUDIO "Enable Audio support" ON) +option(ENABLE_AUDIO "Enable Audio support" OFF) if (ENABLE_AUDIO) set(WITH_AUDIO ON CACHE BOOL "Enable Audio" FORCE) endif() @@ -47,7 +47,7 @@ if (ENABLE_OIDN) set(WITH_OIDN ON CACHE BOOL "Enable OIDN" FORCE) endif() -option(ENABLE_QUICKJS "Enable QuickJS support" ON) +option(ENABLE_QUICKJS "Enable QuickJS support" OFF) if (ENABLE_QUICKJS) set(WITH_QUICKJS ON CACHE BOOL "Enable QuickJS" FORCE) endif() diff --git a/src/Application/MagicaLego/MagicaLegoUserInterface.cpp b/src/Application/MagicaLego/MagicaLegoUserInterface.cpp index 53c2b260..62c01403 100644 --- a/src/Application/MagicaLego/MagicaLegoUserInterface.cpp +++ b/src/Application/MagicaLego/MagicaLegoUserInterface.cpp @@ -1,15 +1,16 @@ #include "MagicaLegoUserInterface.hpp" -#include +#include #include #include -#include +#include +#include -#include "ThirdParty/fontawesome/IconsFontAwesome6.h" -#include "Utilities/FileHelper.hpp" #include "MagicaLegoGameInstance.hpp" #include "Runtime/Editor/UserInterface.hpp" #include "Runtime/Platform/PlatformCommon.h" +#include "ThirdParty/fontawesome/IconsFontAwesome6.h" +#include "Utilities/FileHelper.hpp" #include "Utilities/ImGui.hpp" #include "Utilities/Localization.hpp" @@ -636,7 +637,11 @@ void MagicaLegoUserInterface::RecordTimeline(bool autoRotate) PopLayout(); // sleep os for a while int result = NextRenderer::OSProcess(fmt::format("ffmpeg -framerate 30 -i {}/video_%d.jpg -c:v libx264 -pix_fmt yuv420p {}.mp4", localTempPath, filename).c_str()); - (void)result; + if (result != 0) + { + SPDLOG_ERROR("ffmpeg process failed with exit code {}", result); + } + // delete all *.jpg using std::filesystem std::filesystem::remove_all(localTempPath); diff --git a/src/Assets/Loaders/FSceneLoader.cpp b/src/Assets/Loaders/FSceneLoader.cpp index 2d9a1fd7..606b6452 100644 --- a/src/Assets/Loaders/FSceneLoader.cpp +++ b/src/Assets/Loaders/FSceneLoader.cpp @@ -293,7 +293,7 @@ namespace Assets mikktspaceContext.m_pUserData = m; genTangSpaceDefault(&mikktspaceContext); } - DISABLE_OPTIMIZATION + bool FSceneLoader::LoadGLTFScene(const std::string& filename, Assets::EnvironmentSetting& cameraInit, std::vector< std::shared_ptr >& nodes, std::vector& models, std::vector& materials, std::vector& lights, std::vector& tracks, std::vector& skeletons) @@ -1015,7 +1015,7 @@ namespace Assets //printf("model.cameras: %d\n", i); return true; } - ENABLE_OPTIMIZATION + Camera FSceneLoader::AutoFocusCamera(Assets::EnvironmentSetting& cameraInit, std::vector>& nodes, std::vector& models) { //auto center camera by scene bounds From 2c20100a0828b377d92d3fef68938d740483cf49 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Tue, 3 Feb 2026 09:05:01 +0800 Subject: [PATCH 52/53] fix for mobile platform --- src/Runtime/Platform/PlatformLinux.h | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Runtime/Platform/PlatformLinux.h b/src/Runtime/Platform/PlatformLinux.h index a2cfe05d..4f7883c3 100644 --- a/src/Runtime/Platform/PlatformLinux.h +++ b/src/Runtime/Platform/PlatformLinux.h @@ -15,15 +15,19 @@ namespace NextRenderer } - inline void OSCommand(const char* command) + inline int OSProcess(const char* exe) { - std::string commandline{"xdg-open "}; - commandline += command; - //system(commandline.c_str()); +#if IOS || ANDROID + return 1; +#else + return std::system(exe); +#endif } - inline int OSProcess(const char* exe) + inline void OSCommand(const char* command) { - return std::system(exe); + std::string commandline{"xdg-open "}; + commandline += command; + OSProcess(commandline.c_str()); } } From a98be4f89172d590e2d116cf93c1bc6d7ca41603 Mon Sep 17 00:00:00 2001 From: gameKnife Date: Tue, 3 Feb 2026 09:22:14 +0800 Subject: [PATCH 53/53] fix for ios build --- src/Assets/Savers/FSceneSaver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Assets/Savers/FSceneSaver.cpp b/src/Assets/Savers/FSceneSaver.cpp index 25aa8867..faec84ee 100644 --- a/src/Assets/Savers/FSceneSaver.cpp +++ b/src/Assets/Savers/FSceneSaver.cpp @@ -244,7 +244,7 @@ void FSceneSaver::SerializeMeshes(tinygltf::Model& model, const Scene& scene) } } - for (size_t modelIdx = 0; modelIdx < models.size(); ++modelIdx) + for (uint32_t modelIdx = 0; modelIdx < models.size(); ++modelIdx) { const auto& assetModel = models[modelIdx]; tinygltf::Mesh mesh;