diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml index a2be4e2..e852b34 100644 --- a/.github/workflows/CD.yml +++ b/.github/workflows/CD.yml @@ -1,8 +1,22 @@ -# Binding Simple CD - Template +# Keeps this binding on the latest stable Embree release, headers and native libraries +# together. +# +# Three jobs rather than one, because the two halves cannot share a runner: the native +# libraries need a matrix across Linux, Windows and macOS, and the generator and packaging +# need a single machine holding the whole tree. +# +# `resolve` runs first and alone so exactly one place decides which revision this run is +# about. If the headers and the binaries each worked it out for themselves, a release +# published midway through would leave them on different versions -- and that particular +# mismatch is invisible: P/Invoke binds late, so the package compiles, passes CI, publishes, +# and throws in the consumer's application. name: CD on: + schedule: + # Monthly, in step with the rest of the fleet. + - cron: '0 2 1 * *' workflow_dispatch: inputs: skip-assets-publishing: @@ -10,11 +24,38 @@ on: required: false type: boolean default: false + force-natives: + description: 'Rebuild the native libraries even when the release has not moved' + required: false + type: boolean + default: false + force-publish: + description: 'Publish even when the generated code is unchanged' + required: false + type: boolean + default: false jobs: - cd: + resolve: if: github.event_name != 'schedule' || github.ref == 'refs/heads/main' - uses: EvergineTeam/evergine-standards/.github/workflows/binding-simple-cd.yml@v2 + uses: EvergineTeam/Evergine.Bindings/.github/workflows/binding-resolve-upstream.yml@v1 + + natives: + needs: resolve + # Four platform builds is not something to spend on a maybe, hence resolving first and + # asking. The x64 legs alone take about an hour. + if: needs.resolve.outputs.ref_moved == 'true' || inputs.force-natives + uses: ./.github/workflows/embree-cmake.yml + with: + ref: ${{ needs.resolve.outputs.resolved_ref }} + + cd: + needs: [resolve, natives] + # `natives` is skipped whenever the release has not moved, and a skipped dependency + # would otherwise take this job with it. The run still has to reach the CD so it can + # report the no-op. + if: always() && needs.resolve.result == 'success' && needs.natives.result != 'failure' && !cancelled() + uses: EvergineTeam/Evergine.Bindings/.github/workflows/binding-tracked-cd.yml@v1 with: generator-project: "EmbreeGen/EmbreeGen.csproj" generator-name: "EmbreeGen" @@ -22,9 +63,20 @@ jobs: target-framework: "net10.0" dotnet-version: "10.x" nuget-version: "6.x" + # See CI.yml: this generator parses against its own stub headers, so unlike most of + # the fleet it does not need a Windows runner. + runner-os: ubuntu-latest runtime-identifier: "linux-x64" build-configuration: "Release" revision: ${{ github.run_number }} + # Every artifact already carries its final repository path, so unpacking them over the + # tree is the whole mapping. + # + # Asked for only when the natives job actually ran. It is skipped whenever the release + # has not moved -- most runs -- and asking for artifacts that were never produced fails + # the download and takes the publish with it. + natives-artifact-pattern: ${{ needs.natives.result == 'success' && 'natives-*' || '' }} + force-publish: ${{ inputs.force-publish || false }} publish-enabled: ${{ !inputs.skip-assets-publishing }} enable-email-notifications: true secrets: diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e1bf463..420b24d 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,5 +1,3 @@ -# Binding CI - Simple Template - name: CI on: @@ -17,13 +15,19 @@ on: jobs: ci: - uses: EvergineTeam/evergine-standards/.github/workflows/binding-common-ci.yml@v2 + uses: EvergineTeam/Evergine.Bindings/.github/workflows/binding-common-ci.yml@v1 with: - generator-project: "EmbreeGen/EmbreeGen.csproj" # Path to the generator .csproj - generator-name: "EmbreeGen" # Generator executable name - binding-project: "Evergine.Bindings.Embree/Evergine.Bindings.Embree.csproj" # Path to the binding .csproj + generator-project: "EmbreeGen/EmbreeGen.csproj" + generator-name: "EmbreeGen" + binding-project: "Evergine.Bindings.Embree/Evergine.Bindings.Embree.csproj" target-framework: "net10.0" dotnet-version: "10.x" + # Generation runs on Linux, which most of the fleet cannot do: the libclang in the + # CppAst package carries no system include paths and none of its own builtin headers, + # so and fail to resolve there. This generator sidesteps that + # by parsing against the stubs in EmbreeGen/Headers/stubs with a pinned x86_64 target, + # which also makes its output identical on any host. + runner-os: ubuntu-latest runtime-identifier: "linux-x64" build-configuration: "Release" nuget-artifacts: ${{ inputs.publish-artifacts || false }} diff --git a/.github/workflows/embree-cmake.yml b/.github/workflows/embree-cmake.yml index b6cc2b2..c812c02 100644 --- a/.github/workflows/embree-cmake.yml +++ b/.github/workflows/embree-cmake.yml @@ -12,15 +12,24 @@ name: Build Embree Libraries on: + # Called by CD.yml with the release that `binding-resolve-upstream` settled on, so the + # binaries and the headers committed in the same run come from one revision of Embree. + workflow_call: + inputs: + ref: + description: 'Embree tag to build' + required: false + type: string + default: 'v4.4.1' workflow_dispatch: inputs: - embree-ref: + ref: description: 'Embree tag to build' required: false type: string default: 'v4.4.1' only-rid: - description: 'Build a single RID (e.g. win-arm64). Empty builds all five.' + description: 'Build a single RID (e.g. linux-arm64). Empty builds all four.' required: false type: string default: '' @@ -79,7 +88,7 @@ jobs: uses: actions/checkout@v4 with: repository: RenderKit/embree - ref: ${{ inputs.embree-ref || 'v4.4.1' }} + ref: ${{ inputs.ref || 'v4.4.1' }} - name: Install build dependencies (Linux) if: (inputs.only-rid == '' || inputs.only-rid == matrix.rid) && startsWith(matrix.os, 'ubuntu') @@ -121,6 +130,9 @@ jobs: if: inputs.only-rid == '' || inputs.only-rid == matrix.rid run: cmake --build build --config Release --parallel ${{ matrix.parallel }} + # Each artifact is laid out with the paths it will occupy in the repository, so the CD + # unpacks it over the tree and that is the whole mapping. + # # On Linux and macOS CMake emits versioned names behind symlinks # (libembree4.so -> libembree4.so.4 -> libembree4.so.4.4.1). Symlinks do not survive # NuGet packaging, so the real file is copied out under the plain name that @@ -130,26 +142,34 @@ jobs: shell: bash run: | set -euo pipefail - mkdir -p out + native="out/Evergine.Bindings.Embree/runtimes/${{ matrix.rid }}/native" + mkdir -p "$native" case "${{ matrix.rid }}" in win-*) - cp "$(find build -name 'embree4.dll' -print -quit)" out/embree4.dll + cp "$(find build -name 'embree4.dll' -print -quit)" "$native/embree4.dll" ;; linux-*) - cp "$(readlink -f "$(find build -name 'libembree4.so*' -print -quit)")" out/libembree4.so + cp "$(readlink -f "$(find build -name 'libembree4.so*' -print -quit)")" "$native/libembree4.so" ;; osx-*) - cp "$(readlink -f "$(find build -name 'libembree4*.dylib' -print -quit)")" out/libembree4.dylib + cp "$(readlink -f "$(find build -name 'libembree4*.dylib' -print -quit)")" "$native/libembree4.dylib" ;; esac - cp include/embree4/rtcore_config.h out/ - ls -l out + # rtcore_config.h comes from one leg only. Every leg generates a byte-identical + # file, but the Windows one is written with CRLF, so letting them all contribute + # would make the committed header flip line endings depending on unpack order. + if [ "${{ matrix.rid }}" = "linux-x64" ]; then + mkdir -p out/EmbreeGen/Headers/embree4 + cp include/embree4/rtcore_config.h out/EmbreeGen/Headers/embree4/rtcore_config.h + fi + + find out -type f - name: Upload if: inputs.only-rid == '' || inputs.only-rid == matrix.rid uses: actions/upload-artifact@v4 with: - name: embree-${{ matrix.rid }} + name: natives-${{ matrix.rid }} path: out/ diff --git a/README.md b/README.md index b3a4a96..f42a2a2 100644 --- a/README.md +++ b/README.md @@ -70,11 +70,19 @@ the 1-byte C `_Bool`. ## Repository layout ``` +binding.yml Manifest read by the Evergine.Bindings toolbox EmbreeGen/ Generator console app (CppAst); vendored headers in Headers/ Evergine.Bindings.Embree/ The NuGet package: Generated/ bindings + runtimes/ natives HelloEmbree/ Sample: CPU ray tracer drawn with the Evergine low-level API ``` +CI and CD are the shared workflows from +[EvergineTeam/Evergine.Bindings](https://github.com/EvergineTeam/Evergine.Bindings), and +[`binding.yml`](binding.yml) is what tells them where the upstream headers come from, which +release is tracked, and which paths are generated output. Read its `NOTE` comments before +changing how the headers or the native binaries are refreshed — they record why the two cannot +move independently. + [HelloEmbree](HelloEmbree/README.md) traces a small scene on the CPU with `rtcIntersect1`/`rtcOccluded1`, uploads the result to a texture every frame and blits it to a DX11 swapchain hosted in a Windows Forms window. It also has a `--bench` mode that reports the diff --git a/binding.yml b/binding.yml new file mode 100644 index 0000000..17f0209 --- /dev/null +++ b/binding.yml @@ -0,0 +1,61 @@ +# Binding manifest — see https://github.com/EvergineTeam/Evergine.Bindings +# Schema: https://github.com/EvergineTeam/Evergine.Bindings/blob/main/binding.schema.json +toolbox: 1.29.0 + +package: + id: Evergine.Bindings.Embree + project: Evergine.Bindings.Embree/Evergine.Bindings.Embree.csproj + target-framework: net10.0 + # linux-x64, unlike most bindings in the fleet. The CppAst generator parses against the + # stub system headers in EmbreeGen/Headers/stubs against a pinned x86_64 target, so it + # does not depend on the libclang include paths that force MeshOptimizer, RenderDoc and + # KTX to generate on Windows. Do not "fix" this by moving it to windows-latest. + runtime-identifier: linux-x64 + +upstream: + kind: git-tree + language: cpp + project: https://github.com/RenderKit/embree + version-from: git-release + + release: + # Tagged releases, not main. This binding ships native binaries, and the headers and + # those binaries have to come from one revision of Embree; a branch would move the + # headers underneath libraries built from something else. `current` is rewritten by + # binding-fetch-upstream when it brings in a newer release, which makes this the one + # place the version lives. + track: stable + current: v4.4.1 + + sources: + - repo: RenderKit/embree + remote-path: include/embree4 + path: EmbreeGen/Headers/embree4 + format: c-header + +generator: + project: EmbreeGen/EmbreeGen.csproj + name: EmbreeGen + output: + - Evergine.Bindings.Embree/Generated + +# NOTE — rtcore_config.h is not part of the upstream sources above. Embree's CMake generates +# it, and it decides the layout of several public structs: RTC_MAX_INSTANCE_LEVEL_COUNT sizes +# the instID arrays in RTCHit and RTCRayQueryContext, RTC_GEOMETRY_INSTANCE_ARRAY adds the +# instPrimID ones, and RTC_MIN_WIDTH adds a field to RTCIntersectArguments. The copy vendored +# in EmbreeGen/Headers/embree4/ therefore has to come from the same build as the binaries, and +# it does: embree-cmake.yml emits it alongside them. Refreshing the header tree leaves it +# alone, because the git-tree adapter only writes the files it fetched. +# +# NOTE — this package ships native binaries under Evergine.Bindings.Embree/runtimes. Headers +# and binaries must come from the same upstream revision, so a header bump is only valid +# together with a native rebuild. That rebuild is not optional and CI would not catch it being +# skipped: P/Invoke binds late, so a mismatched package compiles, passes CI, publishes, and +# then throws in the consumer's application. The CD keeps the two in step — embree-cmake.yml +# rebuilds every platform at the resolved release before the headers are committed. Do not +# bump the header by hand. +# +# NOTE — there is no win-arm64. Embree 4.4.1 does not build for Windows on ARM with either +# toolset available on the runner; see the tracking issue. Do not add the RID back without a +# green build, and do not ship a binary produced by forcing flags past Embree's CMake: a wrong +# SIMD path in a ray tracing kernel returns wrong intersections rather than crashing.