Skip to content

Commit 78bd227

Browse files
committed
feat: add QOIZ image codec
Add a new QOIZ codec (UUID 229cc6dc-a860-4b52-b4d8-053a22b3892b) for SetSurface command. The PDU data contains the same data as the QOI codec, with zstd compression. Some benchmarks showing interesting results (using ironrdp/perfenc) QOI: 10s user CPU, 96.20% compression QOIZ: 11s user CPU, 99.76% compression Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
1 parent 2afccde commit 78bd227

File tree

13 files changed

+231
-24
lines changed

13 files changed

+231
-24
lines changed

Cargo.lock

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ironrdp-client/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ default = ["rustls"]
2828
rustls = ["ironrdp-tls/rustls"]
2929
native-tls = ["ironrdp-tls/native-tls"]
3030
qoi = ["ironrdp/qoi"]
31+
qoiz = ["ironrdp/qoiz"]
3132

3233
[dependencies]
3334
# Protocols

crates/ironrdp-connector/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ test = false
1919
default = []
2020
arbitrary = ["dep:arbitrary"]
2121
qoi = ["ironrdp-pdu/qoi"]
22+
qoiz = ["ironrdp-pdu/qoiz"]
2223

2324
[dependencies]
2425
ironrdp-svc = { path = "../ironrdp-svc", version = "0.3" } # public

crates/ironrdp-pdu/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ default = []
2020
std = ["alloc", "ironrdp-error/std", "ironrdp-core/std"]
2121
alloc = ["ironrdp-core/alloc", "ironrdp-error/alloc"]
2222
qoi = []
23+
qoiz = ["qoi"]
2324

2425
[dependencies]
2526
bitflags = "2.4"

crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_codecs.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ const GUID_IGNORE: Guid = Guid(0x9c43_51a6, 0x3535, 0x42ae, 0x91, 0x0c, 0xcd, 0x
4242
#[rustfmt::skip]
4343
#[cfg(feature="qoi")]
4444
const GUID_QOI: Guid = Guid(0x4dae_9af8, 0xb399, 0x4df6, 0xb4, 0x3a, 0x66, 0x2f, 0xd9, 0xc0, 0xf5, 0xd6);
45+
#[rustfmt::skip]
46+
#[cfg(feature="qoiz")]
47+
const GUID_QOIZ: Guid = Guid(0x229c_c6dc, 0xa860, 0x4b52, 0xb4, 0xd8, 0x05, 0x3a, 0x22, 0xb3, 0x89, 0x2b);
4548

4649
#[derive(Debug, PartialEq, Eq)]
4750
pub struct Guid(u32, u16, u16, u8, u8, u8, u8, u8, u8, u8, u8);
@@ -171,6 +174,8 @@ impl Encode for Codec {
171174
CodecProperty::Ignore => GUID_IGNORE,
172175
#[cfg(feature = "qoi")]
173176
CodecProperty::Qoi => GUID_QOI,
177+
#[cfg(feature = "qoiz")]
178+
CodecProperty::QoiZ => GUID_QOIZ,
174179
_ => return Err(other_err!("invalid codec")),
175180
};
176181
guid.encode(dst)?;
@@ -210,6 +215,8 @@ impl Encode for Codec {
210215
}
211216
#[cfg(feature = "qoi")]
212217
CodecProperty::Qoi => dst.write_u16(0),
218+
#[cfg(feature = "qoiz")]
219+
CodecProperty::QoiZ => dst.write_u16(0),
213220
CodecProperty::Ignore => dst.write_u16(0),
214221
CodecProperty::None => dst.write_u16(0),
215222
};
@@ -235,6 +242,8 @@ impl Encode for Codec {
235242
},
236243
#[cfg(feature = "qoi")]
237244
CodecProperty::Qoi => 0,
245+
#[cfg(feature = "qoiz")]
246+
CodecProperty::QoiZ => 0,
238247
CodecProperty::Ignore => 0,
239248
CodecProperty::None => 0,
240249
}
@@ -279,6 +288,13 @@ impl<'de> Decode<'de> for Codec {
279288
}
280289
CodecProperty::Qoi
281290
}
291+
#[cfg(feature = "qoiz")]
292+
GUID_QOIZ => {
293+
if !property_buffer.is_empty() {
294+
return Err(invalid_field_err!("qoi property", "must be empty"));
295+
}
296+
CodecProperty::QoiZ
297+
}
282298
_ => CodecProperty::None,
283299
};
284300

@@ -300,6 +316,8 @@ pub enum CodecProperty {
300316
Ignore,
301317
#[cfg(feature = "qoi")]
302318
Qoi,
319+
#[cfg(feature = "qoiz")]
320+
QoiZ,
303321
None,
304322
}
305323

@@ -639,6 +657,8 @@ pub enum CodecId {
639657
RemoteFx = 0x03,
640658
#[cfg(feature = "qoi")]
641659
Qoi = 0x0A,
660+
#[cfg(feature = "qoiz")]
661+
QoiZ = 0x0B,
642662
}
643663

644664
impl CodecId {
@@ -648,6 +668,8 @@ impl CodecId {
648668
0x03 => Some(Self::RemoteFx),
649669
#[cfg(feature = "qoi")]
650670
0x0A => Some(Self::Qoi),
671+
#[cfg(feature = "qoiz")]
672+
0x0B => Some(Self::QoiZ),
651673
_ => None,
652674
}
653675
}
@@ -690,6 +712,7 @@ fn parse_codecs_config<'a>(codecs: &'a [&'a str]) -> Result<HashMap<&'a str, boo
690712
///
691713
/// * `remotefx` (on by default)
692714
/// * `qoi` (on by default, when feature "qoi")
715+
/// * `qoiz` (on by default, when feature "qoiz")
693716
///
694717
/// # Returns
695718
///
@@ -701,6 +724,7 @@ pub fn client_codecs_capabilities(config: &[&str]) -> Result<BitmapCodecs, Strin
701724
List of codecs:
702725
- `remotefx` (on by default)
703726
- `qoi` (on by default, when feature "qoi")
727+
- `qoiz` (on by default, when feature "qoiz")
704728
"#
705729
.to_owned());
706730
}
@@ -729,6 +753,14 @@ List of codecs:
729753
});
730754
}
731755

