Compare commits

...
11 Commits
Author SHA1 Message Date
Clifford Yapp ad3cf7ecf0 proc-db: initialize gear pitch radii
The active array elements are initialized before use, but GCC PGO
analysis cannot prove the clamped gear count keeps every indexed value
within that initialized range.

Zero-initialize the fixed array so all possible elements have a defined
value without changing the computed radii.
2026-10-06 16:48:12 +00:00
Clifford Yapp 5075668363 g-x3d: suppress false LTO setjmp warning
GCC can report an anonymous value as maybe uninitialized when LTO
examines the BU_SETJUMP setjmp and longjmp control flow.  The diagnostic
is emitted during linking, so source pragmas cannot affect it.

Check for the linker option and suppress this warning only for the
g-x3d LTO link.  Normal compilation continues to treat the warning as
an error, leaving genuine uninitialized uses visible.
2026-10-06 16:47:58 +00:00
Clifford Yapp d09b59863a step: avoid GCC vector insertion false positive
GCC 14 lowers vector range insertion to a bulk move and can infer an
impossible negative size during PGO, reporting stringop-overflow.

Append the known positions individually.  This preserves their order
and complexity while avoiding the invalid range analysis.
2026-10-06 16:47:43 +00:00
Clifford Yapp 685d63dd71 build: use conventional calloc argument order
GCC 14 diagnoses calls with sizeof in the element-count position as
-Wcalloc-transposed-args.  Since multiplication is commutative, these
calls allocate the intended size, but their ordering obscures intent
and fails PGO and LTO builds with -Werror.

Pass the element count first and the element size second.
2026-10-06 16:47:32 +00:00
Clifford Yapp 00e61a90aa librt: let optimizer choose local inlining
GCC PGO decisions can reject explicit inline requests for cold call
sites, and -Winline then fails the build under -Werror.

Keep these helpers private with static linkage but remove inline.
Optimized builds remain free to inline profitable calls.  Remove the
metaball declaration that is no longer externally visible.
2026-10-06 16:47:19 +00:00
Clifford Yapp 07b2237074 libbu: keep UUID namespace optional
The namespace UUID is optional, but the array parameter syntax tells
GCC the pointer is nonnull.  PGO and LTO propagate that contract and
diagnose the existing null check as contradictory.

Declare the namespace as a pointer.  Array parameters already adjust to
pointers in function types, so this preserves the ABI while accurately
describing the accepted input.
2026-10-06 16:47:05 +00:00
Clifford Yapp 5d4f1f3d01 libbu: batch SHA-1 final padding
GCC interprocedural analysis reports a string overread when
SHA1Final repeatedly calls SHA1Update with a one-byte padding
variable.  The direct block loop is unreachable for those calls,
but PGO and LTO do not retain enough range information to prove it.

