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
96 changes: 26 additions & 70 deletions rog-anime/src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,14 @@ use crate::{AnimTime, AnimeGif};
/// the rest of the 640-byte packet is zero padding.
///
/// Per-device packet header bytes are returned by [`usb_prefixes_for`].
/// The chunking strategy — how many LED bytes go in each packet — is
/// per-device-class:
///
/// - GA401, GA402, GU604, G835L: `PANE_LEN` bytes per pane (universal).
/// - G635L: 490 bytes (pane 1), 320 bytes (pane 2) — exact-fit chunking
/// for the 810-LED matrix, per G Helper's `AnimeMatrixDevice`. See
/// the doc comment on [`usb_prefixes_for`] for source citations.
const BLOCK_START: usize = 7;
/// *Not* inclusive — the byte before this is the final usable byte of
/// each "pane" (packet payload region). 640 (packet size) - 6 (trailing
/// padding required by HID feature reports).
const BLOCK_END: usize = 634;
/// Maximum usable LED data length per USB packet. Used by
/// GA401/GA402/GU604/G835L. G635L uses smaller per-pane lengths
/// ([`G635L_PANE1_LEN`], [`G635L_PANE2_LEN`]) — see [`usb_prefixes_for`].
/// GA401/GA402/GU604. STRIX-class (G635L/G835L) uses smaller per-pane
/// lengths ([`STRIX_PANE1_LEN`], [`STRIX_PANE2_LEN`]).
const PANE_LEN: usize = BLOCK_END - BLOCK_START;