756+
#[cfg(feature = "qoiz")]
757+
if config.remove("qoiz").unwrap_or(true) {
758+
codecs.push(Codec {
759+
id: CodecId::QoiZ as u8,
760+
property: CodecProperty::QoiZ,
761+
});
762+
}
763+
732764
let codec_names = config.keys().copied().collect::<Vec<_>>().join(", ");
733765
if !codec_names.is_empty() {
734766
return Err(format!("Unknown codecs: {}", codec_names));
@@ -751,6 +783,7 @@ List of codecs:
751783
///
752784
/// * `remotefx` (on by default)
753785
/// * `qoi` (on by default, when feature "qoi")
786+
/// * `qoiz` (on by default, when feature "qoiz")
754787
///
755788
/// # Returns
756789
///
@@ -761,6 +794,7 @@ pub fn server_codecs_capabilities(config: &[&str]) -> Result<BitmapCodecs, Strin
761794
List of codecs:
762795
- `remotefx` (on by default)
763796
- `qoi` (on by default, when feature "qoi")
797+
- `qoiz` (on by default, when feature "qoiz")
764798
"#
765799
.to_owned());
766800
}
@@ -787,6 +821,14 @@ List of codecs:
787821
});
788822
}
789823

824+
#[cfg(feature = "qoiz")]
825+
if config.remove("qoiz").unwrap_or(true) {
826+
codecs.push(Codec {
827+
id: 0,
828+
property: CodecProperty::QoiZ,
829+
});
830+
}
831+
790832
let codec_names = config.keys().copied().collect::<Vec<_>>().join(", ");
791833
if !codec_names.is_empty() {
792834
return Err(format!("Unknown codecs: {}", codec_names));

crates/ironrdp-server/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ name = "perfenc"
1919
required-features = ["__bench"]
2020

2121
[features]
22-
default = ["rayon", "qoi"]
22+
default = ["rayon", "qoi", "qoiz"]
2323
helper = ["dep:x509-cert", "dep:rustls-pemfile"]
2424
rayon = ["dep:rayon"]
2525
qoi = ["dep:qoi", "ironrdp-pdu/qoi"]
26+
qoiz = ["dep:zstd-safe", "qoi", "ironrdp-pdu/qoiz"]
2627

2728
# Internal (PRIVATE!) features used to aid testing.
2829
# Don't rely on these whatsoever. They may disappear at any time.
@@ -52,6 +53,7 @@ rayon = { version = "1.10.0", optional = true }
5253
bytes = "1"
5354
visibility = { version = "0.1", optional = true }
5455
qoi = { version = "0.4", optional = true }
56+
zstd-safe = { version = "7.2", optional = true }
5557

5658
[dev-dependencies]
5759
tokio = { version = "1", features = ["sync", "fs"] }

crates/ironrdp-server/examples/perfenc.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use ironrdp_server::{
1313
};
1414
use tokio::{fs::File, io::AsyncReadExt, time::sleep};
1515

16+
#[allow(clippy::similar_names)]
1617
#[tokio::main(flavor = "current_thread")]
1718
async fn main() -> Result<(), anyhow::Error> {
1819
setup_logging()?;
@@ -27,7 +28,7 @@ async fn main() -> Result<(), anyhow::Error> {
2728
println!(" --width <WIDTH> Width of the display (default: 3840)");
2829
println!(" --height <HEIGHT> Height of the display (default: 2400)");
2930
println!(" --codec <CODEC> Codec to use (default: remotefx)");
30-
println!(" Valid values: qoi, remotefx, bitmap, none");
31+
println!(" Valid values: qoi, qoiz, remotefx, bitmap, none");
3132
println!(" --fps <FPS> Frames per second (default: none)");
3233
std::process::exit(0);
3334
}
@@ -53,6 +54,8 @@ async fn main() -> Result<(), anyhow::Error> {
5354
OptCodec::None => {}
5455
#[cfg(feature = "qoi")]
5556
OptCodec::Qoi => update_codecs.set_qoi(Some(0)),
57+
#[cfg(feature = "qoiz")]
58+
OptCodec::QoiZ => update_codecs.set_qoiz(Some(0)),
5659
};
5760

5861
let mut encoder = UpdateEncoder::new(DesktopSize { width, height }, flags, update_codecs);
@@ -175,6 +178,8 @@ enum OptCodec {
175178
None,
176179
#[cfg(feature = "qoi")]
177180
Qoi,
181+
#[cfg(feature = "qoiz")]
182+
QoiZ,
178183
}
179184

180185
impl Default for OptCodec {
@@ -193,6 +198,8 @@ impl core::str::FromStr for OptCodec {
193198
"none" => Ok(Self::None),
194199
#[cfg(feature = "qoi")]
195200
"qoi" => Ok(Self::Qoi),
201+
#[cfg(feature = "qoiz")]
202+
"qoiz" => Ok(Self::QoiZ),
196203
_ => Err(anyhow::anyhow!("unknown codec: {}", s)),
197204
}
198205
}

0 commit comments

Comments
 (0)