Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@ rebuilt, and getting that wrong is silent rather than loud. Build the reference
resolution-changing stream to its first size and a whole-stream reference will
not compare.

Pixelforge can also run on a device the caller created, for decode and for
encode. `tests/adopted.rs` builds one from a Vulkan 1.1 instance, as an
application that never asked for 1.3 would, hands pixelforge spare queues, and
checks the output against a context pixelforge created itself. It needs no
ffmpeg. Anything that reaches a core 1.2 or 1.3 entry point panics there, so
run it after touching submission or barrier code.

Make sure there are no Vulkan validation layer errors during execution. Enable
them with `PIXELFORGE_VALIDATION=1`; the layer's messages are routed through
`tracing`, so pair it with `RUST_LOG=warn` (or `debug` for the layer's own
Expand Down
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Validation layer messages are now routed into `tracing` through a
`VK_EXT_debug_utils` messenger. Previously the layer was loaded with nowhere to
report, so enabling validation verified nothing.
- Encoding on a caller-created device: `encode_device_requirements` and
`build_from_existing_encode`, the counterparts of the decode pair. An
application that renders on its own device can encode its images there
directly, with no external memory and no second device.
- `DeviceRequirements::features` names the device features pixelforge needs as
booleans, so a caller can set them in whichever feature structs it already
chains.
- `VideoContextBuilder::with_encode_queue`, `with_decode_queue`,
`with_transfer_queue` and `with_compute_queue` choose exactly which queue an
adopted context submits to, so the caller can keep its own queues to itself.
`DeviceRequirements::queues` says which family serves which role, and
`DeviceRequirements::internally_synchronized_queues` whether the device can
share a queue safely instead.
- `ColorConverter::convert_async` and `Encoder::encode_after` wait for, and in
the converter's case signal, a `TimelinePoint`, so a frame can go from the
caller's rendering through conversion to encode ordered on the GPU, with no
CPU wait in between.

### Changed

Expand All @@ -112,6 +129,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
replaced by `VideoContext::has_push_descriptor`.
- Adopting a caller-created device for decode (`build_from_existing_decode`) now
requires the `timelineSemaphore` feature in addition to `synchronization2`.
- Adopted devices may come from an instance asking for any Vulkan version from
1.1 up. Pixelforge reaches synchronization2 through its extension rather than
core 1.3, and the requirements list `VK_KHR_timeline_semaphore`.
- `DeviceRequirements` is `#[non_exhaustive]`, like the new `DeviceFeatures`
and `QueueRoles`, so later fields are not breaking changes. It can no longer
be built with a struct literal outside pixelforge.

### Fixed

Expand Down
38 changes: 35 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ H.265 and AV1 encode, and H.264 decode.
Encoding hands back an [`EncodeFuture`]; decoding delivers frames through a
[`DecodeSource`] as the GPU finishes with them.
- **GPU color conversion**: RGB/BGR → YUV via Vulkan compute shaders (BT.709, BT.2020, sRGB→BT.2020+PQ, scRGB-linear→BT.2020+PQ).
- **Shared devices**: encode and decode on a Vulkan device your application
created, from Vulkan 1.1 up, on queues you choose, ordered against your own
work with timeline semaphores.
- **HDR support**: 10-bit encoding (P010, YUV444P10), PQ transfer function, BT.2020 color space.
- **GPU-native API**: Encode directly from Vulkan images (`vk::Image`).
- **Flexible configuration**: Rate control (CBR, VBR, CQP), quality levels, GOP settings.
Expand Down Expand Up @@ -247,6 +250,38 @@ let mut encoder = Encoder::new(context, encode_config)?;
// converter.convert(input_image, layout, encoder.input_image())?;
```

### Encoding on your own device

An application that already renders with Vulkan can give pixelforge its own
device instead of letting it create a second one. Frames then never leave
the device: no external memory, no import, and no CPU wait between the
application's work and pixelforge's.

[`VideoContextBuilder::encode_device_requirements`] lists the queue
families, extensions and features to create the device with, and
[`VideoContextBuilder::build_from_existing_encode`] adopts it. A `VkQueue`
may not be submitted to from two threads at once, so give pixelforge queues
the application does not use, with
[`VideoContextBuilder::with_encode_queue`] and friends.

[`ColorConverter::convert_async`] and [`Encoder::encode_after`] then order
each frame on the GPU: the application's submission signals a
[`TimelinePoint`], the conversion waits for it and signals its own, and the
encode waits for that. This stays off the CPU as long as the encoder's own
[`Encoder::input_image`] is what gets encoded, as below: any other image is
copied into it first, and that copy is waited for on the CPU.

```rust
// `rendered` reaches `frame_number` once the application has drawn `image`.
let converted = converter.convert_async(
image,
vk::ImageLayout::GENERAL,
encoder.input_image(),
&[TimelinePoint::new(rendered, frame_number)],
)?;
let packet = encoder.encode_after(encoder.input_image(), &[converted])?;
```

## Benchmarking

Run the encode latency benchmark with:
Expand All @@ -266,9 +301,6 @@ cargo run --example query_capabilities
# Decode H.264 to raw YUV
cargo run --example decode -- input.264 output.yuv

# Decode on a caller-created Vulkan device
cargo run --example decode_adopted -- input.264 output.yuv

# Encode, choosing the codec (h264, h265 or av1)
cargo run --example encode -- h265

Expand Down
148 changes: 0 additions & 148 deletions examples/decode_adopted.rs

This file was deleted.

Loading
Loading