2020-12-05 13:08:24 +01:00
# include "stdafx.h"
2018-03-02 17:51:21 +01:00
# include "cellGem.h"
# include "cellCamera.h"
2020-02-15 23:36:20 +01:00
2025-10-04 15:46:36 +02:00
# include "cellos/sys_event.h"
# include "cellos/sys_memory.h"
2016-03-21 20:43:03 +01:00
# include "Emu/Cell/PPUModule.h"
2021-07-30 15:55:50 +02:00
# include "Emu/Cell/timers.hpp"
2018-01-24 01:27:25 +01:00
# include "Emu/Io/MouseHandler.h"
2024-07-08 20:17:21 +02:00
# include "Emu/Io/PadHandler.h"
2023-05-20 15:05:44 +02:00
# include "Emu/Io/gem_config.h"
2025-01-14 03:03:18 +01:00
# include "Emu/Io/interception.h"
2021-07-30 15:55:50 +02:00
# include "Emu/system_config.h"
2021-10-20 19:47:19 +02:00
# include "Emu/System.h"
2021-07-30 15:55:50 +02:00
# include "Emu/IdManager.h"
2022-07-06 22:08:50 +02:00
# include "Emu/RSX/Overlays/overlay_cursor.h"
2020-02-22 20:42:49 +01:00
# include "Input/pad_thread.h"
2024-07-08 20:17:21 +02:00
# include "Input/ps_move_config.h"
# include "Input/ps_move_tracker.h"
2022-11-27 12:20:05 +01:00
# ifdef HAVE_LIBEVDEV
2022-11-13 18:10:09 +01:00
# include "Input/evdev_gun_handler.h"
2022-11-27 12:20:05 +01:00
# endif
2013-09-28 04:36:57 +02:00
2021-08-04 22:53:07 +02:00
# include <cmath> // for fmod
2022-11-27 12:20:05 +01:00
# include <type_traits>
2021-08-04 22:53:07 +02:00
2018-08-25 14:39:00 +02:00
LOG_CHANNEL ( cellGem ) ;
2013-09-28 04:36:57 +02:00
2023-05-21 15:57:57 +02:00
template < >
void fmt_class_string < gem_btn > : : format ( std : : string & out , u64 arg )
{
format_enum ( out , arg , [ ] ( gem_btn value )
2025-04-05 21:50:45 +02:00
{
switch ( value )
{
case gem_btn : : start : return " Start " ;
case gem_btn : : select : return " Select " ;
case gem_btn : : triangle : return " Triangle " ;
case gem_btn : : circle : return " Circle " ;
case gem_btn : : cross : return " Cross " ;
case gem_btn : : square : return " Square " ;
case gem_btn : : move : return " Move " ;
case gem_btn : : t : return " T " ;
case gem_btn : : x_axis : return " X-Axis " ;
case gem_btn : : y_axis : return " Y-Axis " ;
case gem_btn : : combo : return " Combo " ;
case gem_btn : : combo_start : return " Combo Start " ;
case gem_btn : : combo_select : return " Combo Select " ;
case gem_btn : : combo_triangle : return " Combo Triangle " ;
case gem_btn : : combo_circle : return " Combo Circle " ;
case gem_btn : : combo_cross : return " Combo Cross " ;
case gem_btn : : combo_square : return " Combo Square " ;
case gem_btn : : combo_move : return " Combo Move " ;
case gem_btn : : combo_t : return " Combo T " ;
case gem_btn : : count : return " Count " ;
}
return unknown ;
} ) ;
2023-05-21 15:57:57 +02:00
}
2019-09-08 16:09:44 +02:00
template < >
void fmt_class_string < CellGemError > : : format ( std : : string & out , u64 arg )
{
format_enum ( out , arg , [ ] ( auto error )
{
2025-04-05 21:50:45 +02:00
switch ( error )
{
STR_CASE ( CELL_GEM_ERROR_RESOURCE_ALLOCATION_FAILED ) ;
STR_CASE ( CELL_GEM_ERROR_ALREADY_INITIALIZED ) ;
STR_CASE ( CELL_GEM_ERROR_UNINITIALIZED ) ;
STR_CASE ( CELL_GEM_ERROR_INVALID_PARAMETER ) ;
STR_CASE ( CELL_GEM_ERROR_INVALID_ALIGNMENT ) ;
STR_CASE ( CELL_GEM_ERROR_UPDATE_NOT_FINISHED ) ;
STR_CASE ( CELL_GEM_ERROR_UPDATE_NOT_STARTED ) ;
STR_CASE ( CELL_GEM_ERROR_CONVERT_NOT_FINISHED ) ;
STR_CASE ( CELL_GEM_ERROR_CONVERT_NOT_STARTED ) ;
STR_CASE ( CELL_GEM_ERROR_WRITE_NOT_FINISHED ) ;
STR_CASE ( CELL_GEM_ERROR_NOT_A_HUE ) ;
}
2019-09-08 16:09:44 +02:00
2025-04-05 21:50:45 +02:00
return unknown ;
} ) ;
2019-09-08 16:09:44 +02:00
}
template < >
void fmt_class_string < CellGemStatus > : : format ( std : : string & out , u64 arg )
{
format_enum ( out , arg , [ ] ( auto error )
{
2025-04-05 21:50:45 +02:00
switch ( error )
{
STR_CASE ( CELL_GEM_NOT_CONNECTED ) ;
STR_CASE ( CELL_GEM_SPHERE_NOT_CALIBRATED ) ;
STR_CASE ( CELL_GEM_SPHERE_CALIBRATING ) ;
STR_CASE ( CELL_GEM_COMPUTING_AVAILABLE_COLORS ) ;
STR_CASE ( CELL_GEM_HUE_NOT_SET ) ;
STR_CASE ( CELL_GEM_NO_VIDEO ) ;
STR_CASE ( CELL_GEM_TIME_OUT_OF_RANGE ) ;
STR_CASE ( CELL_GEM_NOT_CALIBRATED ) ;
STR_CASE ( CELL_GEM_NO_EXTERNAL_PORT_DEVICE ) ;
}
2019-09-08 16:09:44 +02:00
2025-04-05 21:50:45 +02:00
return unknown ;
} ) ;
2019-09-08 16:09:44 +02:00
}
2021-10-20 19:47:19 +02:00
template < >
void fmt_class_string < CellGemVideoConvertFormatEnum > : : format ( std : : string & out , u64 arg )
{
format_enum ( out , arg , [ ] ( auto format )
{
2025-04-05 21:50:45 +02:00
switch ( format )
{
STR_CASE ( CELL_GEM_NO_VIDEO_OUTPUT ) ;
STR_CASE ( CELL_GEM_RGBA_640x480 ) ;
STR_CASE ( CELL_GEM_YUV_640x480 ) ;
STR_CASE ( CELL_GEM_YUV422_640x480 ) ;
STR_CASE ( CELL_GEM_YUV411_640x480 ) ;
STR_CASE ( CELL_GEM_RGBA_320x240 ) ;
STR_CASE ( CELL_GEM_BAYER_RESTORED ) ;
STR_CASE ( CELL_GEM_BAYER_RESTORED_RGGB ) ;
STR_CASE ( CELL_GEM_BAYER_RESTORED_RASTERIZED ) ;
}
2021-10-20 19:47:19 +02:00
2025-04-05 21:50:45 +02:00
return unknown ;
} ) ;
2021-10-20 19:47:19 +02:00
}
2024-01-31 22:49:50 +01:00
// last 4 out of 7 ports (7,6,5,4). index starts at 1
static u32 port_num ( u32 gem_num )
{
ensure ( gem_num < CELL_GEM_MAX_NUM ) ;
return CELL_PAD_MAX_PORT_NUM - gem_num ;
}
// last 4 out of 7 ports (6,5,4,3). index starts at 0
static u32 pad_num ( u32 gem_num )
{
ensure ( gem_num < CELL_GEM_MAX_NUM ) ;
return ( CELL_PAD_MAX_PORT_NUM - 1 ) - gem_num ;
}
2018-03-02 17:51:21 +01:00
// **********************
// * HLE helper structs *
// **********************
2022-11-27 12:20:05 +01:00
# ifdef HAVE_LIBEVDEV
struct gun_handler
{
public :
gun_handler ( ) = default ;
static constexpr auto thread_name = " Evdev Gun Thread " sv ;
evdev_gun_handler handler { } ;
atomic_t < u32 > num_devices { 0 } ;
void operator ( ) ( )
{
if ( g_cfg . io . move ! = move_handler : : gun )
{
return ;
}
while ( thread_ctrl : : state ( ) ! = thread_state : : aborting & & ! Emu . IsStopped ( ) )
{
const bool is_active = ! Emu . IsPaused ( ) & & handler . is_init ( ) ;
if ( is_active )
{
for ( u32 i = 0 ; i < num_devices ; i + + )
{
std : : scoped_lock lock ( handler . mutex ) ;
handler . poll ( i ) ;
}
}
thread_ctrl : : wait_for ( is_active ? 1000 : 10000 ) ;
}
}
} ;
using gun_thread = named_thread < gun_handler > ;
# endif
2024-07-08 20:17:21 +02:00
cfg_gems g_cfg_gem_real ;
cfg_fake_gems g_cfg_gem_fake ;
2025-01-09 21:10:28 +01:00
cfg_mouse_gems g_cfg_gem_mouse ;
2023-05-21 13:26:32 +02:00
2021-10-20 19:47:19 +02:00
struct gem_config_data
2015-08-21 22:57:49 +02:00
{
2021-10-20 19:47:19 +02:00
public :
void operator ( ) ( ) ;
static constexpr auto thread_name = " Gem Thread " sv ;
2022-07-04 15:02:17 +02:00
atomic_t < u8 > state = 0 ;
2019-08-25 02:52:28 +02:00
2018-03-02 17:51:21 +01:00
struct gem_color
{
2024-12-20 13:25:13 +01:00
ENABLE_BITWISE_SERIALIZATION ;
2024-12-19 22:19:50 +01:00
f32 r , g , b ;
2018-03-02 17:51:21 +01:00
gem_color ( ) : r ( 0.0f ) , g ( 0.0f ) , b ( 0.0f ) { }
2024-12-19 22:19:50 +01:00
gem_color ( f32 r_ , f32 g_ , f32 b_ )
2018-03-02 17:51:21 +01:00
{
2018-09-05 22:52:30 +02:00
r = std : : clamp ( r_ , 0.0f , 1.0f ) ;
g = std : : clamp ( g_ , 0.0f , 1.0f ) ;
b = std : : clamp ( b_ , 0.0f , 1.0f ) ;
2018-03-02 17:51:21 +01:00
}
2022-07-06 22:08:50 +02:00
static inline const gem_color & get_default_color ( u32 gem_num )
{
static const gem_color gold = gem_color ( 1.0f , 0.85f , 0.0f ) ;
static const gem_color green = gem_color ( 0.0f , 1.0f , 0.0f ) ;
static const gem_color red = gem_color ( 1.0f , 0.0f , 0.0f ) ;
static const gem_color pink = gem_color ( 0.9f , 0.0f , 0.5f ) ;
switch ( gem_num )
{
case 0 : return green ;
case 1 : return gold ;
case 2 : return red ;
case 3 : return pink ;
default : fmt : : throw_exception ( " unexpected gem_num %d " , gem_num ) ;
}
}
2018-03-02 17:51:21 +01:00
} ;
struct gem_controller
{
2018-07-23 19:57:40 +02:00
u32 status = CELL_GEM_STATUS_DISCONNECTED ; // Connection status (CELL_GEM_STATUS_DISCONNECTED or CELL_GEM_STATUS_READY)
u32 ext_status = CELL_GEM_NO_EXTERNAL_PORT_DEVICE ; // External port connection status
2021-07-29 22:33:31 +02:00
u32 ext_id = 0 ; // External device ID (type). For example SHARP_SHOOTER_DEVICE_ID
2018-07-23 19:57:40 +02:00
u32 port = 0 ; // Assigned port
2024-12-21 00:55:05 +01:00
bool enabled_magnetometer = true ; // Whether the magnetometer is enabled (probably used for additional rotational precision)
2018-07-23 19:57:40 +02:00
bool calibrated_magnetometer = false ; // Whether the magnetometer is calibrated
bool enabled_filtering = false ; // Whether filtering is enabled
bool enabled_tracking = false ; // Whether tracking is enabled
bool enabled_LED = false ; // Whether the LED is enabled
2021-07-30 00:32:41 +02:00
bool hue_set = false ; // Whether the hue was set
2018-07-23 19:57:40 +02:00
u8 rumble = 0 ; // Rumble intensity
gem_color sphere_rgb = { } ; // RGB color of the sphere LED
u32 hue = 0 ; // Tracking hue of the motion controller
2024-12-12 23:19:09 +01:00
f32 distance_mm { 3000.0f } ; // Distance from the camera in mm
f32 radius { 5.0f } ; // Radius of the sphere in camera pixels
2025-01-17 00:32:37 +01:00
bool radius_valid = false ; // If the radius and distance of the sphere was computed. Also used for visibility.
2021-07-30 15:55:50 +02:00
2025-04-05 21:50:45 +02:00
bool is_calibrating { false } ; // Whether or not we are currently calibrating
u64 calibration_start_us { 0 } ; // The start timestamp of the calibration in microseconds
u64 calibration_status_flags = 0 ; // The calibration status flags
2021-08-05 00:38:45 +02:00
static constexpr u64 calibration_time_us = 500000 ; // The calibration supposedly takes 0.5 seconds (500000 microseconds)
2018-03-02 17:51:21 +01:00
} ;
2018-07-23 19:57:40 +02:00
CellGemAttribute attribute = { } ;
CellGemVideoConvertAttribute vc_attribute = { } ;
2021-10-20 19:47:19 +02:00
s32 video_data_out_size = - 1 ;
std : : vector < u8 > video_data_in ;
2024-12-20 13:25:13 +01:00
u64 runtime_status_flags = 0 ; // The runtime status flags
2018-07-23 19:57:40 +02:00
bool enable_pitch_correction = false ;
u32 inertial_counter = 0 ;
2018-03-02 17:51:21 +01:00
std : : array < gem_controller , CELL_GEM_MAX_NUM > controllers ;
2018-07-23 19:57:40 +02:00
u32 connected_controllers = 0 ;
2021-10-20 19:47:19 +02:00
atomic_t < bool > video_conversion_in_progress { false } ;
2024-07-08 20:17:21 +02:00
atomic_t < bool > updating { false } ;
2019-10-04 14:37:49 +02:00
u32 camera_frame { } ;
2019-09-21 12:25:53 +02:00
u32 memory_ptr { } ;
2019-10-04 14:37:49 +02:00
shared_mutex mtx ;
2018-03-02 17:51:21 +01:00
2023-02-22 22:14:40 +01:00
u64 start_timestamp_us = 0 ;
2018-03-02 17:51:21 +01:00
2025-01-08 03:19:35 +01:00
atomic_t < u32 > m_wake_up = 0 ;
atomic_t < u32 > m_done = 0 ;
void wake_up ( )
{
m_wake_up . release ( 1 ) ;
m_wake_up . notify_one ( ) ;
}
void done ( )
{
m_done . release ( 1 ) ;
m_done . notify_one ( ) ;
}
2025-01-08 19:08:21 +01:00
bool wait_for_result ( ppu_thread & ppu )
2025-01-08 03:19:35 +01:00
{
2025-01-20 22:54:26 +01:00
// Notify gem thread that the initial state after loading a savestate can be updated.
if ( m_done . compare_and_swap_test ( 2 , 0 ) )
{
m_done . notify_one ( ) ;
}
2025-01-08 19:08:21 +01:00
while ( ! m_done & & ! ppu . is_stopped ( ) )
2025-01-08 03:19:35 +01:00
{
2025-01-08 19:08:21 +01:00
thread_ctrl : : wait_on ( m_done , 0 ) ;
2025-01-08 03:19:35 +01:00
}
2025-01-08 19:08:21 +01:00
if ( ppu . is_stopped ( ) )
{
ppu . state + = cpu_flag : : again ;
return false ;
}
m_done = 0 ;
return true ;
2025-01-08 03:19:35 +01:00
}
2018-03-02 17:51:21 +01:00
// helper functions
bool is_controller_ready ( u32 gem_num ) const
{
return controllers [ gem_num ] . status = = CELL_GEM_STATUS_READY ;
}
2025-01-08 02:56:20 +01:00
void update_connections ( )
2021-08-05 00:38:45 +02:00
{
2025-01-23 09:07:07 +01:00
connected_controllers = 0 ;
const auto update_connection = [ this ] ( u32 i , bool connected )
{
if ( connected )
{
connected_controllers + + ;
controllers [ i ] . status = CELL_GEM_STATUS_READY ;
controllers [ i ] . port = port_num ( i ) ;
}
else
{
controllers [ i ] . status = CELL_GEM_STATUS_DISCONNECTED ;
controllers [ i ] . port = 0 ;
}
} ;
2025-01-08 02:56:20 +01:00
switch ( g_cfg . io . move )
{
case move_handler : : real :
case move_handler : : fake :
{
std : : lock_guard lock ( pad : : g_pad_mutex ) ;
2025-01-20 22:55:21 +01:00
const auto handler = pad : : get_pad_thread ( true ) ;
2025-04-05 21:50:45 +02:00
if ( ! handler )
break ;
2025-01-08 02:56:20 +01:00
for ( u32 i = 0 ; i < CELL_GEM_MAX_NUM ; i + + )
{
const auto & pad = : : at32 ( handler - > GetPads ( ) , pad_num ( i ) ) ;
2025-01-23 09:07:07 +01:00
const bool connected = pad & & ( pad - > m_port_status & CELL_PAD_STATUS_CONNECTED ) & & i < attribute . max_connect ;
2025-01-08 02:56:20 +01:00
const bool is_real_move = g_cfg . io . move ! = move_handler : : real | | pad - > m_pad_handler = = pad_handler : : move ;
2025-01-23 09:07:07 +01:00
update_connection ( i , connected & & is_real_move ) ;
2025-01-08 02:56:20 +01:00
}
break ;
}
2025-01-23 09:07:07 +01:00
case move_handler : : mouse :
2025-01-08 02:56:20 +01:00
case move_handler : : raw_mouse :
{
auto & handler = g_fxo - > get < MouseHandlerBase > ( ) ;
std : : lock_guard mouse_lock ( handler . mutex ) ;
const MouseInfo & info = handler . GetInfo ( ) ;
for ( u32 i = 0 ; i < CELL_GEM_MAX_NUM ; i + + )
{
2025-01-23 09:07:07 +01:00
update_connection ( i , i < attribute . max_connect & & info . status [ i ] = = CELL_MOUSE_STATUS_CONNECTED ) ;
}
break ;
}
# ifdef HAVE_LIBEVDEV
case move_handler : : gun :
{
gun_thread & gun = g_fxo - > get < gun_thread > ( ) ;
std : : scoped_lock lock ( gun . handler . mutex ) ;
gun . num_devices = gun . handler . init ( ) ? gun . handler . get_num_guns ( ) : 0 ;
2025-01-08 02:56:20 +01:00
2025-01-23 09:07:07 +01:00
for ( u32 i = 0 ; i < CELL_GEM_MAX_NUM ; i + + )
{
update_connection ( i , i < attribute . max_connect & & i < gun . num_devices ) ;
2025-01-08 02:56:20 +01:00
}
break ;
}
2025-01-23 09:07:07 +01:00
# endif
case move_handler : : null :
2025-01-08 02:56:20 +01:00
{
break ;
}
}
}
2021-08-05 00:38:45 +02:00
2025-01-08 02:56:20 +01:00
void update_calibration_status ( )
{
2024-12-20 23:38:43 +01:00
for ( u32 gem_num = 0 ; gem_num < CELL_GEM_MAX_NUM ; gem_num + + )
2021-08-05 00:38:45 +02:00
{
2024-12-20 23:38:43 +01:00
gem_controller & controller = controllers [ gem_num ] ;
2025-04-05 21:50:45 +02:00
if ( ! controller . is_calibrating )
continue ;
2024-12-20 23:38:43 +01:00
bool controller_calibrated = true ;
// Request controller calibration
if ( g_cfg . io . move = = move_handler : : real )
2021-08-05 00:38:45 +02:00
{
2024-12-20 23:38:43 +01:00
std : : lock_guard pad_lock ( pad : : g_pad_mutex ) ;
2025-01-20 22:55:21 +01:00
const auto handler = pad : : get_pad_thread ( ) ;
2024-12-20 23:38:43 +01:00
const auto & pad = : : at32 ( handler - > GetPads ( ) , pad_num ( gem_num ) ) ;
if ( pad & & pad - > m_pad_handler = = pad_handler : : move )
{
if ( ! pad - > move_data . calibration_requested | | ! pad - > move_data . calibration_succeeded )
{
pad - > move_data . calibration_requested = true ;
controller_calibrated = false ;
}
}
2021-08-05 00:38:45 +02:00
}
2024-12-20 23:38:43 +01:00
// The calibration takes ~0.5 seconds on real hardware
2025-04-05 21:50:45 +02:00
if ( ( get_guest_system_time ( ) - controller . calibration_start_us ) < gem_controller : : calibration_time_us )
continue ;
2024-12-20 23:38:43 +01:00
if ( ! controller_calibrated )
{
cellGem . warning ( " Reached calibration timeout but ps move controller %d is still calibrating " , gem_num ) ;
}
controller . is_calibrating = false ;
controller . calibration_start_us = 0 ;
controller . calibration_status_flags = CELL_GEM_FLAG_CALIBRATION_SUCCEEDED | CELL_GEM_FLAG_CALIBRATION_OCCURRED ;
controller . calibrated_magnetometer = true ;
controller . enabled_tracking = true ;
// Reset controller calibration request
if ( g_cfg . io . move = = move_handler : : real )
{
std : : lock_guard pad_lock ( pad : : g_pad_mutex ) ;
2025-01-20 22:55:21 +01:00
const auto handler = pad : : get_pad_thread ( ) ;
2024-12-20 23:38:43 +01:00
const auto & pad = : : at32 ( handler - > GetPads ( ) , pad_num ( gem_num ) ) ;
if ( pad & & pad - > m_pad_handler = = pad_handler : : move )
{
pad - > move_data . calibration_requested = false ;
pad - > move_data . calibration_succeeded = false ;
}
}
}
2021-08-05 00:38:45 +02:00
}
2018-03-02 17:51:21 +01:00
void reset_controller ( u32 gem_num )
{
2022-07-06 22:08:50 +02:00
if ( gem_num > = CELL_GEM_MAX_NUM )
{
return ;
}
2024-12-21 00:55:05 +01:00
gem_controller & controller = : : at32 ( controllers , gem_num ) ;
controller = { } ;
controller . sphere_rgb = gem_color : : get_default_color ( gem_num ) ;
2024-01-31 22:49:50 +01:00
bool is_connected = false ;
2018-03-02 17:51:21 +01:00
switch ( g_cfg . io . move )
{
2024-07-08 20:17:21 +02:00
case move_handler : : real :
{
connected_controllers = 0 ;
std : : lock_guard lock ( pad : : g_pad_mutex ) ;
2025-01-20 22:55:21 +01:00
const auto handler = pad : : get_pad_thread ( ) ;
2024-07-08 20:17:21 +02:00
for ( u32 i = 0 ; i < std : : min < u32 > ( attribute . max_connect , CELL_GEM_MAX_NUM ) ; i + + )
{
const auto & pad = : : at32 ( handler - > GetPads ( ) , pad_num ( i ) ) ;
if ( pad & & pad - > m_pad_handler = = pad_handler : : move & & ( pad - > m_port_status & CELL_PAD_STATUS_CONNECTED ) )
{
connected_controllers + + ;
if ( gem_num = = i )
{
2024-12-21 00:55:05 +01:00
pad - > move_data . magnetometer_enabled = controller . enabled_magnetometer ;
2024-07-08 20:17:21 +02:00
is_connected = true ;
}
}
}
break ;
}
2018-03-02 17:51:21 +01:00
case move_handler : : fake :
2024-01-31 22:49:50 +01:00
{
connected_controllers = 0 ;
std : : lock_guard lock ( pad : : g_pad_mutex ) ;
2025-01-20 22:55:21 +01:00
const auto handler = pad : : get_pad_thread ( ) ;
2024-01-31 22:49:50 +01:00
for ( u32 i = 0 ; i < std : : min < u32 > ( attribute . max_connect , CELL_GEM_MAX_NUM ) ; i + + )
{
const auto & pad = : : at32 ( handler - > GetPads ( ) , pad_num ( i ) ) ;
if ( pad & & ( pad - > m_port_status & CELL_PAD_STATUS_CONNECTED ) )
{
connected_controllers + + ;
if ( gem_num = = i )
{
is_connected = true ;
}
}
}
break ;
}
2018-07-23 19:57:40 +02:00
case move_handler : : mouse :
2024-02-05 23:49:00 +01:00
case move_handler : : raw_mouse :
2018-03-02 17:51:21 +01:00
{
2024-02-05 23:49:00 +01:00
auto & handler = g_fxo - > get < MouseHandlerBase > ( ) ;
2024-06-06 23:06:34 +02:00
std : : lock_guard mouse_lock ( handler . mutex ) ;
2024-01-31 22:49:50 +01:00
2024-02-05 23:49:00 +01:00
// Make sure that the mouse handler is initialized
handler . Init ( std : : min < u32 > ( attribute . max_connect , CELL_GEM_MAX_NUM ) ) ;
const MouseInfo & info = handler . GetInfo ( ) ;
2025-04-05 21:50:45 +02:00
connected_controllers = std : : min < u32 > ( { info . now_connect , attribute . max_connect , CELL_GEM_MAX_NUM } ) ;
2024-02-05 23:49:00 +01:00
if ( gem_num < connected_controllers )
2024-01-31 22:49:50 +01:00
{
is_connected = true ;
}
2018-03-02 17:51:21 +01:00
break ;
}
2022-11-27 12:20:05 +01:00
# ifdef HAVE_LIBEVDEV
2022-11-13 18:10:09 +01:00
case move_handler : : gun :
{
2022-11-27 12:20:05 +01:00
gun_thread & gun = g_fxo - > get < gun_thread > ( ) ;
std : : scoped_lock lock ( gun . handler . mutex ) ;
2024-01-31 22:49:50 +01:00
gun . num_devices = gun . handler . init ( ) ? gun . handler . get_num_guns ( ) : 0 ;
connected_controllers = std : : min < u32 > ( std : : min < u32 > ( attribute . max_connect , CELL_GEM_MAX_NUM ) , gun . num_devices ) ;
if ( gem_num < connected_controllers )
{
is_connected = true ;
}
2022-11-13 18:10:09 +01:00
break ;
}
2022-11-27 12:20:05 +01:00
# endif
2021-04-09 21:12:47 +02:00
case move_handler : : null :
break ;
2018-07-23 19:57:40 +02:00
}
// Assign status and port number
2024-01-31 22:49:50 +01:00
if ( is_connected )
2018-07-23 19:57:40 +02:00
{
2022-07-06 22:08:50 +02:00
controller . status = CELL_GEM_STATUS_READY ;
2024-01-31 22:49:50 +01:00
controller . port = port_num ( gem_num ) ;
2018-03-02 17:51:21 +01:00
}
}
2022-07-04 15:02:17 +02:00
2024-12-19 22:30:59 +01:00
void paint_spheres ( CellGemVideoConvertFormatEnum output_format , u32 width , u32 height , u8 * video_data_out , u32 video_data_out_size ) ;
2023-05-20 15:05:44 +02:00
gem_config_data ( )
{
2024-12-20 13:25:13 +01:00
load_configs ( ) ;
2023-05-20 15:05:44 +02:00
} ;
2022-07-04 15:02:17 +02:00
2025-01-20 22:54:26 +01:00
SAVESTATE_INIT_POS ( 16.1 ) ; // Depends on cellCamera
2022-07-04 15:02:17 +02:00
void save ( utils : : serial & ar )
{
ar ( state ) ;
if ( ! state )
{
return ;
}
2024-12-20 13:25:13 +01:00
const s32 version = GET_OR_USE_SERIALIZATION_VERSION ( ar . is_writing ( ) , cellGem ) ;
ar ( attribute , vc_attribute , runtime_status_flags , enable_pitch_correction , inertial_counter ) ;
2022-07-04 15:02:17 +02:00
2024-12-20 13:25:13 +01:00
for ( gem_controller & c : controllers )
{
ar ( c . status , c . ext_status , c . ext_id , c . port , c . enabled_magnetometer , c . calibrated_magnetometer , c . enabled_filtering , c . enabled_tracking , c . enabled_LED , c . hue_set , c . rumble ) ;
ar ( c . sphere_rgb , c . hue , c . distance_mm , c . radius , c . radius_valid , c . is_calibrating ) ;
ar ( c . calibration_start_us ) ;
if ( ar . is_writing ( ) | | version > = 2 )
{
ar ( c . calibration_status_flags ) ;
}
}
ar ( connected_controllers , updating , camera_frame , memory_ptr , start_timestamp_us ) ;
2025-01-20 22:54:26 +01:00
if ( ar . is_writing ( ) | | version > = 3 )
{
ar ( video_conversion_in_progress , video_data_out_size ) ;
}
2022-07-04 15:02:17 +02:00
}
gem_config_data ( utils : : serial & ar )
{
save ( ar ) ;
2024-12-20 13:25:13 +01:00
load_configs ( ) ;
}
static void load_configs ( )
{
2024-07-08 20:17:21 +02:00
if ( ! g_cfg_gem_real . load ( ) )
2023-05-20 15:05:44 +02:00
{
2024-07-08 20:17:21 +02:00
cellGem . notice ( " Could not load real gem config. Using defaults. " ) ;
2023-05-20 15:05:44 +02:00
}
2024-08-08 05:06:28 +02:00
2024-07-08 20:17:21 +02:00
if ( ! g_cfg_gem_fake . load ( ) )
{
cellGem . notice ( " Could not load fake gem config. Using defaults. " ) ;
}
2025-01-09 21:10:28 +01:00
if ( ! g_cfg_gem_mouse . load ( ) )
{
cellGem . notice ( " Could not load mouse gem config. Using defaults. " ) ;
}
2024-07-08 20:17:21 +02:00
cellGem . notice ( " Real gem config= \n " , g_cfg_gem_real . to_string ( ) ) ;
cellGem . notice ( " Fake gem config= \n " , g_cfg_gem_fake . to_string ( ) ) ;
2025-01-09 21:10:28 +01:00
cellGem . notice ( " Mouse gem config= \n " , g_cfg_gem_mouse . to_string ( ) ) ;
2022-07-04 15:02:17 +02:00
}
2015-08-21 22:57:49 +02:00
} ;
2024-07-08 20:17:21 +02:00
extern std : : pair < u32 , u32 > get_video_resolution ( const CellCameraInfoEx & info ) ;
extern u32 get_buffer_size_by_format ( s32 format , s32 width , s32 height ) ;
2024-12-19 22:19:50 +01:00
static inline s32 cellGemGetVideoConvertSize ( s32 output_format )
2021-10-20 19:47:19 +02:00
{
switch ( output_format )
{
case CELL_GEM_RGBA_320x240 : // RGBA output; 320*240*4-byte output buffer required
return 320 * 240 * 4 ;
case CELL_GEM_RGBA_640x480 : // RGBA output; 640*480*4-byte output buffer required
return 640 * 480 * 4 ;
case CELL_GEM_YUV_640x480 : // YUV output; 640*480+640*480+640*480-byte output buffer required (contiguous)
return 640 * 480 + 640 * 480 + 640 * 480 ;
case CELL_GEM_YUV422_640x480 : // YUV output; 640*480+320*480+320*480-byte output buffer required (contiguous)
return 640 * 480 + 320 * 480 + 320 * 480 ;
case CELL_GEM_YUV411_640x480 : // YUV411 output; 640*480+320*240+320*240-byte output buffer required (contiguous)
return 640 * 480 + 320 * 240 + 320 * 240 ;
2025-04-05 21:50:45 +02:00
case CELL_GEM_BAYER_RESTORED : // Bayer pattern output, 640x480, gamma and white balance applied, output buffer required
case CELL_GEM_BAYER_RESTORED_RGGB : // Restored Bayer output, 2x2 pixels rearranged into 320x240 RG1G2B
2021-10-20 19:47:19 +02:00
case CELL_GEM_BAYER_RESTORED_RASTERIZED : // Restored Bayer output, R,G1,G2,B rearranged into 4 contiguous 320x240 1-channel rasters
return 640 * 480 ;
case CELL_GEM_NO_VIDEO_OUTPUT : // Disable video output
return 0 ;
default :
return - 1 ;
}
}
2024-07-08 21:23:51 +02:00
namespace gem
2021-10-20 19:47:19 +02:00
{
2024-12-19 22:30:59 +01:00
struct gem_position
{
public :
void set_position ( f32 x , f32 y )
{
std : : lock_guard lock ( m_mutex ) ;
m_x = x ;
m_y = y ;
}
void get_position ( f32 & x , f32 & y )
{
std : : lock_guard lock ( m_mutex ) ;
x = m_x ;
y = m_y ;
}
2025-04-05 21:50:45 +02:00
2024-12-19 22:30:59 +01:00
private :
std : : mutex m_mutex ;
f32 m_x = 0.0f ;
f32 m_y = 0.0f ;
} ;
2025-04-05 21:50:45 +02:00
std : : array < gem_position , CELL_GEM_MAX_NUM > positions { } ;
2024-12-19 22:30:59 +01:00
2025-01-09 00:35:11 +01:00
struct YUV
{
u8 y = 0 ;
u8 u = 0 ;
u8 v = 0 ;
YUV ( u8 r , u8 g , u8 b )
2025-04-05 21:50:45 +02:00
: y ( Y ( r , g , b ) ) , u ( U ( r , g , b ) ) , v ( V ( r , g , b ) )
2025-01-09 00:35:11 +01:00
{
}
2025-04-05 21:50:45 +02:00
static inline u8 Y ( u8 r , u8 g , u8 b )
{
return static_cast < u8 > ( 0.299f * r + 0.587f * g + 0.114f * b ) ;
}
static inline u8 U ( u8 r , u8 g , u8 b )
{
return static_cast < u8 > ( - 0.14713f * r - 0.28886f * g + 0.436f * b ) ;
}
static inline u8 V ( u8 r , u8 g , u8 b )
{
return static_cast < u8 > ( 0.615f * r - 0.51499f * g - 0.10001f * b ) ;
}
2025-01-09 00:35:11 +01:00
} ;
2024-07-08 21:23:51 +02:00
bool convert_image_format ( CellCameraFormat input_format , CellGemVideoConvertFormatEnum output_format ,
2025-04-05 21:50:45 +02:00
const std : : vector < u8 > & video_data_in , u32 width , u32 height ,
u8 * video_data_out , u32 video_data_out_size , std : : string_view caller )
2021-10-20 19:47:19 +02:00
{
2024-07-08 21:23:51 +02:00
if ( output_format ! = CELL_GEM_NO_VIDEO_OUTPUT & & ! video_data_out )
2021-10-20 19:47:19 +02:00
{
2024-07-08 21:23:51 +02:00
return false ;
2021-10-20 19:47:19 +02:00
}
2024-07-08 21:23:51 +02:00
const u32 required_in_size = get_buffer_size_by_format ( static_cast < s32 > ( input_format ) , width , height ) ;
const s32 required_out_size = cellGemGetVideoConvertSize ( output_format ) ;
2021-10-20 19:47:19 +02:00
if ( video_data_in . size ( ) ! = required_in_size )
{
2025-01-20 22:45:42 +01:00
cellGem . error ( " convert: in_size mismatch: required=%d, actual=%d (called from %s) " , required_in_size , video_data_in . size ( ) , caller ) ;
2024-07-08 21:23:51 +02:00
return false ;
2021-10-20 19:47:19 +02:00
}
2024-07-08 21:23:51 +02:00
if ( required_out_size < 0 | | video_data_out_size ! = static_cast < u32 > ( required_out_size ) )
2021-10-20 19:47:19 +02:00
{
2025-01-20 22:45:42 +01:00
cellGem . error ( " convert: out_size unknown: required=%d, actual=%d, format %d (called from %s) " , required_out_size , video_data_out_size , output_format , caller ) ;
2024-07-08 21:23:51 +02:00
return false ;
2021-10-20 19:47:19 +02:00
}
if ( required_out_size = = 0 )
{
2024-07-08 21:23:51 +02:00
return false ;
2021-10-20 19:47:19 +02:00
}
2024-07-08 21:23:51 +02:00
switch ( output_format )
2021-10-20 19:47:19 +02:00
{
case CELL_GEM_RGBA_640x480 : // RGBA output; 640*480*4-byte output buffer required
{
2024-07-08 21:23:51 +02:00
switch ( input_format )
{
case CELL_CAMERA_RAW8 :
2021-10-20 19:47:19 +02:00
{
2024-07-08 21:23:51 +02:00
const u32 in_pitch = width ;
const u32 out_pitch = width * 4 ;
2021-10-20 19:47:19 +02:00
2024-07-08 21:23:51 +02:00
for ( u32 y = 0 ; y < height - 1 ; y + = 2 )
2021-10-20 19:47:19 +02:00
{
2025-01-09 00:35:11 +01:00
const u8 * src0 = & video_data_in [ y * in_pitch ] ;
const u8 * src1 = src0 + in_pitch ;
2024-04-21 00:10:20 +02:00
2025-01-09 00:35:11 +01:00
u8 * dst0 = video_data_out + y * out_pitch ;
u8 * dst1 = dst0 + out_pitch ;
2024-04-21 00:10:20 +02:00
2024-12-19 22:19:50 +01:00
for ( u32 x = 0 ; x < width - 1 ; x + = 2 , src0 + = 2 , src1 + = 2 , dst0 + = 8 , dst1 + = 8 )
2021-10-20 19:47:19 +02:00
{
2025-04-05 21:50:45 +02:00
const u8 b = src0 [ 0 ] ;
2024-12-19 22:19:50 +01:00
const u8 g0 = src0 [ 1 ] ;
const u8 g1 = src1 [ 0 ] ;
2025-04-05 21:50:45 +02:00
const u8 r = src1 [ 1 ] ;
2021-10-20 19:47:19 +02:00
2025-04-05 21:50:45 +02:00
const u8 top [ 4 ] = { r , g0 , b , 255 } ;
const u8 bottom [ 4 ] = { r , g1 , b , 255 } ;
2021-10-20 19:47:19 +02:00
// Top-Left
2024-07-08 21:23:51 +02:00
std : : memcpy ( dst0 , top , 4 ) ;
2021-10-20 19:47:19 +02:00
// Top-Right Pixel
2024-07-08 21:23:51 +02:00
std : : memcpy ( dst0 + 4 , top , 4 ) ;
2021-10-20 19:47:19 +02:00
// Bottom-Left Pixel
2024-07-08 21:23:51 +02:00
std : : memcpy ( dst1 , bottom , 4 ) ;
2021-10-20 19:47:19 +02:00
// Bottom-Right Pixel
2024-07-08 21:23:51 +02:00
std : : memcpy ( dst1 + 4 , bottom , 4 ) ;
2021-10-20 19:47:19 +02:00
}
}
2024-07-08 21:23:51 +02:00
break ;
2021-10-20 19:47:19 +02:00
}
2024-07-08 21:23:51 +02:00
case CELL_CAMERA_RGBA :
2021-10-20 19:47:19 +02:00
{
2024-07-08 21:23:51 +02:00
std : : memcpy ( video_data_out , video_data_in . data ( ) , std : : min < usz > ( required_in_size , required_out_size ) ) ;
break ;
}
default :
{
2025-01-20 22:45:42 +01:00
cellGem . error ( " Unimplemented: Converting %s to %s (called from %s) " , input_format , output_format , caller ) ;
2024-07-08 21:23:51 +02:00
std : : memcpy ( video_data_out , video_data_in . data ( ) , std : : min < usz > ( required_in_size , required_out_size ) ) ;
return false ;
}
2021-10-20 19:47:19 +02:00
}
break ;
}
case CELL_GEM_BAYER_RESTORED : // Bayer pattern output, 640x480, gamma and white balance applied, output buffer required
{
2024-07-08 21:23:51 +02:00
if ( input_format = = CELL_CAMERA_RAW8 )
2021-10-20 19:47:19 +02:00
{
2024-07-08 21:23:51 +02:00
std : : memcpy ( video_data_out , video_data_in . data ( ) , std : : min < usz > ( required_in_size , required_out_size ) ) ;
2021-10-20 19:47:19 +02:00
}
else
{
2025-01-20 22:45:42 +01:00
cellGem . error ( " Unimplemented: Converting %s to %s (called from %s) " , input_format , output_format , caller ) ;
2024-07-08 21:23:51 +02:00
return false ;
2021-10-20 19:47:19 +02:00
}
break ;
}
case CELL_GEM_YUV_640x480 : // YUV output; 640*480+640*480+640*480-byte output buffer required (contiguous)
2025-01-09 00:35:11 +01:00
{
const u32 yuv_pitch = width ;
u8 * dst_y = video_data_out ;
u8 * dst_u = dst_y + yuv_pitch * height ;
u8 * dst_v = dst_u + yuv_pitch * height ;
switch ( input_format )
{
case CELL_CAMERA_RAW8 :
{
const u32 in_pitch = width ;
for ( u32 y = 0 ; y < height - 1 ; y + = 2 )
{
const u8 * src0 = & video_data_in [ y * in_pitch ] ;
const u8 * src1 = src0 + in_pitch ;
u8 * dst_y0 = dst_y + y * yuv_pitch ;
u8 * dst_y1 = dst_y0 + yuv_pitch ;
u8 * dst_u0 = dst_u + y * yuv_pitch ;
u8 * dst_u1 = dst_u0 + yuv_pitch ;
u8 * dst_v0 = dst_v + y * yuv_pitch ;
u8 * dst_v1 = dst_v0 + yuv_pitch ;
for ( u32 x = 0 ; x < width - 1 ; x + = 2 , src0 + = 2 , src1 + = 2 , dst_y0 + = 2 , dst_y1 + = 2 , dst_u0 + = 2 , dst_u1 + = 2 , dst_v0 + = 2 , dst_v1 + = 2 )
{
2025-04-05 21:50:45 +02:00
const u8 b = src0 [ 0 ] ;
2025-01-09 00:35:11 +01:00
const u8 g0 = src0 [ 1 ] ;
const u8 g1 = src1 [ 0 ] ;
2025-04-05 21:50:45 +02:00
const u8 r = src1 [ 1 ] ;
2025-01-09 00:35:11 +01:00
// Convert RGBA to YUV
2025-04-05 21:50:45 +02:00
const YUV yuv_top = YUV ( r , g0 , b ) ;
2025-01-09 00:35:11 +01:00
const YUV yuv_bottom = YUV ( r , g1 , b ) ;
dst_y0 [ 0 ] = dst_y0 [ 1 ] = yuv_top . y ;
dst_y1 [ 0 ] = dst_y1 [ 1 ] = yuv_bottom . y ;
dst_u0 [ 0 ] = dst_u0 [ 1 ] = yuv_top . u ;
dst_u1 [ 0 ] = dst_u1 [ 1 ] = yuv_bottom . u ;
dst_v0 [ 0 ] = dst_v0 [ 1 ] = yuv_top . v ;
dst_v1 [ 0 ] = dst_v1 [ 1 ] = yuv_bottom . v ;
}
}
break ;
}
case CELL_CAMERA_RGBA :
{
const u32 in_pitch = width / 4 ;
for ( u32 y = 0 ; y < height ; y + + )
{
const u8 * src = & video_data_in [ y * in_pitch ] ;
for ( u32 x = 0 ; x < width ; x + + , src + = 4 )
{
const u8 r = src [ 0 ] ;
const u8 g = src [ 1 ] ;
const u8 b = src [ 2 ] ;
// Convert RGBA to YUV
const YUV yuv = YUV ( r , g , b ) ;
* dst_y + + = yuv . y ;
* dst_u + + = yuv . u ;
* dst_v + + = yuv . v ;
}
}
break ;
}
default :
{
2025-01-20 22:45:42 +01:00
cellGem . error ( " Unimplemented: Converting %s to %s (called from %s) " , input_format , output_format , caller ) ;
2025-01-09 00:35:11 +01:00
std : : memcpy ( video_data_out , video_data_in . data ( ) , std : : min < usz > ( required_in_size , required_out_size ) ) ;
return false ;
}
}
break ;
}
2021-10-20 19:47:19 +02:00
case CELL_GEM_YUV422_640x480 : // YUV output; 640*480+320*480+320*480-byte output buffer required (contiguous)
2025-01-09 00:35:11 +01:00
{
const u32 y_pitch = width ;
const u32 uv_pitch = width / 2 ;
u8 * dst_y = video_data_out ;
u8 * dst_u = dst_y + y_pitch * height ;
u8 * dst_v = dst_u + uv_pitch * height ;
switch ( input_format )
{
case CELL_CAMERA_RAW8 :
{
const u32 in_pitch = width ;
for ( u32 y = 0 ; y < height - 1 ; y + = 2 )
{
const u8 * src0 = & video_data_in [ y * in_pitch ] ;
const u8 * src1 = src0 + in_pitch ;
u8 * dst_y0 = dst_y + y * y_pitch ;
u8 * dst_y1 = dst_y0 + y_pitch ;
u8 * dst_u0 = dst_u + y * uv_pitch ;
u8 * dst_u1 = dst_u0 + uv_pitch ;
u8 * dst_v0 = dst_v + y * uv_pitch ;
u8 * dst_v1 = dst_v0 + uv_pitch ;
for ( u32 x = 0 ; x < width - 1 ; x + = 2 , src0 + = 2 , src1 + = 2 , dst_y0 + = 2 , dst_y1 + = 2 )
{
2025-04-05 21:50:45 +02:00
const u8 b = src0 [ 0 ] ;
2025-01-09 00:35:11 +01:00
const u8 g0 = src0 [ 1 ] ;
const u8 g1 = src1 [ 0 ] ;
2025-04-05 21:50:45 +02:00
const u8 r = src1 [ 1 ] ;
2025-01-09 00:35:11 +01:00
// Convert RGBA to YUV
2025-04-05 21:50:45 +02:00
const YUV yuv_top = YUV ( r , g0 , b ) ;
2025-01-09 00:35:11 +01:00
const YUV yuv_bottom = YUV ( r , g1 , b ) ;
dst_y0 [ 0 ] = dst_y0 [ 1 ] = yuv_top . y ;
dst_y1 [ 0 ] = dst_y1 [ 1 ] = yuv_bottom . y ;
* dst_u0 + + = yuv_top . u ;
* dst_u1 + + = yuv_bottom . u ;
* dst_v0 + + = yuv_top . v ;
* dst_v1 + + = yuv_bottom . v ;
}
}
break ;
}
case CELL_CAMERA_RGBA :
{
const u32 in_pitch = width * 4 ;
for ( u32 y = 0 ; y < height ; y + + )
{
const u8 * src = & video_data_in [ y * in_pitch ] ;
for ( u32 x = 0 ; x < width - 1 ; x + = 2 , src + = 8 , dst_y + = 2 )
{
const u8 r_0 = src [ 0 ] ;
const u8 g_0 = src [ 1 ] ;
const u8 b_0 = src [ 2 ] ;
const u8 r_1 = src [ 4 ] ;
const u8 g_1 = src [ 5 ] ;
const u8 b_1 = src [ 6 ] ;
// Convert RGBA to YUV
const YUV yuv_0 = YUV ( r_0 , g_0 , b_0 ) ;
const u8 y_1 = YUV : : Y ( r_1 , g_1 , b_1 ) ;
dst_y [ 0 ] = yuv_0 . y ;
dst_y [ 1 ] = y_1 ;
* dst_u + + = yuv_0 . u ;
* dst_v + + = yuv_0 . v ;
}
}
break ;
}
default :
{
2025-01-20 22:45:42 +01:00
cellGem . error ( " Unimplemented: Converting %s to %s (called from %s) " , input_format , output_format , caller ) ;
2025-01-09 00:35:11 +01:00
std : : memcpy ( video_data_out , video_data_in . data ( ) , std : : min < usz > ( required_in_size , required_out_size ) ) ;
return false ;
}
}
break ;
}
2021-10-20 19:47:19 +02:00
case CELL_GEM_YUV411_640x480 : // YUV411 output; 640*480+320*240+320*240-byte output buffer required (contiguous)
2025-01-09 00:35:11 +01:00
{
const u32 y_pitch = width ;
const u32 uv_pitch = width / 4 ;
u8 * dst_y = video_data_out ;
u8 * dst_u = dst_y + y_pitch * height ;
u8 * dst_v = dst_u + uv_pitch * height ;
switch ( input_format )
{
case CELL_CAMERA_RAW8 :
{
const u32 in_pitch = width ;
for ( u32 y = 0 ; y < height - 1 ; y + = 2 )
{
const u8 * src0 = & video_data_in [ y * in_pitch ] ;
const u8 * src1 = src0 + in_pitch ;
u8 * dst_y0 = dst_y + y * y_pitch ;
u8 * dst_y1 = dst_y0 + y_pitch ;
u8 * dst_u0 = dst_u + y * uv_pitch ;
u8 * dst_u1 = dst_u0 + uv_pitch ;
u8 * dst_v0 = dst_v + y * uv_pitch ;
u8 * dst_v1 = dst_v0 + uv_pitch ;
for ( u32 x = 0 ; x < width - 3 ; x + = 4 , src0 + = 4 , src1 + = 4 , dst_y0 + = 4 , dst_y1 + = 4 )
{
2025-04-05 21:50:45 +02:00
const u8 b_left = src0 [ 0 ] ;
const u8 g0_left = src0 [ 1 ] ;
const u8 b_right = src0 [ 2 ] ;
2025-01-09 00:35:11 +01:00
const u8 g0_right = src0 [ 3 ] ;
2025-04-05 21:50:45 +02:00
const u8 g1_left = src1 [ 0 ] ;
const u8 r_left = src1 [ 1 ] ;
2025-01-09 00:35:11 +01:00
const u8 g1_right = src1 [ 2 ] ;
2025-04-05 21:50:45 +02:00
const u8 r_right = src1 [ 3 ] ;
2025-01-09 00:35:11 +01:00
// Convert RGBA to YUV
2025-04-05 21:50:45 +02:00
const YUV yuv_top_left = YUV ( r_left , g0_left , b_left ) ; // Re-used for top-right
const u8 y_top_right = YUV : : Y ( r_right , g0_right , b_right ) ;
2025-01-09 00:35:11 +01:00
const YUV yuv_bottom_left = YUV ( r_left , g1_left , b_left ) ; // Re-used for bottom-right
2025-04-05 21:50:45 +02:00
const u8 y_bottom_right = YUV : : Y ( r_right , g1_right , b_right ) ;
2025-01-09 00:35:11 +01:00
dst_y0 [ 0 ] = dst_y0 [ 1 ] = yuv_top_left . y ;
dst_y0 [ 2 ] = dst_y0 [ 3 ] = y_top_right ;
dst_y1 [ 0 ] = dst_y1 [ 1 ] = yuv_bottom_left . y ;
dst_y1 [ 2 ] = dst_y1 [ 3 ] = y_bottom_right ;
* dst_u0 + + = yuv_top_left . u ;
* dst_u1 + + = yuv_bottom_left . u ;
* dst_v0 + + = yuv_top_left . v ;
* dst_v1 + + = yuv_bottom_left . v ;
}
}
break ;
}
case CELL_CAMERA_RGBA :
{
const u32 in_pitch = width * 4 ;
for ( u32 y = 0 ; y < height ; y + + )
{
const u8 * src = & video_data_in [ y * in_pitch ] ;
for ( u32 x = 0 ; x < width - 3 ; x + = 4 , src + = 16 , dst_y + = 4 )
{
const u8 r_0 = src [ 0 ] ;
const u8 g_0 = src [ 1 ] ;
const u8 b_0 = src [ 2 ] ;
const u8 r_1 = src [ 4 ] ;
const u8 g_1 = src [ 5 ] ;
const u8 b_1 = src [ 6 ] ;
const u8 r_2 = src [ 8 ] ;
const u8 g_2 = src [ 9 ] ;
const u8 b_2 = src [ 10 ] ;
const u8 r_3 = src [ 12 ] ;
const u8 g_3 = src [ 13 ] ;
const u8 b_3 = src [ 14 ] ;
// Convert RGBA to YUV
const YUV yuv_0 = YUV ( r_0 , g_0 , b_0 ) ;
const u8 y_1 = YUV : : Y ( r_1 , g_1 , b_1 ) ;
const u8 y_2 = YUV : : Y ( r_2 , g_2 , b_2 ) ;
const u8 y_3 = YUV : : Y ( r_3 , g_3 , b_3 ) ;
dst_y [ 0 ] = yuv_0 . y ;
dst_y [ 1 ] = y_1 ;
dst_y [ 2 ] = y_2 ;
dst_y [ 3 ] = y_3 ;
* dst_u + + = yuv_0 . u ;
* dst_v + + = yuv_0 . v ;
}
}
break ;
}
default :
{
2025-01-20 22:45:42 +01:00
cellGem . error ( " Unimplemented: Converting %s to %s (called from %s) " , input_format , output_format , caller ) ;
2025-01-09 00:35:11 +01:00
std : : memcpy ( video_data_out , video_data_in . data ( ) , std : : min < usz > ( required_in_size , required_out_size ) ) ;
return false ;
}
}
break ;
}
case CELL_GEM_RGBA_320x240 : // RGBA output; 320*240*4-byte output buffer required
{
switch ( input_format )
{
case CELL_CAMERA_RAW8 :
{
const u32 in_pitch = width ;
const u32 out_pitch = width * 4 / 2 ;
for ( u32 y = 0 ; y < height - 1 ; y + = 2 )
{
const u8 * src0 = & video_data_in [ y * in_pitch ] ;
const u8 * src1 = src0 + in_pitch ;
u8 * dst0 = video_data_out + ( y / 2 ) * out_pitch ;
u8 * dst1 = dst0 + out_pitch ;
for ( u32 x = 0 ; x < width - 1 ; x + = 2 , src0 + = 2 , src1 + = 2 , dst0 + = 4 , dst1 + = 4 )
{
2025-04-05 21:50:45 +02:00
const u8 b = src0 [ 0 ] ;
2025-01-09 00:35:11 +01:00
const u8 g0 = src0 [ 1 ] ;
const u8 g1 = src1 [ 0 ] ;
2025-04-05 21:50:45 +02:00
const u8 r = src1 [ 1 ] ;
2025-01-09 00:35:11 +01:00
2025-04-05 21:50:45 +02:00
const u8 top [ 4 ] = { r , g0 , b , 255 } ;
const u8 bottom [ 4 ] = { r , g1 , b , 255 } ;
2025-01-09 00:35:11 +01:00
// Top-Left
std : : memcpy ( dst0 , top , 4 ) ;
// Bottom-Left Pixel
std : : memcpy ( dst1 , bottom , 4 ) ;
}
}
break ;
}
case CELL_CAMERA_RGBA :
{
const u32 in_pitch = width * 4 ;
const u32 out_pitch = width * 4 / 2 ;
for ( u32 y = 0 ; y < height / 2 ; y + + )
{
const u8 * src = & video_data_in [ y * 2 * in_pitch ] ;
u8 * dst = video_data_out + y * out_pitch ;
for ( u32 x = 0 ; x < width / 2 ; x + + , src + = 4 * 2 , dst + = 4 )
{
std : : memcpy ( dst , src , 4 ) ;
}
}
break ;
}
default :
{
2025-01-20 22:45:42 +01:00
cellGem . error ( " Unimplemented: Converting %s to %s (called from %s) " , input_format , output_format , caller ) ;
2025-01-09 00:35:11 +01:00
std : : memcpy ( video_data_out , video_data_in . data ( ) , std : : min < usz > ( required_in_size , required_out_size ) ) ;
return false ;
}
}
break ;
}
2025-04-05 21:50:45 +02:00
case CELL_GEM_BAYER_RESTORED_RGGB : // Restored Bayer output, 2x2 pixels rearranged into 320x240 RG1G2B
2021-10-20 19:47:19 +02:00
case CELL_GEM_BAYER_RESTORED_RASTERIZED : // Restored Bayer output, R,G1,G2,B rearranged into 4 contiguous 320x240 1-channel rasters
{
2025-01-20 22:45:42 +01:00
cellGem . error ( " Unimplemented: Converting %s to %s (called from %s) " , input_format , output_format , caller ) ;
2025-01-09 00:35:11 +01:00
std : : memcpy ( video_data_out , video_data_in . data ( ) , std : : min < usz > ( required_in_size , required_out_size ) ) ;
2024-07-08 21:23:51 +02:00
return false ;
2021-10-20 19:47:19 +02:00
}
case CELL_GEM_NO_VIDEO_OUTPUT : // Disable video output
{
2025-01-20 22:45:42 +01:00
cellGem . trace ( " Ignoring frame conversion for CELL_GEM_NO_VIDEO_OUTPUT (called from %s) " , caller ) ;
2021-10-20 19:47:19 +02:00
break ;
}
default :
{
2025-01-20 22:45:42 +01:00
cellGem . error ( " Trying to convert %s to %s (called from %s) " , input_format , output_format , caller ) ;
2024-07-08 21:23:51 +02:00
return false ;
}
}
return true ;
}
2025-04-05 21:50:45 +02:00
} // namespace gem
2024-07-08 21:23:51 +02:00
2024-12-19 22:30:59 +01:00
void gem_config_data : : paint_spheres ( CellGemVideoConvertFormatEnum output_format , u32 width , u32 height , u8 * video_data_out , u32 video_data_out_size )
{
if ( ! width | | ! height | | ! video_data_out | | ! video_data_out_size )
{
return ;
}
struct sphere_information
{
f32 radius = 0.0f ;
s16 x = 0 ;
s16 y = 0 ;
u8 r = 0 ;
u8 g = 0 ;
u8 b = 0 ;
} ;
std : : vector < sphere_information > sphere_info ;
{
reader_lock lock ( mtx ) ;
for ( u32 gem_num = 0 ; gem_num < CELL_GEM_MAX_NUM ; gem_num + + )
{
const gem_config_data : : gem_controller & controller = controllers [ gem_num ] ;
2025-04-05 21:50:45 +02:00
if ( ! controller . radius_valid | | controller . radius < = 0.0f )
continue ;
2024-12-19 22:30:59 +01:00
f32 x , y ;
: : at32 ( gem : : positions , gem_num ) . get_position ( x , y ) ;
const u8 r = static_cast < u8 > ( std : : clamp ( controller . sphere_rgb . r * 255.0f , 0.0f , 255.0f ) ) ;
const u8 g = static_cast < u8 > ( std : : clamp ( controller . sphere_rgb . g * 255.0f , 0.0f , 255.0f ) ) ;
const u8 b = static_cast < u8 > ( std : : clamp ( controller . sphere_rgb . b * 255.0f , 0.0f , 255.0f ) ) ;
2025-04-05 21:50:45 +02:00
sphere_info . push_back ( { controller . radius , static_cast < s16 > ( x ) , static_cast < s16 > ( y ) , r , g , b } ) ;
2024-12-19 22:30:59 +01:00
}
}
switch ( output_format )
{
case CELL_GEM_RGBA_640x480 : // RGBA output; 640*480*4-byte output buffer required
{
cellGem . trace ( " Painting spheres for CELL_GEM_RGBA_640x480 " ) ;
const u32 out_pitch = width * 4 ;
for ( const sphere_information & info : sphere_info )
{
const s32 x_begin = std : : max ( 0 , static_cast < s32 > ( std : : floor ( info . x - info . radius ) ) ) ;
const s32 x_end = std : : min < s32 > ( width , static_cast < s32 > ( std : : ceil ( info . x + info . radius ) ) ) ;
const s32 y_begin = std : : max ( 0 , static_cast < s32 > ( std : : floor ( info . y - info . radius ) ) ) ;
const s32 y_end = std : : min < s32 > ( height , static_cast < s32 > ( std : : ceil ( info . y + info . radius ) ) ) ;
for ( s32 y = y_begin ; y < y_end ; y + + )
{
u8 * dst = video_data_out + y * out_pitch + x_begin * 4 ;
for ( s32 x = x_begin ; x < x_end ; x + + , dst + = 4 )
{
const f32 distance = static_cast < f32 > ( std : : sqrt ( std : : pow ( info . x - x , 2 ) + std : : pow ( info . y - y , 2 ) ) ) ;
2025-04-05 21:50:45 +02:00
if ( distance > info . radius )
continue ;
2024-12-19 22:30:59 +01:00
dst [ 0 ] = info . r ;
dst [ 1 ] = info . g ;
dst [ 2 ] = info . b ;
dst [ 3 ] = 255 ;
}
}
}
break ;
}
2025-04-05 21:50:45 +02:00
case CELL_GEM_BAYER_RESTORED : // Bayer pattern output, 640x480, gamma and white balance applied, output buffer required
case CELL_GEM_RGBA_320x240 : // RGBA output; 320*240*4-byte output buffer required
case CELL_GEM_YUV_640x480 : // YUV output; 640*480+640*480+640*480-byte output buffer required (contiguous)
case CELL_GEM_YUV422_640x480 : // YUV output; 640*480+320*480+320*480-byte output buffer required (contiguous)
case CELL_GEM_YUV411_640x480 : // YUV411 output; 640*480+320*240+320*240-byte output buffer required (contiguous)
case CELL_GEM_BAYER_RESTORED_RGGB : // Restored Bayer output, 2x2 pixels rearranged into 320x240 RG1G2B
2024-12-19 22:30:59 +01:00
case CELL_GEM_BAYER_RESTORED_RASTERIZED : // Restored Bayer output, R,G1,G2,B rearranged into 4 contiguous 320x240 1-channel rasters
{
cellGem . trace ( " Unimplemented: painting spheres for %s " , output_format ) ;
break ;
}
case CELL_GEM_NO_VIDEO_OUTPUT : // Disable video output
{
cellGem . trace ( " Ignoring painting spheres for CELL_GEM_NO_VIDEO_OUTPUT " ) ;
break ;
}
default :
{
cellGem . trace ( " Ignoring painting spheres for %d " , static_cast < u32 > ( output_format ) ) ;
break ;
}
}
}
2024-07-08 21:23:51 +02:00
void gem_config_data : : operator ( ) ( )
{
cellGem . notice ( " Starting thread " ) ;
2025-01-08 02:56:20 +01:00
u64 last_update_us = 0 ;
2025-01-20 22:54:26 +01:00
// Handle initial state after loading a savestate
if ( state & & video_conversion_in_progress )
{
// Wait for cellGemConvertVideoFinish. The initial savestate loading may take a while.
m_done = 2 ; // Use special value 2 for this case
thread_ctrl : : wait_on ( m_done , 2 , 5'000'000 ) ;
// Just mark this conversion as complete (there's no real downside to this, except for a black image)
video_conversion_in_progress = false ;
done ( ) ;
}
2024-07-08 21:23:51 +02:00
while ( thread_ctrl : : state ( ) ! = thread_state : : aborting & & ! Emu . IsStopped ( ) )
{
2025-01-08 19:08:21 +01:00
u64 timeout = umax ;
2025-01-08 03:19:35 +01:00
if ( state & & ! video_conversion_in_progress )
2024-07-08 21:23:51 +02:00
{
2025-01-08 03:19:35 +01:00
constexpr u64 update_timeout_us = 100'000 ; // Update controllers at 10Hz
const u64 now_us = get_system_time ( ) ;
const u64 elapsed_us = now_us - last_update_us ;
2025-01-08 02:56:20 +01:00
2025-01-08 03:19:35 +01:00
if ( elapsed_us < update_timeout_us )
{
2025-01-08 19:08:21 +01:00
timeout = update_timeout_us - elapsed_us ;
2025-01-08 03:19:35 +01:00
}
else
{
2025-01-08 19:08:21 +01:00
timeout = update_timeout_us ;
2025-01-08 03:19:35 +01:00
last_update_us = now_us ;
2025-01-08 02:56:20 +01:00
2025-01-08 03:19:35 +01:00
std : : scoped_lock lock ( mtx ) ;
update_connections ( ) ;
update_calibration_status ( ) ;
2024-12-20 23:38:43 +01:00
}
2025-01-08 03:19:35 +01:00
}
2024-12-20 23:38:43 +01:00
2025-01-08 03:19:35 +01:00
if ( ! m_wake_up )
{
2025-01-08 19:08:21 +01:00
thread_ctrl : : wait_on ( m_wake_up , 0 , timeout ) ;
2025-01-08 03:19:35 +01:00
}
m_wake_up = 0 ;
if ( ! video_conversion_in_progress )
{
continue ;
2024-07-08 21:23:51 +02:00
}
if ( thread_ctrl : : state ( ) = = thread_state : : aborting | | Emu . IsStopped ( ) )
{
return ;
}
CellGemVideoConvertAttribute vc ;
{
std : : scoped_lock lock ( mtx ) ;
vc = vc_attribute ;
}
if ( g_cfg . io . camera ! = camera_handler : : qt )
{
video_conversion_in_progress = false ;
2025-01-08 03:19:35 +01:00
done ( ) ;
2024-07-08 21:23:51 +02:00
continue ;
2021-10-20 19:47:19 +02:00
}
2024-07-08 21:23:51 +02:00
const auto & shared_data = g_fxo - > get < gem_camera_shared > ( ) ;
2025-01-20 22:45:42 +01:00
if ( gem : : convert_image_format ( shared_data . format , vc . output_format , video_data_in , shared_data . width , shared_data . height , vc_attribute . video_data_out ? vc_attribute . video_data_out . get_ptr ( ) : nullptr , video_data_out_size , " cellGem " ) )
2024-07-08 21:23:51 +02:00
{
cellGem . trace ( " Converted video frame of format %s to %s " , shared_data . format . load ( ) , vc . output_format . get ( ) ) ;
2024-12-19 22:30:59 +01:00
if ( g_cfg . io . paint_move_spheres )
{
paint_spheres ( vc . output_format , shared_data . width , shared_data . height , vc_attribute . video_data_out ? vc_attribute . video_data_out . get_ptr ( ) : nullptr , video_data_out_size ) ;
}
2021-10-20 19:47:19 +02:00
}
video_conversion_in_progress = false ;
2025-01-08 03:19:35 +01:00
done ( ) ;
2021-10-20 19:47:19 +02:00
}
}
using gem_config = named_thread < gem_config_data > ;
2024-07-08 20:17:21 +02:00
class gem_tracker
{
public :
gem_tracker ( )
{
}
bool is_busy ( )
{
return m_busy ;
}
2025-01-08 19:08:21 +01:00
void wake_up_tracker ( )
2024-07-08 20:17:21 +02:00
{
2025-01-08 19:08:21 +01:00
m_wake_up_tracker . release ( 1 ) ;
m_wake_up_tracker . notify_one ( ) ;
2024-07-08 20:17:21 +02:00
}
2025-01-08 19:08:21 +01:00
void tracker_done ( )
2025-01-08 03:19:35 +01:00
{
2025-01-08 19:08:21 +01:00
m_tracker_done . release ( 1 ) ;
m_tracker_done . notify_one ( ) ;
2025-01-08 03:19:35 +01:00
}
2025-01-08 19:08:21 +01:00
bool wait_for_tracker_result ( ppu_thread & ppu )
2024-07-08 20:17:21 +02:00
{
2025-01-08 19:08:21 +01:00
if ( g_cfg . io . move ! = move_handler : : real )
{
m_tracker_done = 0 ;
return true ;
}
while ( ! m_tracker_done & & ! ppu . is_stopped ( ) )
2024-07-08 20:17:21 +02:00
{
2025-01-08 19:08:21 +01:00
thread_ctrl : : wait_on ( m_tracker_done , 0 ) ;
2024-07-08 20:17:21 +02:00
}
2025-01-08 19:08:21 +01:00
if ( ppu . is_stopped ( ) )
{
ppu . state + = cpu_flag : : again ;
return false ;
}
m_tracker_done = 0 ;
return true ;
2024-07-08 20:17:21 +02:00
}
bool set_image ( u32 addr )
{
if ( ! addr )
return false ;
auto & g_camera = g_fxo - > get < camera_thread > ( ) ;
std : : lock_guard lock ( g_camera . mutex ) ;
m_camera_info = g_camera . info ;
if ( m_camera_info . buffer . addr ( ) ! = addr & & m_camera_info . pbuf [ 0 ] . addr ( ) ! = addr & & m_camera_info . pbuf [ 1 ] . addr ( ) ! = addr )
{
2024-07-19 22:45:41 +02:00
cellGem . error ( " gem_tracker: unexpected image address: addr=0x%x, expected one of: 0x%x, 0x%x, 0x%x " , addr , m_camera_info . buffer . addr ( ) , m_camera_info . pbuf [ 0 ] . addr ( ) , m_camera_info . pbuf [ 1 ] . addr ( ) ) ;
2024-07-08 20:17:21 +02:00
return false ;
}
// Copy image data for further processing
const auto & [ width , height ] = get_video_resolution ( m_camera_info ) ;
const u32 expected_size = get_buffer_size_by_format ( m_camera_info . format , width , height ) ;
2024-07-19 22:45:41 +02:00
if ( ! m_camera_info . bytesize | | static_cast < u32 > ( m_camera_info . bytesize ) ! = expected_size )
2024-07-08 20:17:21 +02:00
{
2024-07-19 22:45:41 +02:00
cellGem . error ( " gem_tracker: unexpected image size: size=%d, expected=%d " , m_camera_info . bytesize , expected_size ) ;
2024-07-08 20:17:21 +02:00
return false ;
}
if ( ! m_camera_info . bytesize )
{
2024-07-19 22:45:41 +02:00
cellGem . error ( " gem_tracker: unexpected image size: %d " , m_camera_info . bytesize ) ;
2024-07-08 20:17:21 +02:00
return false ;
}
m_tracker . set_image_data ( m_camera_info . buffer . get_ptr ( ) , m_camera_info . bytesize , m_camera_info . width , m_camera_info . height , m_camera_info . format ) ;
return true ;
}
bool hue_is_trackable ( u32 hue )
{
if ( g_cfg . io . move ! = move_handler : : real )
{
2024-12-20 16:46:48 +01:00
return true ; // potentially true if less than 20 pixels have the hue
2024-07-08 20:17:21 +02:00
}
return hue < m_hues . size ( ) & & m_hues [ hue ] < 20 ; // potentially true if less than 20 pixels have the hue
}
2024-12-12 02:23:01 +01:00
ps_move_info get_info ( u32 gem_num )
2024-07-08 20:17:21 +02:00
{
2024-12-12 02:23:01 +01:00
std : : lock_guard lock ( mutex ) ;
2024-07-08 20:17:21 +02:00
return : : at32 ( m_info , gem_num ) ;
}
void operator ( ) ( )
{
if ( g_cfg . io . move ! = move_handler : : real )
{
return ;
}
if ( ! g_cfg_move . load ( ) )
{
cellGem . notice ( " Could not load PS Move config. Using defaults. " ) ;
}
auto & gem = g_fxo - > get < gem_config > ( ) ;
while ( thread_ctrl : : state ( ) ! = thread_state : : aborting )
{
// Check if we have a new frame
2025-01-08 19:08:21 +01:00
if ( ! m_wake_up_tracker )
2024-07-08 20:17:21 +02:00
{
2025-01-08 19:08:21 +01:00
thread_ctrl : : wait_on ( m_wake_up_tracker , 0 ) ;
m_wake_up_tracker . release ( 0 ) ;
2024-07-08 20:17:21 +02:00
if ( thread_ctrl : : state ( ) = = thread_state : : aborting )
{
break ;
}
}
m_busy . release ( true ) ;
// Update PS Move LED colors
{
std : : lock_guard lock ( pad : : g_pad_mutex ) ;
2025-01-20 22:55:21 +01:00
const auto handler = pad : : get_pad_thread ( ) ;
2024-07-08 20:17:21 +02:00
auto & handlers = handler - > get_handlers ( ) ;
2024-12-20 00:44:31 +01:00
if ( auto it = handlers . find ( pad_handler : : move ) ; it ! = handlers . end ( ) & & it - > second )
2024-07-08 20:17:21 +02:00
{
for ( auto & binding : it - > second - > bindings ( ) )
{
2025-04-05 21:50:45 +02:00
if ( ! binding . device )
continue ;
2024-07-08 20:17:21 +02:00
// last 4 out of 7 ports (6,5,4,3). index starts at 0
const s32 gem_num = std : : abs ( binding . device - > player_id - CELL_PAD_MAX_PORT_NUM ) - 1 ;
2025-04-05 21:50:45 +02:00
if ( gem_num < 0 | | gem_num > = CELL_GEM_MAX_NUM )
continue ;
2024-07-08 20:17:21 +02:00
binding . device - > color_override_active = true ;
2024-12-20 00:23:14 +01:00
if ( g_cfg . io . allow_move_hue_set_by_game )
{
const auto & controller = gem . controllers [ gem_num ] ;
binding . device - > color_override . r = static_cast < u8 > ( std : : clamp ( controller . sphere_rgb . r * 255.0f , 0.0f , 255.0f ) ) ;
binding . device - > color_override . g = static_cast < u8 > ( std : : clamp ( controller . sphere_rgb . g * 255.0f , 0.0f , 255.0f ) ) ;
binding . device - > color_override . b = static_cast < u8 > ( std : : clamp ( controller . sphere_rgb . b * 255.0f , 0.0f , 255.0f ) ) ;
}
else
{
const cfg_ps_move * config = : : at32 ( g_cfg_move . move , gem_num ) ;
binding . device - > color_override . r = config - > r . get ( ) ;
binding . device - > color_override . g = config - > g . get ( ) ;
binding . device - > color_override . b = config - > b . get ( ) ;
}
2024-07-08 20:17:21 +02:00
}
}
}
// Update tracker config
for ( u32 gem_num = 0 ; gem_num < CELL_GEM_MAX_NUM ; gem_num + + )
{
const auto & controller = gem . controllers [ gem_num ] ;
const cfg_ps_move * config = g_cfg_move . move [ gem_num ] ;
m_tracker . set_active ( gem_num , controller . enabled_tracking & & controller . status = = CELL_GEM_STATUS_READY ) ;
2024-12-20 00:23:14 +01:00
m_tracker . set_hue ( gem_num , g_cfg . io . allow_move_hue_set_by_game ? controller . hue : config - > hue ) ;
2024-07-08 20:17:21 +02:00
m_tracker . set_hue_threshold ( gem_num , config - > hue_threshold ) ;
m_tracker . set_saturation_threshold ( gem_num , config - > saturation_threshold ) ;
}
2024-12-21 12:12:14 +01:00
m_tracker . set_min_radius ( static_cast < f32 > ( g_cfg_move . min_radius ) / 100.0f ) ;
m_tracker . set_max_radius ( static_cast < f32 > ( g_cfg_move . max_radius ) / 100.0f ) ;
2024-07-08 20:17:21 +02:00
// Process camera image
m_tracker . process_image ( ) ;
// Update cellGem with results
{
std : : lock_guard lock ( mutex ) ;
m_hues = m_tracker . hues ( ) ;
m_info = m_tracker . info ( ) ;
for ( u32 gem_num = 0 ; gem_num < CELL_GEM_MAX_NUM ; gem_num + + )
{
const ps_move_info & info = m_info [ gem_num ] ;
auto & controller = gem . controllers [ gem_num ] ;
controller . radius_valid = info . valid ;
if ( info . valid )
{
// Only set new radius and distance if the radius is valid
controller . radius = info . radius ;
2024-11-28 22:39:50 +01:00
controller . distance_mm = info . distance_mm ;
2024-07-08 20:17:21 +02:00
}
}
}
// Notify that we are finished with this frame
2025-01-08 19:08:21 +01:00
tracker_done ( ) ;
2024-07-08 20:17:21 +02:00
m_busy . release ( false ) ;
}
}
static constexpr auto thread_name = " GemUpdateThread " sv ;
shared_mutex mutex ;
private :
2025-01-08 19:08:21 +01:00
atomic_t < u32 > m_wake_up_tracker = 0 ;
2025-01-10 20:09:34 +01:00
atomic_t < u32 > m_tracker_done = 0 ;
2024-07-08 20:17:21 +02:00
atomic_t < bool > m_busy = false ;
ps_move_tracker < false > m_tracker { } ;
CellCameraInfoEx m_camera_info { } ;
std : : array < u32 , 360 > m_hues { } ;
std : : array < ps_move_info , CELL_GEM_MAX_NUM > m_info { } ;
} ;
2018-03-02 17:51:21 +01:00
/**
* \ brief Verifies that a Move controller id is valid
* \ param gem_num Move controler ID to verify
* \ return True if the ID is valid , false otherwise
*/
2024-07-08 21:23:51 +02:00
static bool check_gem_num ( u32 gem_num )
2018-03-02 17:51:21 +01:00
{
2021-04-09 21:12:47 +02:00
return gem_num < CELL_GEM_MAX_NUM ;
2018-03-02 17:51:21 +01:00
}
2022-09-13 15:08:55 +02:00
static inline void draw_overlay_cursor ( u32 gem_num , const gem_config : : gem_controller & , s32 x_pos , s32 y_pos , s32 x_max , s32 y_max )
2022-07-06 22:08:50 +02:00
{
2024-02-13 20:16:41 +01:00
const s16 x = static_cast < s16 > ( x_pos / ( x_max / static_cast < f32 > ( rsx : : overlays : : overlay : : virtual_width ) ) ) ;
const s16 y = static_cast < s16 > ( y_pos / ( y_max / static_cast < f32 > ( rsx : : overlays : : overlay : : virtual_height ) ) ) ;
2022-07-06 22:08:50 +02:00
// Note: We shouldn't use sphere_rgb here. The game will set it to black in many cases.
const gem_config_data : : gem_color & rgb = gem_config_data : : gem_color : : get_default_color ( gem_num ) ;
2025-04-05 21:50:45 +02:00
const color4f color = { rgb . r , rgb . g , rgb . b , 0.85f } ;
2022-07-06 22:08:50 +02:00
rsx : : overlays : : set_cursor ( rsx : : overlays : : cursor_offset : : cell_gem + gem_num , x , y , color , 2'000'000 , false ) ;
}
2025-01-17 00:32:37 +01:00
static inline void pos_to_gem_image_state ( u32 gem_num , gem_config : : gem_controller & controller , vm : : ptr < CellGemImageState > & gem_image_state , s32 x_pos , s32 y_pos , s32 x_max , s32 y_max )
2022-07-06 19:32:19 +02:00
{
const auto & shared_data = g_fxo - > get < gem_camera_shared > ( ) ;
2025-04-05 21:50:45 +02:00
if ( x_max < = 0 )
x_max = shared_data . width ;
if ( y_max < = 0 )
y_max = shared_data . height ;
2022-07-06 19:32:19 +02:00
2025-01-22 21:49:13 +01:00
// Move the cursor out of the screen if we're at the screen border (Time Crisis 4 needs this)
2025-04-05 21:50:45 +02:00
if ( x_pos < = 0 )
x_pos - = x_max / 10 ;
else if ( x_pos > = x_max )
x_pos + = x_max / 10 ;
if ( y_pos < = 0 )
y_pos - = y_max / 10 ;
else if ( y_pos > = y_max )
y_pos + = y_max / 10 ;
2025-01-22 21:49:13 +01:00
2022-07-06 19:32:19 +02:00
const f32 scaling_width = x_max / static_cast < f32 > ( shared_data . width ) ;
const f32 scaling_height = y_max / static_cast < f32 > ( shared_data . height ) ;
const f32 mmPerPixel = CELL_GEM_SPHERE_RADIUS_MM / controller . radius ;
// Image coordinates in pixels
const f32 image_x = static_cast < f32 > ( x_pos ) / scaling_width ;
const f32 image_y = static_cast < f32 > ( y_pos ) / scaling_height ;
// Centered image coordinates in pixels
const f32 centered_x = image_x - ( shared_data . width / 2.f ) ;
const f32 centered_y = ( shared_data . height / 2.f ) - image_y ; // Image coordinates increase downwards, so we have to invert this
// Camera coordinates in mm (centered, so it's the same as world coordinates)
const f32 camera_x = centered_x * mmPerPixel ;
const f32 camera_y = centered_y * mmPerPixel ;
// Image coordinates in pixels
gem_image_state - > u = image_x ;
gem_image_state - > v = image_y ;
// Projected camera coordinates in mm
2024-11-28 22:39:50 +01:00
gem_image_state - > projectionx = camera_x / controller . distance_mm ;
gem_image_state - > projectiony = camera_y / controller . distance_mm ;
2022-07-06 22:08:50 +02:00
2025-01-17 00:32:37 +01:00
// Update visibility for fake handlers
if ( g_cfg . io . move ! = move_handler : : real )
{
// Let's say the sphere is not visible if the position is at the edge of the screen
controller . radius_valid = x_pos > 0 & & x_pos < x_max & & y_pos > 0 & & y_pos < y_max ;
}
2022-07-06 22:08:50 +02:00
if ( g_cfg . io . show_move_cursor )
{
draw_overlay_cursor ( gem_num , controller , x_pos , y_pos , x_max , y_max ) ;
}
2024-12-19 22:30:59 +01:00
if ( g_cfg . io . paint_move_spheres )
{
: : at32 ( gem : : positions , gem_num ) . set_position ( image_x , image_y ) ;
}
2022-07-06 19:32:19 +02:00
}
2024-11-29 05:38:44 +01:00
static inline void pos_to_gem_state ( u32 gem_num , gem_config : : gem_controller & controller , vm : : ptr < CellGemState > & gem_state , s32 x_pos , s32 y_pos , s32 x_max , s32 y_max , const ps_move_data & move_data )
2022-07-06 19:32:19 +02:00
{
const auto & shared_data = g_fxo - > get < gem_camera_shared > ( ) ;
2025-04-05 21:50:45 +02:00
if ( x_max < = 0 )
x_max = shared_data . width ;
if ( y_max < = 0 )
y_max = shared_data . height ;
2022-07-06 19:32:19 +02:00
2025-01-22 21:49:13 +01:00
// Move the cursor out of the screen if we're at the screen border (Time Crisis 4 needs this)
2025-04-05 21:50:45 +02:00
if ( x_pos < = 0 )
x_pos - = x_max / 10 ;
else if ( x_pos > = x_max )
x_pos + = x_max / 10 ;
if ( y_pos < = 0 )
y_pos - = y_max / 10 ;
else if ( y_pos > = y_max )
y_pos + = y_max / 10 ;
2025-01-22 21:49:13 +01:00
2022-07-06 19:32:19 +02:00
const f32 scaling_width = x_max / static_cast < f32 > ( shared_data . width ) ;
const f32 scaling_height = y_max / static_cast < f32 > ( shared_data . height ) ;
const f32 mmPerPixel = CELL_GEM_SPHERE_RADIUS_MM / controller . radius ;
// Image coordinates in pixels
const f32 image_x = static_cast < f32 > ( x_pos ) / scaling_width ;
const f32 image_y = static_cast < f32 > ( y_pos ) / scaling_height ;
2024-12-12 23:19:09 +01:00
// Half of the camera image
const f32 half_width = shared_data . width / 2.f ;
const f32 half_height = shared_data . height / 2.f ;
2022-07-06 19:32:19 +02:00
// Centered image coordinates in pixels
2024-12-12 23:19:09 +01:00
const f32 centered_x = image_x - half_width ;
const f32 centered_y = half_height - image_y ; // Image coordinates increase downwards, so we have to invert this
2022-07-06 19:32:19 +02:00
// Camera coordinates in mm (centered, so it's the same as world coordinates)
const f32 camera_x = centered_x * mmPerPixel ;
const f32 camera_y = centered_y * mmPerPixel ;
// World coordinates in mm
gem_state - > pos [ 0 ] = camera_x ;
gem_state - > pos [ 1 ] = camera_y ;
2024-11-28 22:39:50 +01:00
gem_state - > pos [ 2 ] = controller . distance_mm ;
2022-07-06 19:32:19 +02:00
gem_state - > pos [ 3 ] = 0.f ;
// TODO: calculate handle position based on our world coordinate and the angles
gem_state - > handle_pos [ 0 ] = camera_x ;
gem_state - > handle_pos [ 1 ] = camera_y ;
2024-11-28 22:39:50 +01:00
gem_state - > handle_pos [ 2 ] = controller . distance_mm + 10.0f ;
2022-07-06 19:32:19 +02:00
gem_state - > handle_pos [ 3 ] = 0.f ;
2022-07-06 22:08:50 +02:00
2024-11-29 05:38:44 +01:00
// Calculate orientation
2025-01-08 01:22:50 +01:00
if ( g_cfg . io . move = = move_handler : : real | | ( g_cfg . io . move = = move_handler : : fake & & move_data . orientation_enabled ) )
2024-11-29 05:38:44 +01:00
{
2024-12-20 23:38:43 +01:00
gem_state - > quat [ 0 ] = move_data . quaternion [ 0 ] ; // x
gem_state - > quat [ 1 ] = move_data . quaternion [ 1 ] ; // y
gem_state - > quat [ 2 ] = move_data . quaternion [ 2 ] ; // z
gem_state - > quat [ 3 ] = move_data . quaternion [ 3 ] ; // w
2024-11-29 05:38:44 +01:00
}
else
{
2024-12-19 16:03:34 +01:00
const f32 max_angle_per_side_h = g_cfg . io . fake_move_rotation_cone_h / 2.0f ;
const f32 max_angle_per_side_v = g_cfg . io . fake_move_rotation_cone_v / 2.0f ;
2025-01-08 01:22:50 +01:00
const f32 roll = - PadHandlerBase : : degree_to_rad ( ( image_y - half_height ) / half_height * max_angle_per_side_v ) ; // This is actually the pitch
2025-04-05 21:50:45 +02:00
const f32 pitch = - PadHandlerBase : : degree_to_rad ( ( image_x - half_width ) / half_width * max_angle_per_side_h ) ; // This is actually the yaw
2025-01-08 01:22:50 +01:00
const f32 yaw = PadHandlerBase : : degree_to_rad ( 0.0f ) ;
2024-12-12 23:19:09 +01:00
const f32 cr = std : : cos ( roll * 0.5f ) ;
const f32 sr = std : : sin ( roll * 0.5f ) ;
const f32 cp = std : : cos ( pitch * 0.5f ) ;
const f32 sp = std : : sin ( pitch * 0.5f ) ;
const f32 cy = std : : cos ( yaw * 0.5f ) ;
const f32 sy = std : : sin ( yaw * 0.5f ) ;
const f32 q_x = sr * cp * cy - cr * sp * sy ;
const f32 q_y = cr * sp * cy + sr * cp * sy ;
const f32 q_z = cr * cp * sy - sr * sp * cy ;
const f32 q_w = cr * cp * cy + sr * sp * sy ;
gem_state - > quat [ 0 ] = q_x ;
gem_state - > quat [ 1 ] = q_y ;
gem_state - > quat [ 2 ] = q_z ;
gem_state - > quat [ 3 ] = q_w ;
2024-11-29 05:38:44 +01:00
}
2025-01-17 00:32:37 +01:00
// Update visibility for fake handlers
if ( g_cfg . io . move ! = move_handler : : real )
{
// Let's say the sphere is not visible if the position is at the edge of the screen
controller . radius_valid = x_pos > 0 & & x_pos < x_max & & y_pos > 0 & & y_pos < y_max ;
}
2022-07-06 22:08:50 +02:00
if ( g_cfg . io . show_move_cursor )
{
draw_overlay_cursor ( gem_num , controller , x_pos , y_pos , x_max , y_max ) ;
}
2024-12-19 22:30:59 +01:00
if ( g_cfg . io . paint_move_spheres )
{
: : at32 ( gem : : positions , gem_num ) . set_position ( image_x , image_y ) ;
}
2022-07-06 19:32:19 +02:00
}
2022-07-05 21:47:05 +02:00
extern bool is_input_allowed ( ) ;
2018-03-02 17:51:21 +01:00
/**
* \ brief Maps Move controller data ( digital buttons , and analog Trigger data ) to DS3 pad input .
* Unavoidably buttons conflict with DS3 mappings , which is problematic for some games .
2024-01-31 22:49:50 +01:00
* \ param gem_num gem index to use
2018-03-02 17:51:21 +01:00
* \ param digital_buttons Bitmask filled with CELL_GEM_CTRL_ * values
2025-01-22 21:49:13 +01:00
* \ param analog_t Analog value of Move ' s Trigger .
2024-01-31 22:49:50 +01:00
* \ return true on success , false if controller is disconnected
2018-03-02 17:51:21 +01:00
*/
2024-01-31 22:49:50 +01:00
static void ds3_input_to_pad ( const u32 gem_num , be_t < u16 > & digital_buttons , be_t < u16 > & analog_t )
2018-03-02 17:51:21 +01:00
{
2022-07-05 22:49:20 +02:00
digital_buttons = 0 ;
analog_t = 0 ;
2025-01-14 03:03:18 +01:00
if ( ! is_input_allowed ( ) | | input : : g_pads_intercepted ) // Let's intercept the PS Move just like a pad
2022-07-05 21:47:05 +02:00
{
2022-07-05 22:49:20 +02:00
return ;
2022-07-05 21:47:05 +02:00
}
2021-08-08 18:50:37 +02:00
std : : lock_guard lock ( pad : : g_pad_mutex ) ;
2018-12-17 19:13:35 +01:00
2025-01-20 22:55:21 +01:00
const auto handler = pad : : get_pad_thread ( ) ;
2024-01-31 22:49:50 +01:00
const auto & pad = : : at32 ( handler - > GetPads ( ) , pad_num ( gem_num ) ) ;
2018-03-02 17:51:21 +01:00
2018-12-13 06:24:17 +01:00
if ( ! ( pad - > m_port_status & CELL_PAD_STATUS_CONNECTED ) )
2022-07-05 22:49:20 +02:00
{
return ;
}
2018-12-13 06:24:17 +01:00
2025-01-09 21:10:28 +01:00
const auto handle_input = [ & ] ( gem_btn btn , pad_button /*pad_btn*/ , u16 value , bool pressed , bool & /*abort*/ )
2024-07-08 20:17:21 +02:00
{
if ( ! pressed )
return ;
switch ( btn )
2022-07-05 22:49:20 +02:00
{
2024-07-08 20:17:21 +02:00
case gem_btn : : start :
digital_buttons | = CELL_GEM_CTRL_START ;
break ;
case gem_btn : : select :
digital_buttons | = CELL_GEM_CTRL_SELECT ;
break ;
case gem_btn : : square :
digital_buttons | = CELL_GEM_CTRL_SQUARE ;
break ;
case gem_btn : : cross :
digital_buttons | = CELL_GEM_CTRL_CROSS ;
break ;
case gem_btn : : circle :
digital_buttons | = CELL_GEM_CTRL_CIRCLE ;
break ;
case gem_btn : : triangle :
digital_buttons | = CELL_GEM_CTRL_TRIANGLE ;
break ;
case gem_btn : : move :
digital_buttons | = CELL_GEM_CTRL_MOVE ;
break ;
case gem_btn : : t :
digital_buttons | = CELL_GEM_CTRL_T ;
analog_t = std : : max < u16 > ( analog_t , value ) ;
break ;
2025-01-09 21:10:28 +01:00
default :
2024-07-08 20:17:21 +02:00
break ;
}
} ;
2023-05-22 20:49:22 +02:00
2024-07-08 20:17:21 +02:00
if ( g_cfg . io . move = = move_handler : : real )
{
: : at32 ( g_cfg_gem_real . players , gem_num ) - > handle_input ( pad , true , handle_input ) ;
}
else
{
: : at32 ( g_cfg_gem_fake . players , gem_num ) - > handle_input ( pad , true , handle_input ) ;
}
2018-03-02 17:51:21 +01:00
}
2022-07-06 19:32:19 +02:00
constexpr u16 ds3_max_x = 255 ;
constexpr u16 ds3_max_y = 255 ;
2024-01-31 22:49:50 +01:00
static inline void ds3_get_stick_values ( u32 gem_num , const std : : shared_ptr < Pad > & pad , s32 & x_pos , s32 & y_pos )
2022-07-06 19:32:19 +02:00
{
x_pos = 0 ;
y_pos = 0 ;
2024-07-08 20:17:21 +02:00
const auto & cfg = : : at32 ( g_cfg_gem_fake . players , gem_num ) ;
2025-01-09 21:10:28 +01:00
cfg - > handle_input ( pad , true , [ & ] ( gem_btn btn , pad_button /*pad_btn*/ , u16 value , bool pressed , bool & /*abort*/ )
2025-01-22 21:49:13 +01:00
{
2025-04-05 21:50:45 +02:00
if ( ! pressed )
return ;
switch ( btn )
{
case gem_btn : : x_axis : x_pos = value ; break ;
case gem_btn : : y_axis : y_pos = value ; break ;
default : break ;
}
} ) ;
2022-07-06 19:32:19 +02:00
}
2022-11-27 12:20:05 +01:00
template < typename T >
2024-11-29 05:38:44 +01:00
static void ds3_pos_to_gem_state ( u32 gem_num , gem_config : : gem_controller & controller , T & gem_state )
2022-07-06 19:32:19 +02:00
{
2025-01-14 03:03:18 +01:00
if ( ! gem_state | | ! is_input_allowed ( ) | | input : : g_pads_intercepted ) // Let's intercept the PS Move just like a pad
2022-07-06 19:32:19 +02:00
{
return ;
}
std : : lock_guard lock ( pad : : g_pad_mutex ) ;
2025-01-20 22:55:21 +01:00
const auto handler = pad : : get_pad_thread ( ) ;
2024-01-31 22:49:50 +01:00
const auto & pad = : : at32 ( handler - > GetPads ( ) , pad_num ( gem_num ) ) ;
2022-07-06 19:32:19 +02:00
if ( ! ( pad - > m_port_status & CELL_PAD_STATUS_CONNECTED ) )
{
return ;
}
s32 ds3_pos_x , ds3_pos_y ;
2024-01-31 22:49:50 +01:00
ds3_get_stick_values ( gem_num , pad , ds3_pos_x , ds3_pos_y ) ;
2022-07-06 19:32:19 +02:00
2024-03-20 17:16:49 +01:00
if constexpr ( std : : is_same_v < T , vm : : ptr < CellGemState > > )
2022-07-06 19:32:19 +02:00
{
2025-01-08 01:22:50 +01:00
pos_to_gem_state ( gem_num , controller , gem_state , ds3_pos_x , ds3_pos_y , ds3_max_x , ds3_max_y , pad - > move_data ) ;
2022-07-06 19:32:19 +02:00
}
2024-03-20 17:16:49 +01:00
else if constexpr ( std : : is_same_v < T , vm : : ptr < CellGemImageState > > )
2022-07-06 19:32:19 +02:00
{
2024-01-31 22:49:50 +01:00
pos_to_gem_image_state ( gem_num , controller , gem_state , ds3_pos_x , ds3_pos_y , ds3_max_x , ds3_max_y ) ;
2022-07-06 19:32:19 +02:00
}
}
2024-07-08 20:17:21 +02:00
template < typename T >
2024-11-29 05:38:44 +01:00
static void ps_move_pos_to_gem_state ( u32 gem_num , gem_config : : gem_controller & controller , T & gem_state )
2024-07-08 20:17:21 +02:00
{
2025-01-14 03:03:18 +01:00
if ( ! gem_state | | ! is_input_allowed ( ) | | input : : g_pads_intercepted ) // Let's intercept the PS Move just like a pad
2024-07-08 20:17:21 +02:00
{
return ;
}
std : : lock_guard lock ( pad : : g_pad_mutex ) ;
2025-01-20 22:55:21 +01:00
const auto handler = pad : : get_pad_thread ( ) ;
2024-07-08 20:17:21 +02:00
const auto & pad = : : at32 ( handler - > GetPads ( ) , pad_num ( gem_num ) ) ;
if ( pad - > m_pad_handler ! = pad_handler : : move | | ! ( pad - > m_port_status & CELL_PAD_STATUS_CONNECTED ) )
{
return ;
}
2024-12-12 02:23:01 +01:00
auto & tracker = g_fxo - > get < named_thread < gem_tracker > > ( ) ;
const ps_move_info info = tracker . get_info ( gem_num ) ;
2024-07-08 20:17:21 +02:00
if constexpr ( std : : is_same_v < T , vm : : ptr < CellGemState > > )
{
2024-11-27 22:16:04 +01:00
gem_state - > temperature = pad - > move_data . temperature ;
gem_state - > accel [ 0 ] = pad - > move_data . accelerometer_x * 1000 ; // linear velocity in mm/s²
gem_state - > accel [ 1 ] = pad - > move_data . accelerometer_y * 1000 ; // linear velocity in mm/s²
gem_state - > accel [ 2 ] = pad - > move_data . accelerometer_z * 1000 ; // linear velocity in mm/s²
2024-11-29 05:38:44 +01:00
pos_to_gem_state ( gem_num , controller , gem_state , info . x_pos , info . y_pos , info . x_max , info . y_max , pad - > move_data ) ;
2024-07-08 20:17:21 +02:00
}
else if constexpr ( std : : is_same_v < T , vm : : ptr < CellGemImageState > > )
{
pos_to_gem_image_state ( gem_num , controller , gem_state , info . x_pos , info . y_pos , info . x_max , info . y_max ) ;
}
}
2018-03-02 17:51:21 +01:00
/**
2021-07-29 22:34:21 +02:00
* \ brief Maps external Move controller data to DS3 input . ( This can be input from any physical pad , not just the DS3 )
2019-12-10 20:17:39 +01:00
* Implementation detail : CellGemExtPortData ' s digital / analog fields map the same way as
* libPad , so no translation is needed .
2024-01-31 22:49:50 +01:00
* \ param gem_num gem index to use
2018-03-02 17:51:21 +01:00
* \ param ext External data to modify
2024-01-31 22:49:50 +01:00
* \ return true on success , false if controller is disconnected
2018-03-02 17:51:21 +01:00
*/
2024-07-08 20:17:21 +02:00
static void ds3_input_to_ext ( u32 gem_num , gem_config : : gem_controller & controller , CellGemExtPortData & ext )
2018-03-02 17:51:21 +01:00
{
2022-07-05 22:49:20 +02:00
ext = { } ;
2025-01-14 03:03:18 +01:00
if ( ! is_input_allowed ( ) | | input : : g_pads_intercepted ) // Let's intercept the PS Move just like a pad
2022-07-05 21:47:05 +02:00
{
2022-07-05 22:49:20 +02:00
return ;
2022-07-05 21:47:05 +02:00
}
2021-08-08 18:50:37 +02:00
std : : lock_guard lock ( pad : : g_pad_mutex ) ;
2018-12-17 19:13:35 +01:00
2025-01-20 22:55:21 +01:00
const auto handler = pad : : get_pad_thread ( ) ;
2024-01-31 22:49:50 +01:00
const auto & pad = : : at32 ( handler - > GetPads ( ) , pad_num ( gem_num ) ) ;
2018-03-02 17:51:21 +01:00
2018-12-13 06:24:17 +01:00
if ( ! ( pad - > m_port_status & CELL_PAD_STATUS_CONNECTED ) )
2022-07-05 22:49:20 +02:00
{
return ;
}
2018-03-02 17:51:21 +01:00
2024-07-08 20:17:21 +02:00
const auto & move_data = pad - > move_data ;
controller . ext_status = move_data . external_device_connected ? CELL_GEM_EXT_CONNECTED : 0 ; // TODO: | CELL_GEM_EXT_EXT0 | CELL_GEM_EXT_EXT1
controller . ext_id = move_data . external_device_connected ? move_data . external_device_id : 0 ;
ext . status = controller . ext_status ;
for ( const AnalogStick & stick : pad - > m_sticks )
{
switch ( stick . m_offset )
{
case CELL_PAD_BTN_OFFSET_ANALOG_LEFT_X : ext . analog_left_x = stick . m_value ; break ;
case CELL_PAD_BTN_OFFSET_ANALOG_LEFT_Y : ext . analog_left_y = stick . m_value ; break ;
case CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X : ext . analog_right_x = stick . m_value ; break ;
case CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_Y : ext . analog_right_y = stick . m_value ; break ;
default : break ;
}
}
for ( const Button & button : pad - > m_buttons )
{
if ( ! button . m_pressed )
continue ;
switch ( button . m_offset )
{
case CELL_PAD_BTN_OFFSET_DIGITAL1 : ext . digital1 | = button . m_outKeyCode ; break ;
case CELL_PAD_BTN_OFFSET_DIGITAL2 : ext . digital2 | = button . m_outKeyCode ; break ;
default : break ;
}
}
2018-03-02 17:51:21 +01:00
2024-07-08 20:17:21 +02:00
if ( ! move_data . external_device_connected )
2021-07-29 22:33:31 +02:00
{
2024-07-08 20:17:21 +02:00
return ;
2021-07-29 22:33:31 +02:00
}
2024-07-08 20:17:21 +02:00
// The sharpshooter only sets the custom bytes as follows:
// custom[0] (0x01): Firing mode selector is in position 1.
// custom[0] (0x02): Firing mode selector is in position 2.
// custom[0] (0x04): Firing mode selector is in position 3.
// custom[0] (0x40): T button trigger is pressed.
// custom[0] (0x80): RL reload button is pressed.
// The racing wheel sets the custom bytes as follows:
// custom[0] 0-255: Throttle
// custom[1] 0-255: L2
// custom[2] 0-255: R2
// custom[3] (0x01): Left paddle
// custom[3] (0x02): Right paddle
std : : memcpy ( ext . custom , move_data . external_device_data . data ( ) , 5 ) ;
2018-03-02 17:51:21 +01:00
}
2018-01-24 01:27:25 +01:00
/**
2018-07-23 19:57:40 +02:00
* \ brief Maps Move controller data ( digital buttons , and analog Trigger data ) to mouse input .
* \ param mouse_no Mouse index number to use
* \ param digital_buttons Bitmask filled with CELL_GEM_CTRL_ * values
* \ param analog_t Analog value of Move ' s Trigger .
2020-03-23 12:39:10 +01:00
* \ return true on success , false if mouse_no is invalid
2018-07-23 19:57:40 +02:00
*/
2024-07-08 20:17:21 +02:00
static bool mouse_input_to_pad ( u32 mouse_no , be_t < u16 > & digital_buttons , be_t < u16 > & analog_t )
2018-01-24 01:27:25 +01:00
{
2022-07-05 22:49:20 +02:00
digital_buttons = 0 ;
analog_t = 0 ;
2025-01-14 03:03:18 +01:00
if ( ! is_input_allowed ( ) | | input : : g_pads_intercepted ) // Let's intercept the PS Move just like a pad
2022-07-05 21:47:05 +02:00
{
return false ;
}
2021-03-02 12:59:19 +01:00
auto & handler = g_fxo - > get < MouseHandlerBase > ( ) ;
2018-01-24 01:27:25 +01:00
2021-03-02 12:59:19 +01:00
std : : scoped_lock lock ( handler . mutex ) ;
2018-07-23 19:57:40 +02:00
2022-07-06 14:35:50 +02:00
// Make sure that the mouse handler is initialized
handler . Init ( std : : min < u32 > ( g_fxo - > get < gem_config > ( ) . attribute . max_connect , CELL_GEM_MAX_NUM ) ) ;
2021-03-02 12:59:19 +01:00
if ( mouse_no > = handler . GetMice ( ) . size ( ) )
2018-01-24 01:27:25 +01:00
{
return false ;
}
2024-06-06 23:06:34 +02:00
const Mouse & mouse_data = : : at32 ( handler . GetMice ( ) , mouse_no ) ;
2025-01-09 21:10:28 +01:00
auto & cfg = : : at32 ( g_cfg_gem_mouse . players , mouse_no ) ;
bool combo_active = false ;
std : : set < pad_button > combos ;
static const std : : unordered_map < gem_btn , u16 > btn_map =
2025-04-05 21:50:45 +02:00
{
{ gem_btn : : start , CELL_GEM_CTRL_START } ,
{ gem_btn : : select , CELL_GEM_CTRL_SELECT } ,
{ gem_btn : : triangle , CELL_GEM_CTRL_TRIANGLE } ,
{ gem_btn : : circle , CELL_GEM_CTRL_CIRCLE } ,
{ gem_btn : : cross , CELL_GEM_CTRL_CROSS } ,
{ gem_btn : : square , CELL_GEM_CTRL_SQUARE } ,
{ gem_btn : : move , CELL_GEM_CTRL_MOVE } ,
{ gem_btn : : t , CELL_GEM_CTRL_T } ,
{ gem_btn : : combo_start , CELL_GEM_CTRL_START } ,
{ gem_btn : : combo_select , CELL_GEM_CTRL_SELECT } ,
{ gem_btn : : combo_triangle , CELL_GEM_CTRL_TRIANGLE } ,
{ gem_btn : : combo_circle , CELL_GEM_CTRL_CIRCLE } ,
{ gem_btn : : combo_cross , CELL_GEM_CTRL_CROSS } ,
{ gem_btn : : combo_square , CELL_GEM_CTRL_SQUARE } ,
{ gem_btn : : combo_move , CELL_GEM_CTRL_MOVE } ,
{ gem_btn : : combo_t , CELL_GEM_CTRL_T } ,
} ;
2018-01-24 01:27:25 +01:00
2025-01-09 21:10:28 +01:00
// Check combo button first
cfg - > handle_input ( mouse_data , [ & combo_active ] ( gem_btn btn , pad_button /*pad_btn*/ , u16 /*value*/ , bool pressed , bool & abort )
{
2025-04-05 21:50:45 +02:00
if ( pressed & & btn = = gem_btn : : combo )
{
combo_active = true ;
abort = true ;
}
} ) ;
2024-08-02 23:59:51 +02:00
2025-01-09 21:10:28 +01:00
// Check combos
if ( combo_active )
{
cfg - > handle_input ( mouse_data , [ & digital_buttons , & combos ] ( gem_btn btn , pad_button pad_btn , u16 /*value*/ , bool pressed , bool & /*abort*/ )
2025-04-05 21:50:45 +02:00
{
if ( ! pressed )
return ;
switch ( btn )
{
case gem_btn : : combo_start :
case gem_btn : : combo_select :
case gem_btn : : combo_triangle :
case gem_btn : : combo_circle :
case gem_btn : : combo_cross :
case gem_btn : : combo_square :
case gem_btn : : combo_move :
case gem_btn : : combo_t :
digital_buttons | = : : at32 ( btn_map , btn ) ;
combos . insert ( pad_btn ) ;
break ;
default :
break ;
}
} ) ;
}
// Check normal buttons
cfg - > handle_input ( mouse_data , [ & digital_buttons , & combos ] ( gem_btn btn , pad_button pad_btn , u16 /*value*/ , bool pressed , bool & /*abort*/ )
2025-01-09 21:10:28 +01:00
{
if ( ! pressed )
return ;
2024-08-08 19:49:11 +02:00
2025-01-09 21:10:28 +01:00
switch ( btn )
{
2025-04-05 21:50:45 +02:00
case gem_btn : : start :
case gem_btn : : select :
case gem_btn : : square :
case gem_btn : : cross :
case gem_btn : : circle :
case gem_btn : : triangle :
case gem_btn : : move :
case gem_btn : : t :
// Ignore this gem_btn if the same pad_button was already used in a combo
if ( ! combos . contains ( pad_btn ) )
{
digital_buttons | = : : at32 ( btn_map , btn ) ;
}
2025-01-09 21:10:28 +01:00
break ;
default :
break ;
}
} ) ;
2024-08-08 19:49:11 +02:00
2025-01-22 21:08:06 +01:00
analog_t = ( digital_buttons & CELL_GEM_CTRL_T ) ? 255 : 0 ;
2018-01-24 01:27:25 +01:00
2018-07-23 19:57:40 +02:00
return true ;
}
2022-11-27 12:20:05 +01:00
template < typename T >
2024-11-29 05:38:44 +01:00
static void mouse_pos_to_gem_state ( u32 mouse_no , gem_config : : gem_controller & controller , T & gem_state )
2018-07-23 19:57:40 +02:00
{
2025-01-14 03:03:18 +01:00
if ( ! gem_state | | ! is_input_allowed ( ) | | input : : g_pads_intercepted ) // Let's intercept the PS Move just like a pad
2022-07-05 21:47:05 +02:00
{
return ;
}
2021-03-02 12:59:19 +01:00
auto & handler = g_fxo - > get < MouseHandlerBase > ( ) ;
2018-01-24 01:27:25 +01:00
2021-03-02 12:59:19 +01:00
std : : scoped_lock lock ( handler . mutex ) ;
2018-07-23 19:57:40 +02:00
2022-07-06 14:35:50 +02:00
// Make sure that the mouse handler is initialized
handler . Init ( std : : min < u32 > ( g_fxo - > get < gem_config > ( ) . attribute . max_connect , CELL_GEM_MAX_NUM ) ) ;
2022-07-05 21:47:05 +02:00
if ( mouse_no > = handler . GetMice ( ) . size ( ) )
2019-12-10 20:17:39 +01:00
{
2022-07-05 21:47:05 +02:00
return ;
2019-12-10 20:17:39 +01:00
}
2018-07-23 19:57:40 +02:00
2022-09-19 14:57:51 +02:00
const auto & mouse = : : at32 ( handler . GetMice ( ) , mouse_no ) ;
2021-07-30 15:55:50 +02:00
2024-03-20 17:16:49 +01:00
if constexpr ( std : : is_same_v < T , vm : : ptr < CellGemState > > )
2022-07-05 21:47:05 +02:00
{
2024-11-29 05:38:44 +01:00
pos_to_gem_state ( mouse_no , controller , gem_state , mouse . x_pos , mouse . y_pos , mouse . x_max , mouse . y_max , { } ) ;
2022-07-05 21:47:05 +02:00
}
2024-03-20 17:16:49 +01:00
else if constexpr ( std : : is_same_v < T , vm : : ptr < CellGemImageState > > )
2018-07-23 19:57:40 +02:00
{
2022-11-27 12:20:05 +01:00
pos_to_gem_image_state ( mouse_no , controller , gem_state , mouse . x_pos , mouse . y_pos , mouse . x_max , mouse . y_max ) ;
2018-01-24 01:27:25 +01:00
}
}
2022-11-27 12:20:05 +01:00
# ifdef HAVE_LIBEVDEV
2024-07-08 20:17:21 +02:00
static bool gun_input_to_pad ( u32 gem_no , be_t < u16 > & digital_buttons , be_t < u16 > & analog_t )
2022-11-13 18:10:09 +01:00
{
2022-11-27 12:20:05 +01:00
digital_buttons = 0 ;
analog_t = 0 ;
2022-11-13 18:10:09 +01:00
2022-11-27 12:20:05 +01:00
if ( ! is_input_allowed ( ) )
return false ;
2022-11-13 18:10:09 +01:00
2022-11-27 12:20:05 +01:00
gun_thread & gun = g_fxo - > get < gun_thread > ( ) ;
std : : scoped_lock lock ( gun . handler . mutex ) ;
2022-11-13 18:10:09 +01:00
2022-11-27 12:20:05 +01:00
if ( gun . handler . get_button ( gem_no , gun_button : : btn_left ) = = 1 )
digital_buttons | = CELL_GEM_CTRL_T ;
2022-11-13 18:10:09 +01:00
2022-11-27 12:20:05 +01:00
if ( gun . handler . get_button ( gem_no , gun_button : : btn_right ) = = 1 )
digital_buttons | = CELL_GEM_CTRL_MOVE ;
2022-11-13 18:10:09 +01:00
2022-11-27 12:20:05 +01:00
if ( gun . handler . get_button ( gem_no , gun_button : : btn_middle ) = = 1 )
digital_buttons | = CELL_GEM_CTRL_START ;
2022-11-13 18:10:09 +01:00
2023-02-04 10:38:52 +01:00
if ( gun . handler . get_button ( gem_no , gun_button : : btn_1 ) = = 1 )
2022-11-27 12:20:05 +01:00
digital_buttons | = CELL_GEM_CTRL_CROSS ;
2022-11-13 18:10:09 +01:00
2023-02-04 10:38:52 +01:00
if ( gun . handler . get_button ( gem_no , gun_button : : btn_2 ) = = 1 )
2022-11-27 12:20:05 +01:00
digital_buttons | = CELL_GEM_CTRL_CIRCLE ;
2022-11-13 18:10:09 +01:00
2023-02-04 10:38:52 +01:00
if ( gun . handler . get_button ( gem_no , gun_button : : btn_3 ) = = 1 )
digital_buttons | = CELL_GEM_CTRL_SELECT ;
2022-11-13 18:10:09 +01:00
2022-11-27 12:20:05 +01:00
if ( gun . handler . get_button ( gem_no , gun_button : : btn_5 ) = = 1 )
digital_buttons | = CELL_GEM_CTRL_TRIANGLE ;
2022-11-13 18:10:09 +01:00
2023-02-04 10:38:52 +01:00
if ( gun . handler . get_button ( gem_no , gun_button : : btn_6 ) = = 1 )
digital_buttons | = CELL_GEM_CTRL_SQUARE ;
2025-01-22 21:08:06 +01:00
analog_t = gun . handler . get_button ( gem_no , gun_button : : btn_left ) ? 255 : 0 ;
2023-02-04 10:38:52 +01:00
2022-11-27 12:20:05 +01:00
return true ;
2022-11-13 18:10:09 +01:00
}
2022-11-27 12:20:05 +01:00
template < typename T >
2024-11-29 05:38:44 +01:00
static void gun_pos_to_gem_state ( u32 gem_no , gem_config : : gem_controller & controller , T & gem_state )
2022-11-13 18:10:09 +01:00
{
2022-11-27 12:20:05 +01:00
if ( ! gem_state | | ! is_input_allowed ( ) )
return ;
2022-11-13 18:10:09 +01:00
2022-11-27 12:20:05 +01:00
int x_pos , y_pos , x_max , y_max ;
{
gun_thread & gun = g_fxo - > get < gun_thread > ( ) ;
std : : scoped_lock lock ( gun . handler . mutex ) ;
x_pos = gun . handler . get_axis_x ( gem_no ) ;
y_pos = gun . handler . get_axis_y ( gem_no ) ;
x_max = gun . handler . get_axis_x_max ( gem_no ) ;
y_max = gun . handler . get_axis_y_max ( gem_no ) ;
}
2022-11-13 18:10:09 +01:00
2024-03-20 17:16:49 +01:00
if constexpr ( std : : is_same_v < T , vm : : ptr < CellGemState > > )
2022-11-27 12:20:05 +01:00
{
2024-11-29 05:38:44 +01:00
pos_to_gem_state ( gem_no , controller , gem_state , x_pos , y_pos , x_max , y_max , { } ) ;
2022-11-27 12:20:05 +01:00
}
2024-03-20 17:16:49 +01:00
else if constexpr ( std : : is_same_v < T , vm : : ptr < CellGemImageState > > )
2022-11-27 12:20:05 +01:00
{
pos_to_gem_image_state ( gem_no , controller , gem_state , x_pos , y_pos , x_max , y_max ) ;
}
2022-11-13 18:10:09 +01:00
}
2022-11-27 12:20:05 +01:00
# endif
2022-11-13 18:10:09 +01:00
2018-03-02 17:51:21 +01:00
// *********************
// * cellGem functions *
// *********************
2019-09-08 16:09:44 +02:00
error_code cellGemCalibrate ( u32 gem_num )
2018-03-02 17:51:21 +01:00
{
cellGem . todo ( " cellGemCalibrate(gem_num=%d) " , gem_num ) ;
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
std : : scoped_lock lock ( gem . mtx ) ;
2019-09-21 12:25:53 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-03-02 17:51:21 +01:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
if ( ! check_gem_num ( gem_num ) )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2024-12-20 23:38:43 +01:00
auto & controller = gem . controllers [ gem_num ] ;
if ( controller . is_calibrating )
2021-08-05 00:38:45 +02:00
{
return CELL_EBUSY ;
}
2024-12-20 23:38:43 +01:00
controller . is_calibrating = true ;
controller . calibration_start_us = get_guest_system_time ( ) ;
2018-03-02 17:51:21 +01:00
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2019-09-08 16:09:44 +02:00
error_code cellGemClearStatusFlags ( u32 gem_num , u64 mask )
2013-09-28 04:36:57 +02:00
{
2018-03-02 17:51:21 +01:00
cellGem . todo ( " cellGemClearStatusFlags(gem_num=%d, mask=0x%x) " , gem_num , mask ) ;
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
std : : scoped_lock lock ( gem . mtx ) ;
2019-09-21 12:25:53 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-03-02 17:51:21 +01:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
if ( ! check_gem_num ( gem_num ) )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2024-12-20 13:25:13 +01:00
gem . controllers [ gem_num ] . calibration_status_flags & = ~ mask ;
2018-03-02 17:51:21 +01:00
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2025-01-08 19:08:21 +01:00
error_code cellGemConvertVideoFinish ( ppu_thread & ppu )
2013-09-28 04:36:57 +02:00
{
2021-10-26 23:48:42 +02:00
cellGem . warning ( " cellGemConvertVideoFinish() " ) ;
2018-04-01 00:03:37 +02:00
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-04-01 00:03:37 +02:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
2021-10-20 19:47:19 +02:00
if ( ! gem . video_conversion_in_progress )
2021-07-30 00:32:41 +02:00
{
return CELL_GEM_ERROR_CONVERT_NOT_STARTED ;
}
2025-01-08 19:08:21 +01:00
if ( ! gem . wait_for_result ( ppu ) )
{
return { } ;
}
2021-07-30 00:32:41 +02:00
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2019-09-08 16:09:44 +02:00
error_code cellGemConvertVideoStart ( vm : : cptr < void > video_frame )
2013-09-28 04:36:57 +02:00
{
2021-10-26 23:48:42 +02:00
cellGem . warning ( " cellGemConvertVideoStart(video_frame=*0x%x) " , video_frame ) ;
2018-04-01 00:03:37 +02:00
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-04-01 00:03:37 +02:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
2021-07-30 00:32:41 +02:00
if ( ! video_frame )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2021-10-20 19:47:19 +02:00
if ( ! video_frame . aligned ( 128 ) )
{
return CELL_GEM_ERROR_INVALID_ALIGNMENT ;
}
2021-08-05 00:35:13 +02:00
2021-10-20 19:47:19 +02:00
if ( gem . video_conversion_in_progress )
2021-07-30 00:32:41 +02:00
{
return CELL_GEM_ERROR_CONVERT_NOT_FINISHED ;
}
2021-10-20 19:47:19 +02:00
const auto & shared_data = g_fxo - > get < gem_camera_shared > ( ) ;
gem . video_data_in . resize ( shared_data . size ) ;
std : : memcpy ( gem . video_data_in . data ( ) , video_frame . get_ptr ( ) , gem . video_data_in . size ( ) ) ;
2025-01-08 03:19:35 +01:00
2021-10-20 19:47:19 +02:00
gem . video_conversion_in_progress = true ;
2025-01-08 03:19:35 +01:00
gem . wake_up ( ) ;
2021-07-30 00:32:41 +02:00
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2019-09-08 16:09:44 +02:00
error_code cellGemEnableCameraPitchAngleCorrection ( u32 enable_flag )
2013-09-28 04:36:57 +02:00
{
2018-04-01 00:03:37 +02:00
cellGem . todo ( " cellGemEnableCameraPitchAngleCorrection(enable_flag=%d) " , enable_flag ) ;
2018-03-02 17:51:21 +01:00
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
std : : scoped_lock lock ( gem . mtx ) ;
2019-09-21 12:25:53 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-03-02 17:51:21 +01:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
2021-03-02 12:59:19 +01:00
gem . enable_pitch_correction = ! ! enable_flag ;
2018-03-02 17:51:21 +01:00
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2019-09-08 16:09:44 +02:00
error_code cellGemEnableMagnetometer ( u32 gem_num , u32 enable )
2013-09-28 04:36:57 +02:00
{
2018-03-02 17:51:21 +01:00
cellGem . todo ( " cellGemEnableMagnetometer(gem_num=%d, enable=0x%x) " , gem_num , enable ) ;
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
std : : scoped_lock lock ( gem . mtx ) ;
2019-09-21 12:25:53 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-03-02 17:51:21 +01:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
2021-08-05 00:35:13 +02:00
if ( ! check_gem_num ( gem_num ) )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2021-03-02 12:59:19 +01:00
if ( ! gem . is_controller_ready ( gem_num ) )
2018-03-02 17:51:21 +01:00
{
return CELL_GEM_NOT_CONNECTED ;
}
2024-12-21 00:55:05 +01:00
auto & controller = gem . controllers [ gem_num ] ;
2021-08-05 00:35:13 +02:00
// NOTE: RE doesn't show this check but it is mentioned in the docs, so I'll leave it here for now.
2025-04-05 21:50:45 +02:00
// if (!controller.calibrated_magnetometer)
2021-08-05 00:35:13 +02:00
//{
// return CELL_GEM_NOT_CALIBRATED;
//}
2024-12-21 00:55:05 +01:00
controller . enabled_magnetometer = ! ! enable ;
if ( g_cfg . io . move = = move_handler : : real )
{
std : : lock_guard lock ( pad : : g_pad_mutex ) ;
2025-01-20 22:55:21 +01:00
const auto handler = pad : : get_pad_thread ( ) ;
2024-12-21 00:55:05 +01:00
const auto & pad = : : at32 ( handler - > GetPads ( ) , pad_num ( gem_num ) ) ;
if ( pad & & pad - > m_pad_handler = = pad_handler : : move )
{
pad - > move_data . magnetometer_enabled = controller . enabled_magnetometer ;
}
}
2018-03-02 17:51:21 +01:00
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2024-01-14 19:12:38 +01:00
error_code cellGemEnableMagnetometer2 ( u32 gem_num , u32 enable )
2019-04-05 20:14:01 +02:00
{
2024-01-14 19:12:38 +01:00
cellGem . trace ( " cellGemEnableMagnetometer2(gem_num=%d, enable=0x%x) " , gem_num , enable ) ;
auto & gem = g_fxo - > get < gem_config > ( ) ;
std : : scoped_lock lock ( gem . mtx ) ;
if ( ! gem . state )
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
if ( ! check_gem_num ( gem_num ) )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
if ( ! gem . is_controller_ready ( gem_num ) )
{
return CELL_GEM_NOT_CONNECTED ;
}
2024-12-20 23:38:43 +01:00
auto & controller = gem . controllers [ gem_num ] ;
if ( ! controller . calibrated_magnetometer )
2024-01-14 19:12:38 +01:00
{
return CELL_GEM_NOT_CALIBRATED ;
}
2024-12-20 23:38:43 +01:00
controller . enabled_magnetometer = ! ! enable ;
2024-01-14 19:12:38 +01:00
2024-12-21 00:55:05 +01:00
if ( g_cfg . io . move = = move_handler : : real )
{
std : : lock_guard lock ( pad : : g_pad_mutex ) ;
2025-01-20 22:55:21 +01:00
const auto handler = pad : : get_pad_thread ( ) ;
2024-12-21 00:55:05 +01:00
const auto & pad = : : at32 ( handler - > GetPads ( ) , pad_num ( gem_num ) ) ;
if ( pad & & pad - > m_pad_handler = = pad_handler : : move )
{
pad - > move_data . magnetometer_enabled = controller . enabled_magnetometer ;
}
}
2019-04-05 20:14:01 +02:00
return CELL_OK ;
}
2020-10-30 16:09:30 +01:00
error_code cellGemEnd ( ppu_thread & ppu )
2013-09-28 04:36:57 +02:00
{
2016-01-12 22:57:16 +01:00
cellGem . warning ( " cellGemEnd() " ) ;
2015-08-21 22:57:49 +02:00
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2025-01-21 05:34:16 +01:00
std : : unique_lock lock ( gem . mtx ) ;
2019-09-21 12:25:53 +02:00
2021-03-02 12:59:19 +01:00
if ( gem . state . compare_and_swap_test ( 1 , 0 ) )
2015-08-21 22:57:49 +02:00
{
2022-07-04 15:02:17 +02:00
if ( u32 addr = std : : exchange ( gem . memory_ptr , 0 ) )
2019-09-21 12:25:53 +02:00
{
2020-10-30 16:09:30 +01:00
sys_memory_free ( ppu , addr ) ;
2019-09-21 12:25:53 +02:00
}
return CELL_OK ;
2015-08-21 22:57:49 +02:00
}
2025-01-21 05:34:16 +01:00
lock . unlock ( ) ;
2024-07-08 20:17:21 +02:00
auto & tracker = g_fxo - > get < named_thread < gem_tracker > > ( ) ;
2025-01-08 19:08:21 +01:00
if ( ! tracker . wait_for_tracker_result ( ppu ) )
{
return { } ;
}
2024-07-08 20:17:21 +02:00
gem . updating = false ;
2019-09-21 12:25:53 +02:00
return CELL_GEM_ERROR_UNINITIALIZED ;
2013-09-28 04:36:57 +02:00
}
2019-09-08 16:09:44 +02:00
error_code cellGemFilterState ( u32 gem_num , u32 enable )
2013-09-28 04:36:57 +02:00
{
2018-03-02 17:51:21 +01:00
cellGem . warning ( " cellGemFilterState(gem_num=%d, enable=%d) " , gem_num , enable ) ;
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
std : : scoped_lock lock ( gem . mtx ) ;
2019-09-21 12:25:53 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-03-02 17:51:21 +01:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
if ( ! check_gem_num ( gem_num ) )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2021-03-02 12:59:19 +01:00
gem . controllers [ gem_num ] . enabled_filtering = ! ! enable ;
2018-03-02 17:51:21 +01:00
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2024-12-19 22:19:50 +01:00
error_code cellGemForceRGB ( u32 gem_num , f32 r , f32 g , f32 b )
2013-09-28 04:36:57 +02:00
{
2025-01-21 05:34:16 +01:00
cellGem . warning ( " cellGemForceRGB(gem_num=%d, r=%f, g=%f, b=%f) " , gem_num , r , g , b ) ;
2018-03-02 17:51:21 +01:00
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
std : : scoped_lock lock ( gem . mtx ) ;
2019-09-21 12:25:53 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-03-02 17:51:21 +01:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
if ( ! check_gem_num ( gem_num ) )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2021-07-29 22:59:41 +02:00
// TODO: Adjust brightness
2025-04-05 21:50:45 +02:00
// if (const f32 sum = r + g + b; sum > 2.f)
2021-07-29 22:59:41 +02:00
//{
// color = color * (2.f / sum)
//}
2024-12-20 23:38:43 +01:00
auto & controller = gem . controllers [ gem_num ] ;
controller . sphere_rgb = gem_config : : gem_color ( r , g , b ) ;
controller . enabled_tracking = false ;
2018-03-02 17:51:21 +01:00
2024-12-19 23:50:11 +01:00
const auto [ h , s , v ] = ps_move_tracker < false > : : rgb_to_hsv ( r , g , b ) ;
2024-12-20 23:38:43 +01:00
controller . hue = h ;
2024-12-19 23:50:11 +01:00
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2021-07-30 00:32:41 +02:00
error_code cellGemGetAccelerometerPositionInDevice ( u32 gem_num , vm : : ptr < f32 > pos )
2013-09-28 04:36:57 +02:00
{
2021-07-30 00:32:41 +02:00
cellGem . todo ( " cellGemGetAccelerometerPositionInDevice(gem_num=%d, pos=*0x%x) " , gem_num , pos ) ;
auto & gem = g_fxo - > get < gem_config > ( ) ;
if ( ! gem . state )
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
if ( ! check_gem_num ( gem_num ) | | ! pos )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2021-08-05 00:35:13 +02:00
// TODO
2024-07-08 21:23:51 +02:00
pos [ 0 ] = 0.0f ;
pos [ 1 ] = 0.0f ;
pos [ 2 ] = 0.0f ;
pos [ 3 ] = 0.0f ;
2021-08-05 00:35:13 +02:00
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2019-09-08 16:09:44 +02:00
error_code cellGemGetAllTrackableHues ( vm : : ptr < u8 > hues )
2013-09-28 04:36:57 +02:00
{
2018-03-02 17:51:21 +01:00
cellGem . todo ( " cellGemGetAllTrackableHues(hues=*0x%x) " ) ;
2018-04-01 00:03:37 +02:00
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-04-01 00:03:37 +02:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
2021-07-30 00:32:41 +02:00
if ( ! hues )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2024-07-08 20:17:21 +02:00
auto & tracker = g_fxo - > get < named_thread < gem_tracker > > ( ) ;
std : : lock_guard lock ( tracker . mutex ) ;
for ( u32 hue = 0 ; hue < 360 ; hue + + )
2018-04-01 00:03:37 +02:00
{
2024-07-08 20:17:21 +02:00
hues [ hue ] = tracker . hue_is_trackable ( hue ) ;
2018-04-01 00:03:37 +02:00
}
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2019-09-08 16:09:44 +02:00
error_code cellGemGetCameraState ( vm : : ptr < CellGemCameraState > camera_state )
2013-09-28 04:36:57 +02:00
{
2018-03-02 17:51:21 +01:00
cellGem . todo ( " cellGemGetCameraState(camera_state=0x%x) " , camera_state ) ;
2022-03-23 11:26:24 +01:00
[[maybe_unused]] auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2018-03-02 17:51:21 +01:00
if ( ! camera_state )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2021-08-04 22:57:13 +02:00
// TODO: use correct camera settings
camera_state - > exposure = 0 ;
camera_state - > exposure_time = 1.0f / 60.0f ;
2024-12-20 16:46:48 +01:00
camera_state - > gain = 1.0f ;
camera_state - > pitch_angle = 0.0f ;
camera_state - > pitch_angle_estimate = 0.0f ;
2018-03-02 17:51:21 +01:00
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2019-09-08 16:09:44 +02:00
error_code cellGemGetEnvironmentLightingColor ( vm : : ptr < f32 > r , vm : : ptr < f32 > g , vm : : ptr < f32 > b )
2013-09-28 04:36:57 +02:00
{
2018-04-01 00:03:37 +02:00
cellGem . todo ( " cellGemGetEnvironmentLightingColor(r=*0x%x, g=*0x%x, b=*0x%x) " , r , g , b ) ;
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-04-01 00:03:37 +02:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
if ( ! r | | ! g | | ! b )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
// default to 128
* r = 128 ;
* g = 128 ;
* b = 128 ;
2021-08-05 00:35:13 +02:00
// NOTE: RE doesn't show this check but it is mentioned in the docs, so I'll leave it here for now.
2025-04-05 21:50:45 +02:00
// if (!gem.controllers[gem_num].calibrated_magnetometer)
2021-08-05 00:35:13 +02:00
//{
// return CELL_GEM_ERROR_LIGHTING_NOT_CALIBRATED; // This error doesn't really seem to be a real thing.
//}
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2019-09-08 16:09:44 +02:00
error_code cellGemGetHuePixels ( vm : : cptr < void > camera_frame , u32 hue , vm : : ptr < u8 > pixels )
2013-09-28 04:36:57 +02:00
{
2018-04-01 00:03:37 +02:00
cellGem . todo ( " cellGemGetHuePixels(camera_frame=*0x%x, hue=%d, pixels=*0x%x) " , camera_frame , hue , pixels ) ;
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-04-01 00:03:37 +02:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
if ( ! camera_frame | | ! pixels | | hue > 359 )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2024-01-14 19:12:38 +01:00
std : : memset ( pixels . get_ptr ( ) , 0 , 640 * 480 * sizeof ( u8 ) ) ;
2024-07-08 20:17:21 +02:00
if ( g_cfg . io . move = = move_handler : : real )
{
// TODO: get pixels from tracker
}
2021-08-05 00:35:13 +02:00
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2018-07-23 19:57:40 +02:00
error_code cellGemGetImageState ( u32 gem_num , vm : : ptr < CellGemImageState > gem_image_state )
2013-09-28 04:36:57 +02:00
{
2021-10-26 23:48:42 +02:00
cellGem . warning ( " cellGemGetImageState(gem_num=%d, image_state=&0x%x) " , gem_num , gem_image_state ) ;
2018-03-02 17:51:21 +01:00
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2025-01-17 00:32:37 +01:00
std : : scoped_lock lock ( gem . mtx ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-03-02 17:51:21 +01:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
2018-07-23 19:57:40 +02:00
if ( ! check_gem_num ( gem_num ) | | ! gem_image_state )
2018-03-02 17:51:21 +01:00
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2022-07-05 21:47:05 +02:00
* gem_image_state = { } ;
2022-11-27 12:20:05 +01:00
if ( g_cfg . io . move ! = move_handler : : null )
2018-07-23 19:57:40 +02:00
{
2025-01-20 22:54:26 +01:00
const auto & shared_data = g_fxo - > get < gem_camera_shared > ( ) ;
2024-11-29 05:38:44 +01:00
auto & controller = gem . controllers [ gem_num ] ;
2024-07-08 21:23:51 +02:00
2023-02-22 22:14:40 +01:00
gem_image_state - > frame_timestamp = shared_data . frame_timestamp_us . load ( ) ;
2018-07-23 19:57:40 +02:00
gem_image_state - > timestamp = gem_image_state - > frame_timestamp + 10 ;
2021-07-29 22:34:21 +02:00
2022-11-27 12:20:05 +01:00
switch ( g_cfg . io . move )
2022-11-13 18:10:09 +01:00
{
2024-07-08 20:17:21 +02:00
case move_handler : : real :
ps_move_pos_to_gem_state ( gem_num , controller , gem_image_state ) ;
break ;
2022-11-27 12:20:05 +01:00
case move_handler : : fake :
2024-07-08 21:23:51 +02:00
ds3_pos_to_gem_state ( gem_num , controller , gem_image_state ) ;
2022-11-27 12:20:05 +01:00
break ;
case move_handler : : mouse :
2024-02-05 23:49:00 +01:00
case move_handler : : raw_mouse :
2024-07-08 21:23:51 +02:00
mouse_pos_to_gem_state ( gem_num , controller , gem_image_state ) ;
2022-11-27 12:20:05 +01:00
break ;
2022-11-13 18:10:09 +01:00
# ifdef HAVE_LIBEVDEV
2022-11-27 12:20:05 +01:00
case move_handler : : gun :
2024-07-08 21:23:51 +02:00
gun_pos_to_gem_state ( gem_num , controller , gem_image_state ) ;
2022-11-27 12:20:05 +01:00
break ;
2022-11-13 18:10:09 +01:00
# endif
2022-11-27 12:20:05 +01:00
case move_handler : : null :
fmt : : throw_exception ( " Unreachable " ) ;
2022-11-13 18:10:09 +01:00
}
2025-01-17 00:32:37 +01:00
gem_image_state - > r = controller . radius ; // Radius in camera pixels
gem_image_state - > distance = controller . distance_mm ;
gem_image_state - > visible = controller . radius_valid & & gem . is_controller_ready ( gem_num ) ;
gem_image_state - > r_valid = controller . radius_valid ;
2018-03-02 17:51:21 +01:00
}
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2019-09-08 16:09:44 +02:00
error_code cellGemGetInertialState ( u32 gem_num , u32 state_flag , u64 timestamp , vm : : ptr < CellGemInertialState > inertial_state )
2013-09-28 04:36:57 +02:00
{
2019-12-20 08:17:22 +01:00
cellGem . warning ( " cellGemGetInertialState(gem_num=%d, state_flag=%d, timestamp=0x%x, inertial_state=0x%x) " , gem_num , state_flag , timestamp , inertial_state ) ;
2018-03-02 17:51:21 +01:00
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
std : : scoped_lock lock ( gem . mtx ) ;
2019-09-21 12:25:53 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-03-02 17:51:21 +01:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
2021-10-20 19:47:19 +02:00
if ( ! check_gem_num ( gem_num ) | | ! inertial_state | | ! gem . is_controller_ready ( gem_num ) )
2018-03-02 17:51:21 +01:00
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2021-08-05 00:35:13 +02:00
if ( false ) // TODO
{
return CELL_GEM_TIME_OUT_OF_RANGE ;
}
2022-07-05 22:49:20 +02:00
* inertial_state = { } ;
2022-07-05 21:47:05 +02:00
2022-11-27 12:20:05 +01:00
if ( g_cfg . io . move ! = move_handler : : null )
2018-07-23 19:57:40 +02:00
{
2021-07-29 22:33:31 +02:00
ds3_input_to_ext ( gem_num , gem . controllers [ gem_num ] , inertial_state - > ext ) ;
2018-03-02 17:51:21 +01:00
2023-02-22 22:14:40 +01:00
inertial_state - > timestamp = ( get_guest_system_time ( ) - gem . start_timestamp_us ) ;
2021-03-02 12:59:19 +01:00
inertial_state - > counter = gem . inertial_counter + + ;
2021-07-30 15:55:50 +02:00
inertial_state - > accelerometer [ 0 ] = 10 ; // Current gravity in m/s²
2021-07-29 22:34:21 +02:00
2022-11-27 12:20:05 +01:00
switch ( g_cfg . io . move )
2021-07-29 22:34:21 +02:00
{
2024-07-08 20:17:21 +02:00
case move_handler : : real :
2025-01-08 01:22:50 +01:00
case move_handler : : fake :
2024-07-08 20:17:21 +02:00
{
2024-11-27 22:16:04 +01:00
// Get temperature and sensor data
2024-07-08 20:17:21 +02:00
{
std : : lock_guard lock ( pad : : g_pad_mutex ) ;
2025-01-20 22:55:21 +01:00
const auto handler = pad : : get_pad_thread ( ) ;
2024-07-08 20:17:21 +02:00
const auto & pad = : : at32 ( handler - > GetPads ( ) , pad_num ( gem_num ) ) ;
2025-01-08 01:22:50 +01:00
if ( pad & & ( pad - > m_port_status & CELL_PAD_STATUS_CONNECTED ) )
2024-07-08 20:17:21 +02:00
{
inertial_state - > temperature = pad - > move_data . temperature ;
2024-11-27 22:16:04 +01:00
inertial_state - > accelerometer [ 0 ] = pad - > move_data . accelerometer_x ;
inertial_state - > accelerometer [ 1 ] = pad - > move_data . accelerometer_y ;
inertial_state - > accelerometer [ 2 ] = pad - > move_data . accelerometer_z ;
inertial_state - > gyro [ 0 ] = pad - > move_data . gyro_x ;
inertial_state - > gyro [ 1 ] = pad - > move_data . gyro_y ;
inertial_state - > gyro [ 2 ] = pad - > move_data . gyro_z ;
2024-07-08 20:17:21 +02:00
}
}
ds3_input_to_pad ( gem_num , inertial_state - > pad . digitalbuttons , inertial_state - > pad . analog_T ) ;
break ;
}
2022-11-27 12:20:05 +01:00
case move_handler : : mouse :
2024-02-05 23:49:00 +01:00
case move_handler : : raw_mouse :
2021-07-29 22:34:21 +02:00
mouse_input_to_pad ( gem_num , inertial_state - > pad . digitalbuttons , inertial_state - > pad . analog_T ) ;
2022-11-27 12:20:05 +01:00
break ;
2022-11-13 18:10:09 +01:00
# ifdef HAVE_LIBEVDEV
2022-11-27 12:20:05 +01:00
case move_handler : : gun :
2022-11-13 18:10:09 +01:00
gun_input_to_pad ( gem_num , inertial_state - > pad . digitalbuttons , inertial_state - > pad . analog_T ) ;
2022-11-27 12:20:05 +01:00
break ;
2022-11-13 18:10:09 +01:00
# endif
2022-11-27 12:20:05 +01:00
case move_handler : : null :
fmt : : throw_exception ( " Unreachable " ) ;
2022-11-13 18:10:09 +01:00
}
2018-03-02 17:51:21 +01:00
}
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2019-09-08 16:09:44 +02:00
error_code cellGemGetInfo ( vm : : ptr < CellGemInfo > info )
2013-09-28 04:36:57 +02:00
{
2024-04-25 21:47:53 +02:00
cellGem . trace ( " cellGemGetInfo(info=*0x%x) " , info ) ;
2014-10-18 20:14:16 +02:00
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2015-08-21 22:57:49 +02:00
2021-03-02 12:59:19 +01:00
reader_lock lock ( gem . mtx ) ;
2019-09-21 12:25:53 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2015-08-21 22:57:49 +02:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
2018-03-02 17:51:21 +01:00
if ( ! info )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2021-03-02 12:59:19 +01:00
info - > max_connect = gem . attribute . max_connect ;
info - > now_connect = gem . connected_controllers ;
2015-08-21 22:57:49 +02:00
for ( int i = 0 ; i < CELL_GEM_MAX_NUM ; i + + )
{
2021-03-02 12:59:19 +01:00
info - > status [ i ] = gem . controllers [ i ] . status ;
info - > port [ i ] = gem . controllers [ i ] . port ;
2015-08-21 22:57:49 +02:00
}
2015-02-11 22:09:48 +01:00
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2019-09-21 12:25:53 +02:00
u32 GemGetMemorySize ( s32 max_connect )
{
return max_connect < = 2 ? 0x120000 : 0x140000 ;
}
2019-09-08 16:09:44 +02:00
error_code cellGemGetMemorySize ( s32 max_connect )
2013-09-28 04:36:57 +02:00
{
2016-01-12 22:57:16 +01:00
cellGem . warning ( " cellGemGetMemorySize(max_connect=%d) " , max_connect ) ;
2014-08-16 18:20:16 +02:00
2015-07-31 23:47:29 +02:00
if ( max_connect > CELL_GEM_MAX_NUM | | max_connect < = 0 )
{
2014-08-16 18:20:16 +02:00
return CELL_GEM_ERROR_INVALID_PARAMETER ;
2015-07-31 23:47:29 +02:00
}
2014-08-16 18:20:16 +02:00
2019-09-21 12:25:53 +02:00
return not_an_error ( GemGetMemorySize ( max_connect ) ) ;
2013-09-28 04:36:57 +02:00
}
2024-12-19 22:19:50 +01:00
error_code cellGemGetRGB ( u32 gem_num , vm : : ptr < f32 > r , vm : : ptr < f32 > g , vm : : ptr < f32 > b )
2013-09-28 04:36:57 +02:00
{
2018-03-02 17:51:21 +01:00
cellGem . todo ( " cellGemGetRGB(gem_num=%d, r=*0x%x, g=*0x%x, b=*0x%x) " , gem_num , r , g , b ) ;
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
reader_lock lock ( gem . mtx ) ;
2019-09-21 12:25:53 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-03-02 17:51:21 +01:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
2018-07-23 19:57:40 +02:00
if ( ! check_gem_num ( gem_num ) | | ! r | | ! g | | ! b )
2018-03-02 17:51:21 +01:00
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2022-07-06 22:08:50 +02:00
const gem_config_data : : gem_color & sphere_color = gem . controllers [ gem_num ] . sphere_rgb ;
2018-03-02 17:51:21 +01:00
* r = sphere_color . r ;
* g = sphere_color . g ;
* b = sphere_color . b ;
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2019-09-08 16:09:44 +02:00
error_code cellGemGetRumble ( u32 gem_num , vm : : ptr < u8 > rumble )
2013-09-28 04:36:57 +02:00
{
2018-03-02 17:51:21 +01:00
cellGem . todo ( " cellGemGetRumble(gem_num=%d, rumble=*0x%x) " , gem_num , rumble ) ;
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
reader_lock lock ( gem . mtx ) ;
2019-09-21 12:25:53 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-03-02 17:51:21 +01:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
if ( ! check_gem_num ( gem_num ) | | ! rumble )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2021-03-02 12:59:19 +01:00
* rumble = gem . controllers [ gem_num ] . rumble ;
2018-03-02 17:51:21 +01:00
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2019-09-08 16:09:44 +02:00
error_code cellGemGetState ( u32 gem_num , u32 flag , u64 time_parameter , vm : : ptr < CellGemState > gem_state )
2013-09-28 04:36:57 +02:00
{
2019-12-20 08:17:22 +01:00
cellGem . warning ( " cellGemGetState(gem_num=%d, flag=0x%x, time=0x%llx, gem_state=*0x%x) " , gem_num , flag , time_parameter , gem_state ) ;
2017-06-24 13:06:40 +02:00
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
reader_lock lock ( gem . mtx ) ;
2019-09-21 12:25:53 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-03-02 17:51:21 +01:00
{
2017-06-19 07:17:19 +02:00
return CELL_GEM_ERROR_UNINITIALIZED ;
2018-03-02 17:51:21 +01:00
}
2017-06-19 07:17:19 +02:00
2018-04-01 00:03:37 +02:00
if ( ! check_gem_num ( gem_num ) | | flag > CELL_GEM_STATE_FLAG_TIMESTAMP | | ! gem_state )
2018-03-02 17:51:21 +01:00
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2021-07-30 01:52:20 +02:00
if ( ! gem . is_controller_ready ( gem_num ) )
2021-07-30 00:32:41 +02:00
{
2024-03-27 09:46:45 +01:00
return not_an_error ( CELL_GEM_NOT_CONNECTED ) ;
2021-07-30 00:32:41 +02:00
}
// TODO: Get the gem state at the specified time
2025-04-05 21:50:45 +02:00
// if (flag == CELL_GEM_STATE_FLAG_CURRENT_TIME)
2021-07-30 00:32:41 +02:00
//{
// // now + time_parameter (time_parameter in microseconds). Positive values actually allow predictions for the future state.
//}
2025-04-05 21:50:45 +02:00
// else if (flag == CELL_GEM_STATE_FLAG_LATEST_IMAGE_TIME)
2021-07-30 00:32:41 +02:00
//{
// // When the sphere was registered during the last camera frame (time_parameter may also have an impact)
//}
2025-04-05 21:50:45 +02:00
// else // CELL_GEM_STATE_FLAG_TIMESTAMP
2021-07-30 00:32:41 +02:00
//{
// // As specified by time_parameter.
//}
2021-08-05 00:35:13 +02:00
if ( false ) // TODO: check if there is data for the specified time_parameter and flag
{
return CELL_GEM_TIME_OUT_OF_RANGE ;
}
2024-07-08 20:17:21 +02:00
auto & controller = gem . controllers [ gem_num ] ;
2024-07-08 21:23:51 +02:00
2022-07-05 22:49:20 +02:00
* gem_state = { } ;
2022-11-27 12:20:05 +01:00
if ( g_cfg . io . move ! = move_handler : : null )
2018-07-23 19:57:40 +02:00
{
2024-07-08 21:23:51 +02:00
ds3_input_to_ext ( gem_num , controller , gem_state - > ext ) ;
2018-03-02 17:51:21 +01:00
2024-07-08 21:23:51 +02:00
if ( controller . enabled_tracking )
2024-07-08 20:17:21 +02:00
{
gem_state - > tracking_flags | = CELL_GEM_TRACKING_FLAG_POSITION_TRACKED ;
gem_state - > tracking_flags | = CELL_GEM_TRACKING_FLAG_VISIBLE ;
}
2021-07-30 01:52:20 +02:00
2023-02-22 22:14:40 +01:00
gem_state - > timestamp = ( get_guest_system_time ( ) - gem . start_timestamp_us ) ;
2021-07-30 00:32:41 +02:00
gem_state - > camera_pitch_angle = 0.f ;
2018-03-02 17:51:21 +01:00
2022-11-27 12:20:05 +01:00
switch ( g_cfg . io . move )
2021-07-29 22:34:21 +02:00
{
2024-07-08 20:17:21 +02:00
case move_handler : : real :
{
2024-12-12 02:23:01 +01:00
auto & tracker = g_fxo - > get < named_thread < gem_tracker > > ( ) ;
const ps_move_info info = tracker . get_info ( gem_num ) ;
2024-07-08 20:17:21 +02:00
ds3_input_to_pad ( gem_num , gem_state - > pad . digitalbuttons , gem_state - > pad . analog_T ) ;
ps_move_pos_to_gem_state ( gem_num , controller , gem_state ) ;
if ( info . valid )
gem_state - > tracking_flags | = CELL_GEM_TRACKING_FLAG_VISIBLE ;
else
gem_state - > tracking_flags & = ~ CELL_GEM_TRACKING_FLAG_VISIBLE ;
break ;
}
2022-11-27 12:20:05 +01:00
case move_handler : : fake :
2021-07-29 22:34:21 +02:00
ds3_input_to_pad ( gem_num , gem_state - > pad . digitalbuttons , gem_state - > pad . analog_T ) ;
2024-07-08 21:23:51 +02:00
ds3_pos_to_gem_state ( gem_num , controller , gem_state ) ;
2022-11-27 12:20:05 +01:00
break ;
case move_handler : : mouse :
2024-02-05 23:49:00 +01:00
case move_handler : : raw_mouse :
2021-07-29 22:34:21 +02:00
mouse_input_to_pad ( gem_num , gem_state - > pad . digitalbuttons , gem_state - > pad . analog_T ) ;
2024-07-08 21:23:51 +02:00
mouse_pos_to_gem_state ( gem_num , controller , gem_state ) ;
2022-11-27 12:20:05 +01:00
break ;
2022-11-13 18:10:09 +01:00
# ifdef HAVE_LIBEVDEV
2022-11-27 12:20:05 +01:00
case move_handler : : gun :
2022-11-13 18:10:09 +01:00
gun_input_to_pad ( gem_num , gem_state - > pad . digitalbuttons , gem_state - > pad . analog_T ) ;
2024-07-08 21:23:51 +02:00
gun_pos_to_gem_state ( gem_num , controller , gem_state ) ;
2022-11-27 12:20:05 +01:00
break ;
2022-11-13 18:10:09 +01:00
# endif
2022-11-27 12:20:05 +01:00
case move_handler : : null :
fmt : : throw_exception ( " Unreachable " ) ;
2022-11-13 18:10:09 +01:00
}
2021-07-30 01:52:20 +02:00
}
2021-07-29 22:34:21 +02:00
2021-08-05 00:35:13 +02:00
if ( false ) // TODO: check if we are computing colors
{
return CELL_GEM_COMPUTING_AVAILABLE_COLORS ;
}
2024-12-20 23:38:43 +01:00
if ( controller . is_calibrating )
2021-08-05 00:38:45 +02:00
{
return CELL_GEM_SPHERE_CALIBRATING ;
}
2024-07-08 21:23:51 +02:00
if ( ! controller . calibrated_magnetometer )
2021-07-30 01:52:20 +02:00
{
return CELL_GEM_SPHERE_NOT_CALIBRATED ;
2018-03-02 17:51:21 +01:00
}
2017-06-19 07:17:19 +02:00
2024-07-08 21:23:51 +02:00
if ( ! controller . hue_set )
2021-07-30 01:52:20 +02:00
{
return CELL_GEM_HUE_NOT_SET ;
}
return CELL_OK ;
2013-09-28 04:36:57 +02:00
}
2019-09-08 16:09:44 +02:00
error_code cellGemGetStatusFlags ( u32 gem_num , vm : : ptr < u64 > flags )
2013-09-28 04:36:57 +02:00
{
2024-06-27 09:02:41 +02:00
cellGem . trace ( " cellGemGetStatusFlags(gem_num=%d, flags=*0x%x) " , gem_num , flags ) ;
2018-03-02 17:51:21 +01:00
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
reader_lock lock ( gem . mtx ) ;
2019-09-21 12:25:53 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-03-02 17:51:21 +01:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
if ( ! check_gem_num ( gem_num ) | | ! flags )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2024-12-20 13:25:13 +01:00
* flags = gem . runtime_status_flags | gem . controllers [ gem_num ] . calibration_status_flags ;
2018-03-02 17:51:21 +01:00
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2019-09-08 16:09:44 +02:00
error_code cellGemGetTrackerHue ( u32 gem_num , vm : : ptr < u32 > hue )
2013-09-28 04:36:57 +02:00
{
2018-04-01 00:03:37 +02:00
cellGem . warning ( " cellGemGetTrackerHue(gem_num=%d, hue=*0x%x) " , gem_num , hue ) ;
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
reader_lock lock ( gem . mtx ) ;
2019-09-21 12:25:53 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-04-01 00:03:37 +02:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
if ( ! check_gem_num ( gem_num ) | | ! hue )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2024-07-08 21:23:51 +02:00
const auto & controller = gem . controllers [ gem_num ] ;
if ( ! controller . enabled_tracking | | controller . hue > 359 )
2018-04-01 00:03:37 +02:00
{
2025-04-05 21:50:45 +02:00
return { CELL_GEM_ERROR_NOT_A_HUE , controller . hue } ;
2018-04-01 00:03:37 +02:00
}
2024-07-08 21:23:51 +02:00
* hue = controller . hue ;
2018-04-01 00:03:37 +02:00
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2019-09-08 16:09:44 +02:00
error_code cellGemHSVtoRGB ( f32 h , f32 s , f32 v , vm : : ptr < f32 > r , vm : : ptr < f32 > g , vm : : ptr < f32 > b )
2013-09-28 04:36:57 +02:00
{
2021-08-04 22:53:07 +02:00
cellGem . warning ( " cellGemHSVtoRGB(h=%f, s=%f, v=%f, r=*0x%x, g=*0x%x, b=*0x%x) " , h , s , v , r , g , b ) ;
2018-04-01 00:03:37 +02:00
if ( s < 0.0f | | s > 1.0f | | v < 0.0f | | v > 1.0f | | ! r | | ! g | | ! b )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
h = std : : clamp ( h , 0.0f , 360.0f ) ;
2021-08-04 22:53:07 +02:00
const f32 c = v * s ;
2021-08-13 20:20:19 +02:00
const f32 x = c * ( 1.0f - fabs ( fmod ( h / 60.0f , 2.0f ) - 1.0f ) ) ;
2021-08-04 22:53:07 +02:00
const f32 m = v - c ;
2024-01-14 19:12:38 +01:00
f32 r_tmp { } ;
f32 g_tmp { } ;
f32 b_tmp { } ;
2021-08-04 22:53:07 +02:00
if ( h < 60.0f )
{
r_tmp = c ;
g_tmp = x ;
}
else if ( h < 120.0f )
{
r_tmp = x ;
g_tmp = c ;
}
else if ( h < 180.0f )
{
g_tmp = c ;
b_tmp = x ;
}
else if ( h < 240.0f )
{
g_tmp = x ;
b_tmp = c ;
}
else if ( h < 300.0f )
{
r_tmp = x ;
b_tmp = c ;
}
else
{
r_tmp = c ;
b_tmp = x ;
}
* r = ( r_tmp + m ) * 255.0f ;
* g = ( g_tmp + m ) * 255.0f ;
* b = ( b_tmp + m ) * 255.0f ;
2018-04-01 00:03:37 +02:00
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2020-10-30 16:09:30 +01:00
error_code cellGemInit ( ppu_thread & ppu , vm : : cptr < CellGemAttribute > attribute )
2013-09-28 04:36:57 +02:00
{
2016-01-12 22:57:16 +01:00
cellGem . warning ( " cellGemInit(attribute=*0x%x) " , attribute ) ;
2015-08-21 22:57:49 +02:00
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2015-08-21 22:57:49 +02:00
2019-09-21 12:25:53 +02:00
if ( ! attribute | | ! attribute - > spurs_addr | | ! attribute - > max_connect | | attribute - > max_connect > CELL_GEM_MAX_NUM )
2018-03-02 17:51:21 +01:00
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2021-03-02 12:59:19 +01:00
std : : scoped_lock lock ( gem . mtx ) ;
2019-09-21 12:25:53 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state . compare_and_swap_test ( 0 , 1 ) )
2019-08-25 02:52:28 +02:00
{
return CELL_GEM_ERROR_ALREADY_INITIALIZED ;
}
2019-09-21 12:25:53 +02:00
if ( ! attribute - > memory_ptr )
{
vm : : var < u32 > addr ( 0 ) ;
// Decrease memory stats
2020-10-30 16:09:30 +01:00
if ( sys_memory_allocate ( ppu , GemGetMemorySize ( attribute - > max_connect ) , SYS_MEMORY_PAGE_SIZE_64K , + addr ) ! = CELL_OK )
2019-09-21 12:25:53 +02:00
{
return CELL_GEM_ERROR_RESOURCE_ALLOCATION_FAILED ;
}
2021-03-02 12:59:19 +01:00
gem . memory_ptr = * addr ;
2019-09-21 12:25:53 +02:00
}
else
{
2021-03-02 12:59:19 +01:00
gem . memory_ptr = 0 ;
2019-09-21 12:25:53 +02:00
}
2018-01-24 01:27:25 +01:00
2024-07-08 20:17:21 +02:00
gem . updating = false ;
2021-03-02 12:59:19 +01:00
gem . camera_frame = 0 ;
2024-12-20 13:25:13 +01:00
gem . runtime_status_flags = 0 ;
2021-03-02 12:59:19 +01:00
gem . attribute = * attribute ;
2015-08-21 22:57:49 +02:00
2019-09-21 12:25:53 +02:00
for ( int gem_num = 0 ; gem_num < CELL_GEM_MAX_NUM ; gem_num + + )
2018-03-02 17:51:21 +01:00
{
2021-03-02 12:59:19 +01:00
gem . reset_controller ( gem_num ) ;
2018-03-02 17:51:21 +01:00
}
// TODO: is this correct?
2023-02-22 22:14:40 +01:00
gem . start_timestamp_us = get_guest_system_time ( ) ;
2018-03-02 17:51:21 +01:00
2025-01-08 03:19:35 +01:00
gem . wake_up ( ) ;
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2019-09-08 16:09:44 +02:00
error_code cellGemInvalidateCalibration ( s32 gem_num )
2013-09-28 04:36:57 +02:00
{
2018-04-01 00:03:37 +02:00
cellGem . todo ( " cellGemInvalidateCalibration(gem_num=%d) " , gem_num ) ;
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
std : : scoped_lock lock ( gem . mtx ) ;
2019-09-21 12:25:53 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-04-01 00:03:37 +02:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
if ( ! check_gem_num ( gem_num ) )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2024-12-20 23:38:43 +01:00
auto & controller = gem . controllers [ gem_num ] ;
2021-08-05 00:38:45 +02:00
2022-07-06 14:35:50 +02:00
// TODO: does this really stop an ongoing calibration ?
2024-12-20 23:38:43 +01:00
controller . calibrated_magnetometer = false ;
controller . is_calibrating = false ;
controller . calibration_start_us = 0 ;
controller . calibration_status_flags = 0 ;
controller . hue_set = false ;
controller . enabled_tracking = false ;
2018-04-01 00:03:37 +02:00
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2018-03-02 17:51:21 +01:00
s32 cellGemIsTrackableHue ( u32 hue )
2013-09-28 04:36:57 +02:00
{
2018-03-02 17:51:21 +01:00
cellGem . todo ( " cellGemIsTrackableHue(hue=%d) " , hue ) ;
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-10-20 19:47:19 +02:00
if ( ! gem . state )
2018-03-02 17:51:21 +01:00
{
2021-10-20 19:47:19 +02:00
return CELL_GEM_ERROR_UNINITIALIZED ;
2018-03-02 17:51:21 +01:00
}
2021-10-20 19:47:19 +02:00
if ( hue > 359 )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2024-07-08 20:17:21 +02:00
auto & tracker = g_fxo - > get < named_thread < gem_tracker > > ( ) ;
std : : lock_guard lock ( tracker . mutex ) ;
return tracker . hue_is_trackable ( hue ) ;
2013-09-28 04:36:57 +02:00
}
2019-09-08 16:09:44 +02:00
error_code cellGemPrepareCamera ( s32 max_exposure , f32 image_quality )
2013-09-28 04:36:57 +02:00
{
2018-04-01 00:03:37 +02:00
cellGem . todo ( " cellGemPrepareCamera(max_exposure=%d, image_quality=%f) " , max_exposure , image_quality ) ;
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-04-01 00:03:37 +02:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
2021-08-05 00:35:13 +02:00
if ( false ) // TODO: Check if the camera is currently being prepared.
{
return CELL_EBUSY ;
}
2018-04-01 00:03:37 +02:00
max_exposure = std : : clamp ( max_exposure , static_cast < s32 > ( CELL_GEM_MIN_CAMERA_EXPOSURE ) , static_cast < s32 > ( CELL_GEM_MAX_CAMERA_EXPOSURE ) ) ;
image_quality = std : : clamp ( image_quality , 0.0f , 1.0f ) ;
2025-02-03 20:49:58 +01:00
// TODO: prepare camera properly
extern error_code cellCameraGetAttribute ( s32 dev_num , s32 attrib , vm : : ptr < u32 > arg1 , vm : : ptr < u32 > arg2 ) ;
extern error_code cellCameraSetAttribute ( s32 dev_num , s32 attrib , u32 arg1 , u32 arg2 ) ;
extern error_code cellCameraGetBufferInfoEx ( s32 dev_num , vm : : ptr < CellCameraInfoEx > info ) ;
vm : : var < CellCameraInfoEx > info = vm : : make_var < CellCameraInfoEx > ( { } ) ;
vm : : var < u32 > arg1 = vm : : make_var < u32 > ( { } ) ;
vm : : var < u32 > arg2 = vm : : make_var < u32 > ( { } ) ;
cellCameraGetAttribute ( 0 , 0x3e6 , arg1 , arg2 ) ;
cellCameraSetAttribute ( 0 , 0x3e6 , 0x3e , * arg2 | 0x80 ) ;
cellCameraGetBufferInfoEx ( 0 , info ) ;
if ( info - > width = = 640 )
{
// Disable some features
cellCameraSetAttribute ( 0 , CELL_CAMERA_AGC , 0 , 0 ) ;
cellCameraSetAttribute ( 0 , CELL_CAMERA_AWB , 0 , 0 ) ;
cellCameraSetAttribute ( 0 , CELL_CAMERA_AEC , 0 , 0 ) ;
cellCameraSetAttribute ( 0 , CELL_CAMERA_GAMMA , 0 , 0 ) ;
cellCameraSetAttribute ( 0 , CELL_CAMERA_PIXELOUTLIERFILTER , 0 , 0 ) ;
// Set new values for others
cellCameraSetAttribute ( 0 , CELL_CAMERA_GREENGAIN , 96 , 0 ) ;
cellCameraSetAttribute ( 0 , CELL_CAMERA_REDBLUEGAIN , 64 , 96 ) ;
2025-04-05 21:50:45 +02:00
cellCameraSetAttribute ( 0 , CELL_CAMERA_GAIN , 0 , 0 ) ; // TODO
2025-02-03 20:49:58 +01:00
cellCameraSetAttribute ( 0 , CELL_CAMERA_EXPOSURE , 0 , 0 ) ; // TODO
}
2018-04-01 00:03:37 +02:00
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2019-09-08 16:09:44 +02:00
error_code cellGemPrepareVideoConvert ( vm : : cptr < CellGemVideoConvertAttribute > vc_attribute )
2013-09-28 04:36:57 +02:00
{
2021-10-26 23:48:42 +02:00
cellGem . warning ( " cellGemPrepareVideoConvert(vc_attribute=*0x%x) " , vc_attribute ) ;
2018-03-02 17:51:21 +01:00
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-03-02 17:51:21 +01:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
if ( ! vc_attribute )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2021-10-17 15:35:30 +02:00
const CellGemVideoConvertAttribute vc = * vc_attribute ;
2018-03-02 17:51:21 +01:00
2021-10-20 19:47:19 +02:00
if ( vc . version ! = CELL_GEM_VERSION )
2018-03-02 17:51:21 +01:00
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2021-08-02 23:17:52 +02:00
if ( vc . output_format ! = CELL_GEM_NO_VIDEO_OUTPUT )
{
2021-10-20 19:47:19 +02:00
if ( ! vc . video_data_out )
2021-08-02 23:17:52 +02:00
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
}
2021-10-20 19:47:19 +02:00
if ( ( vc . conversion_flags & CELL_GEM_COMBINE_PREVIOUS_INPUT_FRAME ) & & ! vc . buffer_memory )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
if ( ! vc . video_data_out . aligned ( 128 ) | | ! vc . buffer_memory . aligned ( 16 ) )
{
return CELL_GEM_ERROR_INVALID_ALIGNMENT ;
}
2018-03-02 17:51:21 +01:00
2021-03-02 12:59:19 +01:00
gem . vc_attribute = vc ;
2018-03-02 17:51:21 +01:00
2021-10-20 19:47:19 +02:00
const s32 buffer_size = cellGemGetVideoConvertSize ( vc . output_format ) ;
gem . video_data_out_size = buffer_size ;
2015-07-31 23:47:29 +02:00
return CELL_OK ;
}
2014-10-18 20:14:16 +02:00
2019-09-08 16:09:44 +02:00
error_code cellGemReadExternalPortDeviceInfo ( u32 gem_num , vm : : ptr < u32 > ext_id , vm : : ptr < u8 [ CELL_GEM_EXTERNAL_PORT_DEVICE_INFO_SIZE ] > ext_info )
2015-07-31 23:47:29 +02:00
{
2024-07-08 20:17:21 +02:00
cellGem . warning ( " cellGemReadExternalPortDeviceInfo(gem_num=%d, ext_id=*0x%x, ext_info=%s) " , gem_num , ext_id , ext_info ) ;
2018-04-01 00:03:37 +02:00
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-04-01 00:03:37 +02:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
if ( ! check_gem_num ( gem_num ) | | ! ext_id )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2021-08-05 00:36:13 +02:00
if ( ! gem . is_controller_ready ( gem_num ) )
2018-04-01 00:03:37 +02:00
{
return CELL_GEM_NOT_CONNECTED ;
}
2024-07-08 20:17:21 +02:00
const u64 start = get_system_time ( ) ;
auto & controller = gem . controllers [ gem_num ] ;
if ( g_cfg . io . move ! = move_handler : : null )
{
// Get external device status
CellGemExtPortData ext_port_data { } ;
ds3_input_to_ext ( gem_num , controller , ext_port_data ) ;
}
if ( ! ( controller . ext_status & CELL_GEM_EXT_CONNECTED ) )
2018-04-01 00:03:37 +02:00
{
return CELL_GEM_NO_EXTERNAL_PORT_DEVICE ;
}
2024-07-08 20:17:21 +02:00
* ext_id = controller . ext_id ;
if ( ext_info & & g_cfg . io . move = = move_handler : : real )
{
bool read_requested = false ;
while ( true )
{
{
std : : lock_guard lock ( pad : : g_pad_mutex ) ;
2025-01-20 22:55:21 +01:00
const auto handler = pad : : get_pad_thread ( ) ;
2024-07-08 20:17:21 +02:00
const auto & pad = : : at32 ( handler - > GetPads ( ) , pad_num ( gem_num ) ) ;
if ( pad - > m_pad_handler ! = pad_handler : : move | | ! ( pad - > m_port_status & CELL_PAD_STATUS_CONNECTED ) )
{
return CELL_GEM_NOT_CONNECTED ;
}
if ( ! read_requested )
{
pad - > move_data . external_device_read_requested = true ;
read_requested = true ;
}
if ( ! pad - > move_data . external_device_read_requested )
{
* ext_id = controller . ext_id = pad - > move_data . external_device_id ;
std : : memcpy ( pad - > move_data . external_device_read . data ( ) , ext_info . get_ptr ( ) , CELL_GEM_EXTERNAL_PORT_OUTPUT_SIZE ) ;
break ;
}
}
// We wait for 300ms at most
if ( const u64 elapsed_us = get_system_time ( ) - start ; elapsed_us > 300'000 )
{
cellGem . warning ( " cellGemReadExternalPortDeviceInfo(gem_num=%d): timeout " , gem_num ) ;
break ;
}
// TODO: sleep ?
}
}
2021-07-29 22:33:31 +02:00
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2019-09-08 16:09:44 +02:00
error_code cellGemReset ( u32 gem_num )
2013-09-28 04:36:57 +02:00
{
2018-03-02 17:51:21 +01:00
cellGem . todo ( " cellGemReset(gem_num=%d) " , gem_num ) ;
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-03-02 17:51:21 +01:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
if ( ! check_gem_num ( gem_num ) )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2021-03-02 12:59:19 +01:00
gem . reset_controller ( gem_num ) ;
2018-03-02 17:51:21 +01:00
// TODO: is this correct?
2023-02-22 22:14:40 +01:00
gem . start_timestamp_us = get_guest_system_time ( ) ;
2018-03-02 17:51:21 +01:00
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2019-09-08 16:09:44 +02:00
error_code cellGemSetRumble ( u32 gem_num , u8 rumble )
2013-09-28 04:36:57 +02:00
{
2024-06-27 09:02:41 +02:00
cellGem . trace ( " cellGemSetRumble(gem_num=%d, rumble=0x%x) " , gem_num , rumble ) ;
2018-03-02 17:51:21 +01:00
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
std : : scoped_lock lock ( gem . mtx ) ;
2019-09-21 12:25:53 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-03-02 17:51:21 +01:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
if ( ! check_gem_num ( gem_num ) )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2021-03-02 12:59:19 +01:00
gem . controllers [ gem_num ] . rumble = rumble ;
2018-03-02 17:51:21 +01:00
2024-12-20 00:44:31 +01:00
// Set actual device rumble
if ( g_cfg . io . move = = move_handler : : real )
{
std : : lock_guard pad_lock ( pad : : g_pad_mutex ) ;
2025-01-20 22:55:21 +01:00
const auto handler = pad : : get_pad_thread ( ) ;
2024-12-20 00:44:31 +01:00
auto & handlers = handler - > get_handlers ( ) ;
if ( auto it = handlers . find ( pad_handler : : move ) ; it ! = handlers . end ( ) & & it - > second )
{
const u32 pad_index = pad_num ( gem_num ) ;
for ( const auto & binding : it - > second - > bindings ( ) )
{
2025-04-05 21:50:45 +02:00
if ( ! binding . device | | binding . device - > player_id ! = pad_index )
continue ;
2024-12-20 00:44:31 +01:00
handler - > SetRumble ( pad_index , rumble , rumble > 0 ) ;
break ;
}
}
}
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2021-07-30 00:32:41 +02:00
error_code cellGemSetYaw ( u32 gem_num , vm : : ptr < f32 > z_direction )
2013-09-28 04:36:57 +02:00
{
2021-07-30 00:32:41 +02:00
cellGem . todo ( " cellGemSetYaw(gem_num=%d, z_direction=*0x%x) " , gem_num , z_direction ) ;
auto & gem = g_fxo - > get < gem_config > ( ) ;
std : : scoped_lock lock ( gem . mtx ) ;
if ( ! gem . state )
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
2021-10-20 19:47:19 +02:00
if ( ! z_direction | | ! check_gem_num ( gem_num ) )
2021-07-30 00:32:41 +02:00
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
// TODO
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2019-09-08 16:09:44 +02:00
error_code cellGemTrackHues ( vm : : cptr < u32 > req_hues , vm : : ptr < u32 > res_hues )
2013-09-28 04:36:57 +02:00
{
2024-12-19 23:50:11 +01:00
cellGem . todo ( " cellGemTrackHues(req_hues=%s, res_hues=*0x%x) " , req_hues ? fmt : : format ( " *0x%x [%d, %d, %d, %d] " , req_hues , req_hues [ 0 ] , req_hues [ 1 ] , req_hues [ 2 ] , req_hues [ 3 ] ) : " *0x0 " , res_hues ) ;
2018-04-01 00:03:37 +02:00
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
std : : scoped_lock lock ( gem . mtx ) ;
2019-09-21 12:25:53 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-04-01 00:03:37 +02:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
if ( ! req_hues )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2019-06-28 14:41:47 +02:00
for ( u32 i = 0 ; i < CELL_GEM_MAX_NUM ; i + + )
2018-04-01 00:03:37 +02:00
{
2021-07-29 22:53:57 +02:00
if ( req_hues [ i ] = = CELL_GEM_DONT_CARE_HUE )
2018-04-01 00:03:37 +02:00
{
2021-07-29 22:53:57 +02:00
gem . controllers [ i ] . enabled_tracking = true ;
gem . controllers [ i ] . enabled_LED = true ;
2024-11-28 22:51:39 +01:00
gem . controllers [ i ] . hue_set = true ;
2018-04-01 00:03:37 +02:00
2021-07-29 22:53:57 +02:00
switch ( i )
{
default :
case 0 :
gem . controllers [ i ] . hue = 240 ; // blue
break ;
case 1 :
2025-04-05 21:50:45 +02:00
gem . controllers [ i ] . hue = 0 ; // red
2021-07-29 22:53:57 +02:00
break ;
case 2 :
gem . controllers [ i ] . hue = 120 ; // green
break ;
case 3 :
gem . controllers [ i ] . hue = 300 ; // purple
break ;
}
2024-12-19 23:50:11 +01:00
const auto [ r , g , b ] = ps_move_tracker < false > : : hsv_to_rgb ( gem . controllers [ i ] . hue , 1.0f , 1.0f ) ;
gem . controllers [ i ] . sphere_rgb = gem_config : : gem_color ( r / 255.0f , g / 255.0f , b / 255.0f ) ;
2021-07-29 22:53:57 +02:00
if ( res_hues )
{
res_hues [ i ] = gem . controllers [ i ] . hue ;
}
2018-04-01 00:03:37 +02:00
}
2021-07-29 22:53:57 +02:00
else if ( req_hues [ i ] = = CELL_GEM_DONT_TRACK_HUE )
2018-04-01 00:03:37 +02:00
{
2021-03-02 12:59:19 +01:00
gem . controllers [ i ] . enabled_tracking = false ;
gem . controllers [ i ] . enabled_LED = false ;
2024-11-28 22:51:39 +01:00
gem . controllers [ i ] . hue_set = false ;
2021-07-29 22:53:57 +02:00
if ( res_hues )
{
res_hues [ i ] = CELL_GEM_DONT_TRACK_HUE ;
}
2018-04-01 00:03:37 +02:00
}
else
{
if ( req_hues [ i ] > 359 )
{
cellGem . warning ( " cellGemTrackHues: req_hues[%d]=%d -> this can lead to unexpected behavior " , i , req_hues [ i ] ) ;
}
2021-07-29 22:53:57 +02:00
gem . controllers [ i ] . enabled_tracking = true ;
gem . controllers [ i ] . enabled_LED = true ;
2024-11-28 22:51:39 +01:00
gem . controllers [ i ] . hue_set = true ;
2021-07-29 22:53:57 +02:00
gem . controllers [ i ] . hue = req_hues [ i ] ;
2024-12-19 23:50:11 +01:00
const auto [ r , g , b ] = ps_move_tracker < false > : : hsv_to_rgb ( gem . controllers [ i ] . hue , 1.0f , 1.0f ) ;
gem . controllers [ i ] . sphere_rgb = gem_config : : gem_color ( r / 255.0f , g / 255.0f , b / 255.0f ) ;
2021-07-29 22:53:57 +02:00
if ( res_hues )
{
res_hues [ i ] = gem . controllers [ i ] . hue ;
}
2018-04-01 00:03:37 +02:00
}
}
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2025-01-08 19:08:21 +01:00
error_code cellGemUpdateFinish ( ppu_thread & ppu )
2013-09-28 04:36:57 +02:00
{
2019-12-20 08:17:22 +01:00
cellGem . warning ( " cellGemUpdateFinish() " ) ;
2018-04-01 00:03:37 +02:00
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-04-01 00:03:37 +02:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
2024-07-08 20:17:21 +02:00
if ( ! gem . updating )
2019-10-04 14:37:49 +02:00
{
return CELL_GEM_ERROR_UPDATE_NOT_STARTED ;
}
2024-07-08 20:17:21 +02:00
auto & tracker = g_fxo - > get < named_thread < gem_tracker > > ( ) ;
2025-01-08 19:08:21 +01:00
if ( ! tracker . wait_for_tracker_result ( ppu ) )
{
return { } ;
}
2024-07-08 20:17:21 +02:00
2025-01-21 05:34:16 +01:00
std : : scoped_lock lock ( gem . mtx ) ;
2024-07-08 20:17:21 +02:00
gem . updating = false ;
2021-03-02 12:59:19 +01:00
if ( ! gem . camera_frame )
2019-10-04 14:37:49 +02:00
{
2019-12-20 08:17:22 +01:00
return not_an_error ( CELL_GEM_NO_VIDEO ) ;
2019-10-04 14:37:49 +02:00
}
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2019-09-08 16:09:44 +02:00
error_code cellGemUpdateStart ( vm : : cptr < void > camera_frame , u64 timestamp )
2013-09-28 04:36:57 +02:00
{
2019-12-20 08:17:22 +01:00
cellGem . warning ( " cellGemUpdateStart(camera_frame=*0x%x, timestamp=%d) " , camera_frame , timestamp ) ;
2018-03-02 17:51:21 +01:00
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-03-02 17:51:21 +01:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
2024-07-08 20:17:21 +02:00
auto & tracker = g_fxo - > get < named_thread < gem_tracker > > ( ) ;
if ( tracker . is_busy ( ) )
{
return CELL_GEM_ERROR_UPDATE_NOT_FINISHED ;
}
2021-03-02 12:59:19 +01:00
std : : scoped_lock lock ( gem . mtx ) ;
2019-10-04 14:37:49 +02:00
// Update is starting even when camera_frame is null
2024-07-08 20:17:21 +02:00
if ( gem . updating . exchange ( true ) )
2019-10-04 14:37:49 +02:00
{
return CELL_GEM_ERROR_UPDATE_NOT_FINISHED ;
}
2021-10-20 19:47:19 +02:00
if ( ! camera_frame . aligned ( 128 ) )
{
return CELL_GEM_ERROR_INVALID_ALIGNMENT ;
}
2021-08-05 00:35:13 +02:00
2021-03-02 12:59:19 +01:00
gem . camera_frame = camera_frame . addr ( ) ;
2024-07-08 20:17:21 +02:00
if ( ! tracker . set_image ( gem . camera_frame ) )
2018-04-01 00:03:37 +02:00
{
2019-12-20 08:17:22 +01:00
return not_an_error ( CELL_GEM_NO_VIDEO ) ;
2018-04-01 00:03:37 +02:00
}
2025-01-08 19:08:21 +01:00
tracker . wake_up_tracker ( ) ;
2024-07-08 20:17:21 +02:00
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2019-09-08 16:09:44 +02:00
error_code cellGemWriteExternalPort ( u32 gem_num , vm : : ptr < u8 [ CELL_GEM_EXTERNAL_PORT_OUTPUT_SIZE ] > data )
2013-09-28 04:36:57 +02:00
{
2024-07-08 20:17:21 +02:00
cellGem . warning ( " cellGemWriteExternalPort(gem_num=%d, data=%s) " , gem_num , data ) ;
2018-04-01 00:03:37 +02:00
2021-03-02 12:59:19 +01:00
auto & gem = g_fxo - > get < gem_config > ( ) ;
2019-08-25 02:52:28 +02:00
2021-03-02 12:59:19 +01:00
if ( ! gem . state )
2018-04-01 00:03:37 +02:00
{
return CELL_GEM_ERROR_UNINITIALIZED ;
}
if ( ! check_gem_num ( gem_num ) )
{
return CELL_GEM_ERROR_INVALID_PARAMETER ;
}
2021-08-05 00:35:13 +02:00
if ( ! gem . is_controller_ready ( gem_num ) )
{
return CELL_GEM_NOT_CONNECTED ;
}
2024-07-08 20:17:21 +02:00
if ( data & & g_cfg . io . move = = move_handler : : real )
2021-08-05 00:35:13 +02:00
{
2024-07-08 20:17:21 +02:00
std : : lock_guard lock ( pad : : g_pad_mutex ) ;
2025-01-20 22:55:21 +01:00
const auto handler = pad : : get_pad_thread ( ) ;
2024-07-08 20:17:21 +02:00
const auto & pad = : : at32 ( handler - > GetPads ( ) , pad_num ( gem_num ) ) ;
if ( pad - > m_pad_handler ! = pad_handler : : move | | ! ( pad - > m_port_status & CELL_PAD_STATUS_CONNECTED ) )
{
return CELL_GEM_NOT_CONNECTED ;
}
if ( pad - > move_data . external_device_write_requested )
{
return CELL_GEM_ERROR_WRITE_NOT_FINISHED ;
}
std : : memcpy ( pad - > move_data . external_device_write . data ( ) , data . get_ptr ( ) , CELL_GEM_EXTERNAL_PORT_OUTPUT_SIZE ) ;
pad - > move_data . external_device_write_requested = true ;
2021-08-05 00:35:13 +02:00
}
2013-09-28 04:36:57 +02:00
return CELL_OK ;
}
2016-03-21 20:43:03 +01:00
DECLARE ( ppu_module_manager : : cellGem ) ( " libgem " , [ ] ( )
2025-04-05 21:50:45 +02:00
{
REG_FUNC ( libgem , cellGemCalibrate ) ;
REG_FUNC ( libgem , cellGemClearStatusFlags ) ;
REG_FUNC ( libgem , cellGemConvertVideoFinish ) ;
REG_FUNC ( libgem , cellGemConvertVideoStart ) ;
REG_FUNC ( libgem , cellGemEnableCameraPitchAngleCorrection ) ;
REG_FUNC ( libgem , cellGemEnableMagnetometer ) ;
REG_FUNC ( libgem , cellGemEnableMagnetometer2 ) ;
REG_FUNC ( libgem , cellGemEnd ) ;
REG_FUNC ( libgem , cellGemFilterState ) ;
REG_FUNC ( libgem , cellGemForceRGB ) ;
REG_FUNC ( libgem , cellGemGetAccelerometerPositionInDevice ) ;
REG_FUNC ( libgem , cellGemGetAllTrackableHues ) ;
REG_FUNC ( libgem , cellGemGetCameraState ) ;
REG_FUNC ( libgem , cellGemGetEnvironmentLightingColor ) ;
REG_FUNC ( libgem , cellGemGetHuePixels ) ;
REG_FUNC ( libgem , cellGemGetImageState ) ;
REG_FUNC ( libgem , cellGemGetInertialState ) ;
REG_FUNC ( libgem , cellGemGetInfo ) ;
REG_FUNC ( libgem , cellGemGetMemorySize ) ;
REG_FUNC ( libgem , cellGemGetRGB ) ;
REG_FUNC ( libgem , cellGemGetRumble ) ;
REG_FUNC ( libgem , cellGemGetState ) ;
REG_FUNC ( libgem , cellGemGetStatusFlags ) ;
REG_FUNC ( libgem , cellGemGetTrackerHue ) ;
REG_FUNC ( libgem , cellGemHSVtoRGB ) ;
REG_FUNC ( libgem , cellGemInit ) ;
REG_FUNC ( libgem , cellGemInvalidateCalibration ) ;
REG_FUNC ( libgem , cellGemIsTrackableHue ) ;
REG_FUNC ( libgem , cellGemPrepareCamera ) ;
REG_FUNC ( libgem , cellGemPrepareVideoConvert ) ;
REG_FUNC ( libgem , cellGemReadExternalPortDeviceInfo ) ;
REG_FUNC ( libgem , cellGemReset ) ;
REG_FUNC ( libgem , cellGemSetRumble ) ;
REG_FUNC ( libgem , cellGemSetYaw ) ;
REG_FUNC ( libgem , cellGemTrackHues ) ;
REG_FUNC ( libgem , cellGemUpdateFinish ) ;
REG_FUNC ( libgem , cellGemUpdateStart ) ;
REG_FUNC ( libgem , cellGemWriteExternalPort ) ;
} ) ;