mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-07-13 18:28:35 +12:00
[HLE] First steps to Playstation Move (#4083)
* [sysutil] Add Magnetometer system param * [ui] Add UI for Move handler Current options are "Null" and "Fake". * cellGem: Improvements * cellCamera: Improvements
This commit is contained in:
parent
f96e9b6ed7
commit
504e3112dd
11 changed files with 1218 additions and 155 deletions
|
@ -1,12 +1,26 @@
|
||||||
#include "stdafx.h"
|
#include "stdafx.h"
|
||||||
#include "Emu/IdManager.h"
|
|
||||||
#include "Emu/System.h"
|
|
||||||
#include "Emu/Cell/PPUModule.h"
|
|
||||||
|
|
||||||
#include "cellCamera.h"
|
#include "cellCamera.h"
|
||||||
|
|
||||||
|
#include "Emu/Cell/PPUModule.h"
|
||||||
|
#include "Emu/Cell/lv2/sys_event.h"
|
||||||
|
#include "Emu/IdManager.h"
|
||||||
|
#include "Emu/Io/PadHandler.h"
|
||||||
|
#include "Emu/System.h"
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
logs::channel cellCamera("cellCamera");
|
logs::channel cellCamera("cellCamera");
|
||||||
|
|
||||||
|
// **************
|
||||||
|
// * Prototypes *
|
||||||
|
// **************
|
||||||
|
|
||||||
|
s32 cellCameraSetAttribute(s32 dev_num, s32 attrib, u32 arg1, u32 arg2);
|
||||||
|
|
||||||
|
// ************************
|
||||||
|
// * HLE helper functions *
|
||||||
|
// ************************
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
void fmt_class_string<camera_handler>::format(std::string& out, u64 arg)
|
void fmt_class_string<camera_handler>::format(std::string& out, u64 arg)
|
||||||
{
|
{
|
||||||
|
@ -97,71 +111,126 @@ static const char* get_camera_attr_name(s32 value)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom struct to keep track of cameras
|
static bool check_dev_num(u32 dev_num)
|
||||||
struct camera_t
|
|
||||||
{
|
{
|
||||||
struct attr_t
|
return dev_num == 0;
|
||||||
{
|
}
|
||||||
u32 v1, v2;
|
|
||||||
};
|
|
||||||
|
|
||||||
attr_t attr[500]{};
|
/**
|
||||||
};
|
* \brief Sets read mode attribute (used for deciding how image data is passed to games)
|
||||||
|
* Also sends it to the camera thread
|
||||||
|
* NOTE: thread-safe (uses camera_thread::mutex)
|
||||||
|
* \param dev_num Device number (always 0)
|
||||||
|
* \param read_mode Either CELL_CAMERA_READ_FUNCCALL or CELL_CAMERA_READ_DIRECT
|
||||||
|
* \return CELL error code or CELL_OK
|
||||||
|
*/
|
||||||
|
u32 set_and_send_read_mode(s32 dev_num, const s32 read_mode)
|
||||||
|
{
|
||||||
|
if (read_mode == CELL_CAMERA_READ_FUNCCALL ||
|
||||||
|
read_mode == CELL_CAMERA_READ_DIRECT)
|
||||||
|
{
|
||||||
|
if (const auto status = cellCameraSetAttribute(dev_num, CELL_CAMERA_READMODE, read_mode, 0))
|
||||||
|
{
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cellCamera.error("Unknown read mode set: %d", read_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send read mode to camera thread
|
||||||
|
const auto g_camera = fxm::get<camera_thread>();
|
||||||
|
|
||||||
|
g_camera->read_mode.exchange(read_mode);
|
||||||
|
|
||||||
|
return CELL_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<u32, u32> get_video_resolution(const CellCameraInfoEx& info)
|
||||||
|
{
|
||||||
|
std::pair<u32, u32> res;
|
||||||
|
switch (info.resolution)
|
||||||
|
{
|
||||||
|
case CELL_CAMERA_VGA: return{ 640, 480 };
|
||||||
|
case CELL_CAMERA_QVGA: return { 320, 240 };
|
||||||
|
case CELL_CAMERA_WGA: return{ 640, 360 };
|
||||||
|
case CELL_CAMERA_SPECIFIED_WIDTH_HEIGHT: return{ info.width, info.height };
|
||||||
|
case CELL_CAMERA_RESOLUTION_UNKNOWN:
|
||||||
|
default: return{ 0, 0 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 get_video_buffer_size(const CellCameraInfoEx& info)
|
||||||
|
{
|
||||||
|
u32 width, height;
|
||||||
|
std::tie(width, height) = get_video_resolution(info);
|
||||||
|
|
||||||
|
const auto bpp = 4;
|
||||||
|
return width * height * bpp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ************************
|
||||||
|
// * cellCamera functions *
|
||||||
|
// ************************
|
||||||
|
|
||||||
s32 cellCameraInit()
|
s32 cellCameraInit()
|
||||||
{
|
{
|
||||||
cellCamera.warning("cellCameraInit()");
|
cellCamera.todo("cellCameraInit()");
|
||||||
|
|
||||||
if (g_cfg.io.camera == camera_handler::null)
|
if (g_cfg.io.camera == camera_handler::null)
|
||||||
{
|
{
|
||||||
return CELL_CAMERA_ERROR_DEVICE_NOT_FOUND;
|
return CELL_CAMERA_ERROR_DEVICE_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto camera = fxm::make<camera_t>();
|
// Start camera thread
|
||||||
|
const auto g_camera = fxm::make<camera_thread>();
|
||||||
|
|
||||||
if (!camera)
|
if (!g_camera)
|
||||||
{
|
{
|
||||||
return CELL_CAMERA_ERROR_ALREADY_INIT;
|
return CELL_CAMERA_ERROR_ALREADY_INIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
semaphore_lock lock(g_camera->mutex);
|
||||||
|
|
||||||
switch (g_cfg.io.camera_type)
|
switch (g_cfg.io.camera_type)
|
||||||
{
|
{
|
||||||
case fake_camera_type::eyetoy:
|
case fake_camera_type::eyetoy:
|
||||||
{
|
{
|
||||||
camera->attr[CELL_CAMERA_SATURATION] = { 164 };
|
g_camera->attr[CELL_CAMERA_SATURATION] = { 164 };
|
||||||
camera->attr[CELL_CAMERA_BRIGHTNESS] = { 96 };
|
g_camera->attr[CELL_CAMERA_BRIGHTNESS] = { 96 };
|
||||||
camera->attr[CELL_CAMERA_AEC] = { 1 };
|
g_camera->attr[CELL_CAMERA_AEC] = { 1 };
|
||||||
camera->attr[CELL_CAMERA_AGC] = { 1 };
|
g_camera->attr[CELL_CAMERA_AGC] = { 1 };
|
||||||
camera->attr[CELL_CAMERA_AWB] = { 1 };
|
g_camera->attr[CELL_CAMERA_AWB] = { 1 };
|
||||||
camera->attr[CELL_CAMERA_ABC] = { 0 };
|
g_camera->attr[CELL_CAMERA_ABC] = { 0 };
|
||||||
camera->attr[CELL_CAMERA_LED] = { 1 };
|
g_camera->attr[CELL_CAMERA_LED] = { 1 };
|
||||||
camera->attr[CELL_CAMERA_QS] = { 0 };
|
g_camera->attr[CELL_CAMERA_QS] = { 0 };
|
||||||
camera->attr[CELL_CAMERA_NONZEROCOEFFS] = { 32, 32 };
|
g_camera->attr[CELL_CAMERA_NONZEROCOEFFS] = { 32, 32 };
|
||||||
camera->attr[CELL_CAMERA_YUVFLAG] = { 0 };
|
g_camera->attr[CELL_CAMERA_YUVFLAG] = { 0 };
|
||||||
camera->attr[CELL_CAMERA_BACKLIGHTCOMP] = { 0 };
|
g_camera->attr[CELL_CAMERA_BACKLIGHTCOMP] = { 0 };
|
||||||
camera->attr[CELL_CAMERA_MIRRORFLAG] = { 1 };
|
g_camera->attr[CELL_CAMERA_MIRRORFLAG] = { 1 };
|
||||||
camera->attr[CELL_CAMERA_422FLAG] = { 1 };
|
g_camera->attr[CELL_CAMERA_422FLAG] = { 1 };
|
||||||
camera->attr[CELL_CAMERA_USBLOAD] = { 4 };
|
g_camera->attr[CELL_CAMERA_USBLOAD] = { 4 };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case fake_camera_type::eyetoy2:
|
case fake_camera_type::eyetoy2:
|
||||||
{
|
{
|
||||||
camera->attr[CELL_CAMERA_SATURATION] = { 64 };
|
g_camera->attr[CELL_CAMERA_SATURATION] = { 64 };
|
||||||
camera->attr[CELL_CAMERA_BRIGHTNESS] = { 8 };
|
g_camera->attr[CELL_CAMERA_BRIGHTNESS] = { 8 };
|
||||||
camera->attr[CELL_CAMERA_AEC] = { 1 };
|
g_camera->attr[CELL_CAMERA_AEC] = { 1 };
|
||||||
camera->attr[CELL_CAMERA_AGC] = { 1 };
|
g_camera->attr[CELL_CAMERA_AGC] = { 1 };
|
||||||
camera->attr[CELL_CAMERA_AWB] = { 1 };
|
g_camera->attr[CELL_CAMERA_AWB] = { 1 };
|
||||||
camera->attr[CELL_CAMERA_LED] = { 1 };
|
g_camera->attr[CELL_CAMERA_LED] = { 1 };
|
||||||
camera->attr[CELL_CAMERA_BACKLIGHTCOMP] = { 0 };
|
g_camera->attr[CELL_CAMERA_BACKLIGHTCOMP] = { 0 };
|
||||||
camera->attr[CELL_CAMERA_MIRRORFLAG] = { 1 };
|
g_camera->attr[CELL_CAMERA_MIRRORFLAG] = { 1 };
|
||||||
camera->attr[CELL_CAMERA_GAMMA] = { 1 };
|
g_camera->attr[CELL_CAMERA_GAMMA] = { 1 };
|
||||||
camera->attr[CELL_CAMERA_AGCLIMIT] = { 4 };
|
g_camera->attr[CELL_CAMERA_AGCLIMIT] = { 4 };
|
||||||
camera->attr[CELL_CAMERA_DENOISE] = { 0 };
|
g_camera->attr[CELL_CAMERA_DENOISE] = { 0 };
|
||||||
camera->attr[CELL_CAMERA_FRAMERATEADJUST] = { 0 };
|
g_camera->attr[CELL_CAMERA_FRAMERATEADJUST] = { 0 };
|
||||||
camera->attr[CELL_CAMERA_PIXELOUTLIERFILTER] = { 1 };
|
g_camera->attr[CELL_CAMERA_PIXELOUTLIERFILTER] = { 1 };
|
||||||
camera->attr[CELL_CAMERA_AGCLOW] = { 48 };
|
g_camera->attr[CELL_CAMERA_AGCLOW] = { 48 };
|
||||||
camera->attr[CELL_CAMERA_AGCHIGH] = { 64 };
|
g_camera->attr[CELL_CAMERA_AGCHIGH] = { 64 };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -175,9 +244,9 @@ s32 cellCameraInit()
|
||||||
|
|
||||||
s32 cellCameraEnd()
|
s32 cellCameraEnd()
|
||||||
{
|
{
|
||||||
cellCamera.warning("cellCameraEnd()");
|
cellCamera.todo("cellCameraEnd()");
|
||||||
|
|
||||||
if (!fxm::remove<camera_t>())
|
if (!fxm::remove<camera_thread>())
|
||||||
{
|
{
|
||||||
return CELL_CAMERA_ERROR_NOT_INIT;
|
return CELL_CAMERA_ERROR_NOT_INIT;
|
||||||
}
|
}
|
||||||
|
@ -185,7 +254,7 @@ s32 cellCameraEnd()
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellCameraOpen()
|
s32 cellCameraOpen() // seems unused
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellCamera);
|
UNIMPLEMENTED_FUNC(cellCamera);
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
|
@ -193,13 +262,63 @@ s32 cellCameraOpen()
|
||||||
|
|
||||||
s32 cellCameraOpenEx(s32 dev_num, vm::ptr<CellCameraInfoEx> info)
|
s32 cellCameraOpenEx(s32 dev_num, vm::ptr<CellCameraInfoEx> info)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellCamera);
|
cellCamera.todo("cellCameraOpenEx(dev_num=%d, type=*0x%x)", dev_num, info);
|
||||||
|
|
||||||
|
const auto g_camera = fxm::get<camera_thread>();
|
||||||
|
|
||||||
|
if (!g_camera)
|
||||||
|
{
|
||||||
|
return CELL_CAMERA_ERROR_NOT_INIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 read_mode = info->read_mode;
|
||||||
|
|
||||||
|
u32 status = set_and_send_read_mode(dev_num, read_mode);
|
||||||
|
if (status != CELL_OK)
|
||||||
|
{
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = cellCameraSetAttribute(dev_num, CELL_CAMERA_GAMEPID, status, 0); // yup, that's what libGem does
|
||||||
|
if (status != CELL_OK)
|
||||||
|
{
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto vbuf_size = get_video_buffer_size(*info);
|
||||||
|
|
||||||
|
if (info->read_mode == CELL_CAMERA_READ_FUNCCALL && !info->buffer)
|
||||||
|
{
|
||||||
|
info->buffer = vm::cast(vm::alloc(vbuf_size, vm::memory_location_t::main));
|
||||||
|
info->bytesize = vbuf_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tie(info->width, info->height) = get_video_resolution(*info);
|
||||||
|
|
||||||
|
semaphore_lock lock(g_camera->mutex);
|
||||||
|
|
||||||
|
g_camera->is_open = true;
|
||||||
|
g_camera->info = *info;
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellCameraClose(s32 dev_num)
|
s32 cellCameraClose(s32 dev_num)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellCamera);
|
cellCamera.todo("cellCameraClose(dev_num=%d)", dev_num);
|
||||||
|
|
||||||
|
const auto g_camera = fxm::get<camera_thread>();
|
||||||
|
|
||||||
|
if (!g_camera)
|
||||||
|
{
|
||||||
|
return CELL_CAMERA_ERROR_NOT_INIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
semaphore_lock lock(g_camera->mutex);
|
||||||
|
|
||||||
|
vm::dealloc(g_camera->info.buffer.addr(), vm::memory_location_t::main);
|
||||||
|
g_camera->is_open = false;
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,15 +330,20 @@ s32 cellCameraGetDeviceGUID(s32 dev_num, vm::ptr<u32> guid)
|
||||||
|
|
||||||
s32 cellCameraGetType(s32 dev_num, vm::ptr<s32> type)
|
s32 cellCameraGetType(s32 dev_num, vm::ptr<s32> type)
|
||||||
{
|
{
|
||||||
cellCamera.warning("cellCameraGetType(dev_num=%d, type=*0x%x)", dev_num, type);
|
cellCamera.todo("cellCameraGetType(dev_num=%d, type=*0x%x)", dev_num, type);
|
||||||
|
|
||||||
const auto camera = fxm::get<camera_t>();
|
const auto g_camera = fxm::get<camera_thread>();
|
||||||
|
|
||||||
if (!camera)
|
if (!g_camera)
|
||||||
{
|
{
|
||||||
return CELL_CAMERA_ERROR_NOT_INIT;
|
return CELL_CAMERA_ERROR_NOT_INIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!check_dev_num(dev_num) || !type )
|
||||||
|
{
|
||||||
|
return CELL_CAMERA_ERROR_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
switch (g_cfg.io.camera_type)
|
switch (g_cfg.io.camera_type)
|
||||||
{
|
{
|
||||||
case fake_camera_type::unknown: *type = CELL_CAMERA_TYPE_UNKNOWN; break;
|
case fake_camera_type::unknown: *type = CELL_CAMERA_TYPE_UNKNOWN; break;
|
||||||
|
@ -234,42 +358,80 @@ s32 cellCameraGetType(s32 dev_num, vm::ptr<s32> type)
|
||||||
s32 cellCameraIsAvailable(s32 dev_num)
|
s32 cellCameraIsAvailable(s32 dev_num)
|
||||||
{
|
{
|
||||||
cellCamera.todo("cellCameraIsAvailable(dev_num=%d)", dev_num);
|
cellCamera.todo("cellCameraIsAvailable(dev_num=%d)", dev_num);
|
||||||
return CELL_OK;
|
|
||||||
|
const auto g_camera = fxm::get<camera_thread>();
|
||||||
|
|
||||||
|
if (!g_camera || !check_dev_num(dev_num))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellCameraIsAttached(s32 dev_num)
|
s32 cellCameraIsAttached(s32 dev_num)
|
||||||
{
|
{
|
||||||
cellCamera.warning("cellCameraIsAttached(dev_num=%d)", dev_num);
|
cellCamera.todo("cellCameraIsAttached(dev_num=%d)", dev_num);
|
||||||
|
|
||||||
if (g_cfg.io.camera == camera_handler::fake)
|
if (g_cfg.io.camera == camera_handler::null)
|
||||||
{
|
{
|
||||||
return 1;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0; // It's not CELL_OK lol
|
const auto g_camera = fxm::get<camera_thread>();
|
||||||
|
|
||||||
|
semaphore_lock lock(g_camera->mutex);
|
||||||
|
|
||||||
|
bool is_attached = g_camera->is_attached;
|
||||||
|
|
||||||
|
// "attach" camera here
|
||||||
|
if (!is_attached)
|
||||||
|
{
|
||||||
|
g_camera->send_attach_state(true);
|
||||||
|
is_attached = g_camera->is_attached;
|
||||||
|
}
|
||||||
|
|
||||||
|
return is_attached;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellCameraIsOpen(s32 dev_num)
|
s32 cellCameraIsOpen(s32 dev_num)
|
||||||
{
|
{
|
||||||
cellCamera.todo("cellCameraIsOpen(dev_num=%d)", dev_num);
|
cellCamera.todo("cellCameraIsOpen(dev_num=%d)", dev_num);
|
||||||
return CELL_OK;
|
|
||||||
|
if (g_cfg.io.camera == camera_handler::null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto g_camera = fxm::get<camera_thread>();
|
||||||
|
|
||||||
|
bool is_open = g_camera->is_open;
|
||||||
|
return is_open;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellCameraIsStarted(s32 dev_num)
|
s32 cellCameraIsStarted(s32 dev_num)
|
||||||
{
|
{
|
||||||
cellCamera.todo("cellCameraIsStarted(dev_num=%d)", dev_num);
|
cellCamera.todo("cellCameraIsStarted(dev_num=%d)", dev_num);
|
||||||
return CELL_OK;
|
|
||||||
|
if (g_cfg.io.camera == camera_handler::null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto g_camera = fxm::get<camera_thread>();
|
||||||
|
|
||||||
|
bool is_streaming = g_camera->is_streaming;
|
||||||
|
return is_streaming;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellCameraGetAttribute(s32 dev_num, s32 attrib, vm::ptr<u32> arg1, vm::ptr<u32> arg2)
|
s32 cellCameraGetAttribute(s32 dev_num, s32 attrib, vm::ptr<u32> arg1, vm::ptr<u32> arg2)
|
||||||
{
|
{
|
||||||
cellCamera.warning("cellCameraGetAttribute(dev_num=%d, attrib=%d, arg1=*0x%x, arg2=*0x%x)", dev_num, attrib, arg1, arg2);
|
|
||||||
|
|
||||||
const auto attr_name = get_camera_attr_name(attrib);
|
const auto attr_name = get_camera_attr_name(attrib);
|
||||||
|
cellCamera.todo("cellCameraGetAttribute: get attrib %s to: 0x%x - 0x%x)", attr_name ? attr_name : "(invalid)", arg1, arg2);
|
||||||
|
|
||||||
const auto camera = fxm::get<camera_t>();
|
const auto g_camera = fxm::get<camera_thread>();
|
||||||
|
|
||||||
if (!camera)
|
if (!g_camera)
|
||||||
{
|
{
|
||||||
return CELL_CAMERA_ERROR_NOT_INIT;
|
return CELL_CAMERA_ERROR_NOT_INIT;
|
||||||
}
|
}
|
||||||
|
@ -279,21 +441,30 @@ s32 cellCameraGetAttribute(s32 dev_num, s32 attrib, vm::ptr<u32> arg1, vm::ptr<u
|
||||||
return CELL_CAMERA_ERROR_PARAM;
|
return CELL_CAMERA_ERROR_PARAM;
|
||||||
}
|
}
|
||||||
|
|
||||||
*arg1 = camera->attr[attrib].v1;
|
cellCamera.todo("cellCameraGetAttribute: get attrib %s arg1: %d arg2: %d", attr_name, arg1, arg2);
|
||||||
*arg2 = camera->attr[attrib].v2;
|
|
||||||
|
semaphore_lock lock(g_camera->mutex);
|
||||||
|
|
||||||
|
if (arg1)
|
||||||
|
{
|
||||||
|
*arg1 = g_camera->attr[attrib].v1;
|
||||||
|
}
|
||||||
|
if (arg2)
|
||||||
|
{
|
||||||
|
*arg2 = g_camera->attr[attrib].v2;
|
||||||
|
}
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellCameraSetAttribute(s32 dev_num, s32 attrib, u32 arg1, u32 arg2)
|
s32 cellCameraSetAttribute(s32 dev_num, s32 attrib, u32 arg1, u32 arg2)
|
||||||
{
|
{
|
||||||
cellCamera.warning("cellCameraSetAttribute(dev_num=%d, attrib=%d, arg1=%d, arg2=%d)", dev_num, attrib, arg1, arg2);
|
|
||||||
|
|
||||||
const auto attr_name = get_camera_attr_name(attrib);
|
const auto attr_name = get_camera_attr_name(attrib);
|
||||||
|
cellCamera.todo("cellCameraSetAttribute: set attrib %s to: %d - %d)", attr_name ? attr_name : "(invalid)", arg1, arg2);
|
||||||
|
|
||||||
const auto camera = fxm::get<camera_t>();
|
const auto g_camera = fxm::get<camera_thread>();
|
||||||
|
|
||||||
if (!camera)
|
if (!g_camera)
|
||||||
{
|
{
|
||||||
return CELL_CAMERA_ERROR_NOT_INIT;
|
return CELL_CAMERA_ERROR_NOT_INIT;
|
||||||
}
|
}
|
||||||
|
@ -303,15 +474,45 @@ s32 cellCameraSetAttribute(s32 dev_num, s32 attrib, u32 arg1, u32 arg2)
|
||||||
return CELL_CAMERA_ERROR_PARAM;
|
return CELL_CAMERA_ERROR_PARAM;
|
||||||
}
|
}
|
||||||
|
|
||||||
camera->attr[attrib] = { arg1, arg2 };
|
semaphore_lock lock(g_camera->mutex);
|
||||||
|
g_camera->attr[attrib] = { arg1, arg2 };
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellCameraGetBufferSize(s32 dev_num, vm::ptr<CellCameraInfoEx> info)
|
s32 cellCameraGetBufferSize(s32 dev_num, vm::ptr<CellCameraInfoEx> info)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellCamera);
|
cellCamera.todo("cellCameraGetBufferSize(dev_num=%d, info=*0x%x)", dev_num, info);
|
||||||
return CELL_OK;
|
|
||||||
|
const auto g_camera = fxm::get<camera_thread>();
|
||||||
|
|
||||||
|
if (!g_camera)
|
||||||
|
{
|
||||||
|
return CELL_CAMERA_ERROR_NOT_INIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: a bunch of arg checks. here's one
|
||||||
|
if (!cellCameraIsAttached(dev_num))
|
||||||
|
{
|
||||||
|
return CELL_CAMERA_ERROR_DEVICE_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto read_mode = info->read_mode;
|
||||||
|
|
||||||
|
u32 status = set_and_send_read_mode(dev_num, read_mode);
|
||||||
|
if (status != CELL_OK)
|
||||||
|
{
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
semaphore_lock lock(g_camera->mutex);
|
||||||
|
|
||||||
|
info->bytesize = get_video_buffer_size(g_camera->info);
|
||||||
|
info->buffer = g_camera->info.buffer;
|
||||||
|
|
||||||
|
g_camera->info = *info;
|
||||||
|
|
||||||
|
return info->bytesize;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellCameraGetBufferInfo()
|
s32 cellCameraGetBufferInfo()
|
||||||
|
@ -322,7 +523,24 @@ s32 cellCameraGetBufferInfo()
|
||||||
|
|
||||||
s32 cellCameraGetBufferInfoEx(s32 dev_num, vm::ptr<CellCameraInfoEx> info)
|
s32 cellCameraGetBufferInfoEx(s32 dev_num, vm::ptr<CellCameraInfoEx> info)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellCamera);
|
cellCamera.todo("cellCameraReadEx(dev_num=%d, read=0x%x)", dev_num, info);
|
||||||
|
|
||||||
|
const auto g_camera = fxm::get<camera_thread>();
|
||||||
|
|
||||||
|
if (!g_camera)
|
||||||
|
{
|
||||||
|
return CELL_CAMERA_ERROR_NOT_INIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!info)
|
||||||
|
{
|
||||||
|
return CELL_CAMERA_ERROR_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
semaphore_lock lock(g_camera->mutex);
|
||||||
|
|
||||||
|
*info = g_camera->info;
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -353,12 +571,21 @@ s32 cellCameraSetExtensionUnit(s32 dev_num, u16 value, u16 length, vm::ptr<u8> d
|
||||||
s32 cellCameraReset(s32 dev_num)
|
s32 cellCameraReset(s32 dev_num)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellCamera);
|
UNIMPLEMENTED_FUNC(cellCamera);
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellCameraStart(s32 dev_num)
|
s32 cellCameraStart(s32 dev_num)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellCamera);
|
cellCamera.todo("cellCameraStart(dev_num=%d", dev_num);
|
||||||
|
|
||||||
|
const auto g_camera = fxm::get<camera_thread>();
|
||||||
|
|
||||||
|
semaphore_lock lock(g_camera->mutex);
|
||||||
|
|
||||||
|
g_camera->timer.Start();
|
||||||
|
g_camera->is_streaming = true;
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,19 +597,46 @@ s32 cellCameraRead(s32 dev_num, vm::ptr<u32> frame_num, vm::ptr<u32> bytes_read)
|
||||||
|
|
||||||
s32 cellCameraReadEx(s32 dev_num, vm::ptr<CellCameraReadEx> read)
|
s32 cellCameraReadEx(s32 dev_num, vm::ptr<CellCameraReadEx> read)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellCamera);
|
cellCamera.todo("cellCameraReadEx(dev_num=%d, read=0x%x)", dev_num, read);
|
||||||
|
|
||||||
|
const auto g_camera = fxm::get<camera_thread>();
|
||||||
|
|
||||||
|
if (!g_camera)
|
||||||
|
{
|
||||||
|
return CELL_CAMERA_ERROR_NOT_INIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
semaphore_lock lock(g_camera->mutex);
|
||||||
|
|
||||||
|
read->timestamp = g_camera->timer.GetElapsedTimeInMicroSec();
|
||||||
|
read->frame = g_camera->frame_num;
|
||||||
|
read->bytesread = g_camera->is_streaming ?
|
||||||
|
get_video_buffer_size(g_camera->info) : 0;
|
||||||
|
|
||||||
|
auto shared_data = fxm::get_always<gem_camera_shared>();
|
||||||
|
|
||||||
|
shared_data->frame_timestamp.exchange(read->timestamp);
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellCameraReadComplete(s32 dev_num, u32 bufnum, u32 arg2)
|
s32 cellCameraReadComplete(s32 dev_num, u32 bufnum, u32 arg2)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellCamera);
|
cellCamera.todo("cellCameraReadComplete(dev_num=%d, bufnum=%d, arg2=%d)", dev_num, bufnum, arg2);
|
||||||
return CELL_OK;
|
return cellCameraSetAttribute(dev_num, CELL_CAMERA_READFINISH, bufnum, arg2);
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellCameraStop(s32 dev_num)
|
s32 cellCameraStop(s32 dev_num)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellCamera);
|
cellCamera.todo("cellCameraStop(dev_num=%d", dev_num);
|
||||||
|
|
||||||
|
const auto g_camera = fxm::get<camera_thread>();
|
||||||
|
|
||||||
|
semaphore_lock lock(g_camera->mutex);
|
||||||
|
|
||||||
|
g_camera->is_streaming = false;
|
||||||
|
g_camera->timer.Stop();
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -400,13 +654,43 @@ s32 cellCameraRemoveNotifyEventQueue(u64 key)
|
||||||
|
|
||||||
s32 cellCameraSetNotifyEventQueue2(u64 key, u64 source, u64 flag)
|
s32 cellCameraSetNotifyEventQueue2(u64 key, u64 source, u64 flag)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellCamera);
|
cellCamera.todo("cellCameraSetNotifyEventQueue2(key=0x%x, source=%d, flag=%d)", key, source, flag);
|
||||||
|
|
||||||
|
const auto g_camera = fxm::get<camera_thread>();
|
||||||
|
|
||||||
|
if (!g_camera)
|
||||||
|
{
|
||||||
|
return CELL_CAMERA_ERROR_NOT_INIT;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
semaphore_lock lock_data_map(g_camera->mutex_notify_data_map);
|
||||||
|
|
||||||
|
g_camera->notify_data_map[key] = { source, flag };
|
||||||
|
}
|
||||||
|
{
|
||||||
|
semaphore_lock lock(g_camera->mutex);
|
||||||
|
|
||||||
|
// send ATTACH event if necessary - HACKY
|
||||||
|
g_camera->send_attach_state(true);
|
||||||
|
}
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellCameraRemoveNotifyEventQueue2(u64 key)
|
s32 cellCameraRemoveNotifyEventQueue2(u64 key)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellCamera);
|
cellCamera.todo("cellCameraRemoveNotifyEventQueue2(key=0x%x", key);
|
||||||
|
|
||||||
|
const auto g_camera = fxm::get<camera_thread>();
|
||||||
|
|
||||||
|
if (!g_camera)
|
||||||
|
{
|
||||||
|
return CELL_CAMERA_ERROR_NOT_INIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
semaphore_lock lock(g_camera->mutex_notify_data_map);
|
||||||
|
|
||||||
|
g_camera->notify_data_map.erase(key);
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -447,3 +731,114 @@ DECLARE(ppu_module_manager::cellCamera)("cellCamera", []()
|
||||||
REG_FUNC(cellCamera, cellCameraSetNotifyEventQueue2);
|
REG_FUNC(cellCamera, cellCameraSetNotifyEventQueue2);
|
||||||
REG_FUNC(cellCamera, cellCameraRemoveNotifyEventQueue2);
|
REG_FUNC(cellCamera, cellCameraRemoveNotifyEventQueue2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
void camera_thread::on_task()
|
||||||
|
{
|
||||||
|
while (fxm::check<camera_thread>() && !Emu.IsStopped())
|
||||||
|
{
|
||||||
|
std::chrono::steady_clock::time_point frame_start = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
if (Emu.IsPaused())
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for(1ms); // hack
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
semaphore_lock lock(mutex_notify_data_map);
|
||||||
|
|
||||||
|
for (auto const& notify_data_entry : notify_data_map)
|
||||||
|
{
|
||||||
|
const auto& key = notify_data_entry.first;
|
||||||
|
const auto& evt_data = notify_data_entry.second;
|
||||||
|
|
||||||
|
// handle FRAME_UPDATE
|
||||||
|
if (is_streaming &&
|
||||||
|
evt_data.flag & CELL_CAMERA_EFLAG_FRAME_UPDATE &&
|
||||||
|
info.framerate != 0)
|
||||||
|
{
|
||||||
|
if (auto queue = lv2_event_queue::find(key))
|
||||||
|
{
|
||||||
|
u64 data2{ 0 };
|
||||||
|
u64 data3{ 0 };
|
||||||
|
|
||||||
|
switch (read_mode.load())
|
||||||
|
{
|
||||||
|
case CELL_CAMERA_READ_FUNCCALL:
|
||||||
|
{
|
||||||
|
data2 = 0; // device id (always 0)
|
||||||
|
data3 = 0; // unused
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CELL_CAMERA_READ_DIRECT:
|
||||||
|
{
|
||||||
|
const u64 image_data_size = static_cast<u64>(info.bytesize);
|
||||||
|
const u64 buffer_number = 0;
|
||||||
|
const u64 camera_id = 0;
|
||||||
|
|
||||||
|
data2 = image_data_size << 32 | buffer_number << 16 | camera_id;
|
||||||
|
data3 = timer.GetElapsedTimeInMicroSec(); // timestamp
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
cellCamera.error("Unknown read mode set: %d. This should never happen.", read_mode.load());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto send_status = queue->send(evt_data.source, CELL_CAMERA_FRAME_UPDATE, data2, data3);
|
||||||
|
if (LIKELY(send_status))
|
||||||
|
{
|
||||||
|
++frame_num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::chrono::microseconds frame_target_time{ static_cast<u32>(1000000.0 / info.framerate) };
|
||||||
|
|
||||||
|
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
std::chrono::microseconds frame_processing_time = std::chrono::duration_cast<std::chrono::microseconds>(now - frame_start);
|
||||||
|
|
||||||
|
if (frame_processing_time < frame_target_time)
|
||||||
|
{
|
||||||
|
std::chrono::microseconds frame_idle_time = frame_target_time - frame_processing_time;
|
||||||
|
std::this_thread::sleep_for(frame_idle_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void camera_thread::on_init(const std::shared_ptr<void>& _this)
|
||||||
|
{
|
||||||
|
named_thread::on_init(_this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void camera_thread::send_attach_state(bool attached)
|
||||||
|
{
|
||||||
|
semaphore_lock lock(mutex_notify_data_map);
|
||||||
|
|
||||||
|
if (!notify_data_map.empty())
|
||||||
|
{
|
||||||
|
for (auto const& notify_data_entry : notify_data_map)
|
||||||
|
{
|
||||||
|
const auto& key = notify_data_entry.first;
|
||||||
|
const auto& evt_data = notify_data_entry.second;
|
||||||
|
|
||||||
|
if (auto queue = lv2_event_queue::find(key))
|
||||||
|
{
|
||||||
|
const auto send_result = queue->send(evt_data.source, attached ? CELL_CAMERA_ATTACH : CELL_CAMERA_DETACH, 0, 0);
|
||||||
|
if (LIKELY(send_result))
|
||||||
|
{
|
||||||
|
is_attached = attached;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We're not expected to send any events for attaching/detaching
|
||||||
|
is_attached = attached;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "Utilities/Timer.h"
|
||||||
|
#include "Emu/Cell/lv2/sys_memory.h"
|
||||||
|
#include "Utilities/sema.h"
|
||||||
|
#include "Utilities/Thread.h"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
|
||||||
// Error Codes
|
// Error Codes
|
||||||
|
@ -20,7 +26,7 @@ enum
|
||||||
CELL_CAMERA_ERROR_FATAL = 0x8014080f,
|
CELL_CAMERA_ERROR_FATAL = 0x8014080f,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Event types
|
// Event masks
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
CELL_CAMERA_EFLAG_FRAME_UPDATE = 0x00000001,
|
CELL_CAMERA_EFLAG_FRAME_UPDATE = 0x00000001,
|
||||||
|
@ -31,6 +37,26 @@ enum
|
||||||
CELL_CAMERA_EFLAG_RESET = 0x00000020,
|
CELL_CAMERA_EFLAG_RESET = 0x00000020,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Event types
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
CELL_CAMERA_DETACH = 0,
|
||||||
|
CELL_CAMERA_ATTACH = 1,
|
||||||
|
CELL_CAMERA_FRAME_UPDATE = 2,
|
||||||
|
CELL_CAMERA_OPEN = 3,
|
||||||
|
CELL_CAMERA_CLOSE = 4,
|
||||||
|
CELL_CAMERA_START = 5,
|
||||||
|
CELL_CAMERA_STOP = 6,
|
||||||
|
CELL_CAMERA_RESET = 7
|
||||||
|
};
|
||||||
|
|
||||||
|
// Read mode
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
CELL_CAMERA_READ_FUNCCALL = 0,
|
||||||
|
CELL_CAMERA_READ_DIRECT = 1,
|
||||||
|
};
|
||||||
|
|
||||||
// Colormatching
|
// Colormatching
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
|
@ -292,8 +318,8 @@ struct CellCameraInfoEx
|
||||||
|
|
||||||
vm::bptr<u8> buffer;
|
vm::bptr<u8> buffer;
|
||||||
be_t<s32> bytesize;
|
be_t<s32> bytesize;
|
||||||
be_t<s32> width;
|
be_t<s32> width; // only used if resolution == CELL_CAMERA_SPECIFIED_WIDTH_HEIGHT
|
||||||
be_t<s32> height;
|
be_t<s32> height; // likewise
|
||||||
be_t<s32> dev_num;
|
be_t<s32> dev_num;
|
||||||
be_t<s32> guid;
|
be_t<s32> guid;
|
||||||
|
|
||||||
|
@ -311,3 +337,52 @@ struct CellCameraReadEx
|
||||||
be_t<s64> timestamp;
|
be_t<s64> timestamp;
|
||||||
vm::bptr<u8> pbuf;
|
vm::bptr<u8> pbuf;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class camera_thread final : public named_thread
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
struct notify_event_data
|
||||||
|
{
|
||||||
|
u64 source;
|
||||||
|
u64 flag;
|
||||||
|
};
|
||||||
|
|
||||||
|
void on_task() override;
|
||||||
|
|
||||||
|
std::string get_name() const override { return "Camera Thread"; }
|
||||||
|
|
||||||
|
public:
|
||||||
|
void on_init(const std::shared_ptr<void>&) override;
|
||||||
|
void send_attach_state(bool attached);
|
||||||
|
|
||||||
|
std::map<u64, notify_event_data> notify_data_map;
|
||||||
|
|
||||||
|
semaphore<> mutex;
|
||||||
|
semaphore<> mutex_notify_data_map;
|
||||||
|
Timer timer;
|
||||||
|
|
||||||
|
atomic_t<u8> read_mode;
|
||||||
|
atomic_t<bool> is_streaming;
|
||||||
|
atomic_t<bool> is_attached;
|
||||||
|
atomic_t<bool> is_open;
|
||||||
|
|
||||||
|
CellCameraInfoEx info;
|
||||||
|
|
||||||
|
struct attr_t
|
||||||
|
{
|
||||||
|
u32 v1, v2;
|
||||||
|
};
|
||||||
|
attr_t attr[500]{};
|
||||||
|
|
||||||
|
lv2_memory_container container;
|
||||||
|
atomic_t<u32> frame_num;
|
||||||
|
|
||||||
|
camera_thread() : read_mode(CELL_CAMERA_READ_FUNCCALL) {}
|
||||||
|
~camera_thread() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Shared data between cellGem and cellCamera
|
||||||
|
struct gem_camera_shared
|
||||||
|
{
|
||||||
|
atomic_t<s64> frame_timestamp; // latest read timestamp from cellCamera (cellCameraRead(Ex))
|
||||||
|
};
|
||||||
|
|
|
@ -1,25 +1,326 @@
|
||||||
#include "stdafx.h"
|
#include "stdafx.h"
|
||||||
#include "Emu/IdManager.h"
|
|
||||||
#include "Emu/Cell/PPUModule.h"
|
|
||||||
|
|
||||||
#include "cellGem.h"
|
#include "cellGem.h"
|
||||||
|
|
||||||
|
#include "cellCamera.h"
|
||||||
|
#include "Emu/IdManager.h"
|
||||||
|
#include "Emu/System.h"
|
||||||
|
#include "Emu/Cell/PPUModule.h"
|
||||||
|
#include "pad_thread.h"
|
||||||
|
#include "Utilities/Timer.h"
|
||||||
|
|
||||||
logs::channel cellGem("cellGem");
|
logs::channel cellGem("cellGem");
|
||||||
|
|
||||||
|
// **********************
|
||||||
|
// * HLE helper structs *
|
||||||
|
// **********************
|
||||||
|
|
||||||
struct gem_t
|
struct gem_t
|
||||||
{
|
{
|
||||||
CellGemAttribute attribute;
|
struct gem_color
|
||||||
|
{
|
||||||
|
float r, g, b;
|
||||||
|
|
||||||
|
gem_color() : r(0.0f), g(0.0f), b(0.0f) {}
|
||||||
|
gem_color(float r_, float g_, float b_)
|
||||||
|
{
|
||||||
|
r = clamp(r_);
|
||||||
|
g = clamp(g_);
|
||||||
|
b = clamp(b_);
|
||||||
|
}
|
||||||
|
|
||||||
|
float clamp(float f) const
|
||||||
|
{
|
||||||
|
return std::max(0.0f, std::min(f, 1.0f));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
s32 cellGemCalibrate()
|
struct gem_controller
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellGem);
|
u32 status; // connection status (CELL_GEM_STATUS_DISCONNECTED or CELL_GEM_STATUS_READY)
|
||||||
|
u32 port; // assigned port
|
||||||
|
bool enabled_magnetometer; // whether the magnetometer is enabled (probably used for additional rotational precision)
|
||||||
|
bool calibrated_magnetometer; // whether the magnetometer is calibrated
|
||||||
|
bool enabled_filtering; // whether filtering is enabled
|
||||||
|
u8 rumble; // rumble intensity
|
||||||
|
gem_color sphere_rgb; // RGB color of the sphere LED
|
||||||
|
|
||||||
|
gem_controller() :
|
||||||
|
status(CELL_GEM_STATUS_DISCONNECTED),
|
||||||
|
enabled_filtering(false), rumble(0), sphere_rgb() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
CellGemAttribute attribute;
|
||||||
|
CellGemVideoConvertAttribute vc_attribute;
|
||||||
|
u64 status_flags;
|
||||||
|
bool enable_pitch_correction;
|
||||||
|
u32 inertial_counter;
|
||||||
|
|
||||||
|
std::array<gem_controller, CELL_GEM_MAX_NUM> controllers;
|
||||||
|
u32 connected_controllers;
|
||||||
|
|
||||||
|
Timer timer;
|
||||||
|
|
||||||
|
// helper functions
|
||||||
|
bool is_controller_ready(u32 gem_num) const
|
||||||
|
{
|
||||||
|
return controllers[gem_num].status == CELL_GEM_STATUS_READY;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset_controller(u32 gem_num)
|
||||||
|
{
|
||||||
|
switch (g_cfg.io.move)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
case move_handler::null:
|
||||||
|
{
|
||||||
|
connected_controllers = 0;
|
||||||
|
|
||||||
|
controllers[gem_num].status = CELL_GEM_STATUS_DISCONNECTED;
|
||||||
|
controllers[gem_num].port = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case move_handler::fake:
|
||||||
|
{
|
||||||
|
// fake one connected controller
|
||||||
|
connected_controllers = 1;
|
||||||
|
|
||||||
|
if (gem_num < connected_controllers)
|
||||||
|
{
|
||||||
|
controllers[gem_num].status = CELL_GEM_STATUS_READY;
|
||||||
|
controllers[gem_num].port = 7u - gem_num;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
controllers[gem_num].status = CELL_GEM_STATUS_DISCONNECTED;
|
||||||
|
controllers[gem_num].port = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ************************
|
||||||
|
// * HLE helper functions *
|
||||||
|
// ************************
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void fmt_class_string<move_handler>::format(std::string& out, u64 arg)
|
||||||
|
{
|
||||||
|
format_enum(out, arg, [](auto value)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case move_handler::null: return "Null";
|
||||||
|
case move_handler::fake: return "Fake";
|
||||||
|
}
|
||||||
|
|
||||||
|
return unknown;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \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
|
||||||
|
*/
|
||||||
|
static bool check_gem_num(const u32 gem_num)
|
||||||
|
{
|
||||||
|
return gem_num >= 0 && gem_num < CELL_GEM_MAX_NUM;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \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.
|
||||||
|
* \param port_no DS3 port number to use
|
||||||
|
* \param digital_buttons Bitmask filled with CELL_GEM_CTRL_* values
|
||||||
|
* \param analog_t Analog value of Move's Trigger. Currently mapped to R2.
|
||||||
|
* \return true on success, false if port_no controller is invalid
|
||||||
|
*/
|
||||||
|
static bool map_to_ds3_input(const u32 port_no, be_t<u16>& digital_buttons, be_t<u16>& analog_t)
|
||||||
|
{
|
||||||
|
const auto handler = fxm::get<pad_thread>();
|
||||||
|
|
||||||
|
if (!handler)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PadInfo& rinfo = handler->GetInfo();
|
||||||
|
|
||||||
|
if (port_no >= rinfo.max_connect || port_no >= rinfo.now_connect)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& pads = handler->GetPads();
|
||||||
|
auto pad = pads[port_no];
|
||||||
|
|
||||||
|
for (Button& button : pad->m_buttons)
|
||||||
|
{
|
||||||
|
//here we check btns, and set pad accordingly,
|
||||||
|
if (button.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL2)
|
||||||
|
{
|
||||||
|
if (button.m_pressed) pad->m_digital_2 |= button.m_outKeyCode;
|
||||||
|
else pad->m_digital_2 &= ~button.m_outKeyCode;
|
||||||
|
|
||||||
|
switch (button.m_outKeyCode)
|
||||||
|
{
|
||||||
|
case CELL_PAD_CTRL_SQUARE:
|
||||||
|
pad->m_press_square = button.m_value;
|
||||||
|
break;
|
||||||
|
case CELL_PAD_CTRL_CROSS:
|
||||||
|
pad->m_press_cross = button.m_value;
|
||||||
|
break;
|
||||||
|
case CELL_PAD_CTRL_CIRCLE:
|
||||||
|
pad->m_press_circle = button.m_value;
|
||||||
|
break;
|
||||||
|
case CELL_PAD_CTRL_TRIANGLE:
|
||||||
|
pad->m_press_triangle = button.m_value;
|
||||||
|
break;
|
||||||
|
case CELL_PAD_CTRL_R1:
|
||||||
|
pad->m_press_R1 = button.m_value;
|
||||||
|
break;
|
||||||
|
case CELL_PAD_CTRL_L1:
|
||||||
|
pad->m_press_L1 = button.m_value;
|
||||||
|
break;
|
||||||
|
case CELL_PAD_CTRL_R2:
|
||||||
|
pad->m_press_R2 = button.m_value;
|
||||||
|
break;
|
||||||
|
case CELL_PAD_CTRL_L2:
|
||||||
|
pad->m_press_L2 = button.m_value;
|
||||||
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (button.m_flush)
|
||||||
|
{
|
||||||
|
button.m_pressed = false;
|
||||||
|
button.m_flush = false;
|
||||||
|
button.m_value = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&digital_buttons, 0, sizeof(digital_buttons));
|
||||||
|
|
||||||
|
// map the Move key to R1 and the Trigger to R2
|
||||||
|
|
||||||
|
if (pad->m_press_R1)
|
||||||
|
digital_buttons |= CELL_GEM_CTRL_MOVE;
|
||||||
|
if (pad->m_press_R2)
|
||||||
|
digital_buttons |= CELL_GEM_CTRL_T;
|
||||||
|
|
||||||
|
if (pad->m_press_cross)
|
||||||
|
digital_buttons |= CELL_GEM_CTRL_CROSS;
|
||||||
|
if (pad->m_press_circle)
|
||||||
|
digital_buttons |= CELL_GEM_CTRL_CIRCLE;
|
||||||
|
if (pad->m_press_square)
|
||||||
|
digital_buttons |= CELL_GEM_CTRL_SQUARE;
|
||||||
|
if (pad->m_press_triangle)
|
||||||
|
digital_buttons |= CELL_GEM_CTRL_TRIANGLE;
|
||||||
|
if (pad->m_digital_1)
|
||||||
|
digital_buttons |= CELL_GEM_CTRL_SELECT;
|
||||||
|
if (pad->m_digital_2)
|
||||||
|
digital_buttons |= CELL_GEM_CTRL_START;
|
||||||
|
|
||||||
|
analog_t = pad->m_press_R2;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Maps external Move controller data to DS3 input
|
||||||
|
* Implementation detail: CellGemExtPortData's digital/analog fields map the same way as
|
||||||
|
* libPad, so no translation is needed.
|
||||||
|
* \param port_no DS3 port number to use
|
||||||
|
* \param ext External data to modify
|
||||||
|
* \return true on success, false if port_no controller is invalid
|
||||||
|
*/
|
||||||
|
static bool map_ext_to_ds3_input(const u32 port_no, CellGemExtPortData& ext)
|
||||||
|
{
|
||||||
|
const auto handler = fxm::get<pad_thread>();
|
||||||
|
|
||||||
|
if (!handler)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& pads = handler->GetPads();
|
||||||
|
|
||||||
|
const PadInfo& rinfo = handler->GetInfo();
|
||||||
|
|
||||||
|
if (port_no >= rinfo.max_connect)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//We have a choice here of NO_DEVICE or READ_FAILED...lets try no device for now
|
||||||
|
if (port_no >= rinfo.now_connect)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pad = pads[port_no];
|
||||||
|
|
||||||
|
ext.status = 0; // CELL_GEM_EXT_CONNECTED | CELL_GEM_EXT_EXT0 | CELL_GEM_EXT_EXT1
|
||||||
|
ext.analog_left_x = pad->m_analog_left_x;
|
||||||
|
ext.analog_left_y = pad->m_analog_left_y;
|
||||||
|
ext.analog_right_x = pad->m_analog_right_x;
|
||||||
|
ext.analog_right_y = pad->m_analog_right_y;
|
||||||
|
ext.digital1 = pad->m_digital_1;
|
||||||
|
ext.digital2 = pad->m_digital_2;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// *********************
|
||||||
|
// * cellGem functions *
|
||||||
|
// *********************
|
||||||
|
|
||||||
|
s32 cellGemCalibrate(u32 gem_num)
|
||||||
|
{
|
||||||
|
cellGem.todo("cellGemCalibrate(gem_num=%d)", gem_num);
|
||||||
|
const auto gem = fxm::get<gem_t>();
|
||||||
|
|
||||||
|
if (!gem)
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_UNINITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!check_gem_num(gem_num))
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_cfg.io.move == move_handler::fake)
|
||||||
|
{
|
||||||
|
gem->controllers[gem_num].calibrated_magnetometer = true;
|
||||||
|
gem->status_flags = CELL_GEM_FLAG_CALIBRATION_OCCURRED | CELL_GEM_FLAG_CALIBRATION_SUCCEEDED;
|
||||||
|
}
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellGemClearStatusFlags()
|
s32 cellGemClearStatusFlags(u32 gem_num, u64 mask)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellGem);
|
cellGem.todo("cellGemClearStatusFlags(gem_num=%d, mask=0x%x)", gem_num, mask);
|
||||||
|
const auto gem = fxm::get<gem_t>();
|
||||||
|
|
||||||
|
if (!gem)
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_UNINITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!check_gem_num(gem_num))
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
gem->status_flags &= ~mask;
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,15 +336,38 @@ s32 cellGemConvertVideoStart()
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellGemEnableCameraPitchAngleCorrection()
|
s32 cellGemEnableCameraPitchAngleCorrection(u32 enable_flag)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellGem);
|
cellGem.todo("cellGemEnableCameraPitchAngleCorrection(enable_flag=%d", enable_flag);
|
||||||
|
const auto gem = fxm::get<gem_t>();
|
||||||
|
|
||||||
|
if (!gem)
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_UNINITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
gem->enable_pitch_correction = !!enable_flag;
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellGemEnableMagnetometer()
|
s32 cellGemEnableMagnetometer(u32 gem_num, u32 enable)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellGem);
|
cellGem.todo("cellGemEnableMagnetometer(gem_num=%d, enable=0x%x)", gem_num, enable);
|
||||||
|
const auto gem = fxm::get<gem_t>();
|
||||||
|
|
||||||
|
if (!gem)
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_UNINITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gem->is_controller_ready(gem_num))
|
||||||
|
{
|
||||||
|
return CELL_GEM_NOT_CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
gem->controllers[gem_num].enabled_magnetometer = !!enable;
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,15 +383,43 @@ s32 cellGemEnd()
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellGemFilterState()
|
s32 cellGemFilterState(u32 gem_num, u32 enable)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellGem);
|
cellGem.warning("cellGemFilterState(gem_num=%d, enable=%d)", gem_num, enable);
|
||||||
|
const auto gem = fxm::get<gem_t>();
|
||||||
|
|
||||||
|
if (!gem)
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_UNINITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!check_gem_num(gem_num))
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
gem->controllers[gem_num].enabled_filtering = !!enable;
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellGemForceRGB()
|
s32 cellGemForceRGB(u32 gem_num, float r, float g, float b)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellGem);
|
cellGem.todo("cellGemForceRGB(gem_num=%d, r=%f, g=%f, b=%f)", gem_num, r, g, b);
|
||||||
|
const auto gem = fxm::get<gem_t>();
|
||||||
|
|
||||||
|
if (!gem)
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_UNINITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!check_gem_num(gem_num))
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
gem->controllers[gem_num].sphere_rgb = gem_t::gem_color(r, g, b);
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,15 +429,30 @@ s32 cellGemGetAccelerometerPositionInDevice()
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellGemGetAllTrackableHues()
|
s32 cellGemGetAllTrackableHues(vm::ptr<u8> hues)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellGem);
|
cellGem.todo("cellGemGetAllTrackableHues(hues=*0x%x)");
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellGemGetCameraState()
|
s32 cellGemGetCameraState(vm::ptr<CellGemCameraState> camera_state)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellGem);
|
cellGem.todo("cellGemGetCameraState(camera_state=0x%x)", camera_state);
|
||||||
|
const auto gem = fxm::get<gem_t>();
|
||||||
|
|
||||||
|
if (!gem)
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_UNINITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!camera_state)
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
camera_state->exposure_time = 1.0f / 60.0f; // TODO: use correct framerate
|
||||||
|
camera_state->gain = 1.0;
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,15 +468,68 @@ s32 cellGemGetHuePixels()
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellGemGetImageState()
|
s32 cellGemGetImageState(u32 gem_num, vm::ptr<CellGemImageState> image_state)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellGem);
|
cellGem.todo("cellGemGetImageState(gem_num=%d, image_state=&0x%x)", gem_num, image_state);
|
||||||
|
const auto gem = fxm::get<gem_t>();
|
||||||
|
|
||||||
|
if (!gem)
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_UNINITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!check_gem_num(gem_num))
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_cfg.io.move == move_handler::fake)
|
||||||
|
{
|
||||||
|
auto shared_data = fxm::get_always<gem_camera_shared>();
|
||||||
|
|
||||||
|
image_state->frame_timestamp = shared_data->frame_timestamp.load();
|
||||||
|
image_state->timestamp = image_state->frame_timestamp + 10; // arbitrarily define 10 usecs of frame processing
|
||||||
|
image_state->visible = true;
|
||||||
|
image_state->u = 0;
|
||||||
|
image_state->v = 0;
|
||||||
|
image_state->r = 20;
|
||||||
|
image_state->r_valid = true;
|
||||||
|
image_state->distance = 2 * 1000; // 2 meters away from camera
|
||||||
|
// TODO
|
||||||
|
image_state->projectionx = 1;
|
||||||
|
image_state->projectiony = 1;
|
||||||
|
}
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellGemGetInertialState()
|
s32 cellGemGetInertialState(u32 gem_num, u32 state_flag, u64 timestamp, vm::ptr<CellGemInertialState> inertial_state)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellGem);
|
cellGem.todo("cellGemGetInertialState(gem_num=%d, state_flag=%d, timestamp=0x%x, inertial_state=0x%x)", gem_num, state_flag, timestamp, inertial_state);
|
||||||
|
const auto gem = fxm::get<gem_t>();
|
||||||
|
|
||||||
|
if (!gem)
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_UNINITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!check_gem_num(gem_num) || !inertial_state || !gem->is_controller_ready(gem_num))
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_cfg.io.move == move_handler::fake)
|
||||||
|
{
|
||||||
|
map_to_ds3_input(gem_num, inertial_state->pad.digitalbuttons, inertial_state->pad.analog_T);
|
||||||
|
map_ext_to_ds3_input(gem_num, inertial_state->ext);
|
||||||
|
|
||||||
|
inertial_state->timestamp = gem->timer.GetElapsedTimeInMicroSec();
|
||||||
|
|
||||||
|
inertial_state->counter = gem->inertial_counter++;
|
||||||
|
|
||||||
|
inertial_state->accelerometer[0] = 10;
|
||||||
|
}
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,14 +544,19 @@ s32 cellGemGetInfo(vm::ptr<CellGemInfo> info)
|
||||||
return CELL_GEM_ERROR_UNINITIALIZED;
|
return CELL_GEM_ERROR_UNINITIALIZED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!info)
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Support connecting PlayStation Move controllers
|
// TODO: Support connecting PlayStation Move controllers
|
||||||
info->max_connect = gem->attribute.max_connect;
|
info->max_connect = gem->attribute.max_connect;
|
||||||
info->now_connect = 0;
|
info->now_connect = gem->connected_controllers;
|
||||||
|
|
||||||
for (int i = 0; i < CELL_GEM_MAX_NUM; i++)
|
for (int i = 0; i < CELL_GEM_MAX_NUM; i++)
|
||||||
{
|
{
|
||||||
info->status[i] = CELL_GEM_STATUS_DISCONNECTED;
|
info->status[i] = gem->controllers[i].status;
|
||||||
info->port[i] = 0;
|
info->port[i] = gem->controllers[i].port;
|
||||||
}
|
}
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
|
@ -149,15 +574,46 @@ s32 cellGemGetMemorySize(s32 max_connect)
|
||||||
return max_connect <= 2 ? 0x120000 : 0x140000;
|
return max_connect <= 2 ? 0x120000 : 0x140000;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellGemGetRGB()
|
s32 cellGemGetRGB(u32 gem_num, vm::ptr<float> r, vm::ptr<float> g, vm::ptr<float> b)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellGem);
|
cellGem.todo("cellGemGetRGB(gem_num=%d, r=*0x%x, g=*0x%x, b=*0x%x)", gem_num, r, g, b);
|
||||||
|
const auto gem = fxm::get<gem_t>();
|
||||||
|
|
||||||
|
if (!gem)
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_UNINITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!check_gem_num(gem_num) | !r || !g || !b )
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& sphere_color = gem->controllers[gem_num].sphere_rgb;
|
||||||
|
*r = sphere_color.r;
|
||||||
|
*g = sphere_color.g;
|
||||||
|
*b = sphere_color.b;
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellGemGetRumble()
|
s32 cellGemGetRumble(u32 gem_num, vm::ptr<u8> rumble)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellGem);
|
cellGem.todo("cellGemGetRumble(gem_num=%d, rumble=*0x%x)", gem_num, rumble);
|
||||||
|
auto gem = fxm::get<gem_t>();
|
||||||
|
|
||||||
|
if (!gem)
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_UNINITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!check_gem_num(gem_num) || !rumble)
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
*rumble = gem->controllers[gem_num].rumble;
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,17 +623,47 @@ s32 cellGemGetState(u32 gem_num, u32 flag, u64 time_parameter, vm::ptr<CellGemSt
|
||||||
const auto gem = fxm::get<gem_t>();
|
const auto gem = fxm::get<gem_t>();
|
||||||
|
|
||||||
if (!gem)
|
if (!gem)
|
||||||
|
{
|
||||||
return CELL_GEM_ERROR_UNINITIALIZED;
|
return CELL_GEM_ERROR_UNINITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
// clear out gem_state so no games get any funny ideas about them being connected...
|
if (!check_gem_num(gem_num))
|
||||||
std::memset(gem_state.get_ptr(), 0, sizeof(CellGemState));
|
{
|
||||||
|
return CELL_GEM_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_cfg.io.move == move_handler::fake)
|
||||||
|
{
|
||||||
|
map_to_ds3_input(gem_num, gem_state->pad.digitalbuttons, gem_state->pad.analog_T);
|
||||||
|
map_ext_to_ds3_input(gem_num, gem_state->ext);
|
||||||
|
|
||||||
|
gem_state->tracking_flags = CELL_GEM_TRACKING_FLAG_POSITION_TRACKED |
|
||||||
|
CELL_GEM_TRACKING_FLAG_VISIBLE;
|
||||||
|
gem_state->timestamp = gem->timer.GetElapsedTimeInMicroSec();
|
||||||
|
|
||||||
|
gem_state->quat[3] = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
return CELL_GEM_NOT_CONNECTED;
|
return CELL_GEM_NOT_CONNECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellGemGetStatusFlags()
|
s32 cellGemGetStatusFlags(u32 gem_num, vm::ptr<u64> flags)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellGem);
|
cellGem.todo("cellGemGetStatusFlags(gem_num=%d, flags=*0x%x)", gem_num, flags);
|
||||||
|
const auto gem = fxm::get<gem_t>();
|
||||||
|
|
||||||
|
if (!gem)
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_UNINITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!check_gem_num(gem_num) || !flags)
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
*flags = gem->status_flags;
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,7 +682,6 @@ s32 cellGemHSVtoRGB()
|
||||||
s32 cellGemInit(vm::cptr<CellGemAttribute> attribute)
|
s32 cellGemInit(vm::cptr<CellGemAttribute> attribute)
|
||||||
{
|
{
|
||||||
cellGem.warning("cellGemInit(attribute=*0x%x)", attribute);
|
cellGem.warning("cellGemInit(attribute=*0x%x)", attribute);
|
||||||
|
|
||||||
const auto gem = fxm::make<gem_t>();
|
const auto gem = fxm::make<gem_t>();
|
||||||
|
|
||||||
if (!gem)
|
if (!gem)
|
||||||
|
@ -204,8 +689,21 @@ s32 cellGemInit(vm::cptr<CellGemAttribute> attribute)
|
||||||
return CELL_GEM_ERROR_ALREADY_INITIALIZED;
|
return CELL_GEM_ERROR_ALREADY_INITIALIZED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!attribute || !attribute->spurs_addr || attribute->max_connect > CELL_GEM_MAX_NUM)
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
gem->attribute = *attribute;
|
gem->attribute = *attribute;
|
||||||
|
|
||||||
|
for (auto gem_num = 0; gem_num < CELL_GEM_MAX_NUM; gem_num++)
|
||||||
|
{
|
||||||
|
gem->reset_controller(gem_num);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: is this correct?
|
||||||
|
gem->timer.Start();
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,10 +713,17 @@ s32 cellGemInvalidateCalibration()
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellGemIsTrackableHue()
|
s32 cellGemIsTrackableHue(u32 hue)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellGem);
|
cellGem.todo("cellGemIsTrackableHue(hue=%d)", hue);
|
||||||
return CELL_OK;
|
const auto gem = fxm::get<gem_t>();
|
||||||
|
|
||||||
|
if (!gem || hue > 359)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellGemPrepareCamera()
|
s32 cellGemPrepareCamera()
|
||||||
|
@ -227,9 +732,36 @@ s32 cellGemPrepareCamera()
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellGemPrepareVideoConvert()
|
s32 cellGemPrepareVideoConvert(vm::cptr<CellGemVideoConvertAttribute> vc_attribute)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellGem);
|
cellGem.todo("cellGemPrepareVideoConvert(vc_attribute=*0x%x)", vc_attribute);
|
||||||
|
auto gem = fxm::get<gem_t>();
|
||||||
|
|
||||||
|
if (!gem)
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_UNINITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vc_attribute)
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto vc = *vc_attribute;
|
||||||
|
|
||||||
|
if (!vc_attribute || vc.version == 0 || vc.output_format == 0 ||
|
||||||
|
vc.conversion_flags & CELL_GEM_VIDEO_CONVERT_UNK3 && !vc.buffer_memory)
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vc.video_data_out & 0x1f || vc.buffer_memory & 0xff)
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_INVALID_ALIGNMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
gem->vc_attribute = vc;
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,15 +771,46 @@ s32 cellGemReadExternalPortDeviceInfo()
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellGemReset()
|
s32 cellGemReset(u32 gem_num)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellGem);
|
cellGem.todo("cellGemReset(gem_num=%d)", gem_num);
|
||||||
|
auto gem = fxm::get<gem_t>();
|
||||||
|
|
||||||
|
if (!gem)
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_UNINITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!check_gem_num(gem_num))
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
gem->reset_controller(gem_num);
|
||||||
|
|
||||||
|
// TODO: is this correct?
|
||||||
|
gem->timer.Start();
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellGemSetRumble()
|
s32 cellGemSetRumble(u32 gem_num, u8 rumble)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellGem);
|
cellGem.todo("cellGemSetRumble(gem_num=%d, rumble=0x%x)", gem_num, rumble);
|
||||||
|
auto gem = fxm::get<gem_t>();
|
||||||
|
|
||||||
|
if (!gem)
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_UNINITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!check_gem_num(gem_num))
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
gem->controllers[gem_num].rumble = rumble;
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,9 +832,16 @@ s32 cellGemUpdateFinish()
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellGemUpdateStart()
|
s32 cellGemUpdateStart(vm::cptr<void> camera_frame, u64 timestamp)
|
||||||
{
|
{
|
||||||
UNIMPLEMENTED_FUNC(cellGem);
|
cellGem.todo("cellGemUpdateStart(camera_frame=*0x%x, timestamp=%d)", camera_frame, timestamp);
|
||||||
|
auto gem = fxm::get<gem_t>();
|
||||||
|
|
||||||
|
if (!gem)
|
||||||
|
{
|
||||||
|
return CELL_GEM_ERROR_UNINITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
static const float CELL_GEM_SPHERE_RADIUS_MM = 22.5f;
|
static const float CELL_GEM_SPHERE_RADIUS_MM = 22.5f;
|
||||||
|
|
||||||
// Error Codes
|
// Error codes
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
CELL_GEM_ERROR_RESOURCE_ALLOCATION_FAILED = 0x80121801,
|
CELL_GEM_ERROR_RESOURCE_ALLOCATION_FAILED = 0x80121801,
|
||||||
|
@ -34,7 +34,7 @@ enum
|
||||||
CELL_GEM_NO_EXTERNAL_PORT_DEVICE = 9,
|
CELL_GEM_NO_EXTERNAL_PORT_DEVICE = 9,
|
||||||
};
|
};
|
||||||
|
|
||||||
// General constents
|
// General constants
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
CELL_GEM_CTRL_CIRCLE = 1 << 5,
|
CELL_GEM_CTRL_CIRCLE = 1 << 5,
|
||||||
|
@ -59,7 +59,7 @@ enum
|
||||||
CELL_GEM_FLAG_CALIBRATION_OCCURRED = 1 << 0,
|
CELL_GEM_FLAG_CALIBRATION_OCCURRED = 1 << 0,
|
||||||
CELL_GEM_FLAG_CALIBRATION_SUCCEEDED = 1 << 1,
|
CELL_GEM_FLAG_CALIBRATION_SUCCEEDED = 1 << 1,
|
||||||
CELL_GEM_FLAG_CALIBRATION_WARNING_BRIGHT_LIGHTING = 1 << 6,
|
CELL_GEM_FLAG_CALIBRATION_WARNING_BRIGHT_LIGHTING = 1 << 6,
|
||||||
ELL_GEM_FLAG_CALIBRATION_WARNING_MOTION_DETECTED = 1 << 5,
|
CELL_GEM_FLAG_CALIBRATION_WARNING_MOTION_DETECTED = 1 << 5,
|
||||||
CELL_GEM_FLAG_CAMERA_PITCH_ANGLE_CHANGED = 1 << 9,
|
CELL_GEM_FLAG_CAMERA_PITCH_ANGLE_CHANGED = 1 << 9,
|
||||||
CELL_GEM_FLAG_CURRENT_HUE_CONFLICTS_WITH_ENVIRONMENT = 1 << 13,
|
CELL_GEM_FLAG_CURRENT_HUE_CONFLICTS_WITH_ENVIRONMENT = 1 << 13,
|
||||||
CELL_GEM_FLAG_LIGHTING_CHANGED = 1 << 7,
|
CELL_GEM_FLAG_LIGHTING_CHANGED = 1 << 7,
|
||||||
|
@ -84,6 +84,15 @@ enum
|
||||||
CELL_GEM_VERSION = 2,
|
CELL_GEM_VERSION = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Video conversion flags
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
CELL_GEM_VIDEO_CONVERT_UNK1 = 1 << 0,
|
||||||
|
CELL_GEM_VIDEO_CONVERT_UNK2 = 1 << 1,
|
||||||
|
CELL_GEM_VIDEO_CONVERT_UNK3 = 1 << 2,
|
||||||
|
CELL_GEM_VIDEO_CONVERT_UNK4 = 1 << 3,
|
||||||
|
};
|
||||||
|
|
||||||
struct CellGemAttribute
|
struct CellGemAttribute
|
||||||
{
|
{
|
||||||
be_t<u32> version;
|
be_t<u32> version;
|
||||||
|
@ -118,9 +127,9 @@ struct CellGemImageState
|
||||||
{
|
{
|
||||||
be_t<u64> frame_timestamp;
|
be_t<u64> frame_timestamp;
|
||||||
be_t<u64> timestamp;
|
be_t<u64> timestamp;
|
||||||
be_t<f32> u;
|
be_t<f32> u; // horizontal screen position in pixels
|
||||||
be_t<f32> v;
|
be_t<f32> v; // vertical screen position in pixels
|
||||||
be_t<f32> r;
|
be_t<f32> r; // size of sphere on screen in pixels
|
||||||
be_t<f32> projectionx;
|
be_t<f32> projectionx;
|
||||||
be_t<f32> projectiony;
|
be_t<f32> projectiony;
|
||||||
be_t<f32> distance;
|
be_t<f32> distance;
|
||||||
|
|
|
@ -198,6 +198,10 @@ s32 cellSysutilGetSystemParamInt(CellSysutilParamId id, vm::ptr<s32> value)
|
||||||
*value = 0;
|
*value = 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case CELL_SYSUTIL_SYSTEMPARAM_ID_MAGNETOMETER:
|
||||||
|
*value = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return CELL_EINVAL;
|
return CELL_EINVAL;
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ enum CellSysutilParamId: s32
|
||||||
CELL_SYSUTIL_SYSTEMPARAM_ID_JAPANESE_KEYBOARD_ENTRY_METHOD = 0x0154,
|
CELL_SYSUTIL_SYSTEMPARAM_ID_JAPANESE_KEYBOARD_ENTRY_METHOD = 0x0154,
|
||||||
CELL_SYSUTIL_SYSTEMPARAM_ID_CHINESE_KEYBOARD_ENTRY_METHOD = 0x0155,
|
CELL_SYSUTIL_SYSTEMPARAM_ID_CHINESE_KEYBOARD_ENTRY_METHOD = 0x0155,
|
||||||
CELL_SYSUTIL_SYSTEMPARAM_ID_PAD_AUTOOFF = 0x0156,
|
CELL_SYSUTIL_SYSTEMPARAM_ID_PAD_AUTOOFF = 0x0156,
|
||||||
|
CELL_SYSUTIL_SYSTEMPARAM_ID_MAGNETOMETER = 0x0157,
|
||||||
|
|
||||||
// Strings
|
// Strings
|
||||||
CELL_SYSUTIL_SYSTEMPARAM_ID_NICKNAME = 0x0113,
|
CELL_SYSUTIL_SYSTEMPARAM_ID_NICKNAME = 0x0113,
|
||||||
|
|
|
@ -105,6 +105,12 @@ enum class fake_camera_type
|
||||||
uvc1_1,
|
uvc1_1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class move_handler
|
||||||
|
{
|
||||||
|
null,
|
||||||
|
fake,
|
||||||
|
};
|
||||||
|
|
||||||
enum class video_resolution
|
enum class video_resolution
|
||||||
{
|
{
|
||||||
_1080,
|
_1080,
|
||||||
|
@ -392,6 +398,7 @@ struct cfg_root : cfg::node
|
||||||
cfg::_enum<pad_handler> pad{this, "Pad", pad_handler::keyboard};
|
cfg::_enum<pad_handler> pad{this, "Pad", pad_handler::keyboard};
|
||||||
cfg::_enum<camera_handler> camera{this, "Camera", camera_handler::null};
|
cfg::_enum<camera_handler> camera{this, "Camera", camera_handler::null};
|
||||||
cfg::_enum<fake_camera_type> camera_type{this, "Camera type", fake_camera_type::unknown};
|
cfg::_enum<fake_camera_type> camera_type{this, "Camera type", fake_camera_type::unknown};
|
||||||
|
cfg::_enum<move_handler> move{this, "Move", move_handler::null};
|
||||||
|
|
||||||
} io{this};
|
} io{this};
|
||||||
|
|
||||||
|
|
|
@ -98,7 +98,8 @@
|
||||||
"keyboardHandlerBox": "Some games support native keyboard input.\nBasic will work in these cases.",
|
"keyboardHandlerBox": "Some games support native keyboard input.\nBasic will work in these cases.",
|
||||||
"mouseHandlerBox": "Some games support native mouse input.\nBasic will work in these cases.",
|
"mouseHandlerBox": "Some games support native mouse input.\nBasic will work in these cases.",
|
||||||
"cameraBox": "Camera support is not implemented, leave this on null.",
|
"cameraBox": "Camera support is not implemented, leave this on null.",
|
||||||
"cameraTypeBox": "Camera support is not implemented, leave this on unknown."
|
"cameraTypeBox": "Camera support is not implemented, leave this on unknown.",
|
||||||
|
"moveBox": "Playstation Move support.\nFake: Experimental, maps Move controls to DS4 controller mappings."
|
||||||
},
|
},
|
||||||
"network": {
|
"network": {
|
||||||
"netStatusBox": "Leave as disconnected unless you're debugging.\nRPCS3 has no online support."
|
"netStatusBox": "Leave as disconnected unless you're debugging.\nRPCS3 has no online support."
|
||||||
|
|
|
@ -78,6 +78,7 @@ public:
|
||||||
MouseHandler,
|
MouseHandler,
|
||||||
Camera,
|
Camera,
|
||||||
CameraType,
|
CameraType,
|
||||||
|
Move,
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
ExitRPCS3OnFinish,
|
ExitRPCS3OnFinish,
|
||||||
|
@ -236,6 +237,7 @@ private:
|
||||||
{ MouseHandler, { "Input/Output", "Mouse"}},
|
{ MouseHandler, { "Input/Output", "Mouse"}},
|
||||||
{ Camera, { "Input/Output", "Camera"}},
|
{ Camera, { "Input/Output", "Camera"}},
|
||||||
{ CameraType, { "Input/Output", "Camera type"}},
|
{ CameraType, { "Input/Output", "Camera type"}},
|
||||||
|
{ Move, { "Input/Output", "Move" }},
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
{ExitRPCS3OnFinish, { "Miscellaneous", "Exit RPCS3 when process finishes" }},
|
{ExitRPCS3OnFinish, { "Miscellaneous", "Exit RPCS3 when process finishes" }},
|
||||||
|
|
|
@ -652,6 +652,9 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> guiSettings, std:
|
||||||
xemu_settings->EnhanceComboBox(ui->cameraBox, emu_settings::Camera);
|
xemu_settings->EnhanceComboBox(ui->cameraBox, emu_settings::Camera);
|
||||||
SubscribeTooltip(ui->cameraBox, json_input["cameraBox"].toString());
|
SubscribeTooltip(ui->cameraBox, json_input["cameraBox"].toString());
|
||||||
|
|
||||||
|
xemu_settings->EnhanceComboBox(ui->moveBox, emu_settings::Move);
|
||||||
|
SubscribeTooltip(ui->moveBox, json_input["moveBox"].toString());
|
||||||
|
|
||||||
// _____ _ _______ _
|
// _____ _ _______ _
|
||||||
// / ____| | | |__ __| | |
|
// / ____| | | |__ __| | |
|
||||||
// | (___ _ _ ___| |_ ___ _ __ ___ | | __ _| |__
|
// | (___ _ _ ___| |_ ___ _ __ ___ | | __ _| |__
|
||||||
|
|
|
@ -890,20 +890,16 @@
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<spacer name="horizontalSpacer_6">
|
<widget class="QGroupBox" name="groupBox_2">
|
||||||
<property name="orientation">
|
<property name="title">
|
||||||
<enum>Qt::Horizontal</enum>
|
<string>Move Handler</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeType">
|
<layout class="QVBoxLayout" name="verticalLayout_62">
|
||||||
<enum>QSizePolicy::MinimumExpanding</enum>
|
<item>
|
||||||
</property>
|
<widget class="QComboBox" name="moveBox"/>
|
||||||
<property name="sizeHint" stdset="0">
|
</item>
|
||||||
<size>
|
</layout>
|
||||||
<width>0</width>
|
</widget>
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue