diff --git a/.github/actions/prepare-shaders/action.yml b/.github/actions/prepare-shaders/action.yml index 8e7c475a35..3a5b620b44 100644 --- a/.github/actions/prepare-shaders/action.yml +++ b/.github/actions/prepare-shaders/action.yml @@ -1,28 +1,51 @@ name: "Prepare Shaders" -description: "Prepare shaders for validation or analysis (requires setup-build-environment to be called first)" +description: "Copy shaders into build/ALL/aio/Shaders for validation. Uses direct robocopy rather than cmake so the action needs no MSVC toolchain." inputs: cmake-preset: - description: "The CMake configure/build preset to use." + description: "Unused — kept for API compatibility. Shaders are now copied directly without invoking cmake." required: false default: "ALL" + output-dir: + description: "Destination directory for the assembled shader tree." + required: false + default: "build/ALL/aio/Shaders" runs: using: "composite" steps: - - name: Prepare shaders - uses: lukka/run-cmake@v10 - with: - configurePreset: ${{ inputs.cmake-preset }} - configurePresetAdditionalArgs: "['-DBUILD_SHADER_TESTS=OFF']" - buildPreset: ${{ inputs.cmake-preset }} - buildPresetAdditionalArgs: "['--target prepare_shaders']" + # Copy shaders without invoking cmake or requiring a C++ toolchain. + # Matches the logic of the cmake prepare_shaders target: + # 1. package/Shaders/** → / + # 2. features/*/Shaders/** → / (Tests/ excluded) + - name: Copy shaders + shell: pwsh + run: | + $dest = "${{ inputs.output-dir }}" + New-Item -ItemType Directory -Force -Path $dest | Out-Null + + # Package base shaders + robocopy "package/Shaders" $dest /E /COPY:DAT /NFL /NDL /NJH /NJS /R:1 /W:1 + $rc = $LASTEXITCODE; if ($rc -ge 8) { throw "robocopy package/Shaders failed (rc=$rc)" } + + # Feature shaders (skip Tests subdirectories) + Get-ChildItem -Path features -Filter "Shaders" -Recurse -Directory | + Where-Object { $_.FullName -notmatch '\\Tests($|\\)' } | + ForEach-Object { + robocopy $_.FullName $dest /E /COPY:DAT /NFL /NDL /NJH /NJS /R:1 /W:1 /XD "$($_.FullName)\Tests" + $rc = $LASTEXITCODE; if ($rc -ge 8) { throw "robocopy $($_.FullName) failed (rc=$rc)" } + } + + $count = (Get-ChildItem -Recurse -File -Path $dest).Count + Write-Host "Prepared $count shader files in $dest" - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.11" + cache: "pip" + cache-dependency-path: ".github/configs/hlslkit-requirements.txt" - name: Install hlslkit - run: pip install git+https://github.com/alandtse/hlslkit.git + run: pip install -r .github/configs/hlslkit-requirements.txt shell: bash diff --git a/.github/actions/setup-build-environment/action.yaml b/.github/actions/setup-build-environment/action.yaml index 12f5614df3..f7ff78a14a 100644 --- a/.github/actions/setup-build-environment/action.yaml +++ b/.github/actions/setup-build-environment/action.yaml @@ -100,7 +100,11 @@ runs: uses: actions/cache@v5 with: path: ${{ inputs.build-dir }} - key: ${{ runner.os }}-cmake-msvc-${{ steps.msvc_version.outputs.version }}-${{ inputs.cache-key-suffix }}-${{ github.event.inputs.cache-key-suffix || 'default' }}-${{ hashFiles('.gitmodules', 'extern/**', 'CMakePresets.json', 'vcpkg.json', 'vcpkg-configuration.json') }} + # Use submodule HEAD refs instead of hashing all of extern/** (~79 MB) + # to avoid slow hash computation on every cache lookup. + # actions/checkout with submodules:recursive writes .git/modules//HEAD + # for each submodule; hashing those 3 small files detects any submodule SHA change. + key: ${{ runner.os }}-cmake-msvc-${{ steps.msvc_version.outputs.version }}-${{ inputs.cache-key-suffix }}-${{ github.event.inputs.cache-key-suffix || 'default' }}-${{ hashFiles('.gitmodules', '.git/modules/CommonLibSSE-NG/HEAD', '.git/modules/FidelityFX-SDK/HEAD', '.git/modules/Streamline-DX12/HEAD', 'CMakePresets.json', 'vcpkg.json', 'vcpkg-configuration.json') }} restore-keys: | ${{ runner.os }}-cmake-msvc-${{ steps.msvc_version.outputs.version }}-${{ inputs.cache-key-suffix }}-${{ github.event.inputs.cache-key-suffix || 'default' }}- ${{ runner.os }}-cmake-msvc-${{ steps.msvc_version.outputs.version }}-${{ inputs.cache-key-suffix }}- diff --git a/.github/configs/hlslkit-requirements.txt b/.github/configs/hlslkit-requirements.txt new file mode 100644 index 0000000000..379dff632e --- /dev/null +++ b/.github/configs/hlslkit-requirements.txt @@ -0,0 +1,5 @@ +# hlslkit is pinned by commit hash so the pip wheel cache key is stable. +# To update: find the latest commit on https://github.com/alandtse/hlslkit +# and replace the @ below, then delete the stale cache entry in +# GitHub → Actions → Caches so the next CI run rebuilds the wheel. +hlslkit @ git+https://github.com/alandtse/hlslkit.git@d30266d2d5280a2d8c5bef1949c5a8ae1d550c60 diff --git a/.github/workflows/_shared-build.yaml b/.github/workflows/_shared-build.yaml index ecf4319149..a9c6342465 100644 --- a/.github/workflows/_shared-build.yaml +++ b/.github/workflows/_shared-build.yaml @@ -142,19 +142,15 @@ jobs: with: ref: ${{ inputs.ref }} repository: ${{ inputs.repository }} - submodules: recursive + # Shader validation only needs source files — submodules (CommonLibSSE-NG, + # FidelityFX, Streamline) are C++ dependencies not required here. + submodules: false - name: Check for HLSL Changes id: check-hlsl uses: ./.github/actions/check-hlsl-changes with: hlsl-should-build: ${{ inputs.hlsl-should-build }} - - name: Setup Build Environment - id: setup - if: steps.check-hlsl.outputs.skip != 'true' - uses: ./.github/actions/setup-build-environment - with: - cache-key-suffix: "validation-${{ matrix.config.name }}${{ inputs.cache-key-suffix }}" - name: Prepare shaders if: steps.check-hlsl.outputs.skip != 'true' uses: ./.github/actions/prepare-shaders @@ -195,7 +191,7 @@ jobs: echo "fxc.exe not found - shader validation requires fxc.exe. Set --fxc to a valid path or ensure fxc.exe is in PATH." >&2 exit 1 fi - hlslkit-compile --fxc "${{ steps.find_fxc.outputs.fxc_path }}" --shader-dir build/ALL/aio/Shaders --output-dir build/ShaderCache --config ${{ matrix.config.file }} --max-warnings 0 --suppress-warnings X1519 + hlslkit-compile --fxc "${{ steps.find_fxc.outputs.fxc_path }}" --shader-dir build/ALL/aio/Shaders --output-dir build/ShaderCache --config ${{ matrix.config.file }} --max-warnings 0 --suppress-warnings X1519 --jobs $(nproc) --strip-debug-defines - name: Upload shader validation logs if: failure() && steps.check-hlsl.outputs.skip != 'true' diff --git a/.github/workflows/ci-manual-test.yaml b/.github/workflows/ci-manual-test.yaml new file mode 100644 index 0000000000..dc7d4544c6 --- /dev/null +++ b/.github/workflows/ci-manual-test.yaml @@ -0,0 +1,41 @@ +name: "Manual CI Test (branch build)" + +# Runs the full build from THIS branch's ref using workflow_dispatch. +# Unlike pull_request_target (which resolves workflow/action files from BASE), +# workflow_dispatch resolves everything from the selected ref — so this +# actually exercises changes to _shared-build.yaml, setup-build-environment, +# prepare-shaders, and any other action files on the branch. +# +# Usage: Actions → "Manual CI Test" → Run workflow → select branch +on: + workflow_dispatch: + inputs: + run-cpp: + description: "Run C++ plugin build" + type: boolean + default: true + run-shader-validation: + description: "Run HLSL shader validation" + type: boolean + default: true + run-shader-tests: + description: "Run shader unit tests" + type: boolean + default: true + +permissions: + contents: read + +jobs: + build: + name: Build and Validate + uses: ./.github/workflows/_shared-build.yaml + with: + ref: ${{ github.ref }} + repository: ${{ github.repository }} + run-cpp: ${{ inputs.run-cpp }} + run-shader-validation: ${{ inputs.run-shader-validation }} + run-shader-tests: ${{ inputs.run-shader-tests }} + hlsl-should-build: "true" + shader-tests-should-build: "true" + cache-key-suffix: "-manual-test" diff --git a/BuildRelease.bat b/BuildRelease.bat index 18946987f5..e20c9b4a8b 100755 --- a/BuildRelease.bat +++ b/BuildRelease.bat @@ -1,13 +1,30 @@ @echo off set preset="ALL" -if NOT "%1" == "" ( +set skip_configure=0 + +:parse_args +if "%1"=="" goto done_args +if /I "%1"=="--build-only" ( + set skip_configure=1 + shift + goto parse_args +) +if not "%1"=="" ( set preset=%1 + shift + goto parse_args ) +:done_args echo Running preset %preset% -cmake -S . --preset=%preset% -if %ERRORLEVEL% NEQ 0 exit 1 +if "%skip_configure%"=="1" ( + echo Skipping CMake configure ^(--build-only^) +) else ( + cmake -S . --preset=%preset% + if %ERRORLEVEL% NEQ 0 exit 1 +) + cmake --build --preset=%preset% if %ERRORLEVEL% NEQ 0 exit 1 diff --git a/CMakePresets.json b/CMakePresets.json index 790a0b13ad..da929e7032 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -71,6 +71,20 @@ "AUTO_PLUGIN_DEPLOYMENT": "OFF" }, "inherits": "skyrim" + }, + { + "name": "ALL-FastDev", + "description": "Like ALL but disables zip packaging so the configure step is leaner", + "cacheVariables": { + "ENABLE_SKYRIM_AE": "ON", + "ENABLE_SKYRIM_SE": "ON", + "ENABLE_SKYRIM_VR": "ON", + "AUTO_PLUGIN_DEPLOYMENT": "OFF", + "ZIP_TO_DIST": "OFF", + "AIO_ZIP_TO_DIST": "OFF", + "BUILD_SHADER_TESTS": "OFF" + }, + "inherits": "skyrim" } ], "buildPresets": [ @@ -115,6 +129,13 @@ "configurePreset": "ALL", "configuration": "Debug", "targets": ["CommunityShaders", "AIO"] + }, + { + "name": "FastDev", + "description": "RelWithDebInfo build: /O2 optimisation but NO LTO/LTCG and no zip packaging. Typical link is 3-4x faster than Release. Use for rapid shader/feature iteration.", + "configurePreset": "ALL-FastDev", + "configuration": "RelWithDebInfo", + "targets": ["CommunityShaders", "PREPARE_AIO"] } ] } diff --git a/cmake/XSEPlugin.cmake b/cmake/XSEPlugin.cmake index d23742ded0..abd6acfadb 100644 --- a/cmake/XSEPlugin.cmake +++ b/cmake/XSEPlugin.cmake @@ -42,6 +42,9 @@ target_precompile_headers( set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_DEBUG OFF) +# RelWithDebInfo is the fast-iteration developer config; skip LTO so +# link time stays short while keeping /O2 optimisation for realistic perf. +set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO OFF) set(Boost_USE_STATIC_LIBS ON) set(Boost_USE_STATIC_RUNTIME ON) diff --git a/tools/benchmark-build.ps1 b/tools/benchmark-build.ps1 new file mode 100644 index 0000000000..f08ed0dc05 --- /dev/null +++ b/tools/benchmark-build.ps1 @@ -0,0 +1,166 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + Benchmarks each major build phase and reports timings. + +.DESCRIPTION + Runs cmake configure, DLL compilation, AIO packaging, and local shader + validation in sequence, timing each phase. Use this to measure the impact + of build-system changes. + +.PARAMETER Preset + CMake preset to benchmark. Defaults to ALL. Use FastDev for the + faster RelWithDebInfo variant (no LTO, ~3-4x quicker link step). + +.PARAMETER SkipConfigure + Skip the cmake configure phase (assumes build directory already exists). + +.PARAMETER SkipValidation + Skip the local hlslkit shader validation phase. + +.PARAMETER Jobs + Number of parallel jobs for hlslkit-compile. Defaults to the number of + logical CPU cores. + +.EXAMPLE + # Full benchmark with default (ALL/Release) preset + powershell.exe -File tools/benchmark-build.ps1 + +.EXAMPLE + # Benchmark the fast developer preset (no LTO) + powershell.exe -File tools/benchmark-build.ps1 -Preset FastDev + +.EXAMPLE + # Measure only the compile + package phases (skip configure and validation) + powershell.exe -File tools/benchmark-build.ps1 -SkipConfigure -SkipValidation +#> + +param( + [string]$Preset = "ALL", + [switch]$SkipConfigure, + [switch]$SkipValidation, + [int]$Jobs = [Environment]::ProcessorCount +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +function Invoke-Timed { + param([string]$Label, [scriptblock]$Block) + Write-Host "" + Write-Host ">>> $Label" -ForegroundColor Cyan + $sw = [System.Diagnostics.Stopwatch]::StartNew() + try { + & $Block + if ($LASTEXITCODE -and $LASTEXITCODE -ne 0) { + throw "Command exited with code $LASTEXITCODE" + } + } finally { + $sw.Stop() + } + $elapsed = $sw.Elapsed + $fmt = "{0:D2}m {1:D2}s" -f [int]$elapsed.TotalMinutes, $elapsed.Seconds + Write-Host "<<< $Label completed in $fmt" -ForegroundColor Green + return $elapsed +} + +$results = [ordered]@{} +$buildDir = "build/$Preset" + +Write-Host "============================================" -ForegroundColor Yellow +Write-Host " Community Shaders build benchmark" -ForegroundColor Yellow +Write-Host " Preset : $Preset" -ForegroundColor Yellow +Write-Host " Build dir: $buildDir" -ForegroundColor Yellow +Write-Host " CPU cores: $Jobs" -ForegroundColor Yellow +Write-Host "============================================" -ForegroundColor Yellow + +# ── Configure ───────────────────────────────────────────────────────────────── +if (-not $SkipConfigure) { + $results["Configure"] = Invoke-Timed "CMake configure (--preset $Preset)" { + cmake -S . --preset $Preset + } +} else { + Write-Host "Skipping configure (--SkipConfigure)" -ForegroundColor DarkGray +} + +# ── DLL compile ─────────────────────────────────────────────────────────────── +$results["Compile DLL"] = Invoke-Timed "Compile CommunityShaders.dll" { + cmake --build $buildDir --target CommunityShaders --config Release +} + +# ── AIO packaging ───────────────────────────────────────────────────────────── +$results["Package AIO"] = Invoke-Timed "Create AIO zip (Package-AIO-Manual)" { + cmake --build $buildDir --target Package-AIO-Manual +} + +# ── Shader validation ───────────────────────────────────────────────────────── +if (-not $SkipValidation) { + # Prepare shaders (copies HLSL files into AIO layout) + $results["Prepare Shaders"] = Invoke-Timed "Prepare shaders for validation" { + cmake --build $buildDir --target prepare_shaders + } + + # Locate fxc.exe + $fxcPath = "" + $fxcCmd = Get-Command fxc.exe -ErrorAction SilentlyContinue + if ($fxcCmd) { + $fxcPath = $fxcCmd.Source + } else { + $sdkRoot = "C:\Program Files (x86)\Windows Kits\10\bin" + if (Test-Path $sdkRoot) { + Get-ChildItem $sdkRoot -Directory | Sort-Object Name -Descending | ForEach-Object { + $candidate = Join-Path $_.FullName "x64\fxc.exe" + if ((Test-Path $candidate) -and -not $fxcPath) { $fxcPath = $candidate } + } + } + } + + if ($fxcPath) { + $results["Shader Validation (Flatrim)"] = Invoke-Timed "hlslkit-compile Flatrim ($Jobs jobs)" { + hlslkit-compile ` + --fxc $fxcPath ` + --shader-dir "$buildDir/aio/Shaders" ` + --output-dir build/ShaderCache ` + --config .github/configs/shader-validation.yaml ` + --max-warnings 0 ` + --suppress-warnings X1519 ` + --jobs $Jobs ` + --strip-debug-defines + } + $results["Shader Validation (VR)"] = Invoke-Timed "hlslkit-compile VR ($Jobs jobs)" { + hlslkit-compile ` + --fxc $fxcPath ` + --shader-dir "$buildDir/aio/Shaders" ` + --output-dir build/ShaderCache ` + --config .github/configs/shader-validation-vr.yaml ` + --max-warnings 0 ` + --suppress-warnings X1519 ` + --jobs $Jobs ` + --strip-debug-defines + } + } else { + Write-Warning "fxc.exe not found — skipping shader validation timing." + Write-Warning "Install Windows SDK or add fxc.exe to PATH." + } +} + +# ── Summary ─────────────────────────────────────────────────────────────────── +Write-Host "" +Write-Host "============================================" -ForegroundColor Yellow +Write-Host " Benchmark results" -ForegroundColor Yellow +Write-Host "============================================" -ForegroundColor Yellow + +$totalSeconds = 0 +foreach ($phase in $results.Keys) { + $elapsed = $results[$phase] + $totalSeconds += $elapsed.TotalSeconds + $fmt = "{0:D2}m {1:D2}s" -f [int]$elapsed.TotalMinutes, $elapsed.Seconds + Write-Host (" {0,-35} {1}" -f $phase, $fmt) +} + +Write-Host "--------------------------------------------" +$totalFmt = "{0:D2}m {1:D2}s" -f [int]($totalSeconds / 60), [int]($totalSeconds % 60) +Write-Host (" {0,-35} {1}" -f "TOTAL", $totalFmt) -ForegroundColor Cyan +Write-Host "" +Write-Host "Tip: run with -Preset FastDev to benchmark the no-LTO build." -ForegroundColor DarkGray +Write-Host "Tip: run with -SkipConfigure to skip configure on repeat runs." -ForegroundColor DarkGray