/// First packet is for GA401 + GA402
Expand All @@ -51,52 +44,31 @@ pub const USB_PREFIX2: [u8; 7] = [
pub const USB_PREFIX3: [u8; 7] = [
0x5e, 0xc0, 0x02, 0xe7, 0x04, 0x73, 0x02,
];
/// First packet header for G635L: start=1, length=490 (per G Helper STRIX-class chunking)
pub const USB_PREFIX_G635L_1: [u8; 7] = [
/// First packet header for STRIX class (G635L/G835L): start=1, length=490.
pub const USB_PREFIX_STRIX_1: [u8; 7] = [
0x5e, 0xc0, 0x02, 0x01, 0x00, 0xea, 0x01,
];
/// Second packet header for G635L: start=491, length=320 (per G Helper STRIX-class chunking)
pub const USB_PREFIX_G635L_2: [u8; 7] = [
/// Second packet header for STRIX class (G635L/G835L): start=491, length=320.
pub const USB_PREFIX_STRIX_2: [u8; 7] = [
0x5e, 0xc0, 0x02, 0xeb, 0x01, 0x40, 0x01,
];
/// LED data length carried in G635L packet 1 (pane 1).
/// G635L's 810-LED matrix splits exactly as 490 + 320 per G Helper STRIX-class chunking.
pub const G635L_PANE1_LEN: usize = 490;
/// LED data length carried in G635L packet 2 (pane 2). Equals
/// `AnimeType::G635L.data_length() - G635L_PANE1_LEN`.
pub const G635L_PANE2_LEN: usize = 320;
/// LED data length carried in STRIX-class packet 1 (pane 1). The
/// 810-LED matrix splits exactly as 490 + 320 per G Helper.
pub const STRIX_PANE1_LEN: usize = 490;
/// LED data length carried in STRIX-class packet 2 (pane 2). Equals
/// `AnimeType::G635L.data_length() - STRIX_PANE1_LEN`.
pub const STRIX_PANE2_LEN: usize = 320;

/// USB packet header bytes for each pane of an AniMe Matrix data write
/// transaction, per device class.
///
/// The protocol structure for each data packet is:
/// `[0x5e, 0xc0, 0x02, START_LO, START_HI, LEN_LO, LEN_HI, ...payload..., zeros]`
/// where `START` and `LEN` are little-endian `u16` values describing the
/// LED index range this packet writes (LED indices are 1-based on the wire).
///
/// Per-device chunking strategy (UpdatePageLength values, sourced from
/// G Helper's AnimeMatrixDevice — see citation at the top of the
/// `G635L` arm below):
/// - GA401: PANE_LEN bytes per pane (universal)
/// - GA402: PANE_LEN bytes per pane (universal)
/// - GU604: PANE_LEN bytes per pane (universal)
/// - G835L: PANE_LEN bytes per pane — currently shares the universal
/// chunking. Per G Helper this should be 490+320 (STRIX class),
/// but G835L is intentionally left unchanged here pending
/// hardware verification by a G835L owner.
/// - G635L: 490 bytes (pane 1), 320 bytes (pane 2) — exact fit for the
/// 810-LED matrix.
/// transaction, per device class. Per-device chunking strategy
/// (UpdatePageLength values) sourced from G Helper's `AnimeMatrixDevice`:
/// https://github.com/seerge/g-helper/blob/main/app/AnimeMatrix/AnimeMatrixDevice.cs
pub fn usb_prefixes_for(anime_type: AnimeType) -> Vec<[u8; 7]> {
match anime_type {
// G635L: STRIX-class chunking (490 + 320), per G Helper:
// https://github.com/seerge/g-helper/blob/main/app/AnimeMatrix/AnimeMatrixDevice.cs
// The original protocol knowledge comes from vddCore/Starlight
// (referenced at the top of the same G Helper file).
AnimeType::G635L => vec![
USB_PREFIX_G635L_1, USB_PREFIX_G635L_2,
AnimeType::G635L | AnimeType::G835L => vec![
USB_PREFIX_STRIX_1, USB_PREFIX_STRIX_2,
],
// GA401 and G835L: two panes, universal PANE_LEN-based prefixes.
AnimeType::GA401 | AnimeType::G835L => vec![
AnimeType::GA401 => vec![
USB_PREFIX1, USB_PREFIX2,
],
// GA402, GU604, Unsupported: three panes, universal PANE_LEN-based prefixes.
Expand Down Expand Up @@ -190,9 +162,7 @@ impl AnimeType {
pub fn width(&self) -> usize {
match self {
AnimeType::GU604 => 70,
// TODO: Find G635L W*H
AnimeType::G635L => 68,
AnimeType::G835L => 68,
AnimeType::G635L | AnimeType::G835L => 68,
_ => 74,
}
}
Expand All @@ -202,8 +172,7 @@ impl AnimeType {
match self {
AnimeType::GA401 => 36,
AnimeType::GU604 => 43,
AnimeType::G635L => 34,
AnimeType::G835L => 34,
AnimeType::G635L | AnimeType::G835L => 34,
_ => 39,
}
}
Expand All @@ -212,14 +181,9 @@ impl AnimeType {
pub fn data_length(&self) -> usize {
match self {
AnimeType::GA401 => PANE_LEN * 2,
// G835L has 810 LEDs: 210 (triangle) + 600 (staggered rectangle)
// 810 verified on G635L hardware: 210 (triangle, rows 0-27 with
// lengths 1+1+2+2+...+14+14) + 600 (rectangle, rows 28-67 × 15
// LEDs each). USB capture evidence is shared on the asus-linux
// dev Discord (see commit message of the G635L USB packet
// chunking fix for context).
AnimeType::G635L => 810,
AnimeType::G835L => 810,
// STRIX class: 810 LEDs = 210 (triangle, rows 0-27 with lengths
// 1+1+2+2+...+14+14) + 600 (rectangle, rows 28-67 × 15 LEDs).
AnimeType::G635L | AnimeType::G835L => 810,
_ => PANE_LEN * 3,
}
}
Expand Down Expand Up @@ -276,7 +240,7 @@ pub type AnimePacketType = Vec<[u8; 640]>;

/// Split LED `data` into two panes within the USB packet `buffers`, with
/// pane 1 carrying `pane1_len` bytes and pane 2 carrying the remainder.
/// Used for G635L (490 bytes pane 1) and G835L (PANE_LEN bytes pane 1).
/// Used by STRIX class (G635L/G835L, 490 bytes pane 1).
fn split_into_panes(buffers: &mut [[u8; 640]], data: &[u8], pane1_len: usize) {
let pane1_actual = pane1_len.min(data.len());
buffers[0][BLOCK_START..BLOCK_START + pane1_actual].copy_from_slice(&data[..pane1_actual]);
Expand All @@ -301,16 +265,8 @@ impl TryFrom<AnimeDataBuffer> for AnimePacketType {
}
};

if anime.anime == AnimeType::G835L {
// G835L: 627 bytes in pane 1, 183 bytes in pane 2 (universal
// PANE_LEN-based chunking; pane 2 is shorter than the announced
// length but the firmware tolerates it). Behavior unchanged
// pending hardware verification by a G835L owner.
split_into_panes(&mut buffers, anime.data.as_slice(), PANE_LEN);
} else if anime.anime == AnimeType::G635L {
// G635L: 490 bytes in pane 1, 320 bytes in pane 2 — exact-fit
// STRIX-class chunking per G Helper's AnimeMatrixDevice.
split_into_panes(&mut buffers, anime.data.as_slice(), G635L_PANE1_LEN);
if matches!(anime.anime, AnimeType::G635L | AnimeType::G835L) {
split_into_panes(&mut buffers, anime.data.as_slice(), STRIX_PANE1_LEN);
} else {
for (idx, chunk) in anime.data.as_slice().chunks(PANE_LEN).enumerate() {
buffers[idx][BLOCK_START..BLOCK_START + chunk.len()].copy_from_slice(chunk);
Expand Down
23 changes: 7 additions & 16 deletions rog-anime/src/diagonal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,7 @@ impl AnimeDiagonal {
match anime_type {
AnimeType::GA401 => self.to_ga401_packets(),
AnimeType::GU604 => self.to_gu604_packets(),
AnimeType::G635L => self.to_g635l_packets(),
AnimeType::G835L => self.to_g835l_packets(),
AnimeType::G635L | AnimeType::G835L => self.to_strix_packets(anime_type),
_ => self.to_ga402_packets(),
}
}
Expand Down Expand Up @@ -465,20 +464,12 @@ impl AnimeDiagonal {
debug!("STRIX packing complete: {} bytes written", buf_idx);
}

/// G635L diagonal packing — STRIX-class LED ordering, G635L buffer size.
/// Kept separate from `to_g835l_packets` so G635L behavior can evolve
/// (e.g. when calibration items A and B from the cross-repo handoff
/// produce a G635L-specific simulator map) without touching G835L.
fn to_g635l_packets(&self) -> Result<AnimeDataBuffer> {
let mut buf = vec![0u8; AnimeType::G635L.data_length()];
/// STRIX-class diagonal packing (G635L, G835L). Caller passes the
/// concrete `AnimeType` so the resulting `AnimeDataBuffer` carries
/// the right device identity downstream.
fn to_strix_packets(&self, anime_type: AnimeType) -> Result<AnimeDataBuffer> {
let mut buf = vec![0u8; anime_type.data_length()];
Self::pack_strix_diagonal_into(&mut buf, &self.1);
AnimeDataBuffer::from_vec(AnimeType::G635L, buf)
}

/// G835L diagonal packing — unchanged behavior, now via shared helper.
fn to_g835l_packets(&self) -> Result<AnimeDataBuffer> {
let mut buf = vec![0u8; AnimeType::G835L.data_length()];
Self::pack_strix_diagonal_into(&mut buf, &self.1);
AnimeDataBuffer::from_vec(AnimeType::G835L, buf)
AnimeDataBuffer::from_vec(anime_type, buf)
}
}
9 changes: 3 additions & 6 deletions rog-anime/src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,7 @@ impl AnimeImage {
// 33.0 = Longest row LED count (physical) plus half-pixel offset
AnimeType::GA401 => (33.0 + 0.5) * Self::scale_x(anime_type),
AnimeType::GU604 => (38.0 + 0.5) * Self::scale_x(anime_type),
AnimeType::G635L => (33.0 + 0.5) * Self::scale_x(anime_type),
AnimeType::G835L => (33.0 + 0.5) * Self::scale_x(anime_type),
AnimeType::G635L | AnimeType::G835L => (33.0 + 0.5) * Self::scale_x(anime_type),
_ => (35.0 + 0.5) * Self::scale_x(anime_type),
}
}
Expand All @@ -281,8 +280,7 @@ impl AnimeImage {
match anime_type {
AnimeType::GA401 => 55,
AnimeType::GU604 => 62,
AnimeType::G635L => 68,
AnimeType::G835L => 68,
AnimeType::G635L | AnimeType::G835L => 68,
_ => 61,
}
}
Expand All @@ -293,8 +291,7 @@ impl AnimeImage {
// 54.0 = End column LED count (physical) plus one dead pixel
AnimeType::GA401 => (54.0 + 1.0) * Self::scale_y(anime_type),
AnimeType::GU604 => 62.0 * Self::scale_y(anime_type),
AnimeType::G635L => 68.0 * Self::scale_y(anime_type),
AnimeType::G835L => 68.0 * Self::scale_y(anime_type),
AnimeType::G635L | AnimeType::G835L => 68.0 * Self::scale_y(anime_type),
// GA402 may not have dead pixels and require only the physical LED count
_ => 61.0 * Self::scale_y(anime_type),
}
Expand Down
Loading