v0.4.0: Direct3D 11/12 backends, in-game renderer selection, per-run logging
- ra3.dx: D3D11 and D3D12 presentation backends (runtime HLSL via d3dcompiler); 2D image blit and the GPU heightfield terrain raymarch, with the corner minimap/FPS overlays. Non-Windows builds link a null fallback. - ra3.display: preferred backend plus ordered fallback (Vulkan/D3D11/D3D12/SDL); menu gains a Renderer option and the CLI gains --dx11/--dx12/--sdl, which the render command now honours too. - vendor libenderlog (MIT): every run writes openra3.log next to the exe and archives the previous run's log; records at warn and above carry a call stack (native fallback, since libc++ has no <stacktrace>). - Windows crash reporter writes openra3_crash.log (faulting module + backtrace); D3D/DXGI diagnostics are routed through the logger.
This commit is contained in:
Vendored
+170
@@ -0,0 +1,170 @@
|
||||
# libenderlog
|
||||
|
||||
A standalone C++26 logging library, built as a C++20/26 **module** (`import ender.log;`)
|
||||
with **`std::stacktrace`** call-stack capture on severe records.
|
||||
|
||||
It was extracted from the `ender-physics` engine, where it started life as
|
||||
`ender.log`, and turned into a library that has no other dependency — not on the
|
||||
engine, not on a logging framework.
|
||||
|
||||
## Features
|
||||
|
||||
- **Leveled records.** `trace`, `debug`, `info`, `warn`, `error`, `critical`,
|
||||
filtered by an atomic `minimum` check that is cheap enough to guard expensive
|
||||
message construction: `if (log::enabled(log::level::debug)) { ... }`.
|
||||
- **Source location.** Every record carries the file, line and function of the
|
||||
caller, taken from `std::source_location` at the call site — exact even in a
|
||||
stripped release binary, because it is a compile-time constant.
|
||||
- **Call stacks.** Records at or above `options::stacktrace_from` carry a
|
||||
formatted `std::stacktrace`. The frames belonging to the library itself are
|
||||
stripped by symbol, so the first reported frame is the caller regardless of
|
||||
the optimisation level (the level wrappers get inlined away under `-O`).
|
||||
- **Pluggable sinks.** A `console_sink` (stderr by default) and a `memory_sink`
|
||||
(for tests and in-game consoles) ship; `sink` is a small interface.
|
||||
- **File output with archiving.** `file_sink` writes to a file and, on open,
|
||||
moves an existing log aside to a timestamped archive, so a run never appends
|
||||
onto a previous run's log. It can also rotate by size and bound how many
|
||||
archives are kept.
|
||||
- **No stacktrace? No problem.** Where `<stacktrace>` is missing (libc++, and
|
||||
therefore every cross target), the module still compiles and records still
|
||||
carry their call site — they simply have no stack.
|
||||
|
||||
## Requirements
|
||||
|
||||
C++26 modules and `import std;` need a recent toolchain:
|
||||
|
||||
| Requirement | Version |
|
||||
|---|---|
|
||||
| Compiler | **GCC 15+** (or Clang with a standard library that provides the `std` module) |
|
||||
| CMake | **3.30+** (for `CMAKE_EXPERIMENTAL_CXX_IMPORT_STD`) |
|
||||
| Standard library | libstdc++ for `std::stacktrace` |
|
||||
|
||||
Ubuntu 26.04's default `g++` (GCC 15) and CMake 4 satisfy this, and that is the
|
||||
release the CI targets and the `.deb` is built for. Ubuntu 24.04 ships GCC 13 and
|
||||
CMake 3.28 and cannot build `import std;` without extra toolchains, so it is not
|
||||
supported.
|
||||
|
||||
## Building
|
||||
|
||||
```sh
|
||||
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build -j
|
||||
ctest --test-dir build --output-on-failure
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
| Option | Default | Description |
|
||||
|---|---|---|
|
||||
| `ENDERLOG_WERROR` | `OFF` | Treat warnings as errors |
|
||||
| `ENDERLOG_BACKTRACE_SYMBOLS` | `ON` | Link executables with `-rdynamic` so traces can name frames |
|
||||
| `ENDERLOG_BUILD_TESTS` | `ON` | Build the test suite |
|
||||
| `ENDERLOG_BUILD_EXAMPLES` | `ON` | Build the example program |
|
||||
|
||||
Build Debug or RelWithDebInfo when you need to read a trace: frame names come
|
||||
from debug information, and a release build often reports application frames as
|
||||
`<unknown>`.
|
||||
|
||||
## Using the library
|
||||
|
||||
### `add_subdirectory`
|
||||
|
||||
```cmake
|
||||
add_subdirectory(libenderlog)
|
||||
target_link_libraries(my_app PRIVATE enderlog::enderlog)
|
||||
```
|
||||
|
||||
### Installed package
|
||||
|
||||
```cmake
|
||||
find_package(enderlog REQUIRED)
|
||||
target_link_libraries(my_app PRIVATE enderlog::enderlog)
|
||||
```
|
||||
|
||||
The static archive is installed together with the module interface source
|
||||
(`ender.log.cppm`) because a module's BMI is compiler-version-specific — the
|
||||
consumer rebuilds it from the source.
|
||||
|
||||
### In code
|
||||
|
||||
```cpp
|
||||
import std;
|
||||
import ender.log;
|
||||
|
||||
namespace log = ender::log;
|
||||
|
||||
auto main() -> int {
|
||||
log::configure({.minimum = log::level::debug, .stacktrace_from = log::level::warn});
|
||||
|
||||
log::info(std::format("body {} moved to {:.2f}", 7, 12.35)); // formatted by the caller
|
||||
log::error("a body left the world"); // carries a stack trace
|
||||
}
|
||||
```
|
||||
|
||||
Example output:
|
||||
|
||||
```
|
||||
[11:32:18] ERROR example: a body left the world (examples/main.cpp:8)
|
||||
#0 simulate_one_step (examples/main.cpp:8)
|
||||
#1 main (examples/main.cpp:20)
|
||||
#2 <unknown>
|
||||
#3 __libc_start_main
|
||||
#4 _start
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
```cpp
|
||||
log::configure({
|
||||
.minimum = log::level::debug, // drop everything below this
|
||||
.stacktrace_from = log::level::error, // capture a stack at/above this
|
||||
.stacktrace_depth = 16, // max frames kept
|
||||
.stacktrace_skip = 2, // frames dropped before the caller is found
|
||||
});
|
||||
```
|
||||
|
||||
`log::current_options()` reads it back, `log::add_sink(...)` adds a destination,
|
||||
and `log::set_sinks({...})` replaces them.
|
||||
|
||||
### Writing to a file
|
||||
|
||||
```cpp
|
||||
namespace log = ender::log;
|
||||
|
||||
// Archive any existing enderlog.log to enderlog.log.<timestamp>, then start a
|
||||
// fresh file for this run. Rotate at 64 KiB and keep the last 5 archives.
|
||||
auto sink = log::add_file_sink("enderlog.log", {.max_file_size = 64 * 1024, .max_archives = 5});
|
||||
```
|
||||
|
||||
- **No appending onto a previous run.** On open, an existing non-empty
|
||||
`enderlog.log` is renamed to `enderlog.log.<YYYYmmdd-HHMMSS>` before the new
|
||||
file is created, so every run gets its own file and the previous run's log is
|
||||
preserved. A leftover empty file is simply replaced.
|
||||
- `file_options::max_file_size` (0 disables) rotates the active file mid-run the
|
||||
same way, and never archives an empty file. `file_options::max_archives`
|
||||
(0 keeps all) deletes the oldest archives beyond the limit.
|
||||
- `file_options::flush_each_record` (on by default) flushes after every record so
|
||||
a crash keeps the tail.
|
||||
- `add_file_sink` adds the sink to the global logger and returns it; `path()` and
|
||||
`archives()` expose what it wrote. The `file_sink` class can also be used
|
||||
directly and installed with `set_sinks`.
|
||||
|
||||
## Packaging
|
||||
|
||||
`cpack` produces a Debian package:
|
||||
|
||||
```sh
|
||||
cmake -S . -B build -DENDERLOG_DISTRO=ubuntu26.04
|
||||
cmake --build build -j
|
||||
cd build
|
||||
cpack
|
||||
# -> libenderlog-dev_0.0.1_amd64_ubuntu26.04.deb
|
||||
```
|
||||
|
||||
The package installs the static archive, the module interface source and the
|
||||
CMake package config. CI builds it for Ubuntu 26.04 and publishes it as a job
|
||||
artifact.
|
||||
|
||||
## License
|
||||
|
||||
MIT — see [LICENSE](LICENSE).
|
||||
Reference in New Issue
Block a user