From e137f7f878cb62df4f1a74650b1cded9d9340cad Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Fri, 30 Sep 2022 04:42:08 +0200 Subject: [PATCH] WiimoteEmu: Rewrite serialization of DesiredWiimoteState to optimize for network bandwidth. --- .../HW/WiimoteEmu/DesiredWiimoteState.cpp | 290 ++++++++++++------ 1 file changed, 196 insertions(+), 94 deletions(-) diff --git a/Source/Core/Core/HW/WiimoteEmu/DesiredWiimoteState.cpp b/Source/Core/Core/HW/WiimoteEmu/DesiredWiimoteState.cpp index 8c01c1232c..d99efefdee 100644 --- a/Source/Core/Core/HW/WiimoteEmu/DesiredWiimoteState.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/DesiredWiimoteState.cpp @@ -8,6 +8,7 @@ #include #include +#include "Common/BitUtils.h" #include "Common/CommonTypes.h" #include "Core/HW/WiimoteEmu/DesiredWiimoteState.h" @@ -26,55 +27,75 @@ namespace WiimoteEmu { SerializedWiimoteState SerializeDesiredState(const DesiredWiimoteState& state) { - // If no motion or extension data is present, we can serialize into a much smaller representation, - // which reduces data sent over the network. - const bool is_buttons_only = - (state.acceleration == DesiredWiimoteState::DEFAULT_ACCELERATION && - state.camera_points == DesiredWiimoteState::DEFAULT_CAMERA && - !state.motion_plus.has_value() && state.extension.data.index() == ExtensionNumber::NONE); + const u8 has_buttons = (state.buttons.hex & WiimoteCommon::ButtonData::BUTTON_MASK) != 0 ? 1 : 0; + const u8 has_accel = state.acceleration != DesiredWiimoteState::DEFAULT_ACCELERATION ? 1 : 0; + const u8 has_camera = state.camera_points != DesiredWiimoteState::DEFAULT_CAMERA ? 1 : 0; + const u8 has_motion_plus = state.motion_plus.has_value() ? 1 : 0; + + // Right now we support < 16 extensions so the info which extension is in use fits into 4 bits. + // This allows 'empty' packets to be a single byte, which is very nice for reducing bandwidth. + // If we ever support 16 or more we have to redesign this a bit; ideally use a variable-length + // encoding so that typical extensions (None, Nunchuk, Classic Controller) still fit into the + // initial 4 bits. + static_assert(std::variant_size_v <= (1 << 4)); + const u8 extension = u8(state.extension.data.index()); SerializedWiimoteState s; s.length = 0; - s.data[s.length++] = - u8((state.buttons.a) | (state.buttons.b << 1) | (state.buttons.plus << 2) | - (state.buttons.minus << 3) | (state.buttons.one << 4) | (state.buttons.two << 5) | - (state.buttons.home << 6) | ((is_buttons_only ? 0 : 1) << 7)); - const u8 dpad = u8((state.buttons.up) | (state.buttons.down << 1) | (state.buttons.left << 2) | - (state.buttons.right << 3)); - if (is_buttons_only) + s.data[s.length++] = u8(has_buttons | (has_accel << 1) | (has_camera << 2) | + (has_motion_plus << 3) | (extension << 4)); + + if (has_buttons) { - // There's four unused bits here, which could be used to further optimize for common situations. + const u8 buttons = u8((state.buttons.a) | (state.buttons.b << 1) | (state.buttons.plus << 2) | + (state.buttons.minus << 3) | (state.buttons.one << 4) | + (state.buttons.two << 5) | (state.buttons.home << 6)); + const u8 dpad = u8((state.buttons.up) | (state.buttons.down << 1) | (state.buttons.left << 2) | + (state.buttons.right << 3)); + s.data[s.length++] = buttons; s.data[s.length++] = dpad; - return s; } - const u16 accel_x = state.acceleration.value.x; // 10 bits - const u16 accel_y = state.acceleration.value.y; // 10 bits - const u16 accel_z = state.acceleration.value.z; // 10 bits - const u16 camera_p0_x = state.camera_points[0].position.x; // 10 bits - const u16 camera_p0_y = state.camera_points[0].position.y; // 10 bits - const u16 camera_p1_x = state.camera_points[1].position.x; // 10 bits - const u16 camera_p1_y = state.camera_points[1].position.y; // 10 bits - const u8 camera_p0_size = state.camera_points[0].size; // 4 bits - const u8 camera_p1_size = state.camera_points[1].size; // 4 bits - static_assert(std::variant_size_v < (1 << 5)); - const u8 extension = u8(state.extension.data.index()); // maximum of 5 bits - const u8 motion_plus = state.motion_plus.has_value() ? 1 : 0; - s.data[s.length++] = u8(dpad | (camera_p0_size << 4)); - s.data[s.length++] = u8(camera_p0_x); - s.data[s.length++] = u8(camera_p0_y); - s.data[s.length++] = u8(camera_p1_x); - s.data[s.length++] = u8(camera_p1_y); - s.data[s.length++] = u8(((camera_p0_x >> 8) & 3) | (((camera_p0_y >> 8) & 3) << 2) | - (((camera_p1_x >> 8) & 3) << 4) | (((camera_p1_y >> 8) & 3) << 6)); - s.data[s.length++] = - u8(((accel_x >> 8) & 3) | (((accel_y >> 8) & 3) << 2) | (camera_p1_size << 4)); - s.data[s.length++] = u8(accel_x); - s.data[s.length++] = u8(accel_y); - s.data[s.length++] = u8(accel_z); - s.data[s.length++] = u8(((accel_z >> 8) & 3) | (motion_plus << 2) | (extension << 3)); + if (has_accel) + { + const u16 accel_x = state.acceleration.value.x; // 10 bits + const u16 accel_y = state.acceleration.value.y; // 9 bits (ignore lowest bit) + const u16 accel_z = state.acceleration.value.z; // 9 bits (ignore lowest bit) + const u8 accel_x_high = u8(accel_x >> 2); + const u8 accel_y_high = u8(accel_y >> 2); + const u8 accel_z_high = u8(accel_z >> 2); + const u8 accel_low = u8((accel_x & 0b11) | (Common::ExtractBit<1>(accel_y) << 2) | + (Common::ExtractBit<1>(accel_z) << 3)); - if (motion_plus) + if (has_buttons) + { + // can use the high bits of the dpad field from buttons + s.data[s.length - 1] |= u8(accel_low << 4); + } + else + { + s.data[s.length++] = u8(accel_low << 4); + } + + s.data[s.length++] = accel_x_high; + s.data[s.length++] = accel_y_high; + s.data[s.length++] = accel_z_high; + } + + if (has_camera) + { + for (size_t i = 0; i < 2; ++i) + { + const u16 camera_x = state.camera_points[i].position.x; // 10 bits + const u16 camera_y = state.camera_points[i].position.y; // 10 bits + const u8 camera_size = state.camera_points[i].size; // 4 bits + s.data[s.length++] = u8((camera_x & 0b11) | ((camera_y & 0b11) << 2) | (camera_size << 4)); + s.data[s.length++] = u8(camera_x >> 2); + s.data[s.length++] = u8(camera_y >> 2); + } + } + + if (has_motion_plus) { const u16 pitch_slow = state.motion_plus->is_slow.x ? 1 : 0; const u16 roll_slow = state.motion_plus->is_slow.y ? 1 : 0; @@ -83,11 +104,11 @@ SerializedWiimoteState SerializeDesiredState(const DesiredWiimoteState& state) const u16 roll_value = state.motion_plus->gyro.value.y; // 14 bits const u16 yaw_value = state.motion_plus->gyro.value.z; // 14 bits s.data[s.length++] = u8(pitch_value); - s.data[s.length++] = u8((pitch_value >> 8) | (pitch_slow << 7)); + s.data[s.length++] = u8(((pitch_value >> 8) & 0x3f) | (pitch_slow << 7)); s.data[s.length++] = u8(roll_value); - s.data[s.length++] = u8((roll_value >> 8) | (roll_slow << 7)); + s.data[s.length++] = u8(((roll_value >> 8) & 0x3f) | (roll_slow << 7)); s.data[s.length++] = u8(yaw_value); - s.data[s.length++] = u8((yaw_value >> 8) | (yaw_slow << 7)); + s.data[s.length++] = u8(((yaw_value >> 8) & 0x3f) | (yaw_slow << 7)); } if (extension) @@ -130,93 +151,174 @@ bool DeserializeDesiredState(DesiredWiimoteState* state, const SerializedWiimote state->motion_plus = std::nullopt; state->extension.data = std::monostate(); - if (serialized.length < 2) + if (serialized.length < 1) { // can't be valid return false; } const auto& d = serialized.data; - state->buttons.a = d[0] & 1; - state->buttons.b = (d[0] >> 1) & 1; - state->buttons.plus = (d[0] >> 2) & 1; - state->buttons.minus = (d[0] >> 3) & 1; - state->buttons.one = (d[0] >> 4) & 1; - state->buttons.two = (d[0] >> 5) & 1; - state->buttons.home = (d[0] >> 6) & 1; - const u8 has_motion_or_extension_data = (d[0] >> 7) & 1; - state->buttons.up = d[1] & 1; - state->buttons.down = (d[1] >> 1) & 1; - state->buttons.left = (d[1] >> 2) & 1; - state->buttons.right = (d[1] >> 3) & 1; + const u8 has_buttons = d[0] & 1; + const u8 has_accel = (d[0] >> 1) & 1; + const u8 has_camera = (d[0] >> 2) & 1; + const u8 has_motion_plus = (d[0] >> 3) & 1; + const u8 extension = (d[0] >> 4); - if (!has_motion_or_extension_data) + if (extension >= ExtensionNumber::MAX) { - // is button-only state, we're done - return true; - } - - if (serialized.length < 12) - { - // if it's not a button-only state it needs to have at least 12 bytes for the basic Wiimote data + // invalid extension return false; } - state->camera_points[0].size = d[1] >> 4; - state->camera_points[0].position.x = d[2] | ((d[6] & 3) << 8); - state->camera_points[0].position.y = d[3] | (((d[6] >> 2) & 3) << 8); - state->camera_points[1].position.x = d[4] | (((d[6] >> 4) & 3) << 8); - state->camera_points[1].position.y = d[5] | (((d[6] >> 6) & 3) << 8); - state->camera_points[1].size = d[7] >> 4; - state->acceleration.value.x = d[8] | ((d[7] & 3) << 8); - state->acceleration.value.y = d[9] | (((d[7] >> 2) & 3) << 8); - state->acceleration.value.z = d[10] | ((d[11] & 3) << 8); - const u8 has_motion_plus = (d[11] >> 2) & 1; - const u8 extension = d[11] >> 3; + const size_t expected_size = [&]() { + size_t s = 1; + if (has_buttons && has_accel) + s += 5; + else if (has_buttons) + s += 2; + else if (has_accel) + s += 4; + if (has_camera) + s += 6; + if (has_motion_plus) + s += 6; + switch (extension) + { + case ExtensionNumber::NONE: + break; + case ExtensionNumber::NUNCHUK: + s += sizeof(Nunchuk::DataFormat); + break; + case ExtensionNumber::CLASSIC: + s += sizeof(Classic::DataFormat); + break; + case ExtensionNumber::GUITAR: + s += sizeof(Guitar::DataFormat); + break; + case ExtensionNumber::DRUMS: + s += sizeof(Drums::DesiredState); + break; + case ExtensionNumber::TURNTABLE: + s += sizeof(Turntable::DataFormat); + break; + case ExtensionNumber::UDRAW_TABLET: + s += sizeof(UDrawTablet::DataFormat); + break; + case ExtensionNumber::DRAWSOME_TABLET: + s += sizeof(DrawsomeTablet::DataFormat); + break; + case ExtensionNumber::TATACON: + s += sizeof(TaTaCon::DataFormat); + break; + case ExtensionNumber::SHINKANSEN: + s += sizeof(Shinkansen::DesiredState); + break; + default: + break; + } + return s; + }(); - if (has_motion_plus && serialized.length < 18) + if (serialized.length != expected_size) { - // can't hold motion plus data + // invalid length return false; } + size_t pos = 1; + + if (has_buttons) + { + state->buttons.a = d[pos] & 1; + state->buttons.b = (d[pos] >> 1) & 1; + state->buttons.plus = (d[pos] >> 2) & 1; + state->buttons.minus = (d[pos] >> 3) & 1; + state->buttons.one = (d[pos] >> 4) & 1; + state->buttons.two = (d[pos] >> 5) & 1; + state->buttons.home = (d[pos] >> 6) & 1; + state->buttons.up = d[pos + 1] & 1; + state->buttons.down = (d[pos + 1] >> 1) & 1; + state->buttons.left = (d[pos + 1] >> 2) & 1; + state->buttons.right = (d[pos + 1] >> 3) & 1; + pos += 2; + } + + if (has_accel) + { + if (has_buttons) + pos -= 1; + const u8 accel_low = d[pos] >> 4; + const u8 accel_x_high = d[pos + 1]; + const u8 accel_y_high = d[pos + 2]; + const u8 accel_z_high = d[pos + 3]; + state->acceleration.value.x = (accel_x_high << 2) | (accel_low & 0b11); + state->acceleration.value.y = + Common::ExpandValue((accel_y_high << 1) | Common::ExtractBit<2>(accel_low), 1); + state->acceleration.value.z = + Common::ExpandValue((accel_z_high << 1) | Common::ExtractBit<3>(accel_low), 1); + pos += 4; + } + + if (has_camera) + { + for (size_t i = 0; i < 2; ++i) + { + const u8 camera_misc = d[pos]; + const u8 camera_x_high = d[pos + 1]; + const u8 camera_y_high = d[pos + 2]; + const u16 camera_x = (camera_x_high << 2) | (camera_misc & 0b11); + const u16 camera_y = (camera_y_high << 2) | ((camera_misc >> 2) & 0b11); + const u8 camera_size = camera_misc >> 4; + if (camera_y < CameraLogic::CAMERA_RES_Y) + { + state->camera_points[i] = CameraPoint({camera_x, camera_y}, camera_size); + } + else + { + // indicates an invalid camera point + state->camera_points[i] = CameraPoint(); + } + pos += 3; + } + } + if (has_motion_plus) { - const u16 pitch_value = d[12] | ((d[13] & 0x3f) << 8); - const u16 roll_value = d[14] | ((d[15] & 0x3f) << 8); - const u16 yaw_value = d[16] | ((d[17] & 0x3f) << 8); - const bool pitch_slow = (d[13] & 0x80) != 0; - const bool roll_slow = (d[15] & 0x80) != 0; - const bool yaw_slow = (d[17] & 0x80) != 0; + const u16 pitch_value = d[pos] | ((d[pos + 1] & 0x3f) << 8); + const u16 roll_value = d[pos + 2] | ((d[pos + 3] & 0x3f) << 8); + const u16 yaw_value = d[pos + 4] | ((d[pos + 5] & 0x3f) << 8); + const bool pitch_slow = (d[pos + 1] & 0x80) != 0; + const bool roll_slow = (d[pos + 3] & 0x80) != 0; + const bool yaw_slow = (d[pos + 5] & 0x80) != 0; state->motion_plus = MotionPlus::DataFormat::Data{ MotionPlus::DataFormat::GyroRawValue{ MotionPlus::DataFormat::GyroType(pitch_value, roll_value, yaw_value)}, MotionPlus::DataFormat::SlowType(pitch_slow, roll_slow, yaw_slow)}; + pos += 6; } - const size_t offset = has_motion_plus ? 18 : 12; switch (extension) { case ExtensionNumber::NONE: return true; case ExtensionNumber::NUNCHUK: - return DeserializeExtensionState(state, serialized, offset); + return DeserializeExtensionState(state, serialized, pos); case ExtensionNumber::CLASSIC: - return DeserializeExtensionState(state, serialized, offset); + return DeserializeExtensionState(state, serialized, pos); case ExtensionNumber::GUITAR: - return DeserializeExtensionState(state, serialized, offset); + return DeserializeExtensionState(state, serialized, pos); case ExtensionNumber::DRUMS: - return DeserializeExtensionState(state, serialized, offset); + return DeserializeExtensionState(state, serialized, pos); case ExtensionNumber::TURNTABLE: - return DeserializeExtensionState(state, serialized, offset); + return DeserializeExtensionState(state, serialized, pos); case ExtensionNumber::UDRAW_TABLET: - return DeserializeExtensionState(state, serialized, offset); + return DeserializeExtensionState(state, serialized, pos); case ExtensionNumber::DRAWSOME_TABLET: - return DeserializeExtensionState(state, serialized, offset); + return DeserializeExtensionState(state, serialized, pos); case ExtensionNumber::TATACON: - return DeserializeExtensionState(state, serialized, offset); + return DeserializeExtensionState(state, serialized, pos); case ExtensionNumber::SHINKANSEN: - return DeserializeExtensionState(state, serialized, offset); + return DeserializeExtensionState(state, serialized, pos); default: break; }