feat(imaging): default to pure-Rust codecs
Some checks failed
CI / rust-skia (Rust only) (push) Successful in 2m51s
CI / required (push) Failing after 3m23s

This commit is contained in:
2026-08-13 11:24:38 +00:00
parent e16ad04dbd
commit 25fdffbd3b
25 changed files with 124 additions and 103 deletions

View File

@@ -49,20 +49,22 @@ python3 tools/check_test_parity.py
python3 tools/audit_red_suite.py
```
### Optional codec features
### Image codec features
The default build needs no system image-codec library. It includes the
project-owned TGA and DDS implementations; the default `dds-bc67` feature uses
the pure-Rust `bcdec_rs` dependency for BC6H and BC7. Build without BC6H/BC7
support with `cargo build -p libremetaverse --no-default-features`.
project-owned TGA and DDS implementations, pure-Rust J2K/JP2 through
`rust-j2k`, and pure-Rust common-raster decoding through the default
`libremetaverse-imaging-skia` `rust-skia` feature. The default `dds-bc67`
feature uses the pure-Rust `bcdec_rs` dependency for BC6H and BC7. Use
`--no-default-features` only for a deliberately minimal codec-free build.
The currently available opt-in codec backends are:
The available default and optional codec backends are:
| Capability | Feature | Build command | External prerequisite |
| --- | --- | --- | --- |
| Raw J2K and JP2 decoding/encoding through OpenJPEG | `libremetaverse/jpeg2000` | `cargo build -p libremetaverse --features jpeg2000` | OpenJPEG 2.5.4 or newer |
| Raw J2K and JP2 decoding/encoding in pure Rust | `libremetaverse/rust-j2k` | `cargo build -p libremetaverse --no-default-features --features rust-j2k` | none |
| BMP, GIF, ICO, JPEG, PNG, WBMP, and WebP decoding in pure Rust | `libremetaverse-imaging-skia/rust-skia` | `cargo build -p libremetaverse-imaging-skia --features rust-skia` | None |
| Raw J2K and JP2 decoding/encoding in pure Rust (default) | `libremetaverse/rust-j2k` | `cargo build -p libremetaverse` | none |
| BMP, GIF, ICO, JPEG, PNG, WBMP, and WebP decoding in pure Rust (default) | `libremetaverse-imaging-skia/rust-skia` | `cargo build -p libremetaverse-imaging-skia` | none |
| Raw J2K and JP2 decoding/encoding through OpenJPEG (optional) | `libremetaverse/jpeg2000` | `cargo build -p libremetaverse --features jpeg2000` | OpenJPEG 2.5.4 or newer |
| BMP, GIF, ICO, JPEG, PNG, WBMP, and WebP decoding through native Skia | `libremetaverse-imaging-skia/skia` | `cargo build -p libremetaverse-imaging-skia --features skia` | A matching rust-skia binary cache, or the documented native source-build tools |
| PCM to Ogg Vorbis encoding | `libremetaverse/vorbis` | `cargo build -p libremetaverse --features vorbis` | `vorbis_rs` 0.5.6 and its native encoder build prerequisites |
@@ -70,9 +72,9 @@ Run their focused test suites with:
```sh
cargo test -p libremetaverse-imaging --features jpeg2000
cargo test -p libremetaverse-imaging --no-default-features --features rust-j2k
cargo test -p libremetaverse-imaging
cargo test -p libremetaverse-imaging-skia --features skia
cargo test -p libremetaverse-imaging-skia --features rust-skia
cargo test -p libremetaverse-imaging-skia
cargo test -p libremetaverse --no-default-features --features vorbis
```
@@ -95,10 +97,11 @@ Deterministic task, socket, file, subscription, cancellation, and allocation
baselines are documented in the
[concurrency hardening guide](docs/concurrency-hardening.md).
The independent `rust-j2k` backend is off by default and never enables the
native OpenJPEG adapter. Its explicit `RustJ2kCodec` preserves the existing
`J2kCodec` meaning when both features are enabled. Use the combined feature set
to run backend-interoperability tests without changing application selection.
The default `rust-j2k` backend never enables the native OpenJPEG adapter. Its
explicit `RustJ2kCodec` preserves the existing `J2kCodec` meaning when both
features are enabled. Likewise, default `RustSkiaTextureCodec` selection does
not rename or enable native `SkiaTextureCodec`. The native features remain
available for backend-interoperability testing or explicit application use.
`tests/upstream-tests.json` is the machine-readable NUnit parity catalog.
Translated tests live in hand-written Rust files with the `parity-case` marker
@@ -208,8 +211,14 @@ Legacy and DX10 BC1 through BC5 decoding is built in. The default `dds-bc67`
feature adds BC6H/BC7 decoding through the safe, pure-Rust `bcdec_rs` crate;
disabling default features removes that optional dependency and makes those
two formats return a typed unsupported-format error.
JPEG 2000 is available through the opt-in `jpeg2000` feature. It provides raw
J2K and JP2 lossless/lossy encoding and decoding, preserves one through five
The default `rust-j2k` feature provides raw J2K and JP2 lossless/lossy encoding
and decoding through `RustJ2kCodec`, backed by the exact pure-Rust `j2k` 0.8.1
release. It supports native component precision/signedness/order,
reduced-resolution and progressive quality-layer decoding, and strict or
explicitly permissive truncation handling. It has no C/C++ codec, CMake,
pkg-config, vcpkg, CLR, or platform-specific API.
The optional `jpeg2000` feature provides the native OpenJPEG alternative. It
provides raw J2K and JP2 lossless/lossy encoding and decoding, preserves one through five
component order, precision, signedness, and alpha metadata until explicit byte
conversion, and bounds encoded input, output, dimensions, and decoded samples.
The compatibility encoder reproduces CoreJ2K's four-plane RGB/alpha view,
@@ -217,20 +226,15 @@ including its alpha-only and opaque-alpha substitutions. See
[`crates/libremetaverse-openjpeg/README.md`](crates/libremetaverse-openjpeg/README.md)
for OpenJPEG prerequisites, licensing, and deployment details. Default builds
do not discover or link OpenJPEG.
The independent opt-in `rust-j2k` feature provides the same bounded public
options through `RustJ2kCodec`, backed by the exact pure-Rust `j2k` 0.8.1
release. It supports J2K and JP2, reversible and irreversible encoding,
native component precision/signedness/order, reduced-resolution and progressive
quality-layer decoding, and strict or explicitly permissive truncation handling.
It has no C/C++ codec, CMake, pkg-config, vcpkg, CLR, or platform-specific API.
The optional Skia adapter decodes BMP, GIF, ICO, JPEG, PNG, WBMP, and WebP into
the same checked planar representation. Its `skia` feature uses pinned
The default pure-Rust raster adapter decodes BMP, GIF, ICO, JPEG, PNG, WBMP,
and WebP into the same checked planar representation through
`RustSkiaTextureCodec`. Its optional native `skia` feature uses pinned
rust-skia binary-cache configurations on macOS, Linux, and Windows, normalizes
premultiplied color at the imaging boundary, and keeps encoded input, decoded
dimensions, strides, and allocations bounded. See
[`crates/libremetaverse-imaging-skia/README.md`](crates/libremetaverse-imaging-skia/README.md)
for native prerequisites, cache/source-build controls, licenses, and packaging
details. Default workspace builds do not compile or link Skia.
details. Default workspace builds compile neither OpenJPEG nor native Skia.
Primitive profile and path meshing is implemented in native Rust from the
pinned `PrimMesher.cs` reference. It covers checked linear, flexible, and
circular extrusion; profile cuts and hollows; twist, taper, shear, skew,

View File

@@ -46,7 +46,7 @@ The exact mapping, exported-marker, feature, and normalized Rust declaration has
- .NET events become `subscribe_*` calls returning an owned `Subscription`; callbacks are `Send + Sync` and dispatch outside internal locks.
- `IDisposable` becomes explicit idempotent shutdown plus `Drop`; cancellation remains a typed `Error::Cancelled`.
- Closed C# enums retain fixed discriminants and are SemVer-breaking to extend; flags retain unknown bits, while public Rust error enums are `non_exhaustive`.
- Optional native and pure-Rust backends remain feature-gated (`jpeg2000`, `rust-j2k`, `skia`, `rust-skia`, `vorbis`, `real-audio`); default and empty-default sets are part of the baseline.
- Pure-Rust image backends are enabled by default (`rust-j2k`, `rust-skia`); native alternatives remain feature-gated (`jpeg2000`, `skia`, `vorbis`, `real-audio`), and empty-default sets remain part of the baseline.
- Capability HTTP injection uses project-owned `HttpMessageHandler`, `CapsHttpLimits`, and `with_native_transport`; the internal `reqwest` client is not part of the public surface.
- External C# signature types map to core/std, adopted cross-platform crates, or project-owned boundary types; no third-party crate path leaks through mapped signatures or public Rust declarations.

View File

@@ -30,7 +30,8 @@
"dep:bcdec_rs"
],
"default": [
"dds-bc67"
"dds-bc67",
"rust-j2k"
],
"jpeg2000": [
"libremetaverse-imaging/jpeg2000"
@@ -69,7 +70,9 @@
"name": "libremetaverse-imaging",
"version": "0.0.1",
"features": {
"default": [],
"default": [
"rust-j2k"
],
"jpeg2000": [
"dep:libremetaverse-openjpeg"
],
@@ -86,7 +89,9 @@
"name": "libremetaverse-imaging-skia",
"version": "0.0.1",
"features": {
"default": [],
"default": [
"rust-skia"
],
"rust-skia": [
"dep:image-webp",
"dep:skia-rs-codec",

View File

@@ -15,8 +15,8 @@
{ "name": "getrandom", "versions": ["0.4.3"], "purpose": "Operating-system entropy for protocol identifiers", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`getrandom`" },
{ "name": "goblin", "versions": ["0.10.5"], "purpose": "Cross-platform ELF, PE, and Mach-O release artifact inspection", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`goblin`" },
{ "name": "hound", "versions": ["3.5.1"], "purpose": "Bounded PCM WAV parsing for WebRTC playback", "maintenance": "stable", "transitive_cost": "low", "native": false, "rewrite_anchor": "`hound`" },
{ "name": "image-webp", "versions": ["0.2.4"], "purpose": "Opt-in pure-Rust bounded WebP image decoding", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`image-webp`" },
{ "name": "j2k", "versions": ["0.8.1"], "purpose": "Opt-in pure-Rust bounded JPEG 2000 and JP2 encoding and decoding", "maintenance": "active", "transitive_cost": "medium", "native": false, "rewrite_anchor": "`j2k`" },
{ "name": "image-webp", "versions": ["0.2.4"], "purpose": "Default pure-Rust bounded WebP image decoding", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`image-webp`" },
{ "name": "j2k", "versions": ["0.8.1"], "purpose": "Default pure-Rust bounded JPEG 2000 and JP2 encoding and decoding", "maintenance": "active", "transitive_cost": "medium", "native": false, "rewrite_anchor": "`j2k`" },
{ "name": "mac_address2", "versions": ["2.0.2"], "purpose": "Cross-platform machine identity compatibility input", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`mac_address2`" },
{ "name": "md-5", "versions": ["0.10.6"], "purpose": "Legacy protocol checksum compatibility", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`md-5`" },
{ "name": "os_info", "versions": ["3.15.0"], "purpose": "Cross-platform login platform reporting", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`os_info`" },
@@ -30,7 +30,7 @@
{ "name": "sha1", "versions": ["0.10.7"], "purpose": "Legacy protocol hash compatibility", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`sha1`" },
{ "name": "sha2", "versions": ["0.11.0"], "purpose": "Manifest integrity and protocol hashing", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`sha2`" },
{ "name": "skia-safe", "versions": ["0.99.0"], "purpose": "Opt-in cross-platform Skia image decoding", "maintenance": "monitored-native", "transitive_cost": "high", "native": true, "rewrite_anchor": "`skia-safe`" },
{ "name": "skia-rs-codec", "versions": ["0.3.0"], "purpose": "Opt-in pure-Rust BMP, GIF, ICO, JPEG, PNG, and WBMP image decoding", "maintenance": "active", "transitive_cost": "high", "native": false, "rewrite_anchor": "`skia-rs-codec`" },
{ "name": "skia-rs-codec", "versions": ["0.3.0"], "purpose": "Default pure-Rust BMP, GIF, ICO, JPEG, PNG, and WBMP image decoding", "maintenance": "active", "transitive_cost": "high", "native": false, "rewrite_anchor": "`skia-rs-codec`" },
{ "name": "stats_alloc", "versions": ["0.1.10"], "purpose": "Allocation-budget compatibility tests, concurrency leak audits, and reproducible performance evidence", "maintenance": "stable", "transitive_cost": "low", "native": false, "rewrite_anchor": "`stats_alloc`" },
{ "name": "str0m", "versions": ["0.22.0"], "purpose": "Native Rust ICE, DTLS, SRTP, RTP, and SCTP WebRTC transport", "maintenance": "active", "transitive_cost": "high", "native": false, "rewrite_anchor": "`str0m`" },
{ "name": "syn", "versions": ["2.0.119"], "purpose": "Syntax validation for generated Rust sources", "maintenance": "active", "transitive_cost": "medium", "native": false, "rewrite_anchor": "`syn`" },

View File

@@ -8,12 +8,12 @@ repository.workspace = true
description = "Optional Skia image decoder for the MetaCrate LibreMetaverse rewrite"
[features]
default = []
default = ["rust-skia"]
skia = ["dep:skia-safe"]
rust-skia = ["dep:image-webp", "dep:skia-rs-codec", "skia-rs-codec/gif", "skia-rs-codec/jpeg", "skia-rs-codec/png"]
[dependencies]
libremetaverse-imaging = { version = "0.0.1", path = "../libremetaverse-imaging" }
libremetaverse-imaging = { version = "0.0.1", path = "../libremetaverse-imaging", default-features = false }
libremetaverse-types = { version = "0.0.1", path = "../libremetaverse-types" }
image-webp = { version = "=0.2.4", optional = true }
skia-rs-codec = { version = "=0.3.0", default-features = false, optional = true }

View File

@@ -6,7 +6,8 @@ exposes a `skia-safe` type and never discovers or links Skia.
## Features and formats
The default feature set has no Skia dependency. `SkiaTextureCodec::decode` and
The default feature set enables the pure-Rust `rust-skia` backend and has no
native Google Skia dependency. `SkiaTextureCodec::decode` and
`RustSkiaTextureCodec::decode` return a typed `InvalidOperation` error when
their respective feature is disabled, while conversion from the checked
project-owned `backend::SKBitmap` remains available for tests and callers that
@@ -15,7 +16,7 @@ already own decoded pixels.
For a fully Rust implementation, enable:
```sh
cargo test -p libremetaverse-imaging-skia --no-default-features --features rust-skia
cargo test -p libremetaverse-imaging-skia
```
`rust-skia` pins [`skia-rs-codec` 0.3.0](https://github.com/rust-skia/skia-rs)

View File

@@ -1,9 +1,9 @@
//! Optional Skia imaging adapter corresponding to `LibreMetaverse.Imaging.Skia`.
//!
//! Enable `skia` or the fully native-Rust `rust-skia` alternative for bounded
//! `BMP`, `GIF`, `ICO`, `JPEG`, `PNG`, `WBMP`, and `WebP` decoding.
//! The default build remains inert. See the crate README for native cache,
//! source-build, licensing, and cross-platform prerequisite details.
//! The default `rust-skia` backend provides bounded pure-Rust `BMP`, `GIF`,
//! `ICO`, `JPEG`, `PNG`, `WBMP`, and `WebP` decoding. Enable the optional
//! `skia` feature when native Google Skia is specifically required. See the
//! crate README for native cache, source-build, licensing, and deployment details.
extern crate self as libremetaverse_imaging_skia;

View File

@@ -8,7 +8,7 @@ repository.workspace = true
description = "Texture codec abstractions for the MetaCrate LibreMetaverse rewrite"
[features]
default = []
default = ["rust-j2k"]
jpeg2000 = ["dep:libremetaverse-openjpeg"]
rust-j2k = ["dep:j2k"]

View File

@@ -1,9 +1,9 @@
//! Image and texture abstractions corresponding to `LibreMetaverse.Imaging`.
//!
//! The default surface provides managed images plus native `TGA`/`DDS` handling.
//! Enable `jpeg2000` for the audited system `OpenJPEG` adapter or `rust-j2k`
//! for the independently selectable pure-Rust JPEG 2000 backend. Ordinary
//! users can consume the traits without either codec dependency.
//! The default surface provides managed images, native `TGA`/`DDS` handling,
//! and the pure-Rust `rust-j2k` JPEG 2000 backend. Enable `jpeg2000` for the
//! optional audited system `OpenJPEG` adapter. Consumers that only need the
//! format-neutral image types can disable default features.
extern crate self as libremetaverse_imaging;

View File

@@ -8,7 +8,7 @@ repository.workspace = true
description = "Primitive meshing shims for the MetaCrate LibreMetaverse rewrite"
[dependencies]
libremetaverse-imaging = { version = "0.0.1", path = "../libremetaverse-imaging" }
libremetaverse-imaging = { version = "0.0.1", path = "../libremetaverse-imaging", default-features = false }
libremetaverse-types = { version = "0.0.1", path = "../libremetaverse-types" }
[dev-dependencies]

View File

@@ -8,7 +8,7 @@ repository.workspace = true
description = "Rust rewrite shell for the LibreMetaverse client library"
[features]
default = ["dds-bc67"]
default = ["dds-bc67", "rust-j2k"]
dds-bc67 = ["dep:bcdec_rs"]
jpeg2000 = ["libremetaverse-imaging/jpeg2000"]
rust-j2k = ["libremetaverse-imaging/rust-j2k"]
@@ -20,7 +20,7 @@ bcdec_rs = { version = "0.2.0", optional = true }
flate2 = "1.1.2"
futures-channel = "0.3.31"
futures-util = "0.3.31"
libremetaverse-imaging = { version = "0.0.1", path = "../libremetaverse-imaging" }
libremetaverse-imaging = { version = "0.0.1", path = "../libremetaverse-imaging", default-features = false }
libremetaverse-prim-mesher = { version = "0.0.1", path = "../libremetaverse-prim-mesher" }
libremetaverse-structured-data = { version = "0.0.1", path = "../libremetaverse-structured-data" }
libremetaverse-types = { version = "0.0.1", path = "../libremetaverse-types" }

View File

@@ -15,11 +15,11 @@ they use those APIs directly.
| Crate | Choose it for | Features or system boundary |
| --- | --- | --- |
| `libremetaverse` | Grid client, login, network, agents, inventory, assets, appearance, world, and social APIs | Default pure-Rust BC6H/BC7; optional `jpeg2000`, `rust-j2k`, and `vorbis` |
| `libremetaverse` | Grid client, login, network, agents, inventory, assets, appearance, world, and social APIs | Default pure-Rust BC6H/BC7 and J2K/JP2; optional native `jpeg2000` and `vorbis` |
| `libremetaverse-types` | UUIDs, vectors, matrices, colors, cancellation, compatibility collections, and boundary types | Pure Rust |
| `libremetaverse-structured-data` | LLSD/OSD XML, JSON, binary, and notation | Pure Rust |
| `libremetaverse-imaging` | Managed images, TGA/DDS, codec traits | Optional `jpeg2000` or pure-Rust `rust-j2k` |
| `libremetaverse-imaging-skia` | Common raster formats through native Skia or pure Rust | Optional `skia` native build/cache; optional `rust-skia` has no native prerequisites |
| `libremetaverse-imaging` | Managed images, TGA/DDS, codec traits | Default pure-Rust `rust-j2k`; optional native `jpeg2000` |
| `libremetaverse-imaging-skia` | Common raster formats through native Skia or pure Rust | Default pure-Rust `rust-skia`; optional `skia` native build/cache |
| `libremetaverse-prim-mesher` | Legacy prim and sculpt geometry | Pure Rust |
| `libremetaverse-rendering-simple` | Deterministic reference geometry | Pure Rust |
| `libremetaverse-rendering-mesh-foundry` | Prim, terrain, sculpt, and mesh-asset rendering | Pure Rust |
@@ -31,8 +31,8 @@ they use those APIs directly.
| `libremetaverse-openjpeg` | Audited `OpenJPEG` adapter | System `OpenJPEG` 2.5.4 or newer |
| `libremetaverse-opus` | Audited Opus encoder/decoder adapter | System libopus |
Features are additive. Keep defaults unless you need a codec, and enable one
native adapter at a time while diagnosing installation problems. The
Features are additive. Keep the pure-Rust defaults for normal applications,
and enable a native adapter only when it is specifically needed. The
[release CI matrix](https://git.rfc1437.de/hugo/MetaCrate/src/branch/main/docs/release-ci-matrix.md) records every validated combination.
## Naming and overload migration
@@ -190,7 +190,7 @@ The default client build needs no image-codec system library. Optional native
features require:
- `OpenJPEG` 2.5.4 or newer for `jpeg2000`;
- no native prerequisite for the pure-Rust `rust-j2k` feature;
- no native prerequisite for the default pure-Rust `rust-j2k` and `rust-skia` features;
- the rust-skia prerequisites/cache for `skia`;
- system libopus for WebRTC voice and the `libremetaverse-opus` adapter;
- ALSA development headers on Linux, `CoreAudio` on macOS, or WASAPI on Windows

View File

@@ -6,11 +6,11 @@ and mutable raw payload. Concrete animation, sound, script, calling-card,
landmark, settings, mesh, texture, wearable, and notecard models enforce a 64 MiB
input ceiling and reject malformed structured formats with typed errors. Mesh and
settings parsing use StructuredData. Texture encode/decode uses the imaging
crate's bounded `jpeg2000` adapter, while the opt-in `vorbis` feature streams
PCM conversion through bounded planar Ogg Vorbis blocks; neither API exposes
codec implementation types. With either native codec feature disabled, its
public API remains present and returns a typed `InvalidOperation` for otherwise
valid input instead of discovering or linking a system codec implicitly.
crate's default bounded pure-Rust JPEG 2000 adapter, while the opt-in `vorbis`
feature streams PCM conversion through bounded planar Ogg Vorbis blocks;
neither API exposes codec implementation types. The optional native OpenJPEG
path is selected only by its explicit feature and never discovered or linked
by a default build.
`AssetManager` is client-owned and shares one `DownloadManager` and `AssetCache`.
ViewerAsset, GetMesh, GetTexture, and server-bake requests check the cache before

View File

@@ -49,7 +49,8 @@ All native boundaries have Linux, Windows, and macOS strategies:
|---|---|---|---|
| libopus 1.3+ | `pkg-config` package `opus` | vcpkg `opus` for MSVC; `pkg-config` for GNU | WebRTC voice crate |
| OpenJPEG 2.5.4+ | `pkg-config` package `libopenjp2` | vcpkg `openjpeg` for MSVC; `pkg-config` for GNU | opt-in `jpeg2000` |
| j2k 0.8.1 | pure Rust, no platform prerequisite | same pure-Rust graph | opt-in `rust-j2k` |
| j2k 0.8.1 | pure Rust, no platform prerequisite | same pure-Rust graph | default `rust-j2k` |
| skia-rs-codec 0.3.0 and image-webp 0.2.4 | pure Rust, no platform prerequisite | same pure-Rust graph | default `rust-skia` |
| Skia 0.99.0 | target-specific official binary cache or source build | same target-specific strategy | opt-in `skia` |
| Vorbis 0.5.6 stack | `vorbis_rs` builds its reviewed C codec stack | same crate strategy | opt-in `vorbis` |
| Physical audio | ALSA development files on Linux; CoreAudio is system-provided on macOS | WASAPI is system-provided | opt-in `real-audio` |

View File

@@ -17,18 +17,19 @@ and [`MeshFoundry` guide](../crates/libremetaverse-rendering-mesh-foundry/README
| --- | --- | --- | --- |
| Managed images and TGA | enabled | n/a | none |
| JPEG 2000 | disabled | `libremetaverse-imaging/jpeg2000` | OpenJPEG 2.5.x |
| JPEG 2000 (pure Rust) | disabled | `libremetaverse-imaging/rust-j2k` | none (`j2k` 0.8.1) |
| BMP/GIF/ICO/JPEG/PNG/WBMP/WebP (pure Rust) | disabled | `libremetaverse-imaging-skia/rust-skia` | none (`skia-rs-codec` 0.3.0 plus `image-webp` 0.2.4) |
| JPEG 2000 (pure Rust) | enabled | `libremetaverse-imaging/rust-j2k` | none (`j2k` 0.8.1) |
| BMP/GIF/ICO/JPEG/PNG/WBMP/WebP (pure Rust) | enabled | `libremetaverse-imaging-skia/rust-skia` | none (`skia-rs-codec` 0.3.0 plus `image-webp` 0.2.4) |
| BMP/GIF/ICO/JPEG/PNG/WBMP/WebP (native) | disabled | `libremetaverse-imaging-skia/skia` | Skia |
| Prim and sculpt meshing, OBJ ingestion | enabled | n/a | none |
OpenJPEG is a non-default workspace member so release tooling can package the
adapter explicitly, and imaging reaches it only through the optional
dependency. The independent `rust-j2k` feature reaches only the audited
pure-Rust `j2k` graph; enabling both features keeps both explicit codec types
available for interoperability testing. Skia is likewise an optional,
target-specific dependency. The consolidated required Gitea gate exercises the
native feature, while a dedicated `ubuntu-latest` job tests `rust-skia`, audits
dependency. The default `rust-j2k` feature reaches only the audited pure-Rust
`j2k` graph; enabling both features keeps both explicit codec types available
for interoperability testing. Native Skia is likewise an optional,
target-specific dependency, while `rust-skia` is the raster crate default. The
consolidated required Gitea gate exercises the native feature, while a dedicated
`ubuntu-latest` job tests `rust-skia`, audits
its normal/build graph without installing native codec tools, and compiles the
same code for WASM. Minimal-feature checks still prove no codec is needed for
the owned Rust and translated compatibility tests.

View File

@@ -17,14 +17,14 @@ is evidence of remaining work, never permission to waive it.
## Features and prerequisites
The default `libremetaverse` build uses the pure-Rust `dds-bc67` image path.
Optional boundaries are explicit:
The default MetaCrate build uses the pure-Rust `dds-bc67`, `rust-j2k`, and
`rust-skia` image paths. Native alternatives remain explicit opt-ins:
| Feature | Capability | Native/build prerequisite |
| --- | --- | --- |
| `jpeg2000` | J2K/JP2 through the safe OpenJPEG adapter | OpenJPEG 2.5.4 or newer |
| `rust-j2k` | J2K/JP2 through `RustJ2kCodec` | none; exact pure-Rust `j2k` 0.8.1 |
| `rust-skia` | BMP/GIF/ICO/JPEG/PNG/WBMP/WebP through `RustSkiaTextureCodec` | none; exact pure-Rust `skia-rs-codec` 0.3.0 and `image-webp` 0.2.4 |
| `rust-j2k` (default) | J2K/JP2 through `RustJ2kCodec` | none; exact pure-Rust `j2k` 0.8.1 |
| `rust-skia` (default) | BMP/GIF/ICO/JPEG/PNG/WBMP/WebP through `RustSkiaTextureCodec` | none; exact pure-Rust `skia-rs-codec` 0.3.0 and `image-webp` 0.2.4 |
| `skia` | PNG/JPEG/WebP and other Skia formats | Skia 0.99 target cache or documented source tools |
| `vorbis` | PCM to Ogg Vorbis | the locked `vorbis_rs` native build stack |
| default WebRTC | native ICE/DTLS/SRTP/RTP/SCTP and Opus | system libopus 1.3 or newer |

View File

@@ -15,11 +15,11 @@ they use those APIs directly.
| Crate | Choose it for | Features or system boundary |
| --- | --- | --- |
| `libremetaverse` | Grid client, login, network, agents, inventory, assets, appearance, world, and social APIs | Default pure-Rust BC6H/BC7; optional `jpeg2000`, `rust-j2k`, and `vorbis` |
| `libremetaverse` | Grid client, login, network, agents, inventory, assets, appearance, world, and social APIs | Default pure-Rust BC6H/BC7 and J2K/JP2; optional native `jpeg2000` and `vorbis` |
| `libremetaverse-types` | UUIDs, vectors, matrices, colors, cancellation, compatibility collections, and boundary types | Pure Rust |
| `libremetaverse-structured-data` | LLSD/OSD XML, JSON, binary, and notation | Pure Rust |
| `libremetaverse-imaging` | Managed images, TGA/DDS, codec traits | Optional `jpeg2000` or pure-Rust `rust-j2k` |
| `libremetaverse-imaging-skia` | Common raster formats through native Skia or pure Rust | Optional `skia` native build/cache; optional `rust-skia` has no native prerequisites |
| `libremetaverse-imaging` | Managed images, TGA/DDS, codec traits | Default pure-Rust `rust-j2k`; optional native `jpeg2000` |
| `libremetaverse-imaging-skia` | Common raster formats through native Skia or pure Rust | Default pure-Rust `rust-skia`; optional `skia` native build/cache |
| `libremetaverse-prim-mesher` | Legacy prim and sculpt geometry | Pure Rust |
| `libremetaverse-rendering-simple` | Deterministic reference geometry | Pure Rust |
| `libremetaverse-rendering-mesh-foundry` | Prim, terrain, sculpt, and mesh-asset rendering | Pure Rust |
@@ -31,8 +31,8 @@ they use those APIs directly.
| `libremetaverse-openjpeg` | Audited `OpenJPEG` adapter | System `OpenJPEG` 2.5.4 or newer |
| `libremetaverse-opus` | Audited Opus encoder/decoder adapter | System libopus |
Features are additive. Keep defaults unless you need a codec, and enable one
native adapter at a time while diagnosing installation problems. The
Features are additive. Keep the pure-Rust defaults for normal applications,
and enable a native adapter only when it is specifically needed. The
[release CI matrix](release-ci-matrix.md) records every validated combination.
## Naming and overload migration
@@ -190,7 +190,7 @@ The default client build needs no image-codec system library. Optional native
features require:
- `OpenJPEG` 2.5.4 or newer for `jpeg2000`;
- no native prerequisite for the pure-Rust `rust-j2k` feature;
- no native prerequisite for the default pure-Rust `rust-j2k` and `rust-skia` features;
- the rust-skia prerequisites/cache for `skia`;
- system libopus for WebRTC voice and the `libremetaverse-opus` adapter;
- ALSA development headers on Linux, `CoreAudio` on macOS, or WASAPI on Windows

View File

@@ -10,9 +10,9 @@ publish = false
[dependencies]
clap = { version = "4.5", features = ["derive"] }
libremetaverse = { version = "0.0.1", path = "../crates/libremetaverse", default-features = false }
libremetaverse-imaging = { version = "0.0.1", path = "../crates/libremetaverse-imaging", features = ["jpeg2000"] }
libremetaverse-imaging-skia = { version = "0.0.1", path = "../crates/libremetaverse-imaging-skia", features = ["skia"] }
libremetaverse = { version = "0.0.1", path = "../crates/libremetaverse" }
libremetaverse-imaging = { version = "0.0.1", path = "../crates/libremetaverse-imaging" }
libremetaverse-imaging-skia = { version = "0.0.1", path = "../crates/libremetaverse-imaging-skia" }
libremetaverse-structured-data = { version = "0.0.1", path = "../crates/libremetaverse-structured-data" }
libremetaverse-voice-vivox = { version = "0.0.1", path = "../crates/libremetaverse-voice-vivox" }
libremetaverse-voice-webrtc = { version = "0.0.1", path = "../crates/libremetaverse-voice-webrtc" }

View File

@@ -14,10 +14,10 @@ use libremetaverse::types::compat::CancellationToken;
use libremetaverse::types::{AssetType, FolderType, InventoryType, UUID};
use libremetaverse::{PermissionMask, Permissions};
use libremetaverse_imaging::{
J2kCodec, J2kCompression, J2kDecodeOptions, J2kEncodeOptions, ManagedImage,
ManagedImageImageChannels,
J2kCompression, J2kDecodeOptions, J2kEncodeOptions, ManagedImage, ManagedImageImageChannels,
RustJ2kCodec,
};
use libremetaverse_imaging_skia::SkiaTextureCodec;
use libremetaverse_imaging_skia::RustSkiaTextureCodec;
use std::collections::{HashMap, HashSet};
use std::fs::{self, File};
use std::io::{Cursor, Read, Write};
@@ -1465,14 +1465,14 @@ fn image_to_j2k(path: &Path) -> Result<Vec<u8>, String> {
.unwrap_or("")
.to_ascii_lowercase();
if matches!(extension.as_str(), "jp2" | "j2c" | "j2k") {
J2kCodec::decode_bytes(&bytes, J2kDecodeOptions::default())
RustJ2kCodec::decode_bytes(&bytes, J2kDecodeOptions::default())
.map_err(|_| "Invalid JPEG 2000 image".to_owned())?;
return Ok(bytes);
}
let mut image = if matches!(extension.as_str(), "tga" | "targa") {
decode_tga(&bytes)?
} else {
SkiaTextureCodec::new()
RustSkiaTextureCodec::new()
.map_err(|_| "Could not initialize the image decoder".to_owned())?
.decode(Box::new(Cursor::new(bytes)))
.map_err(|_| "Failed to decode image".to_owned())?
@@ -1491,7 +1491,7 @@ fn image_to_j2k(path: &Path) -> Result<Vec<u8>, String> {
.resize_bilinear(width.min(1024), height.min(1024))
.map_err(|_| "Failed to limit image dimensions".to_owned())?;
}
J2kCodec::encode(
RustJ2kCodec::encode(
&image,
J2kEncodeOptions::default().with_compression(J2kCompression::Lossy {
compression_ratio: 10.0,
@@ -1583,7 +1583,7 @@ async fn dump_outfit<B: Backend + ?Sized>(
.await?;
let jp2 = directory.join(format!("{texture_id}.jp2"));
write_bounded(&jp2, &bytes)?;
let image = J2kCodec::decode_bytes(&bytes, J2kDecodeOptions::default())
let image = RustJ2kCodec::decode_bytes(&bytes, J2kDecodeOptions::default())
.map_err(|_| format!("Failed to decode outfit texture {texture_id}"))?;
let tga = directory.join(format!("{texture_id}.tga"));
write_bounded(&tga, &encode_tga(&image)?)?;

View File

@@ -1466,7 +1466,7 @@ async fn download_texture<B: Backend + ?Sized>(
.as_str(),
)?;
write_bounded(&path, &data)?;
let dimensions = libremetaverse_imaging::J2kCodec::decode_bytes(
let dimensions = libremetaverse_imaging::RustJ2kCodec::decode_bytes(
&data,
libremetaverse_imaging::J2kDecodeOptions::default(),
)
@@ -1574,7 +1574,7 @@ async fn export<B: Backend + ?Sized>(
.await?;
let path = texture_directory.join(format!("{texture}.jp2"));
write_bounded(&path, &data)?;
let image = libremetaverse_imaging::J2kCodec::decode_bytes(
let image = libremetaverse_imaging::RustJ2kCodec::decode_bytes(
&data,
libremetaverse_imaging::J2kDecodeOptions::default(),
)

View File

@@ -1,4 +1,6 @@
use libremetaverse_imaging::{J2kCodec, J2kEncodeOptions, ManagedImage, ManagedImageImageChannels};
use libremetaverse_imaging::{
J2kEncodeOptions, ManagedImage, ManagedImageImageChannels, RustJ2kCodec,
};
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio};
@@ -111,7 +113,7 @@ fn one_pixel_jp2() -> Vec<u8> {
image.red[0] = 10;
image.green[0] = 20;
image.blue[0] = 30;
J2kCodec::encode(&image, J2kEncodeOptions::default()).expect("encode JPEG2000 fixture")
RustJ2kCodec::encode(&image, J2kEncodeOptions::default()).expect("encode JPEG2000 fixture")
}
fn one_pixel_tga() -> Vec<u8> {
@@ -277,7 +279,7 @@ fn fake_grid_exercises_every_owned_inventory_appearance_and_asset_command() {
b"default { state_entry() {} }"
);
assert_eq!(
J2kCodec::decode_bytes(
RustJ2kCodec::decode_bytes(
&fs::read(outfit.join(format!("{UPLOADED_TEXTURE_ASSET}.jp2")))
.expect("uploaded outfit jp2"),
libremetaverse_imaging::J2kDecodeOptions::default(),

View File

@@ -1,4 +1,6 @@
use libremetaverse_imaging::{J2kCodec, J2kEncodeOptions, ManagedImage, ManagedImageImageChannels};
use libremetaverse_imaging::{
J2kEncodeOptions, ManagedImage, ManagedImageImageChannels, RustJ2kCodec,
};
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio};
@@ -85,7 +87,7 @@ fn one_pixel_jp2() -> Vec<u8> {
image.red[0] = 10;
image.green[0] = 20;
image.blue[0] = 30;
J2kCodec::encode(&image, J2kEncodeOptions::default()).expect("encode JPEG2000")
RustJ2kCodec::encode(&image, J2kEncodeOptions::default()).expect("encode JPEG2000")
}
#[test]

View File

@@ -40,8 +40,8 @@ def check_owned_rust() -> None:
def check_features() -> None:
imaging = load_toml(OWNED_CRATES[0] / "Cargo.toml")
if imaging["features"].get("default") != []:
raise SystemExit("imaging default features must stay empty")
if imaging["features"].get("default") != ["rust-j2k"]:
raise SystemExit("imaging defaults must select only pure-Rust JPEG 2000")
if imaging["features"].get("jpeg2000") != ["dep:libremetaverse-openjpeg"]:
raise SystemExit("JPEG 2000 must stay isolated behind its optional feature")
if imaging["features"].get("rust-j2k") != ["dep:j2k"]:
@@ -50,8 +50,8 @@ def check_features() -> None:
raise SystemExit("the OpenJPEG adapter dependency must stay optional")
skia = load_toml(OWNED_CRATES[1] / "Cargo.toml")
if skia["features"].get("default") != []:
raise SystemExit("Skia adapter default features must stay empty")
if skia["features"].get("default") != ["rust-skia"]:
raise SystemExit("raster adapter defaults must select only pure-Rust codecs")
if skia["features"].get("skia") != ["dep:skia-safe"]:
raise SystemExit("Skia must stay isolated behind its optional feature")
targets = skia.get("target", {}).values()

View File

@@ -911,7 +911,10 @@ fn validate_features(name: &str, actual: &BTreeMap<String, Vec<String>>) -> Resu
let expected = match name {
"libremetaverse" => BTreeMap::from([
("dds-bc67".to_owned(), vec!["dep:bcdec_rs".to_owned()]),
("default".to_owned(), vec!["dds-bc67".to_owned()]),
(
"default".to_owned(),
vec!["dds-bc67".to_owned(), "rust-j2k".to_owned()],
),
(
"jpeg2000".to_owned(),
vec!["libremetaverse-imaging/jpeg2000".to_owned()],
@@ -923,7 +926,7 @@ fn validate_features(name: &str, actual: &BTreeMap<String, Vec<String>>) -> Resu
("vorbis".to_owned(), vec!["dep:vorbis_rs".to_owned()]),
]),
"libremetaverse-imaging" => BTreeMap::from([
("default".to_owned(), Vec::new()),
("default".to_owned(), vec!["rust-j2k".to_owned()]),
(
"jpeg2000".to_owned(),
vec!["dep:libremetaverse-openjpeg".to_owned()],
@@ -931,7 +934,7 @@ fn validate_features(name: &str, actual: &BTreeMap<String, Vec<String>>) -> Resu
("rust-j2k".to_owned(), vec!["dep:j2k".to_owned()]),
]),
"libremetaverse-imaging-skia" => BTreeMap::from([
("default".to_owned(), Vec::new()),
("default".to_owned(), vec!["rust-skia".to_owned()]),
(
"rust-skia".to_owned(),
vec![
@@ -1242,7 +1245,7 @@ The exact mapping, exported-marker, feature, and normalized Rust declaration has
- .NET events become `subscribe_*` calls returning an owned `Subscription`; callbacks are `Send + Sync` and dispatch outside internal locks.\n\
- `IDisposable` becomes explicit idempotent shutdown plus `Drop`; cancellation remains a typed `Error::Cancelled`.\n\
- Closed C# enums retain fixed discriminants and are SemVer-breaking to extend; flags retain unknown bits, while public Rust error enums are `non_exhaustive`.\n\
- Optional native and pure-Rust backends remain feature-gated (`jpeg2000`, `rust-j2k`, `skia`, `rust-skia`, `vorbis`, `real-audio`); default and empty-default sets are part of the baseline.\n\
- Pure-Rust image backends are enabled by default (`rust-j2k`, `rust-skia`); native alternatives remain feature-gated (`jpeg2000`, `skia`, `vorbis`, `real-audio`), and empty-default sets remain part of the baseline.\n\
- Capability HTTP injection uses project-owned `HttpMessageHandler`, `CapsHttpLimits`, and `with_native_transport`; the internal `reqwest` client is not part of the public surface.\n\
- External C# signature types map to core/std, adopted cross-platform crates, or project-owned boundary types; no third-party crate path leaks through mapped signatures or public Rust declarations.\n\n\
## Compile consumers\n\n\

View File

@@ -422,7 +422,7 @@ fn audit_cargo_features(root: &Path) -> Result<()> {
if defaults.iter().any(|feature| {
matches!(
feature.as_str(),
Some("jpeg2000" | "vorbis" | "dep:vorbis_rs")
Some("jpeg2000" | "skia" | "vorbis" | "dep:vorbis_rs")
)
}) {
return Err(MatrixError::new(
@@ -450,6 +450,8 @@ fn audit_cargo_features(root: &Path) -> Result<()> {
let default_tree = String::from_utf8_lossy(&default_tree.stdout);
for forbidden in [
"libremetaverse-openjpeg ",
"skia-safe ",
"skia-bindings ",
"vorbis_rs ",
"ogg_next_sys ",
"aotuv_lancer_vorbis_sys ",