Define the SHA-1 block size once and submit the complete final padding
in one update.  This avoids the false object-size path, reduces
repeated update calls, and preserves the required one- or two-block
SHA-1 finalization.
2026-10-06 15:24:32 +00:00
Clifford Yapp 0d4dfb570d Stash (not yet enabling) STABLE brancy autosyncing 2026-10-06 14:56:43 +00:00
Clifford Yapp ea81a17325 Test CI PGO building, as well as MSVC version of PGO 2026-10-06 14:56:43 +00:00
Christopher Sean Morrison e7c61497df new doc on representations 2026-10-06 10:51:33 -04:00
Christopher Sean Morrison 08da151a6e add new documentation explaining the differences between geometry representation schemes with examples 2026-10-06 10:50:13 -04:00
41 changed files with 1788 additions and 103 deletions
+42
View File
@@ -447,3 +447,45 @@ jobs:
export PATH=$GITHUB_WORKSPACE:$PATH
make package
working-directory: ${{ github.workspace }}/build_osx
# BRL-CAD's STABLE branch is intended to represent the most recent
# SHA1 that has successfully passed our primary tests on our CI
# platforms. This job is intended to execute that update if the
# prior tasks succeed. It's "needs" section should contain every
# test that is not already the dependency of another tests.
# sync-stable-branch:
# name: Sync STABLE branch
# runs-on: ubuntu-latest
# needs: [archives, componentbext, windows_std_tools, osx]
# # This job should only run on a successful push to the main branch
# if: github.ref == 'refs/heads/main' && github.event_name == 'push'
# steps:
# # 1. Generate a short-lived token using the GitHub App
# - name: Generate GitHub App Token
# id: generate_token
# uses: tibdex/github-app-token@v2
# with:
# app_id: ${{ secrets.STABLESYNC_APP_ID }}
# private_key: ${{ secrets.STABLESYNC_APP_PRIVATE_KEY }}
# # 2. Checkout the repository using the App Token
# - name: Checkout Code
# uses: actions/checkout@v4
# with:
# token: ${{ steps.generate_token.outputs.token }}
# fetch-depth: 0 # Fetches all history to safely move branches
# # 3. Securely update and push the STABLE branch pointer
# - name: Update STABLE Branch Pointer
# run: |
# # Configure Git to identify as the GitHub Actions bot
# git config --global user.name "github-actions[bot]"
# git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
# echo "Updating STABLE branch to point to the latest successfully CI tested main SHA: ${{ github.sha }}"
# # Force the local STABLE branch to point directly to the current main SHA
# git branch -f STABLE ${{ github.sha }}
# # Push the updated stable branch reference securely back to origin
# git push origin STABLE --force
+352 -21
View File
@@ -15,16 +15,24 @@ on:
schedule:
- cron: '0 3 * * *' # daily at 03:00 UTC
workflow_dispatch:
inputs:
branch:
description: 'The branch ref to run the conditional jobs on (e.g., refs/heads/pgo)'
required: true
default: 'refs/heads/main'
type: string
env:
DEBIAN_FRONTEND: noninteractive
CLANG_VERSION: 18
jobs:
linux_bext:
name: Linux - BRL-CAD bext build
runs-on: ubuntu-latest
runs-on: &ubuntu_runner ubuntu-24.04 # Define the ubuntu runner anchor here
strategy:
fail-fast: true
env:
DEBIAN_FRONTEND: noninteractive
steps:
- name: Setup - CMake
uses: lukka/get-cmake@latest
@@ -225,12 +233,13 @@ jobs:
linux_static:
name: Clang Static Analyser
needs: [linux_bext]
runs-on: ubuntu-24.04
runs-on: *ubuntu_runner # Use the ubuntu runner alias
strategy:
fail-fast: true
env:
DEBIAN_FRONTEND: noninteractive
if: ${{ !cancelled() && github.ref == 'refs/heads/main' }}
if: |
!cancelled() &&
(github.event_name == 'workflow_dispatch' && github.ref == inputs.branch ||
github.event_name == 'schedule' && github.ref == 'refs/heads/main')
steps:
- name: Setup - CMake
uses: lukka/get-cmake@latest
@@ -239,19 +248,19 @@ jobs:
- name: Clang Static Analyzer Setup
run: |
sudo apt-get update
sudo apt-get install clang-18 clang-tools-18
sudo apt-get install -y clang-${{ env.CLANG_VERSION }} clang-tools-${{ env.CLANG_VERSION }}
# Install X/OpenGL dev pkgs
sudo apt-get install xserver-xorg-dev libx11-dev libxi-dev libxext-dev libglu1-mesa-dev libfontconfig-dev
sudo apt-get install -y xserver-xorg-dev libx11-dev libxi-dev libxext-dev libglu1-mesa-dev libfontconfig-dev
# Install tools
sudo apt-get install astyle re2c xsltproc libxml2-utils
sudo apt-get install -y astyle re2c xsltproc libxml2-utils
# Install dependency dev pkgs
sudo apt-get install zlib1g-dev libpng-dev libjpeg-dev libtiff-dev libeigen3-dev libgdal-dev libassimp-dev libopencv-dev tcl-dev tk-dev
sudo apt-get install -y zlib1g-dev libpng-dev libjpeg-dev libtiff-dev libeigen3-dev libgdal-dev libassimp-dev libopencv-dev tcl-dev tk-dev
# Install XCB/OpenGL dev pkgs for Qt - see:
# https://wiki.qt.io/Building_Qt_6_from_Git
# https://doc.qt.io/qt-6/linux-requirements.html
sudo apt-get install libfontconfig1-dev libfreetype6-dev libx11-dev libx11-xcb-dev libxext-dev libxfixes-dev libxi-dev libxrender-dev libxcb1-dev libxcb-cursor-dev libxcb-glx0-dev libxcb-keysyms1-dev libxcb-image0-dev libxcb-shm0-dev libxcb-icccm4-dev libxcb-sync-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-randr0-dev libxcb-render-util0-dev libxcb-util-dev libxcb-xinerama0-dev libxcb-xkb-dev libxcb-xinput-dev libxcb-xrm-dev libxkbcommon-dev libxkbcommon-x11-dev
sudo apt-get install libgl-dev
sudo apt-get install libinput-dev # Cleanup
sudo apt-get install -y libfontconfig1-dev libfreetype6-dev libx11-dev libx11-xcb-dev libxext-dev libxfixes-dev libxi-dev libxrender-dev libxcb1-dev libxcb-cursor-dev libxcb-glx0-dev libxcb-keysyms1-dev libxcb-image0-dev libxcb-shm0-dev libxcb-icccm4-dev libxcb-sync-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-randr0-dev libxcb-render-util0-dev libxcb-util-dev libxcb-xinerama0-dev libxcb-xkb-dev libxcb-xinput-dev libxcb-xrm-dev libxkbcommon-dev libxkbcommon-x11-dev
sudo apt-get install -y libgl-dev
sudo apt-get install -y libinput-dev
- name: Checkout
uses: actions/checkout@v6
@@ -261,7 +270,7 @@ jobs:
- name: Prepare bext dependencies
uses: ./brlcad/.github/actions/prepare-bext
with:
platform-channel: ubuntu-24.04-${{ runner.arch }}-release
platform-channel: ubuntu-*ubuntu_version-${{ runner.arch }}-release
source-dir: ${{ github.workspace }}/brlcad
output-dir: ${{ runner.temp }}/bext_output
build-type: Release
@@ -278,7 +287,7 @@ jobs:
env:
BRLCAD_EXT_DIR: ${{ runner.temp }}/bext_output
run: |
export PATH=$GITHUB_WORKSPACE:/usr/share/clang/scan-build-18/bin:/usr/share/clang/scan-build-18/libexec:$PATH
# The script will now receive the version number and find the tools itself
cp $GITHUB_WORKSPACE/brlcad/misc/clang-static-analyzer-run.sh .
./clang-static-analyzer-run.sh
working-directory: ${{ github.workspace }}/brlcad/build_clangstatic
@@ -290,17 +299,339 @@ jobs:
html2text scan-reports-all/*/index.html
working-directory: ${{ github.workspace }}/brlcad/build_clangstatic
linux_pgo:
name: GCC Profile-Guided Optimization
needs: [linux_bext]
runs-on: *ubuntu_runner # Use the ubuntu runner alias
timeout-minutes: 180
strategy:
fail-fast: true
env:
CMAKE_BUILD_PARALLEL_LEVEL: "2"
PGO_PATH: ${{ github.workspace }}/pgo-profile
PGO_BENCHMARK_TARGETS: >-
rt pixcmp benchmark_cp BENCHMARK_PIX_cp PIX_LOG_cp
bldg391.g m35.g moss.g sphflake.g star.g world.g
PGO_TRAINING_TARGETS: >-
havoc.g Generic_Twin.g NIST_MBE_PMI_2.g
PGO_TRAINING_SIZE: "512"
PGO_TRAINING_AZIMUTH: "35"
PGO_TRAINING_ELEVATION: "25"
PGO_BENCHMARK_TIMEFRAME: "8"
PGO_BENCHMARK_MAXTIME: "60"
PGO_BENCHMARK_AVERAGE: "3"
PGO_BENCHMARK_DEVIATION: "3"
PGO_MIN_SPEEDUP: "1.0"
if: |
!cancelled() &&
(github.event_name == 'workflow_dispatch' && github.ref == inputs.branch ||
github.event_name == 'schedule' && github.ref == 'refs/heads/main')
steps:
- name: Setup - CMake
uses: lukka/get-cmake@latest
- name: Install apt dependencies
run: |
sudo apt-get update
# Install X/OpenGL dev pkgs
sudo apt-get install xserver-xorg-dev libx11-dev libxi-dev libxext-dev libglu1-mesa-dev libfontconfig-dev
# Install tools
sudo apt-get install astyle re2c xsltproc libxml2-utils
# Install dependency dev pkgs
sudo apt-get install zlib1g-dev libpng-dev libjpeg-dev libtiff-dev libeigen3-dev libgdal-dev libassimp-dev libopencv-dev tcl-dev tk-dev
# Install XCB/OpenGL dev pkgs for a fallback bext build
sudo apt-get install libfontconfig1-dev libfreetype6-dev libx11-dev libx11-xcb-dev libxext-dev libxfixes-dev libxi-dev libxrender-dev libxcb1-dev libxcb-cursor-dev libxcb-glx0-dev libxcb-keysyms1-dev libxcb-image0-dev libxcb-shm0-dev libxcb-icccm4-dev libxcb-sync-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-randr0-dev libxcb-render-util0-dev libxcb-util-dev libxcb-xinerama0-dev libxcb-xkb-dev libxcb-xinput-dev libxcb-xrm-dev libxkbcommon-dev libxkbcommon-x11-dev
sudo apt-get install libgl-dev libinput-dev
- name: Checkout
uses: actions/checkout@v6
with:
path: brlcad
- name: Prepare bext dependencies
uses: ./brlcad/.github/actions/prepare-bext
with:
platform-channel: ubuntu-*ubuntu_version-${{ runner.arch }}-release
source-dir: ${{ github.workspace }}/brlcad
output-dir: ${{ runner.temp }}/bext_output
build-type: Release
artifact-token: ${{ secrets.BEXT_ARTIFACT_TOKEN }}
- name: Directory setup
run: |
set -euo pipefail
cmake -E make_directory $HOME/.cache/BRL-CAD
if [[ -e "$PGO_PATH" ]]; then
echo "::error::PGO profile path unexpectedly exists before the generation build"
exit 1
fi
- name: Configure baseline build
run: |
cmake -S brlcad -B build_pgo_baseline -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DBUILD_TESTING=ON \
-DBRLCAD_EXT_DIR=${{ runner.temp }}/bext_output \
-DBRLCAD_ENABLE_QT=OFF \
-DBRLCAD_ENABLE_STEP=ON \
-DBRLCAD_ENABLE_STRICT=ON
- name: Build baseline
run: cmake --build build_pgo_baseline --target $PGO_BENCHMARK_TARGETS
- name: Configure profile-generation build
run: |
cmake -S brlcad -B build_pgo -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DBUILD_TESTING=ON \
-DBRLCAD_EXT_DIR=${{ runner.temp }}/bext_output \
-DBRLCAD_ENABLE_QT=OFF \
-DBRLCAD_ENABLE_STEP=ON \
-DBRLCAD_ENABLE_STRICT=ON \
-DBRLCAD_PGO=ON
grep -q -- '-fprofile-generate=' build_pgo/compile_commands.json
- name: Build profile-generation binaries
run: cmake --build build_pgo --target $PGO_BENCHMARK_TARGETS $PGO_TRAINING_TARGETS
- name: Generate profile data
run: |
cmake \
-DPGO_MODE=TRAIN \
-DPGO_BUILD_DIR="$GITHUB_WORKSPACE/build_pgo" \
-DPGO_RESULTS_DIR="$GITHUB_WORKSPACE/pgo-training" \
-DPGO_PROFILE_DIR="$PGO_PATH" \
-DPGO_TIMEFRAME="$PGO_BENCHMARK_TIMEFRAME" \
-DPGO_MAXTIME="$PGO_BENCHMARK_MAXTIME" \
-DPGO_AVERAGE="$PGO_BENCHMARK_AVERAGE" \
-DPGO_DEVIATION="$PGO_BENCHMARK_DEVIATION" \
-DPGO_TRAINING_SIZE="$PGO_TRAINING_SIZE" \
-DPGO_TRAINING_AZIMUTH="$PGO_TRAINING_AZIMUTH" \
-DPGO_TRAINING_ELEVATION="$PGO_TRAINING_ELEVATION" \
-P brlcad/misc/CMake/scripts/pgo_benchmark.cmake
- name: Configure profile-use build
run: |
# GCC incorporates the build path into profile filenames, so both
# compilation passes must use the same build directory.
cmake -E rm -rf build_pgo
cmake -S brlcad -B build_pgo -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DBUILD_TESTING=ON \
-DBRLCAD_EXT_DIR=${{ runner.temp }}/bext_output \
-DBRLCAD_ENABLE_QT=OFF \
-DBRLCAD_ENABLE_STEP=ON \
-DBRLCAD_ENABLE_STRICT=ON \
-DBRLCAD_PGO=ON
grep -q -- '-fprofile-use=' build_pgo/compile_commands.json
- name: Build profile-guided binaries
run: cmake --build build_pgo --target $PGO_BENCHMARK_TARGETS
- name: Benchmark and compare profile-guided build
run: |
cmake \
-DPGO_MODE=COMPARE \
-DPGO_BASELINE_BUILD_DIR="$GITHUB_WORKSPACE/build_pgo_baseline" \
-DPGO_OPTIMIZED_BUILD_DIR="$GITHUB_WORKSPACE/build_pgo" \
-DPGO_RESULTS_DIR="$GITHUB_WORKSPACE/pgo-results" \
-DPGO_TIMEFRAME="$PGO_BENCHMARK_TIMEFRAME" \
-DPGO_MAXTIME="$PGO_BENCHMARK_MAXTIME" \
-DPGO_AVERAGE="$PGO_BENCHMARK_AVERAGE" \
-DPGO_DEVIATION="$PGO_BENCHMARK_DEVIATION" \
-DPGO_MIN_SPEEDUP="$PGO_MIN_SPEEDUP" \
-P brlcad/misc/CMake/scripts/pgo_benchmark.cmake
windows_pgo:
name: MSVC Profile-Guided Optimization
needs: [windows_bext]
runs-on: windows-latest
timeout-minutes: 180
strategy:
fail-fast: true
env:
CMAKE_BUILD_PARALLEL_LEVEL: "2"
PGO_BENCHMARK_TARGETS: >-
rt pixcmp benchmark_cp BENCHMARK_PIX_cp PIX_LOG_cp
bldg391.g m35.g moss.g sphflake.g star.g world.g
PGO_TRAINING_TARGETS: >-
havoc.g Generic_Twin.g NIST_MBE_PMI_2.g
PGO_TRAINING_SIZE: "512"
PGO_TRAINING_AZIMUTH: "35"
PGO_TRAINING_ELEVATION: "25"
PGO_BENCHMARK_TIMEFRAME: "8"
PGO_BENCHMARK_MAXTIME: "60"
PGO_BENCHMARK_AVERAGE: "3"
PGO_BENCHMARK_DEVIATION: "3"
PGO_MIN_SPEEDUP: "1.0"
if: |
!cancelled() &&
(github.event_name == 'workflow_dispatch' && github.ref == inputs.branch ||
github.event_name == 'schedule' && github.ref == 'refs/heads/main')
steps:
- name: Setup - CMake
uses: lukka/get-cmake@latest
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v3
- name: Add cl.exe to PATH
uses: ilammy/msvc-dev-cmd@v1
- name: Checkout
uses: actions/checkout@v6
with:
path: brlcad
- name: Prepare bext dependencies
uses: ./brlcad/.github/actions/prepare-bext
with:
platform-channel: windows-2025-${{ runner.arch }}-release
source-dir: ${{ github.workspace }}/brlcad
output-dir: ${{ runner.temp }}/bext_output
build-type: Release
artifact-token: ${{ secrets.BEXT_ARTIFACT_TOKEN }}
- name: Set up PGO paths
shell: pwsh
run: |
# Short build roots keep STEP-generated paths below Windows MAX_PATH.
$paths = @{
PGO_PATH = Join-Path $env:RUNNER_TEMP 'pgo-d'
PGO_BASELINE_BUILD_DIR = Join-Path $env:RUNNER_TEMP 'pgo-b'
PGO_GENERATE_BUILD_DIR = Join-Path $env:RUNNER_TEMP 'pgo-g'
PGO_OPTIMIZED_BUILD_DIR = Join-Path $env:RUNNER_TEMP 'pgo-u'
PGO_TRAINING_RESULTS_DIR = Join-Path $env:RUNNER_TEMP 'pgo-t'
PGO_RESULTS_DIR = Join-Path $env:RUNNER_TEMP 'pgo-r'
}
if (Test-Path -LiteralPath $paths.PGO_PATH) {
throw "PGO profile path unexpectedly exists before the generation build: $($paths.PGO_PATH)"
}
foreach ($path in $paths.GetEnumerator()) {
Add-Content -Path $env:GITHUB_ENV -Value "$($path.Key)=$($path.Value)"
}
- name: Configure baseline build
shell: pwsh
run: |
cmake -S brlcad -B "$env:PGO_BASELINE_BUILD_DIR" -G Ninja `
-DCMAKE_C_COMPILER=cl.exe `
-DCMAKE_CXX_COMPILER=cl.exe `
-DCMAKE_BUILD_TYPE=Release `
-DBUILD_TESTING=ON `
"-DBRLCAD_EXT_DIR=${{ runner.temp }}/bext_output" `
-DBRLCAD_ENABLE_QT=OFF `
-DBRLCAD_ENABLE_STEP=ON `
-DBRLCAD_ENABLE_STRICT=ON
- name: Build baseline
shell: pwsh
run: |
$targets = $env:PGO_BENCHMARK_TARGETS -split '\s+'
$arguments = @('--build', $env:PGO_BASELINE_BUILD_DIR, '--target') + $targets
cmake @arguments
- name: Configure profile-generation build
shell: pwsh
run: |
cmake -S brlcad -B "$env:PGO_GENERATE_BUILD_DIR" -G Ninja `
-DCMAKE_C_COMPILER=cl.exe `
-DCMAKE_CXX_COMPILER=cl.exe `
-DCMAKE_BUILD_TYPE=Release `
-DBUILD_TESTING=ON `
"-DBRLCAD_EXT_DIR=${{ runner.temp }}/bext_output" `
-DBRLCAD_ENABLE_QT=OFF `
-DBRLCAD_ENABLE_STEP=ON `
-DBRLCAD_ENABLE_STRICT=ON `
-DBRLCAD_PGO=ON
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
if (-not (Select-String -Path "$env:PGO_GENERATE_BUILD_DIR/build.ninja" -SimpleMatch '/GENPROFILE:PGD=' -Quiet)) {
throw 'The profile-generation build does not contain /GENPROFILE.'
}
- name: Build profile-generation binaries
shell: pwsh
run: |
$targets = ($env:PGO_BENCHMARK_TARGETS + ' ' + $env:PGO_TRAINING_TARGETS) -split '\s+'
$arguments = @('--build', $env:PGO_GENERATE_BUILD_DIR, '--target') + $targets
cmake @arguments
- name: Generate profile data
shell: pwsh
run: |
cmake `
-DPGO_MODE=TRAIN `
"-DPGO_BUILD_DIR=$env:PGO_GENERATE_BUILD_DIR" `
"-DPGO_RESULTS_DIR=$env:PGO_TRAINING_RESULTS_DIR" `
"-DPGO_PROFILE_DIR=$env:PGO_PATH" `
"-DPGO_TIMEFRAME=$env:PGO_BENCHMARK_TIMEFRAME" `
"-DPGO_MAXTIME=$env:PGO_BENCHMARK_MAXTIME" `
"-DPGO_AVERAGE=$env:PGO_BENCHMARK_AVERAGE" `
"-DPGO_DEVIATION=$env:PGO_BENCHMARK_DEVIATION" `
"-DPGO_TRAINING_SIZE=$env:PGO_TRAINING_SIZE" `
"-DPGO_TRAINING_AZIMUTH=$env:PGO_TRAINING_AZIMUTH" `
"-DPGO_TRAINING_ELEVATION=$env:PGO_TRAINING_ELEVATION" `
-P brlcad/misc/CMake/scripts/pgo_benchmark.cmake
- name: Configure profile-use build
shell: pwsh
run: |
cmake -S brlcad -B "$env:PGO_OPTIMIZED_BUILD_DIR" -G Ninja `
-DCMAKE_C_COMPILER=cl.exe `
-DCMAKE_CXX_COMPILER=cl.exe `
-DCMAKE_BUILD_TYPE=Release `
-DBUILD_TESTING=ON `
"-DBRLCAD_EXT_DIR=${{ runner.temp }}/bext_output" `
-DBRLCAD_ENABLE_QT=OFF `
-DBRLCAD_ENABLE_STEP=ON `
-DBRLCAD_ENABLE_STRICT=ON `
-DBRLCAD_PGO=ON
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
if (-not (Select-String -Path "$env:PGO_OPTIMIZED_BUILD_DIR/build.ninja" -SimpleMatch '/USEPROFILE:PGD=' -Quiet)) {
throw 'The profile-use build does not contain /USEPROFILE.'
}
- name: Build profile-guided binaries
shell: pwsh
run: |
$targets = $env:PGO_BENCHMARK_TARGETS -split '\s+'
$arguments = @('--build', $env:PGO_OPTIMIZED_BUILD_DIR, '--target') + $targets
cmake @arguments
- name: Benchmark and compare profile-guided build
shell: pwsh
run: |
cmake `
-DPGO_MODE=COMPARE `
"-DPGO_BASELINE_BUILD_DIR=$env:PGO_BASELINE_BUILD_DIR" `
"-DPGO_OPTIMIZED_BUILD_DIR=$env:PGO_OPTIMIZED_BUILD_DIR" `
"-DPGO_RESULTS_DIR=$env:PGO_RESULTS_DIR" `
"-DPGO_TIMEFRAME=$env:PGO_BENCHMARK_TIMEFRAME" `
"-DPGO_MAXTIME=$env:PGO_BENCHMARK_MAXTIME" `
"-DPGO_AVERAGE=$env:PGO_BENCHMARK_AVERAGE" `
"-DPGO_DEVIATION=$env:PGO_BENCHMARK_DEVIATION" `
"-DPGO_MIN_SPEEDUP=$env:PGO_MIN_SPEEDUP" `
-P brlcad/misc/CMake/scripts/pgo_benchmark.cmake
# See https://app.codecov.io/gh/BRL-CAD/brlcad for analysis results
linux_cov:
name: LCOV Coverage testing
needs: [linux_bext]
runs-on: ubuntu-24.04
runs-on: *ubuntu_runner # Use the ubuntu runner alias
strategy:
fail-fast: true
env:
DEBIAN_FRONTEND: noninteractive
if: ${{ !cancelled() && github.ref == 'refs/heads/main' }}
if: |
!cancelled() &&
(github.event_name == 'workflow_dispatch' && github.ref == inputs.branch ||
github.event_name == 'schedule' && github.ref == 'refs/heads/main')
steps:
- name: Setup - CMake
uses: lukka/get-cmake@latest
@@ -331,7 +662,7 @@ jobs:
- name: Prepare bext dependencies
uses: ./brlcad/.github/actions/prepare-bext
with:
platform-channel: ubuntu-24.04-${{ runner.arch }}-release
platform-channel: ubuntu-*ubuntu_version-${{ runner.arch }}-release
source-dir: ${{ github.workspace }}/brlcad
output-dir: ${{ runner.temp }}/bext_output
build-type: Release
+104 -6
View File
@@ -1338,6 +1338,90 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
brlcad_deferred_define("BRLCAD_HAS_BIG_STACK 1")
endif()
# Profile-Guided Optimization uses a shared profile location for both compiler
# families. A path that does not exist selects the instrumentation pass; an
# existing path selects the profile-use pass in a fresh build tree.
if("$ENV{PGO_PATH}" STREQUAL "")
set(BRLCAD_PGO_PATH "${CMAKE_BINARY_DIR}/profiling")
else()
file(TO_CMAKE_PATH "$ENV{PGO_PATH}" BRLCAD_PGO_PATH)
endif()
# MSVC creates one profile database per linked image. The target-dependent
# generator expression gives every executable and DLL a distinct database,
# while /GL also covers code contributed through object and static libraries.
if(MSVC AND BRLCAD_PGO)
if(DEFINED BRLCAD_MSVC_PGO_PHASE)
if(NOT "${BRLCAD_MSVC_PGO_PATH}" STREQUAL "${BRLCAD_PGO_PATH}")
message(
FATAL_ERROR
"PGO_PATH changed from '${BRLCAD_MSVC_PGO_PATH}' to '${BRLCAD_PGO_PATH}'. "
"Use a fresh build directory when changing PGO phases or profile locations."
)
endif()
elseif(EXISTS "${BRLCAD_PGO_PATH}")
set(BRLCAD_MSVC_PGO_PHASE USE)
else()
set(BRLCAD_MSVC_PGO_PHASE GENERATE)
endif()
if(NOT BRLCAD_MSVC_PGO_PHASE MATCHES "^(GENERATE|USE)$")
message(FATAL_ERROR "Invalid internal MSVC PGO phase '${BRLCAD_MSVC_PGO_PHASE}'")
endif()
set(BRLCAD_MSVC_PGO_PHASE "${BRLCAD_MSVC_PGO_PHASE}" CACHE INTERNAL "MSVC PGO build phase" FORCE)
set(BRLCAD_MSVC_PGO_PATH "${BRLCAD_PGO_PATH}" CACHE INTERNAL "MSVC PGO profile path" FORCE)
if(BRLCAD_MSVC_PGO_PHASE STREQUAL "GENERATE")
# link.exe requires the PGD parent directory to exist before it links the
# instrumented image. Cache the phase above so an in-place CMake rerun
# does not mistake this newly created directory for completed profiles.
file(MAKE_DIRECTORY "${BRLCAD_PGO_PATH}")
set(_brlcad_msvc_pgo_link_mode GENPROFILE)
message(
STATUS
"MSVC PGO generation enabled; set VCPROFILE_PATH=${BRLCAD_PGO_PATH} while running training workloads"
)
distclean("${BRLCAD_PGO_PATH}")
else()
message(STATUS "MSVC PGO profile use enabled from ${BRLCAD_PGO_PATH}")
endif()
add_compile_options("$<$<COMPILE_LANGUAGE:C,CXX>:/GL>")
add_link_options(/LTCG)
if(_brlcad_msvc_pgo_link_mode)
add_link_options(
"/${_brlcad_msvc_pgo_link_mode}:PGD=${BRLCAD_PGO_PATH}/$<TARGET_PROPERTY:NAME>.pgd"
)
endif()
unset(_brlcad_msvc_pgo_link_mode)
endif()
# Apply profile-use only to images exercised during training. link.exe emits
# C4961 for an untrained PGD, and strict builds promote that warning to an
# error during LTCG. The generation pass names PGC files after the target's
# PGD, so this check also works when OUTPUT_NAME changes the binary name.
function(_BRLCAD_MSVC_PGO_APPLY_PROFILES directory)
get_property(_brlcad_pgo_targets DIRECTORY "${directory}" PROPERTY BUILDSYSTEM_TARGETS)
foreach(_brlcad_pgo_target IN LISTS _brlcad_pgo_targets)
get_target_property(_brlcad_pgo_target_type ${_brlcad_pgo_target} TYPE)
if(_brlcad_pgo_target_type MATCHES "^(EXECUTABLE|SHARED_LIBRARY|MODULE_LIBRARY)$")
set(_brlcad_pgo_database "${BRLCAD_PGO_PATH}/${_brlcad_pgo_target}.pgd")
file(GLOB _brlcad_pgo_counts LIST_DIRECTORIES FALSE "${BRLCAD_PGO_PATH}/${_brlcad_pgo_target}![0-9]*.pgc")
if(EXISTS "${_brlcad_pgo_database}" AND _brlcad_pgo_counts)
target_link_options(${_brlcad_pgo_target} PRIVATE "/USEPROFILE:PGD=${_brlcad_pgo_database}")
set_property(GLOBAL APPEND PROPERTY BRLCAD_MSVC_PGO_TRAINED_TARGETS ${_brlcad_pgo_target})
endif()
unset(_brlcad_pgo_counts)
unset(_brlcad_pgo_database)
endif()
endforeach()
get_property(_brlcad_pgo_subdirectories DIRECTORY "${directory}" PROPERTY SUBDIRECTORIES)
foreach(_brlcad_pgo_subdirectory IN LISTS _brlcad_pgo_subdirectories)
_brlcad_msvc_pgo_apply_profiles("${_brlcad_pgo_subdirectory}")
endforeach()
endfunction()
# The following tests are almost entirely for gcc/llvm style flags, so
# we don't unnecessarily slow down the Windows build.
if(NOT MSVC)
@@ -1400,11 +1484,6 @@ if(NOT MSVC)
# variable set to a temp folder
set(pgo_flags_found "")
if("$ENV{PGO_PATH}" STREQUAL "")
set(BRLCAD_PGO_PATH "${CMAKE_BINARY_DIR}/profiling")
else(NOT "$ENV{PGO_PATH}" STREQUAL "")
set(BRLCAD_PGO_PATH "$ENV{PGO_PATH}")
endif("$ENV{PGO_PATH}" STREQUAL "")
if(BRLCAD_PGO AND NOT EXISTS "${BRLCAD_PGO_PATH}")
if("${CMAKE_C_FLAGS}" MATCHES ".*fprofile-use.*")
@@ -1450,7 +1529,8 @@ if(NOT MSVC)
# gcc-style USE
if("${pgo_flags_found}" STREQUAL "")
cmake_push_check_state()
set(flags "fprofile-use=\"${BRLCAD_PGO_PATH}\" -Wno-error=coverage-mismatch -fprofile-correction")
set(flags "fprofile-use=\"${BRLCAD_PGO_PATH}\" -Wno-error=coverage-mismatch")
string(APPEND flags " -Wno-error=missing-profile -fprofile-correction")
set(CMAKE_REQUIRED_FLAGS "-${flags}")
check_c_flag("${flags}" GROUPS OPTIMIZE_C_FLAGS VARS pgo_flags_found)
check_cxx_flag("${flags}" GROUPS OPTIMIZE_CXX_FLAGS)
@@ -2973,6 +3053,24 @@ verbose_add_subdirectory("" doc)
# NOTE: misc/tools is added separately (earlier)
verbose_add_subdirectory("" misc)
if(MSVC AND BRLCAD_PGO AND BRLCAD_MSVC_PGO_PHASE STREQUAL "USE")
set_property(GLOBAL PROPERTY BRLCAD_MSVC_PGO_TRAINED_TARGETS)
_brlcad_msvc_pgo_apply_profiles("${CMAKE_SOURCE_DIR}")
get_property(_brlcad_msvc_pgo_targets GLOBAL PROPERTY BRLCAD_MSVC_PGO_TRAINED_TARGETS)
list(LENGTH _brlcad_msvc_pgo_targets _brlcad_msvc_pgo_target_count)
if(_brlcad_msvc_pgo_target_count EQUAL 0)
message(
FATAL_ERROR
"No MSVC PGO counter files were found in ${BRLCAD_PGO_PATH}. "
"Set VCPROFILE_PATH to that directory while running the training workloads "
"before configuring the profile-use build."
)
endif()
message(STATUS "MSVC PGO will use training data for ${_brlcad_msvc_pgo_target_count} linked targets")
unset(_brlcad_msvc_pgo_target_count)
unset(_brlcad_msvc_pgo_targets)
endif()
#------------------------------------------------------------------------------
# CMake package configuration files for modern find_package(BRLCAD CONFIG)
#
+1
View File
@@ -38,6 +38,7 @@ dependencies also receive substantial updates.
* fixed mged view panning, edit rotation, & ray queries - Cliff Yapp
* preserved ARB, CLINE, TGC, & extrusion geometry edits - Cliff Yapp
* restored mged command prompt focus at GUI startup - Cliff Yapp
* new documentation on geometry representation schemes - Sean Morrison
* improved pixscale & bwscale scaling and image piping - Sean Morrison
* restored pixdiff behavior on .pix, added -b for .bw - Sean Morrison
* improved facetize fallback methods and reliability - Cliff Yapp
-9
View File
@@ -2668,15 +2668,6 @@ DOCUMENTATION
Comparison of how BRL-CAD overlaps feature-wise with various
major commercial CAD systems.
* Solid Geometry Representation Comparisons
Describe the implications, tradeoffs, and conversion issues
involved when converting between different geometry
representations. Includes describing implicit geometry,
explicit polygonal boundary representations, explicit spline
surface representations, volumetric models, wireframes,
boolean operations, feature edit operations, and parametric
modeling.
* BRL-CAD Open Source
An overview of how BRL-CAD is organized as an open source
project and community describing the developer meritocracy,
+62
View File
@@ -20,6 +20,7 @@ set(
procedural_geometry.adoc
projection_shader.adoc
renderer_showcase.adoc
solid_geometry_comparison.adoc
taxonomy.adoc
tire.adoc
)
@@ -227,6 +228,17 @@ set(
images/renderer_gallery_rthide.png
images/renderer_gallery_rtdepth.png
images/renderer_gallery_rtxray.png
images/solid_rep_csg.png
images/solid_rep_brep_boolean.png
images/solid_rep_brep_evaluated.png
images/solid_rep_polygonal_boolean.png
images/solid_rep_nmg.png
images/solid_rep_bot.png
images/solid_rep_revolve.png
images/solid_rep_voxel_cells.png
images/solid_rep_volume.png
images/solid_rep_point_cloud.png
images/solid_rep_wireframe.png
images/adrt_castle_path.png
images/adrt_castle_rt.png
images/adrt_castle_rt_ao.png
@@ -409,6 +421,54 @@ if(
set_target_properties(renderer-gallery-snapshot PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD 1)
endif()
# Solid geometry comparison -- opt-in regeneration of committed snapshots.
#
# The article and solid_rep_*.png images are normal committed documentation.
# This target rebuilds the common pawn, derives each representation with the
# built tools, and refreshes the images for review.
option(
BRLCAD_GENERATE_SOLID_REPRESENTATION_COMPARISON
"Regenerate the solid geometry representation comparison snapshots"
OFF
)
if(
BRLCAD_GENERATE_SOLID_REPRESENTATION_COMPARISON
AND TARGET mged
AND TARGET rt
AND TARGET g-voxel
AND TARGET asc2pix
AND TARGET fbclear
AND TARGET plot3-fb
AND TARGET fb-png
)
add_custom_target(
solid-representation-comparison-snapshot
COMMAND
${CMAKE_COMMAND}
-DMGED=$<TARGET_FILE:mged>
-DRT=$<TARGET_FILE:rt>
-DGVOXEL=$<TARGET_FILE:g-voxel>
-DASC2PIX=$<TARGET_FILE:asc2pix>
-DFBCLEAR=$<TARGET_FILE:fbclear>
-DPLOT3FB=$<TARGET_FILE:plot3-fb>
-DFBPNG=$<TARGET_FILE:fb-png>
-DMODEL_SCRIPT=${CMAKE_CURRENT_SOURCE_DIR}/solid_geometry_comparison/create_pawn.tcl
-DIMGDIR=${CMAKE_CURRENT_SOURCE_DIR}/images
-DWORKDIR=${CMAKE_CURRENT_BINARY_DIR}/solid_representation_comparison_work
-DSIZE=512
-P ${CMAKE_CURRENT_SOURCE_DIR}/solid_geometry_comparison/generate_comparison.cmake
DEPENDS mged rt g-voxel asc2pix fbclear plot3-fb fb-png
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMENT "Regenerating solid geometry representation comparison snapshots"
VERBATIM
USES_TERMINAL
)
set_target_properties(
solid-representation-comparison-snapshot
PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD 1
)
endif()
# ADRT path-tracing gallery -- opt-in regeneration of the committed snapshot.
#
# Like the galleries above, the adrt_pathtrace.adoc article is hand-written and
@@ -464,6 +524,8 @@ cmakefiles(
gallery/generate_gallery.cmake
renderer_gallery/renderer_gallery_manifest.cmake
renderer_gallery/generate_renderer_gallery.cmake
solid_geometry_comparison/create_pawn.tcl
solid_geometry_comparison/generate_comparison.cmake
adrt_gallery/generate_adrt_gallery.cmake
gallery/inputs/pipe.stdin
gallery/inputs/wire.stdin
Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

@@ -0,0 +1,294 @@
= Solid Geometry Representation Comparisons
:doctype: article
:toc:
:toclevels: 2
The same shape can be stored in very different ways. This comparison uses a
purpose-built chess pawn to show which information BRL-CAD retains, evaluates,
or samples when moving among its principal geometry representations. It is a
guide to semantics and conversion choices, not a claim that one representation
is universally best.
== Common model and method
The reference pawn is a region containing seven overlapping ellipsoids. Their
union is non-trivial enough to exercise curved surfaces, overlapping interiors,
and several component boundaries while remaining reliable across the
conversions shown here. The sketch-and-revolve version independently follows
the same nominal profile; it is not an exact conversion of the seven-piece CSG
model.
Every shaded image uses the same orthographic view, 512x512 image size,
material, and single-threaded renderer. The wireframe uses the same view and
image size. The faceting and sampling settings are intentionally coarse enough
to remain visible. A similar silhouette therefore does not mean that two
database objects have the same structure or numerical boundary.
NOTE: This article and its images are a committed snapshot. Developers can
regenerate the figures from built BRL-CAD tools by configuring with
`-DBRLCAD_GENERATE_SOLID_REPRESENTATION_COMPARISON=ON` and building the
`solid-representation-comparison-snapshot` target.
== At a glance
[cols="2,2,1,2,4",options="header"]
|===
|Form
|BRL-CAD storage
|Solid?
|Boolean state
|Editing and conversion consequences
|Analytic CSG
|Seven `ell` leaves in a combination
|Yes
|Retained
|Compact primitive parameters and tree edits preserve construction intent.
|B-rep tree
|Closed `brep` leaves in a combination
|Yes
|Retained
|NURBS surfaces and trims replace analytic leaves, but the assembly tree remains.
|Evaluated B-rep
|One closed `brep`
|Yes
|Evaluated
|Direct face, edge, trim, and control-point editing is possible; original
features and hierarchy are gone.
|Polygonal tree
|Solid-mode `bot` leaves in a combination
|Yes
|Retained
|Leaf tessellation is approximate, but components and their union tree remain
independently replaceable.
|Evaluated NMG
|One `nmg`
|Yes
|Evaluated
|Stores explicit, topology-rich polygonal boundaries and can record
non-manifold relationships.
|Evaluated triangle mesh
|One solid-mode `bot`
|Yes
|Evaluated
|Portable triangle connectivity, but curved geometry and construction history
have been sampled away.
|Sketch and revolve
|One `sketch` referenced by one `revolve`
|Yes
|Not needed here
|The profile, axis, and sweep angle are compact, meaningful parameters.
|Voxel cells
|A region containing many `arb8` boxes
|Yes (sampled)
|Cell union retained
|Individual cells are ordinary CSG geometry; size grows rapidly as cells
shrink.
|Sampled volume
|A file-backed `vol` scalar grid
|Yes (thresholded)
|None
|Compact regular-grid occupancy; resolution and threshold determine the boundary.
|Point cloud
|One `pnts` object with positions and normals
|No pawn interior
|None
|Useful as measured or sampled evidence, but it has no inherent connected
boundary or inside/outside rule.
|Wireframe
|Derived line display of the CSG object
|No
|Uses source tree
|A view-dependent drawing aid, not an independent solid representation.
|===
"Solid" in this table means that the stored object supplies usable
inside/outside semantics for the pawn. An arbitrary B-rep or mesh is not
automatically a solid: its boundary must be closed and consistently oriented.
The point size used to render `pnts` makes each sample a small sphere, but those
spheres do not reconstruct the pawn interior. A wireframe likewise does not
become a solid merely because its curves appear closed in one view.
== Implicit and parametric forms
[cols="1,1"]
[%noheader]
|===
|image:images/solid_rep_csg.png[Analytic CSG pawn,512]
|image:images/solid_rep_revolve.png[Sketch and revolve pawn,512]
|*Analytic CSG.* Native primitives retain their equations and dimensions; the
region evaluates their union during ray tracing.
|*Sketch plus revolve.* A closed line-segment profile is swept through 360
degrees. The stepped profile is intentional and remains editable in two
dimensions.
|===
Analytic CSG is an implicit representation: intersections with ideal primitive
surfaces are computed as needed rather than read from a stored boundary mesh.
It is usually the most compact form here and makes edits such as changing the
head radius or stem proportions direct. Its Boolean hierarchy also records why the
shape was assembled.
`sketch` plus `revolve` is another procedural, parameterized description. It
works especially well for an axisymmetric part, although a sketch with only
line segments produces conical and cylindrical bands rather than the pawn's
original ellipsoidal surfaces. BRL-CAD supports primitive parameters, placement
matrices, sketches, and sweeps; that should not be confused with a general
constraint solver or an associative feature-history system.
== Spline boundary representations
[cols="1,1"]
[%noheader]
|===
|image:images/solid_rep_brep_boolean.png[B-rep leaves with Boolean tree,512]
|image:images/solid_rep_brep_evaluated.png[Single evaluated B-rep,512]
|*B-rep with Booleans.* Each analytic leaf becomes a closed B-rep whose
boundaries are trimmed NURBS surfaces. The original union tree is retained.
|*B-rep without Booleans.* Boolean evaluation produces one explicit boundary;
operand identities and the construction tree no longer exist as editable
features.
|===
A B-rep stores faces, trims, edges, vertices, and their topology explicitly.
Rational NURBS can represent the source quadrics exactly as surfaces, but that
does not make every CSG-to-B-rep conversion exact: intersections, trimming,
topology classification, and repair use numerical tolerances. Keeping a tree
of B-rep leaves isolates conversion to each primitive and retains high-level
editing. Evaluating the tree yields a more self-contained boundary for
exchange and face-level work, at the cost of that history. This snapshot's
evaluated result passes both `brep ... valid` and `brep ... solid`; conversion
results should be checked rather than assumed to be closed.
== Polygonal boundaries
[cols="1,1,1"]
[%noheader]
|===
|image:images/solid_rep_polygonal_boolean.png[BoT leaves with Boolean tree,512]
|image:images/solid_rep_nmg.png[Evaluated NMG pawn,512]
|image:images/solid_rep_bot.png[Evaluated BoT pawn,512]
|*Polygonal with Booleans.* Each leaf is independently tessellated to a BoT,
then placed in the original union tree.
|*Evaluated NMG.* One non-manifold geometry object records the evaluated
polygonal boundary and its richer topology.
|*Evaluated triangle mesh.* One solid, counter-clockwise BoT stores 982
vertices and 1,960 triangular faces in this snapshot.
|===
All three images expose the same coarse tessellation. Their similar appearance
hides an important distinction: the first still has seven editable operands,
whereas the NMG and single BoT are evaluated results. NMG can represent
polygonal faces and non-manifold adjacency; BoT faces are triangles only.
Neither recreates analytic curvature when converted back. Finer tessellation
reduces visible error but increases storage and processing cost.
== Sampled forms
[cols="1,1,1"]
[%noheader]
|===
|image:images/solid_rep_voxel_cells.png[Pawn represented by CSG voxel cells,512]
|image:images/solid_rep_volume.png[Pawn represented by a VOL grid,512]
|image:images/solid_rep_point_cloud.png[Pawn represented by surface points,512]
|*Explicit voxel cells.* `voxelize` tests 3 mm cells and creates a CSG union of
699 boxes for cells exceeding the occupancy threshold.
|*Sampled volume.* `g-voxel` samples a 2 mm grid; a file-backed `vol` interprets
the resulting 17x17x30 byte array through an occupancy window.
|*Point cloud.* `pnts gen` samples the analytic surface on a 2 mm grid. Small
rendered spheres make the otherwise zero-area sample locations visible.
|===
The two voxel images are deliberately separate. The `voxelize` command emits
ordinary box primitives and a combination, while `vol` reads scalar samples on
a regular grid and derives a threshold boundary at ray-trace time. Both are
volumetric approximations whose accuracy, memory, and feature size depend on
cell spacing. A point cloud samples only the surface and has no connectivity;
surface reconstruction requires additional assumptions and may invent or omit
geometry.
== Wireframe display
image::images/solid_rep_wireframe.png[Wireframe display of the analytic CSG pawn,512]
This wireframe is a view-plane plot of MGED's unevaluated display of the
analytic CSG pawn; hidden lines are not removed. Wireframes are useful for
selection, orientation, and seeing component curves, but they are ambiguous:
curves alone do not say which side is material, which loops bound faces, or
which surfaces enclose volume. Keep the solid that generated the display
rather than treating the drawing as an equivalent conversion target.
== Conversion and editing consequences
A conversion command is not an equivalence guarantee. Before replacing source
data, check at least these effects:
* *Tolerance and resolution:* B-rep Boolean evaluation uses intersection and
topology tolerances; tessellation uses chord and angular tolerances; voxels
and points use sample spacing and thresholds. Each can erase a feature
smaller than its controlling tolerance.
* *Construction intent:* evaluating a Boolean tree discards operand identity,
primitive parameters, and ordering. Converting leaves while retaining the
tree keeps more of that intent.
* *Topology:* a visually plausible B-rep or mesh may contain gaps, duplicate
faces, self-intersections, or inconsistent orientation. Validate closure
and ray behavior, not only a shaded image.
* *Attributes:* collapsing a hierarchy may change where region identifiers,
materials, colors, and other attributes reside. Verify them separately
from geometry.
* *Round trips:* CSG to mesh to B-rep does not recover the original equations;
points to a surface requires reconstruction; resampling a volume compounds
discretization. Retain the highest-level trustworthy source.
For parameter changes and repeated design work, prefer analytic primitives,
sketch sweeps, and preserved combination trees. Prefer a validated B-rep when
trimmed-surface topology or exchange requires it. Use NMG for topology-rich
polygonal processing and BoT for triangle-oriented interchange or algorithms.
Use voxels for occupancy operations and point clouds for measured or sampled
data, not as silent substitutes for a watertight solid.
BRL-CAD also supports specialized forms such as DSP height fields, EBM
extrusions, ARS surfaces, metaballs, and legacy `poly` and `bspline` objects.
They are not shown as interchangeable pawn encodings: DSP and EBM are
domain-specific, metaballs change the modeling method, and BoT and B-rep are the
preferred general successors to the legacy polygon and spline forms.
== Reproducing the conversions
The snapshot generator creates the database and runs these principal MGED
operations (names shortened here only where noted):
[source]
----
brep comparison.csg.r brep --no-evaluation
brep comparison.csg.r brep comparison.brep_evaluated.s
facetize -n comparison.csg.r comparison.nmg.s
facetize --methods NMG comparison.csg.r comparison.bot.s
facetize --methods NMG base.s base.bot # repeated for all seven leaves
voxelize -s "3 3 3" -d 4 -t 0.45 comparison.voxel_cells.r comparison.csg.r
pnts gen -t 2 --surface --grid comparison.csg.r comparison.samples
pnts write comparison.samples comparison.xyz
pnts read -f xyzijk --size 0.45 comparison.xyz comparison.point_cloud.s
----
The generator also runs `g-voxel` and expands its sparse occupied-cell report
into the x-fastest byte array used by `vol`. It then renders every shaded
object with `rt -B -M -P1` and the saved view; MGED's `plot -2d`, `plot3-fb`,
and `fb-png` produce the wireframe image. See
`solid_geometry_comparison/generate_comparison.cmake` and
`solid_geometry_comparison/create_pawn.tcl` for the complete reproducible
inputs.
@@ -0,0 +1,24 @@
# Create the shared pawn used by the solid-representation comparison.
title {Solid geometry representation comparison pawn}
units mm
# Deliberate overlaps keep the pawn connected while exercising curved-surface
# intersections during Boolean evaluation.
db put base.s ell V {0 0 4.5} A {17 0 0} B {0 17 0} C {0 0 4.5}
db put base_bead.s ell V {0 0 9.5} A {14 0 0} B {0 14 0} C {0 0 5}
db put body.s ell V {0 0 18} A {12 0 0} B {0 12 0} C {0 0 9}
db put stem.s ell V {0 0 29} A {7 0 0} B {0 7 0} C {0 0 13}
db put collar.s ell V {0 0 39} A {9 0 0} B {0 9 0} C {0 0 3.5}
db put neck.s ell V {0 0 43} A {6.5 0 0} B {0 6.5 0} C {0 0 5}
db put head.s ell V {0 0 50} A {9 0 0} B {0 9 0} C {0 0 9}
db put comparison.csg.r comb region yes id 1000 los 100 GIFTmater 1 rgb {235 190 90} shader {plastic} tree {u {u {u {l base.s} {l base_bead.s}} {u {l body.s} {l stem.s}}} {u {u {l collar.s} {l neck.s}} {l head.s}}}
# The revolve uses a deliberately independent outline. Its single closed
# sketch approximates the same pawn silhouette without copying the CSG tree.
db put comparison.profile sketch V {0 0 0} A {1 0 0} B {0 0 1} VL {{0 0} {17 0} {17 2} {16.3 4} {15 6} {14 8} {15.4 7} {16 9} {15.4 11} {13.5 12.5} {11.2 14} {12 18} {11 21} {9.5 23} {8 26} {7.5 32} {6.5 36} {7.5 37} {9 39} {7.5 41} {6.5 42} {6.5 43} {7.5 44} {8.5 46} {9 50} {8.5 54} {6.7 56.5} {0 59}} SL {{line S 0 E 1} {line S 1 E 2} {line S 2 E 3} {line S 3 E 4} {line S 4 E 5} {line S 5 E 6} {line S 6 E 7} {line S 7 E 8} {line S 8 E 9} {line S 9 E 10} {line S 10 E 11} {line S 11 E 12} {line S 12 E 13} {line S 13 E 14} {line S 14 E 15} {line S 15 E 16} {line S 16 E 17} {line S 17 E 18} {line S 18 E 19} {line S 19 E 20} {line S 20 E 21} {line S 21 E 22} {line S 22 E 23} {line S 23 E 24} {line S 24 E 25} {line S 25 E 26} {line S 26 E 27} {line S 27 E 0}}
db put comparison.revolve.s revolve V {0 0 0} axis {0 0 1} R {1 0 0} ang 6.283185307179586 sk_name comparison.profile
db put comparison.revolve.r comb region yes id 1001 los 100 GIFTmater 1 rgb {235 190 90} shader {plastic} tree {l comparison.revolve.s}
q
@@ -0,0 +1,372 @@
# Regenerate the solid geometry representation comparison snapshots.
#
# The article is hand-written. This script builds its common pawn model,
# derives each representation using BRL-CAD tools, and replaces the committed
# images. All databases and sidecar data remain in WORKDIR.
cmake_minimum_required(VERSION 3.20)
foreach(v MGED RT GVOXEL ASC2PIX FBCLEAR PLOT3FB FBPNG
MODEL_SCRIPT IMGDIR WORKDIR)
if(NOT DEFINED ${v})
message(FATAL_ERROR "generate_comparison: -D${v}=... is required")
endif()
endforeach()
if(NOT DEFINED SIZE)
set(SIZE 512)
endif()
set(RENDER_TIMEOUT 1800)
set(PAWN_DB "${WORKDIR}/solid_representation_pawn.g")
set(VIEW_SCRIPT "${WORKDIR}/pawn.view")
set(MGED_SCRIPT "${WORKDIR}/assemble_variants.tcl")
set(VOXELIZE_SCRIPT "${WORKDIR}/voxelize.tcl")
set(VOXEL_LIST "${WORKDIR}/voxels1.txt")
set(VOLUME_ASCII "${WORKDIR}/comparison_volume.asc")
set(VOLUME_FILE "${WORKDIR}/comparison.vol")
file(REMOVE_RECURSE "${WORKDIR}")
file(MAKE_DIRECTORY "${WORKDIR}")
file(MAKE_DIRECTORY "${IMGDIR}")
function(run_process label)
execute_process(
COMMAND ${ARGN}
WORKING_DIRECTORY "${WORKDIR}"
TIMEOUT ${RENDER_TIMEOUT}
RESULT_VARIABLE process_result
OUTPUT_VARIABLE process_stdout
ERROR_VARIABLE process_stderr
)
if(NOT process_result EQUAL 0)
message(
FATAL_ERROR
"${label} failed (exit ${process_result})\n${process_stdout}${process_stderr}"
)
endif()
message(STATUS "${label}")
endfunction()
function(require_brep_property object property expected_output)
execute_process(
COMMAND "${MGED}" --no-rc -c "${PAWN_DB}" brep "${object}" "${property}"
WORKING_DIRECTORY "${WORKDIR}"
TIMEOUT ${RENDER_TIMEOUT}
RESULT_VARIABLE property_result
OUTPUT_VARIABLE property_stdout
ERROR_VARIABLE property_stderr
)
set(property_output "${property_stdout}${property_stderr}")
if(NOT property_result EQUAL 0 OR NOT property_output MATCHES "${expected_output}")
message(
FATAL_ERROR
"B-rep ${object} is not ${property}\n${property_output}"
)
endif()
message(STATUS "Validated B-rep ${object}: ${property}")
endfunction()
execute_process(
COMMAND "${MGED}" --no-rc -c "${PAWN_DB}"
WORKING_DIRECTORY "${WORKDIR}"
TIMEOUT ${RENDER_TIMEOUT}
INPUT_FILE "${MODEL_SCRIPT}"
RESULT_VARIABLE create_result
OUTPUT_VARIABLE create_stdout
ERROR_VARIABLE create_stderr
)
if(NOT create_result EQUAL 0 OR NOT EXISTS "${PAWN_DB}")
message(
FATAL_ERROR
"Creating the comparison pawn failed (exit ${create_result})\n${create_stdout}${create_stderr}"
)
endif()
# Preserve one Boolean hierarchy as B-rep leaves, and evaluate another into a
# single B-rep. The default suffix is intentional: supplying an explicit
# suffix changes parsing in older MGED versions.
set(PAWN_LEAVES base base_bead body stem collar neck head)
run_process(
"Created Boolean tree of B-rep leaves"
"${MGED}" --no-rc -c "${PAWN_DB}" brep comparison.csg.r brep --no-evaluation
)
foreach(leaf IN LISTS PAWN_LEAVES)
require_brep_property("${leaf}.s.brep" solid "brep is solid")
endforeach()
run_process(
"Created evaluated B-rep"
"${MGED}" --no-rc -c "${PAWN_DB}" brep comparison.csg.r brep comparison.brep_evaluated.s
)
require_brep_property(comparison.brep_evaluated.s valid "brep is valid")
require_brep_property(comparison.brep_evaluated.s solid "brep is solid")
# Generate both the topology-rich NMG form and the triangle-only BoT form.
run_process(
"Created evaluated NMG"
"${MGED}" --no-rc -c "${PAWN_DB}" facetize -n comparison.csg.r comparison.nmg.s
)
run_process(
"Created evaluated BoT"
"${MGED}" --no-rc -c "${PAWN_DB}" facetize --methods NMG comparison.csg.r comparison.bot.s
)
# Tessellating leaves independently preserves the editable Boolean hierarchy.
foreach(leaf IN LISTS PAWN_LEAVES)
run_process(
"Tessellated ${leaf}.s"
"${MGED}" --no-rc -c "${PAWN_DB}" facetize --methods NMG "${leaf}.s" "${leaf}.bot"
)
endforeach()
file(WRITE "${VOXELIZE_SCRIPT}" "voxelize -s {3 3 3} -d 4 -t 0.45 comparison.voxel_cells.r comparison.csg.r\nq\n")
execute_process(
COMMAND "${MGED}" --no-rc -c "${PAWN_DB}"
WORKING_DIRECTORY "${WORKDIR}"
TIMEOUT ${RENDER_TIMEOUT}
INPUT_FILE "${VOXELIZE_SCRIPT}"
RESULT_VARIABLE voxelize_result
OUTPUT_VARIABLE voxelize_stdout
ERROR_VARIABLE voxelize_stderr
)
if(NOT voxelize_result EQUAL 0)
message(
FATAL_ERROR
"Creating explicit voxel cells failed (exit ${voxelize_result})\n${voxelize_stdout}${voxelize_stderr}"
)
endif()
message(STATUS "Created explicit voxel cells")
# PNTS stores point size at import time. The generator first writes all
# sampled fields (xyzijk), then reads them back with a visible point diameter.
run_process(
"Sampled pawn surface"
"${MGED}" --no-rc -c "${PAWN_DB}" pnts gen -t 2 --surface --grid
comparison.csg.r comparison.samples
)
run_process(
"Exported pawn point samples"
"${MGED}" --no-rc -c "${PAWN_DB}" pnts write comparison.samples comparison.xyz
)
run_process(
"Created visible point cloud"
"${MGED}" --no-rc -c "${PAWN_DB}" pnts read -f xyzijk --size 0.45
comparison.xyz comparison.point_cloud.s
)
# g-voxel reports occupied cells. Expand that sparse report into the regular,
# x-fastest byte array required by VOL. The pawn bounds are [-17,17] in X/Y
# and [0,60] in Z; 2 mm cells therefore form a 17 x 17 x 30 grid.
file(REMOVE "${VOXEL_LIST}")
run_process(
"Sampled VOL grid"
"${GVOXEL}" -s "2 2 2" -d 4 -t 0.5 "${PAWN_DB}" comparison.csg.r
)
if(NOT EXISTS "${VOXEL_LIST}")
message(FATAL_ERROR "g-voxel did not create ${VOXEL_LIST}")
endif()
file(STRINGS "${VOXEL_LIST}" voxel_lines)
list(LENGTH voxel_lines occupied_voxels)
if(occupied_voxels EQUAL 0)
message(FATAL_ERROR "g-voxel reported no occupied cells")
endif()
foreach(line IN LISTS voxel_lines)
string(
REGEX MATCH
"^\\((-?[0-9]+)\\.0+, (-?[0-9]+)\\.0+, (-?[0-9]+)\\.0+\\)[ \t]+[^ \t]+[ \t]+[0-9.]+$"
parsed "${line}"
)
if(NOT parsed)
message(FATAL_ERROR "Cannot parse g-voxel output: ${line}")
endif()
set(x_coord "${CMAKE_MATCH_1}")
set(y_coord "${CMAKE_MATCH_2}")
set(z_coord "${CMAKE_MATCH_3}")
math(EXPR x_index "(${x_coord} + 16) / 2")
math(EXPR y_index "(${y_coord} + 16) / 2")
math(EXPR z_index "(${z_coord} - 1) / 2")
if(x_index LESS 0 OR x_index GREATER 16 OR
y_index LESS 0 OR y_index GREATER 16 OR
z_index LESS 0 OR z_index GREATER 29)
message(FATAL_ERROR "g-voxel coordinate outside the expected pawn grid: ${line}")
endif()
set("voxel_${x_index}_${y_index}_${z_index}" 255)
endforeach()
set(volume_text "")
foreach(z_index RANGE 0 29)
foreach(y_index RANGE 0 16)
foreach(x_index RANGE 0 16)
if(DEFINED voxel_${x_index}_${y_index}_${z_index})
set(sample ff)
else()
set(sample 00)
endif()
string(APPEND volume_text "${sample}\n")
endforeach()
endforeach()
endforeach()
file(WRITE "${VOLUME_ASCII}" "${volume_text}")
execute_process(
COMMAND "${ASC2PIX}"
WORKING_DIRECTORY "${WORKDIR}"
TIMEOUT ${RENDER_TIMEOUT}
INPUT_FILE "${VOLUME_ASCII}"
OUTPUT_FILE "${VOLUME_FILE}"
RESULT_VARIABLE asc2pix_result
ERROR_VARIABLE asc2pix_error
)
if(NOT asc2pix_result EQUAL 0)
message(FATAL_ERROR "asc2pix failed (exit ${asc2pix_result})\n${asc2pix_error}")
endif()
file(SIZE "${VOLUME_FILE}" volume_size)
if(NOT volume_size EQUAL 8670)
message(FATAL_ERROR "VOL sidecar is ${volume_size} bytes; expected 8670")
endif()
message(STATUS "Encoded ${occupied_voxels} occupied VOL cells")
# Wrap single primitives in regions so every shaded comparison uses the same
# material. The polygonal tree repeats the original CSG union hierarchy with
# BoT leaves. VOL references a sidecar basename resolved beside the database.
file(WRITE "${MGED_SCRIPT}" [=[
db put comparison.brep_evaluated.r comb region yes id 1010 los 100 GIFTmater 1 rgb {235 190 90} shader {plastic} tree {l comparison.brep_evaluated.s}
db put comparison.nmg.r comb region yes id 1020 los 100 GIFTmater 1 rgb {235 190 90} shader {plastic} tree {l comparison.nmg.s}
db put comparison.bot.r comb region yes id 1030 los 100 GIFTmater 1 rgb {235 190 90} shader {plastic} tree {l comparison.bot.s}
db put comparison.polygonal_boolean.r comb region yes id 1040 los 100 GIFTmater 1 rgb {235 190 90} shader {plastic} tree {u {u {u {l base.bot} {l base_bead.bot}} {u {l body.bot} {l stem.bot}}} {u {u {l collar.bot} {l neck.bot}} {l head.bot}}}
attr set comparison.voxel_cells.r region R region_id 1050 material_id 1 los 100 shader plastic color 235/190/90
db put comparison.point_cloud.r comb region yes id 1060 los 100 GIFTmater 1 rgb {235 190 90} shader {plastic} tree {l comparison.point_cloud.s}
db put comparison.volume.s vol file {comparison.vol} name {comparison.vol} src f w 17 n 17 d 30 lo 1 hi 255 size {2 2 2} mat {1 0 0 -17 0 1 0 -17 0 0 1 0 0 0 0 1}
db put comparison.volume.r comb region yes id 1070 los 100 GIFTmater 1 rgb {235 190 90} shader {plastic} tree {l comparison.volume.s}
q
]=])
execute_process(
COMMAND "${MGED}" --no-rc -c "${PAWN_DB}"
WORKING_DIRECTORY "${WORKDIR}"
TIMEOUT ${RENDER_TIMEOUT}
INPUT_FILE "${MGED_SCRIPT}"
RESULT_VARIABLE assemble_result
OUTPUT_VARIABLE assemble_stdout
ERROR_VARIABLE assemble_stderr
)
if(NOT assemble_result EQUAL 0)
message(
FATAL_ERROR
"Assembling comparison regions failed (exit ${assemble_result})\n${assemble_stdout}${assemble_stderr}"
)
endif()
file(WRITE "${VIEW_SCRIPT}" "viewsize 8.000000000000000e+01;\n")
file(APPEND "${VIEW_SCRIPT}" "orientation 2.480970000000000e-01 4.765910000000000e-01 7.480970000000000e-01 3.894350000000000e-01;\n")
file(APPEND "${VIEW_SCRIPT}" "eye_pt 4.199070000000000e+01 2.940060000000000e+01 5.329140000000000e+01;\n")
file(APPEND "${VIEW_SCRIPT}" "start 0; clean;\n")
file(APPEND "${VIEW_SCRIPT}" "end;\n")
function(copy_snapshot local_image)
if(NOT EXISTS "${local_image}")
message(FATAL_ERROR "Expected image was not created: ${local_image}")
endif()
file(SIZE "${local_image}" image_size)
if(image_size EQUAL 0)
message(FATAL_ERROR "Rendered image is empty: ${local_image}")
endif()
file(COPY "${local_image}" DESTINATION "${IMGDIR}")
get_filename_component(image_name "${local_image}" NAME)
file(
CHMOD "${IMGDIR}/${image_name}"
PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
)
endfunction()
function(render_snapshot name object)
set(filename "solid_rep_${name}.png")
set(local_image "${WORKDIR}/${filename}")
file(REMOVE "${local_image}")
execute_process(
COMMAND "${RT}" -B -M -W "-s${SIZE}" -P1 -o "${filename}" "${PAWN_DB}" "${object}"
WORKING_DIRECTORY "${WORKDIR}"
TIMEOUT ${RENDER_TIMEOUT}
INPUT_FILE "${VIEW_SCRIPT}"
RESULT_VARIABLE render_result
OUTPUT_VARIABLE render_stdout
ERROR_VARIABLE render_stderr
)
if(NOT render_result EQUAL 0)
message(
FATAL_ERROR
"Rendering ${name} failed (exit ${render_result})\n${render_stdout}${render_stderr}"
)
endif()
copy_snapshot("${local_image}")
message(STATUS "Rendered ${filename}")
endfunction()
render_snapshot(csg comparison.csg.r)
render_snapshot(brep_boolean comparison.csg.r.brep)
render_snapshot(brep_evaluated comparison.brep_evaluated.r)
render_snapshot(polygonal_boolean comparison.polygonal_boolean.r)
render_snapshot(nmg comparison.nmg.r)
render_snapshot(bot comparison.bot.r)
render_snapshot(revolve comparison.revolve.r)
render_snapshot(voxel_cells comparison.voxel_cells.r)
render_snapshot(volume comparison.volume.r)
render_snapshot(point_cloud comparison.point_cloud.r)
# MGED exports its current wireframe display as view-plane plot3. Rasterize
# that plot through a disk framebuffer so regeneration needs no display.
set(WIREFRAME_PLOT "${WORKDIR}/solid_rep_wireframe.plot3")
set(WIREFRAME_PNG "${WORKDIR}/solid_rep_wireframe.png")
set(WIREFRAME_FB_NAME "solid_rep_wireframe.fb")
set(WIREFRAME_SCRIPT "${WORKDIR}/wireframe.tcl")
file(REMOVE "${WIREFRAME_PLOT}" "${WORKDIR}/${WIREFRAME_FB_NAME}" "${WIREFRAME_PNG}")
file(WRITE "${WIREFRAME_SCRIPT}" [=[
draw comparison.csg.r
size 80
orientation 0.248097 0.476591 0.748097 0.389435
eye_pt 41.9907 29.4006 53.2914
plot -2d solid_rep_wireframe.plot3
q
]=])
execute_process(
COMMAND "${MGED}" --no-rc -c "${PAWN_DB}"
WORKING_DIRECTORY "${WORKDIR}"
TIMEOUT ${RENDER_TIMEOUT}
INPUT_FILE "${WIREFRAME_SCRIPT}"
RESULT_VARIABLE wireframe_result
OUTPUT_VARIABLE wireframe_stdout
ERROR_VARIABLE wireframe_stderr
)
if(NOT wireframe_result EQUAL 0 OR NOT EXISTS "${WIREFRAME_PLOT}")
message(
FATAL_ERROR
"MGED wireframe export failed (exit ${wireframe_result})\n${wireframe_stdout}${wireframe_stderr}"
)
endif()
message(STATUS "Exported wireframe plot")
run_process(
"Cleared wireframe framebuffer"
"${FBCLEAR}" -c -F "${WIREFRAME_FB_NAME}" -w "${SIZE}" -n "${SIZE}" 24 24 24
)
execute_process(
COMMAND "${PLOT3FB}" -F "${WIREFRAME_FB_NAME}" -o -w "${SIZE}" -n "${SIZE}"
WORKING_DIRECTORY "${WORKDIR}"
TIMEOUT ${RENDER_TIMEOUT}
INPUT_FILE "${WIREFRAME_PLOT}"
RESULT_VARIABLE plot_result
OUTPUT_VARIABLE plot_stdout
ERROR_VARIABLE plot_stderr
)
if(NOT plot_result EQUAL 0)
message(FATAL_ERROR "plot3-fb failed (exit ${plot_result})\n${plot_stdout}${plot_stderr}")
endif()
run_process(
"Rasterized wireframe snapshot"
"${FBPNG}" -F "${WIREFRAME_FB_NAME}" -w "${SIZE}" -n "${SIZE}"
"solid_rep_wireframe.png"
)
copy_snapshot("${WIREFRAME_PNG}")
message(STATUS "Solid representation comparison: 11 snapshots regenerated")
+1
View File
@@ -22,4 +22,5 @@ Task-focused guides and how-to articles.
* link:articles/procedural_geometry.html[BRL-CAD Procedural Geometry Gallery]
* link:articles/projection_shader.html[Using the Projection Shader]
* link:articles/renderer_showcase.html[BRL-CAD Renderer Showcase]
* link:articles/solid_geometry_comparison.html[Solid Geometry Representation Comparisons]
* link:articles/tire.html[Vehicle Tire and Wheel Creation in BRL-CAD]
+1 -1
View File
@@ -55,7 +55,7 @@ __BEGIN_DECLS
* version 5 UUID suitable for repeatable identifier hashing.
*/
BU_EXPORT int
bu_uuid_create(uint8_t uuid[STATIC_ARRAY(16)], size_t nbytes, const uint8_t *bytes, const uint8_t namespace_uuid[STATIC_ARRAY(16)]);
bu_uuid_create(uint8_t uuid[STATIC_ARRAY(16)], size_t nbytes, const uint8_t *bytes, const uint8_t *namespace_uuid);
/**
* This is a convenience UUID comparison routine compatible with
+7 -5
View File
@@ -471,11 +471,13 @@ if(BRLCAD_VERBOSE)
set(CMAKE_VERBOSE_MAKEFILE ON)
endif(BRLCAD_VERBOSE)
# Build with profile-guided optimization support. this requires a two-pass
# compile, once with BRLCAD_PGO=ON on a location that did not exist beforehand
# (specified via the PGO_PATH environment variable), and again to use profiling
# metrics captured on "typical" operations and data. By default, path is
# BUILDDIR/profiling
# Build with profile-guided optimization support. This requires a two-pass
# compile, once with BRLCAD_PGO=ON and a profile location that did not exist
# beforehand (specified via the PGO_PATH environment variable), and again in a
# fresh build directory to use metrics captured on "typical" operations and
# data. By default, the path is BUILDDIR/profiling. For MSVC, set
# VCPROFILE_PATH to PGO_PATH while running the instrumented programs so their
# per-binary .pgc files are available beside the corresponding .pgd files.
option(BRLCAD_PGO "Enable profile-guided optimization (set PGO_PATH environment variable)")
mark_as_advanced(BRLCAD_PGO)
+1
View File
@@ -108,6 +108,7 @@ set(
probe_validation/README.md
probe_validation/CMakeLists.txt
probe_validation/probe_validation.cpp
scripts/pgo_benchmark.cmake
scripts/printtime.cmake
scripts/tcl_mkindex.cmake
scripts/timestamp.cmake
+435
View File
@@ -0,0 +1,435 @@
# P G O _ B E N C H M A R K . C M A K E
# BRL-CAD
#
# Copyright (c) 2026 United States Government as represented by
# the U.S. Army Research Laboratory.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# 3. The name of the author may not be used to endorse or promote
# products derived from this software without specific prior written
# permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
cmake_minimum_required(VERSION 3.22)
# TRAIN runs the standard benchmark and additional geometry, then verifies
# that compiler profile counters were produced. COMPARE runs the standard
# benchmark from baseline and profile-use build trees and requires its
# equal-weight mean speedup to exceed PGO_MIN_SPEEDUP. The training model
# list accepts build-relative or absolute paths, so external model sets can be
# supplied without changing this script.
#
# Required inputs:
# TRAIN: PGO_BUILD_DIR, PGO_RESULTS_DIR, PGO_PROFILE_DIR
# COMPARE: PGO_BASELINE_BUILD_DIR, PGO_OPTIMIZED_BUILD_DIR,
# PGO_RESULTS_DIR
set(_pgo_valid_modes TRAIN COMPARE)
if(NOT DEFINED PGO_MODE)
message(FATAL_ERROR "PGO_MODE must be TRAIN or COMPARE")
endif()
string(TOUPPER "${PGO_MODE}" PGO_MODE)
if(NOT PGO_MODE IN_LIST _pgo_valid_modes)
message(FATAL_ERROR "PGO_MODE must be TRAIN or COMPARE, not '${PGO_MODE}'")
endif()
set(_pgo_default_models moss world star bldg391 m35 sphflake)
set(
_pgo_default_training_models
share/db/havoc.g
share/db/faa/Generic_Twin.g
share/db/nist/NIST_MBE_PMI_2.g
)
if(NOT DEFINED PGO_MODELS)
set(PGO_MODELS ${_pgo_default_models})
endif()
if(NOT DEFINED PGO_TRAINING_MODELS)
set(PGO_TRAINING_MODELS ${_pgo_default_training_models})
endif()
if(NOT DEFINED PGO_TIMEFRAME)
set(PGO_TIMEFRAME 8)
endif()
if(NOT DEFINED PGO_MAXTIME)
set(PGO_MAXTIME 60)
endif()
if(NOT DEFINED PGO_AVERAGE)
set(PGO_AVERAGE 3)
endif()
if(NOT DEFINED PGO_DEVIATION)
set(PGO_DEVIATION 3)
endif()
if(NOT DEFINED PGO_TRAINING_SIZE)
set(PGO_TRAINING_SIZE 512)
endif()
if(NOT DEFINED PGO_TRAINING_AZIMUTH)
set(PGO_TRAINING_AZIMUTH 35)
endif()
if(NOT DEFINED PGO_TRAINING_ELEVATION)
set(PGO_TRAINING_ELEVATION 25)
endif()
if(NOT DEFINED PGO_MIN_SPEEDUP)
set(PGO_MIN_SPEEDUP 1.0)
endif()
function(_pgo_require variable_name)
if(NOT DEFINED "${variable_name}" OR "${${variable_name}}" STREQUAL "")
message(FATAL_ERROR "${variable_name} is required for PGO_MODE=${PGO_MODE}")
endif()
endfunction()
function(_pgo_absolute_path input_path output_variable)
get_filename_component(_pgo_path "${input_path}" ABSOLUTE)
file(TO_CMAKE_PATH "${_pgo_path}" _pgo_path)
set("${output_variable}" "${_pgo_path}" PARENT_SCOPE)
endfunction()
function(_pgo_cache_value build_directory variable_name output_variable)
set(_pgo_cache_file "${build_directory}/CMakeCache.txt")
if(NOT EXISTS "${_pgo_cache_file}")
message(FATAL_ERROR "CMake cache does not exist: ${_pgo_cache_file}")
endif()
file(
STRINGS
"${_pgo_cache_file}"
_pgo_cache_entry
LIMIT_COUNT 1
REGEX "^${variable_name}(:[^=]*)?="
)
if(_pgo_cache_entry)
string(REGEX REPLACE "^[^=]*=" "" _pgo_value "${_pgo_cache_entry}")
else()
set(_pgo_value "")
endif()
set("${output_variable}" "${_pgo_value}" PARENT_SCOPE)
endfunction()
function(_pgo_validate_build build_directory expected_pgo expected_msvc_phase)
_pgo_cache_value("${build_directory}" BRLCAD_PGO _pgo_enabled)
if(expected_pgo AND NOT _pgo_enabled)
message(FATAL_ERROR "BRLCAD_PGO is not enabled in ${build_directory}")
elseif(NOT expected_pgo AND _pgo_enabled)
message(FATAL_ERROR "The baseline build unexpectedly enables BRLCAD_PGO: ${build_directory}")
endif()
_pgo_cache_value("${build_directory}" BRLCAD_MSVC_PGO_PHASE _pgo_msvc_phase)
if(_pgo_msvc_phase AND NOT _pgo_msvc_phase STREQUAL expected_msvc_phase)
message(
FATAL_ERROR
"Expected MSVC PGO phase ${expected_msvc_phase} in ${build_directory}, found ${_pgo_msvc_phase}"
)
endif()
endfunction()
function(_pgo_find_build_file build_directory file_name output_variable)
set(_pgo_candidates "${build_directory}/bin/${file_name}")
if(DEFINED PGO_CONFIG AND NOT "${PGO_CONFIG}" STREQUAL "")
list(
APPEND
_pgo_candidates
"${build_directory}/bin/${PGO_CONFIG}/${file_name}"
"${build_directory}/${PGO_CONFIG}/bin/${file_name}"
)
endif()
if(CMAKE_HOST_WIN32)
set(_pgo_executable_candidates "")
foreach(_pgo_candidate IN LISTS _pgo_candidates)
list(APPEND _pgo_executable_candidates "${_pgo_candidate}.exe")
endforeach()
list(APPEND _pgo_candidates ${_pgo_executable_candidates})
endif()
foreach(_pgo_candidate IN LISTS _pgo_candidates)
if(EXISTS "${_pgo_candidate}")
set("${output_variable}" "${_pgo_candidate}" PARENT_SCOPE)
return()
endif()
endforeach()
message(FATAL_ERROR "Could not find ${file_name} under ${build_directory}/bin")
endfunction()
function(_pgo_find_shell build_directory output_variable)
if(DEFINED PGO_SHELL AND NOT "${PGO_SHELL}" STREQUAL "")
set(_pgo_shell "${PGO_SHELL}")
elseif(EXISTS "${build_directory}/CMakeCache.txt")
file(
STRINGS
"${build_directory}/CMakeCache.txt"
_pgo_shell_cache_entry
LIMIT_COUNT 1
REGEX "^SH_EXEC(:[^=]*)?="
)
if(_pgo_shell_cache_entry)
string(REGEX REPLACE "^[^=]*=" "" _pgo_shell "${_pgo_shell_cache_entry}")
endif()
endif()
if(NOT DEFINED _pgo_shell OR "${_pgo_shell}" STREQUAL "" OR NOT EXISTS "${_pgo_shell}")
unset(_pgo_shell)
unset(_pgo_shell CACHE)
find_program(_pgo_shell NAMES sh bash)
endif()
if(NOT _pgo_shell)
message(FATAL_ERROR "The BRL-CAD benchmark requires a POSIX shell; set PGO_SHELL to its path")
endif()
set("${output_variable}" "${_pgo_shell}" PARENT_SCOPE)
endfunction()
function(_pgo_run_benchmark build_directory result_directory)
_pgo_find_build_file("${build_directory}" benchmark _pgo_benchmark)
_pgo_find_build_file("${build_directory}" rt _pgo_rt)
_pgo_find_build_file("${build_directory}" pixcmp _pgo_pixcmp)
_pgo_find_shell("${build_directory}" _pgo_shell)
file(MAKE_DIRECTORY "${result_directory}")
message(STATUS "Running the BRL-CAD benchmark from ${build_directory}")
execute_process(
COMMAND
"${_pgo_shell}" "${_pgo_benchmark}" run
"TIMEFRAME=${PGO_TIMEFRAME}"
"MAXTIME=${PGO_MAXTIME}"
"AVERAGE=${PGO_AVERAGE}"
"DEVIATION=${PGO_DEVIATION}"
"RT=${_pgo_rt}"
"CMP=${_pgo_pixcmp}"
-P1
WORKING_DIRECTORY "${result_directory}"
RESULT_VARIABLE _pgo_benchmark_result
)
if(NOT "${_pgo_benchmark_result}" STREQUAL "0")
message(FATAL_ERROR "The benchmark from ${build_directory} failed with status ${_pgo_benchmark_result}")
endif()
endfunction()
function(_pgo_extract_rate log_file output_text output_integer)
if(NOT EXISTS "${log_file}")
message(FATAL_ERROR "Benchmark log does not exist: ${log_file}")
endif()
file(READ "${log_file}" _pgo_log)
string(REGEX MATCHALL "[0-9]+(\\.[0-9]+)? rays/sec \\(RTFM\\)" _pgo_rate_lines "${_pgo_log}")
if(NOT _pgo_rate_lines)
message(FATAL_ERROR "No RTFM result was found in ${log_file}")
endif()
list(GET _pgo_rate_lines -1 _pgo_rate_line)
string(REGEX REPLACE " rays/sec.*$" "" _pgo_rate "${_pgo_rate_line}")
if(NOT _pgo_rate MATCHES "^([0-9]+)(\\.([0-9]+))?$")
message(FATAL_ERROR "Invalid RTFM result '${_pgo_rate}' in ${log_file}")
endif()
set(_pgo_whole "${CMAKE_MATCH_1}")
set(_pgo_fraction "${CMAKE_MATCH_3}00")
string(SUBSTRING "${_pgo_fraction}" 0 2 _pgo_fraction)
string(REGEX REPLACE "^0+" "" _pgo_whole "${_pgo_whole}")
string(REGEX REPLACE "^0+" "" _pgo_fraction "${_pgo_fraction}")
if("${_pgo_whole}" STREQUAL "")
set(_pgo_whole 0)
endif()
if("${_pgo_fraction}" STREQUAL "")
set(_pgo_fraction 0)
endif()
math(EXPR _pgo_rate_integer "${_pgo_whole} * 100 + ${_pgo_fraction}")
if(_pgo_rate_integer LESS_EQUAL 0)
message(FATAL_ERROR "Non-positive RTFM result '${_pgo_rate}' in ${log_file}")
endif()
set("${output_text}" "${_pgo_rate}" PARENT_SCOPE)
set("${output_integer}" "${_pgo_rate_integer}" PARENT_SCOPE)
endfunction()
function(_pgo_decimal_to_thousandths value output_variable)
if(NOT "${value}" MATCHES "^([0-9]+)(\\.([0-9]+))?$")
message(FATAL_ERROR "Expected a non-negative decimal number, not '${value}'")
endif()
set(_pgo_whole "${CMAKE_MATCH_1}")
set(_pgo_fraction "${CMAKE_MATCH_3}000")
string(SUBSTRING "${_pgo_fraction}" 0 3 _pgo_fraction)
string(REGEX REPLACE "^0+" "" _pgo_whole "${_pgo_whole}")
string(REGEX REPLACE "^0+" "" _pgo_fraction "${_pgo_fraction}")
if("${_pgo_whole}" STREQUAL "")
set(_pgo_whole 0)
endif()
if("${_pgo_fraction}" STREQUAL "")
set(_pgo_fraction 0)
endif()
math(EXPR _pgo_value "${_pgo_whole} * 1000 + ${_pgo_fraction}")
set("${output_variable}" "${_pgo_value}" PARENT_SCOPE)
endfunction()
function(_pgo_format_thousandths value output_variable)
math(EXPR _pgo_whole "${value} / 1000")
math(EXPR _pgo_fraction "${value} % 1000")
set(_pgo_fraction "00${_pgo_fraction}")
string(LENGTH "${_pgo_fraction}" _pgo_fraction_length)
math(EXPR _pgo_fraction_offset "${_pgo_fraction_length} - 3")
string(SUBSTRING "${_pgo_fraction}" ${_pgo_fraction_offset} 3 _pgo_fraction)
set("${output_variable}" "${_pgo_whole}.${_pgo_fraction}" PARENT_SCOPE)
endfunction()
if(PGO_MODE STREQUAL "TRAIN")
_pgo_require(PGO_BUILD_DIR)
_pgo_require(PGO_RESULTS_DIR)
_pgo_require(PGO_PROFILE_DIR)
_pgo_absolute_path("${PGO_BUILD_DIR}" PGO_BUILD_DIR)
_pgo_absolute_path("${PGO_RESULTS_DIR}" PGO_RESULTS_DIR)
_pgo_absolute_path("${PGO_PROFILE_DIR}" PGO_PROFILE_DIR)
if(NOT IS_DIRECTORY "${PGO_BUILD_DIR}")
message(FATAL_ERROR "PGO_BUILD_DIR does not exist: ${PGO_BUILD_DIR}")
endif()
_pgo_validate_build("${PGO_BUILD_DIR}" TRUE GENERATE)
# MSVC's profile runtime uses this location for each image's PGC files.
# GCC ignores it and writes its GCDA files to the compiled-in PGO path.
file(MAKE_DIRECTORY "${PGO_PROFILE_DIR}")
set(ENV{VCPROFILE_PATH} "${PGO_PROFILE_DIR}")
_pgo_run_benchmark("${PGO_BUILD_DIR}" "${PGO_RESULTS_DIR}/benchmark")
_pgo_find_build_file("${PGO_BUILD_DIR}" rt _pgo_rt)
if(CMAKE_HOST_WIN32)
set(_pgo_null_device NUL)
else()
set(_pgo_null_device /dev/null)
endif()
set(_pgo_training_index 0)
foreach(_pgo_model IN LISTS PGO_TRAINING_MODELS)
math(EXPR _pgo_training_index "${_pgo_training_index} + 1")
if(IS_ABSOLUTE "${_pgo_model}")
set(_pgo_model_path "${_pgo_model}")
else()
set(_pgo_model_path "${PGO_BUILD_DIR}/${_pgo_model}")
endif()
if(NOT EXISTS "${_pgo_model_path}")
message(FATAL_ERROR "Training model does not exist: ${_pgo_model_path}")
endif()
get_filename_component(_pgo_log_name "${_pgo_model_path}" NAME_WE)
string(REGEX REPLACE "[^A-Za-z0-9_.-]" "_" _pgo_log_name "${_pgo_log_name}")
set(_pgo_log_file "${PGO_RESULTS_DIR}/${_pgo_training_index}-${_pgo_log_name}.log")
message(STATUS "Training with ${_pgo_model_path}")
execute_process(
COMMAND
"${_pgo_rt}" -B -P1 "-s${PGO_TRAINING_SIZE}"
"-a${PGO_TRAINING_AZIMUTH}" "-e${PGO_TRAINING_ELEVATION}"
-o "${_pgo_null_device}" "${_pgo_model_path}"
RESULT_VARIABLE _pgo_rt_result
OUTPUT_VARIABLE _pgo_rt_output
ERROR_VARIABLE _pgo_rt_error
)
file(WRITE "${_pgo_log_file}" "${_pgo_rt_output}${_pgo_rt_error}")
if(NOT "${_pgo_rt_result}" STREQUAL "0")
message(FATAL_ERROR "Training ${_pgo_model} failed with status ${_pgo_rt_result}; see ${_pgo_log_file}")
endif()
string(REGEX MATCH "[^\r\n]*rays/sec \\(RTFM\\)" _pgo_rtfm_line "${_pgo_rt_output}${_pgo_rt_error}")
if(NOT _pgo_rtfm_line)
message(FATAL_ERROR "Training ${_pgo_model} did not complete a raytrace; see ${_pgo_log_file}")
endif()
message(STATUS "${_pgo_model}: ${_pgo_rtfm_line}")
endforeach()
file(GLOB_RECURSE _pgo_gcda_files LIST_DIRECTORIES FALSE "${PGO_PROFILE_DIR}/*.gcda")
file(GLOB_RECURSE _pgo_pgc_files LIST_DIRECTORIES FALSE "${PGO_PROFILE_DIR}/*.pgc")
list(LENGTH _pgo_gcda_files _pgo_gcda_count)
list(LENGTH _pgo_pgc_files _pgo_pgc_count)
math(EXPR _pgo_profile_count "${_pgo_gcda_count} + ${_pgo_pgc_count}")
if(_pgo_profile_count EQUAL 0)
message(FATAL_ERROR "Training did not generate GCC GCDA or MSVC PGC data under ${PGO_PROFILE_DIR}")
endif()
message(STATUS "Training generated ${_pgo_profile_count} profile counter files under ${PGO_PROFILE_DIR}")
else()
_pgo_require(PGO_BASELINE_BUILD_DIR)
_pgo_require(PGO_OPTIMIZED_BUILD_DIR)
_pgo_require(PGO_RESULTS_DIR)
_pgo_absolute_path("${PGO_BASELINE_BUILD_DIR}" PGO_BASELINE_BUILD_DIR)
_pgo_absolute_path("${PGO_OPTIMIZED_BUILD_DIR}" PGO_OPTIMIZED_BUILD_DIR)
_pgo_absolute_path("${PGO_RESULTS_DIR}" PGO_RESULTS_DIR)
_pgo_validate_build("${PGO_BASELINE_BUILD_DIR}" FALSE "")
_pgo_validate_build("${PGO_OPTIMIZED_BUILD_DIR}" TRUE USE)
_pgo_run_benchmark("${PGO_BASELINE_BUILD_DIR}" "${PGO_RESULTS_DIR}/baseline")
_pgo_run_benchmark("${PGO_OPTIMIZED_BUILD_DIR}" "${PGO_RESULTS_DIR}/optimized")
set(_pgo_report "### Profile-guided optimization results\n\n")
string(APPEND _pgo_report "| Model | Baseline rays/sec | PGO rays/sec | Speedup |\n")
string(APPEND _pgo_report "| --- | ---: | ---: | ---: |\n")
set(_pgo_speedup_sum 0)
set(_pgo_model_count 0)
set(_pgo_tsv "model\tbaseline_rays_per_second\tpgo_rays_per_second\tspeedup\n")
foreach(_pgo_model IN LISTS PGO_MODELS)
_pgo_extract_rate("${PGO_RESULTS_DIR}/baseline/${_pgo_model}.log" _pgo_baseline_rate _pgo_baseline_integer)
_pgo_extract_rate("${PGO_RESULTS_DIR}/optimized/${_pgo_model}.log" _pgo_optimized_rate _pgo_optimized_integer)
math(
EXPR
_pgo_speedup
"(${_pgo_optimized_integer} * 1000 + ${_pgo_baseline_integer} / 2) / ${_pgo_baseline_integer}"
)
_pgo_format_thousandths("${_pgo_speedup}" _pgo_speedup_text)
math(EXPR _pgo_speedup_sum "${_pgo_speedup_sum} + ${_pgo_speedup}")
math(EXPR _pgo_model_count "${_pgo_model_count} + 1")
string(
APPEND
_pgo_report
"| `${_pgo_model}` | ${_pgo_baseline_rate} | ${_pgo_optimized_rate} | ${_pgo_speedup_text}x |\n"
)
string(
APPEND
_pgo_tsv
"${_pgo_model}\t${_pgo_baseline_rate}\t${_pgo_optimized_rate}\t${_pgo_speedup_text}\n"
)
endforeach()
if(_pgo_model_count EQUAL 0)
message(FATAL_ERROR "PGO_MODELS must name at least one benchmark model")
endif()
math(EXPR _pgo_mean_speedup "(${_pgo_speedup_sum} + ${_pgo_model_count} / 2) / ${_pgo_model_count}")
_pgo_format_thousandths("${_pgo_mean_speedup}" _pgo_mean_speedup_text)
string(APPEND _pgo_report "\nArithmetic mean speedup: ${_pgo_mean_speedup_text}x\n")
file(MAKE_DIRECTORY "${PGO_RESULTS_DIR}")
file(WRITE "${PGO_RESULTS_DIR}/results.tsv" "${_pgo_tsv}")
if(DEFINED PGO_SUMMARY_FILE AND NOT "${PGO_SUMMARY_FILE}" STREQUAL "")
_pgo_absolute_path("${PGO_SUMMARY_FILE}" _pgo_summary_file)
elseif(NOT "$ENV{GITHUB_STEP_SUMMARY}" STREQUAL "")
set(_pgo_summary_file "$ENV{GITHUB_STEP_SUMMARY}")
endif()
if(DEFINED _pgo_summary_file)
file(APPEND "${_pgo_summary_file}" "${_pgo_report}")
endif()
message("${_pgo_report}")
_pgo_decimal_to_thousandths("${PGO_MIN_SPEEDUP}" _pgo_min_speedup)
if(NOT _pgo_mean_speedup GREATER _pgo_min_speedup)
message(
FATAL_ERROR
"PGO raytracing speedup ${_pgo_mean_speedup_text}x did not exceed ${PGO_MIN_SPEEDUP}x"
)
endif()
endif()
# Local Variables:
# tab-width: 8
# mode: cmake
# indent-tabs-mode: t
# End:
# ex: shiftwidth=2 tabstop=8
+35 -19
View File
@@ -7,14 +7,25 @@
set -e
set -o pipefail
# Note: we set these variables to tell the ccc-analyzer script which compilers
# to use, but it is not always enough - when some of the sub-builds are
# triggered, they do not seem to inherit these settings and revert to the
# default compiler. The only way so far to avoid this issue reliably seems to
# be to edit the ccc-analyzer script to use the compiler we want as the default
# compiler.
export CCC_CC=clang
export CCC_CXX=clang++
# CLANG_VERSION should be set by User or CI environments to avoid hard-coding
# a particular version into the script
if [ -z "$CLANG_VERSION" ]; then
echo "Error: CLANG_VERSION environment variable is not set." >&2
exit 1
fi
# Derive tool paths from the version
SCAN_BUILD_DIR="/usr/share/clang/scan-build-${CLANG_VERSION}"
SCAN_BUILD_BIN="${SCAN_BUILD_DIR}/bin"
SCAN_BUILD_LIBEXEC="${SCAN_BUILD_DIR}/libexec"
CLANG_ANALYZER_BIN="/usr/bin/clang-${CLANG_VERSION}"
# Add scan-build to the PATH
export PATH="${SCAN_BUILD_BIN}:${SCAN_BUILD_LIBEXEC}:${PATH}"
# Set compilers for the analyzer scripts
export CCC_CC="clang-${CLANG_VERSION}"
export CCC_CXX="clang++-${CLANG_VERSION}"
# This appears to be a workable way to enable the new Z3 static analyzer
# support, but at least as of 2017-12 it greatly slows the testing (by orders
@@ -45,19 +56,24 @@ fi
# Encapsulate the logic to do a scan build
function runtest {
echo "$1"
scan-build --use-analyzer=/usr/bin/$CCC_CC -o ./scan-reports-$1 make -j12 $1
if [ "$(ls -A ./scan-reports-$1)" ]; then
report_cnt=$(ls -l ./scan-reports-$1/*/report* | wc -l)
failure=$(($failure + $report_cnt))
else
rm -rf ./scan-reports-$1
fi
echo "$1"
scan-build --use-analyzer="${CLANG_ANALYZER_BIN}" -o "./scan-reports-$1" make -j12 "$1"
if [ -d "./scan-reports-$1" ] && [ -n "$(ls -A "./scan-reports-$1")" ]; then
report_cnt=$(find "./scan-reports-$1" -name 'report-*.html' | wc -l)
failure=$((failure + report_cnt))
else
rm -rf "./scan-reports-$1"
fi
}
# configure using the correct compiler and values. We don't
# need this report, so clear it after configure is done
scan-build --use-analyzer=/usr/bin/$CCC_CC -o ./scan-reports-config cmake .. -DBRLCAD_EXTRADOCS=OFF -DBRLCAD_ENABLE_QT=ON -DBRLCAD_LTO_MODE=OFF -DBRLCAD_EXT_DIR="$bext_dir" -DCMAKE_C_COMPILER=ccc-analyzer -DCMAKE_CXX_COMPILER=c++-analyzer
# Configure using the correct compiler and values.
scan-build --use-analyzer="${CLANG_ANALYZER_BIN}" -o ./scan-reports-config cmake .. \
-DBRLCAD_EXTRADOCS=OFF \
-DBRLCAD_ENABLE_QT=ON \
-DBRLCAD_LTO_MODE=OFF \
-DBRLCAD_EXT_DIR="$bext_dir" \
-DCMAKE_C_COMPILER=ccc-analyzer \
-DCMAKE_CXX_COMPILER=c++-analyzer
# clear out any old reports
rm -rfv ./scan-reports-*
+10
View File
@@ -109,6 +109,16 @@ brlcad_addexec(g-var g-var.c "librt;libbu;${M_LIBRARY}" FOLDER Conv)
brlcad_addexec(g-vrml g-vrml.c "librt;libnmg;libbu;${M_LIBRARY}" FOLDER Conv)
brlcad_addexec(g-x3d g-x3d.c "librt;libnmg;libbu;${M_LIBRARY}" FOLDER Conv)
# GCC cannot model BU_SETJUMP's longjmp path during LTO and reports an
# anonymous value as potentially uninitialized.
check_linker_flag(
"C"
"-Wno-error=maybe-uninitialized"
G_X3D_LINKER_WNO_MAYBE_UNINITIALIZED
)
if(G_X3D_LINKER_WNO_MAYBE_UNINITIALIZED)
target_link_options(g-x3d PRIVATE -Wno-maybe-uninitialized)
endif()
brlcad_addexec(nastran-g nastran-g.c "libwdb;librt;libnmg;libbu;${M_LIBRARY}" FOLDER Conv)
+6 -6
View File
@@ -326,7 +326,7 @@ SHPOpenLL( const char * pszLayer, const char * pszAccess, SAHooks *psHooks ) {
/* -------------------------------------------------------------------- */
/* Initialize the info structure. */
/* -------------------------------------------------------------------- */
SHPHandle psSHP = STATIC_CAST(SHPHandle, calloc(sizeof(SHPInfo),1));
SHPHandle psSHP = STATIC_CAST(SHPHandle, calloc(1,sizeof(SHPInfo)));
psSHP->bUpdated = FALSE;
memcpy( &(psSHP->sHooks), psHooks, sizeof(SAHooks) );
@@ -1176,7 +1176,7 @@ SHPCreateObject( int nSHPType, int nShapeId, int nParts,
psObject->nParts = MAX(1,nParts);
psObject->panPartStart = STATIC_CAST(int *,
calloc(sizeof(int), psObject->nParts));
calloc(psObject->nParts, sizeof(int)));
psObject->panPartType = STATIC_CAST(int *,
malloc(sizeof(int) * psObject->nParts));
@@ -1205,13 +1205,13 @@ SHPCreateObject( int nSHPType, int nShapeId, int nParts,
{
const size_t nSize = sizeof(double) * nVertices;
psObject->padfX = STATIC_CAST(double *, padfX ? malloc(nSize) :
calloc(sizeof(double),nVertices));
calloc(nVertices,sizeof(double)));
psObject->padfY = STATIC_CAST(double *, padfY ? malloc(nSize) :
calloc(sizeof(double),nVertices));
calloc(nVertices,sizeof(double)));
psObject->padfZ = STATIC_CAST(double *, padfZ && bHasZ ? malloc(nSize) :
calloc(sizeof(double),nVertices));
calloc(nVertices,sizeof(double)));
psObject->padfM = STATIC_CAST(double *, padfM && bHasM ? malloc(nSize) :
calloc(sizeof(double),nVertices));
calloc(nVertices,sizeof(double)));
if( padfX != SHPLIB_NULLPTR )
memcpy(psObject->padfX, padfX, nSize);
if( padfY != SHPLIB_NULLPTR )
+2 -2
View File
@@ -5545,8 +5545,8 @@ repair_ambiguous_singular_periodic_loop_branches(ON_Brep *brep,
std::vector<int> affected_positions;
affected_positions.reserve(arc_positions.size() + 2);
affected_positions.push_back(first_position);
affected_positions.insert(affected_positions.end(),
arc_positions.begin(), arc_positions.end());
for (const int position : arc_positions)
affected_positions.push_back(position);
affected_positions.push_back(second_position);
for (int shift_sign = -1; shift_sign <= 1 && !repaired;
+20 -16
View File
@@ -50,16 +50,16 @@
/* Hash a single 512-bit block. This is the core of the algorithm. */
void SHA1Transform(uint32_t state[5], const unsigned char buffer[64])
void SHA1Transform(uint32_t state[5], const unsigned char buffer[SHA1_BLOCK_SIZE])
{
uint32_t a, b, c, d, e;
typedef union {
unsigned char c[64];
unsigned char c[SHA1_BLOCK_SIZE];
uint32_t l[16];
} CHAR64LONG16;
#ifdef SHA1HANDSOFF
CHAR64LONG16 block[1]; /* use array to appear as a pointer */
memcpy(block, buffer, 64);
memcpy(block, buffer, sizeof(block));
#else
/* The following had better never be used because it causes the
* pointer-to-const buffer to be cast into a pointer to non-const.
@@ -131,11 +131,11 @@ void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len)
if ((context->count[0] += len << 3) < j)
context->count[1]++;
context->count[1] += (len>>29);
j = (j >> 3) & 63;
if ((j + len) > 63) {
memcpy(&context->buffer[j], data, (i = 64-j));
j = (j >> 3) & (SHA1_BLOCK_SIZE - 1);
if ((j + len) >= SHA1_BLOCK_SIZE) {
memcpy(&context->buffer[j], data, (i = SHA1_BLOCK_SIZE-j));
SHA1Transform(context->state, context->buffer);
for ( ; i + 63 < len; i += 64) {
for ( ; i + SHA1_BLOCK_SIZE - 1 < len; i += SHA1_BLOCK_SIZE) {
SHA1Transform(context->state, &data[i]);
}
j = 0;
@@ -149,22 +149,26 @@ void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len)
void SHA1Final(unsigned char digest[20], SHA1_CTX* context)
{
unsigned i;
static const unsigned char padding[SHA1_BLOCK_SIZE] = {0x80};
unsigned char finalcount[8];
unsigned char c;
const uint32_t block_size = (uint32_t)sizeof(context->buffer);
const uint32_t length_offset = block_size - (uint32_t)sizeof(finalcount);
uint32_t buffer_used;
uint32_t padding_length;
unsigned i;
for (i = 0; i < 8; i++) {
finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)]
>> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */
}
c = 0200;
SHA1Update(context, &c, 1);
while ((context->count[0] & 504) != 448) {
c = 0000;
SHA1Update(context, &c, 1);
}
SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */
buffer_used = (context->count[0] >> 3) & (block_size - 1);
if (buffer_used < length_offset)
padding_length = length_offset - buffer_used;
else
padding_length = block_size + length_offset - buffer_used;
SHA1Update(context, padding, padding_length);
SHA1Update(context, finalcount, (uint32_t)sizeof(finalcount));
for (i = 0; i < 20; i++) {
digest[i] = (unsigned char)
((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);
+4 -2
View File
@@ -7,15 +7,17 @@
#include "common.h"
#define SHA1_BLOCK_SIZE 64
typedef struct {
uint32_t state[5];
uint32_t count[2];
unsigned char buffer[64];
unsigned char buffer[SHA1_BLOCK_SIZE];
} SHA1_CTX;
__BEGIN_DECLS
void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]);
void SHA1Transform(uint32_t state[5], const unsigned char buffer[SHA1_BLOCK_SIZE]);
void SHA1Init(SHA1_CTX* context);
void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len);
void SHA1Final(unsigned char digest[20], SHA1_CTX* context);
+1 -1
View File
@@ -46,7 +46,7 @@ extern double drand48(void);
int
bu_uuid_create(uint8_t uuid[STATIC_ARRAY(16)], size_t nbytes, const uint8_t *bytes, const uint8_t namespace_uuid[STATIC_ARRAY(16)])
bu_uuid_create(uint8_t uuid[STATIC_ARRAY(16)], size_t nbytes, const uint8_t *bytes, const uint8_t *namespace_uuid)
{
int type = 4; /* random */
+4 -4
View File
@@ -934,7 +934,7 @@ x24_setup(struct fb *ifp, int width, int height)
case FLG_VP8:
case FLG_VS8:
case FLG_VG8:
if ((xi->xi_pix = (unsigned char *) calloc(sizeof(uint8_t), width*height)) == NULL) {
if ((xi->xi_pix = (unsigned char *) calloc(width*height, sizeof(uint8_t))) == NULL) {
fb_log("X24_open: pix8 malloc failed\n");
return -1;
}
@@ -950,7 +950,7 @@ x24_setup(struct fb *ifp, int width, int height)
xi->xi_visual, xi->xi_depth, XYBitmap, 0,
NULL, width, height, 32, 0);
if ((xi->xi_pix = (unsigned char *) calloc(sizeof(uint8_t), xi->xi_image->bytes_per_line * height)) == NULL) {
if ((xi->xi_pix = (unsigned char *) calloc(xi->xi_image->bytes_per_line * height, sizeof(uint8_t))) == NULL) {
fb_log("X24_open: pix1 malloc failed\n");
return -1;
}
@@ -2653,7 +2653,7 @@ X24_configureWindow(struct fb *ifp, int width, int height)
XDestroyImage(xi->xi_image);
/* Make new buffers and new image */
if ((xi->xi_pix = (unsigned char *)calloc(sizeof(uint8_t), xi->xi_xwidth * xi->xi_xheight)) == NULL) {
if ((xi->xi_pix = (unsigned char *)calloc(xi->xi_xwidth * xi->xi_xheight, sizeof(uint8_t))) == NULL) {
fb_log("X24: pix8 malloc failed in resize!\n");
return 1;
}
@@ -2671,7 +2671,7 @@ X24_configureWindow(struct fb *ifp, int width, int height)
xi->xi_visual, xi->xi_depth, XYBitmap, 0,
NULL, xi->xi_xwidth, xi->xi_xheight, 32, 0);
if ((xi->xi_pix = (unsigned char *)calloc(sizeof(uint8_t), xi->xi_image->bytes_per_line * xi->xi_xheight)) == NULL) {
if ((xi->xi_pix = (unsigned char *)calloc(xi->xi_image->bytes_per_line * xi->xi_xheight, sizeof(uint8_t))) == NULL) {
fb_log("X24: pix1 malloc failed in resize!\n");
return 1;
}
+2 -2
View File
@@ -422,12 +422,12 @@ fb_open(const char *file, int width, int height)
if (width < 0 || height < 0)
return FB_NULL;
ifp = (struct fb *)calloc(sizeof(struct fb), 1);
ifp = (struct fb *)calloc(1, sizeof(struct fb));
if (ifp == FB_NULL) {
Malloc_Bomb(sizeof(struct fb));
return FB_NULL;
}
ifp->i = (struct fb_impl *)calloc(sizeof(struct fb_impl), 1);
ifp->i = (struct fb_impl *)calloc(1, sizeof(struct fb_impl));
if (file == NULL || *file == '\0') {
/* No name given, check environment variable first. */
file = (const char *)getenv("FB_FILE");
+2 -2
View File
@@ -114,8 +114,8 @@ struct fb *
fb_open_existing(const char *file, int width, int height, struct fb_platform_specific *fb_p)
{
struct fb *ifp;
ifp = (struct fb *)calloc(sizeof(struct fb), 1);
ifp->i = (struct fb_impl *) calloc(sizeof(struct fb_impl), 1);
ifp = (struct fb *)calloc(1, sizeof(struct fb));
ifp->i = (struct fb_impl *) calloc(1, sizeof(struct fb_impl));
fb_set_interface(ifp, file);
fb_set_magic(ifp, FB_MAGIC);
fb_setup_existing(ifp, width, height, fb_p);
+2 -2
View File
@@ -111,7 +111,7 @@ hlbvh_init_pool(size_t n_primatives) {
}
/* utility functions */
static inline uint32_t left_shift3(uint32_t x)
static uint32_t left_shift3(uint32_t x)
{
BU_ASSERT(x <= (1 << 10));
if (x == (1 << 10)) --x;
@@ -299,7 +299,7 @@ maximum_extent(const fastf_t b[6])
return 2;
}
static inline int
static int
pred(const struct bvh_build_node *node, fastf_t *centroid_bounds,
uint8_t dim, long min_cost_split_bucket)
{
+1 -1
View File
@@ -730,7 +730,7 @@ bot_makesegs_specific(hit_da *hits,
/* r_min can describe a spatial partition cell that begins inside this
* solid. Start BVH traversal just before the ray enters the BoT's own
* root box so entrance triangles behind the ray origin remain visible. */
static inline void
static void
bot_backed_origin(point_t b_pt, const struct xray *rp, const struct bvh_flat_node *root,
const vect_t inv_dir, const struct soltab *stp)
{
+1 -1
View File
@@ -517,7 +517,7 @@ rt_metaball_vshot(struct soltab *stp[], struct xray *rp[], struct seg *segp, int
}
inline void
static void
rt_metaball_norm_internal(vect_t *n, point_t *p, struct rt_metaball_internal *mb)
{
struct wdb_metaball_pnt *mbpt;
-1
View File
@@ -27,7 +27,6 @@ __BEGIN_DECLS
int rt_metaball_bbox(struct rt_db_internal *ip, point_t *min, point_t *max, const struct bn_tol *tol);
fastf_t rt_metaball_get_bounding_sphere(point_t *center, fastf_t threshold, struct rt_metaball_internal *mb);
int rt_metaball_find_intersection(point_t *intersect, const struct rt_metaball_internal *mb, const point_t *a, const point_t *b, fastf_t step, const fastf_t finalstep);
void rt_metaball_norm_internal(vect_t *n, point_t *p, struct rt_metaball_internal *mb);
__END_DECLS
#endif /* LIBRT_PRIMITIVES_METABALL_METABALL_H */
+1 -1
View File
@@ -33,7 +33,7 @@
#define PART_SPHERE_HEIGHT_RATIO 0.001
#define PART_CYLINDER_RADIUS_RATIO 0.001
static inline int
static int
part_update_type(struct rt_part_internal *part)
{
if (!part || !isfinite(part->part_vrad) ||
+1 -1
View File
@@ -337,7 +337,7 @@ main(int ac, char *av[])
unsigned char ground_rgb[3];
unsigned char light_rgb[3];
double pitch_r[MAX_GEARS];
double pitch_r[MAX_GEARS] = {0.0};
double center_x[MAX_GEARS];
double total_span;
double max_outer;