mirror of
https://github.com/cemu-project/Cemu.git
synced 2026-08-09 14:06:50 -05:00
Merge f9b663d621 into daacdda0ba
This commit is contained in:
commit
febb8c8edd
3
BUILD.md
3
BUILD.md
|
|
@ -242,11 +242,12 @@ Example usage: `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DENABLE_SDL=ON -
|
|||
|--------------------|:--|-----------------------------------------------------------------------------|---------|--------------------|
|
||||
| ALLOW_PORTABLE | | Allow Cemu to use the `portable` directory to store configs and data | ON | |
|
||||
| CEMU_CXX_FLAGS | | Flags passed straight to the compiler, e.g. `-march=native`, `-Wall`, `/W3` | "" | |
|
||||
| ENABLE_CAMERA | | Enable camera support | ON | |
|
||||
| ENABLE_CUBEB | | Enable cubeb audio backend | ON | |
|
||||
| ENABLE_DISCORD_RPC | | Enable Discord Rich presence support | ON | |
|
||||
| ENABLE_OPENGL | | Enable OpenGL graphics backend | ON | |
|
||||
| ENABLE_HIDAPI | | Enable HIDAPI (used for Wiimote controller API) | ON | |
|
||||
| ENABLE_SDL | | Enable SDLController controller API | ON | |
|
||||
| ENABLE_SDL | | Enable SDLController controller API/SDLCamera implementation | ON | |
|
||||
| ENABLE_VCPKG | | Use VCPKG package manager to obtain dependencies | ON | |
|
||||
| ENABLE_VULKAN | | Enable the Vulkan graphics backend | ON | |
|
||||
| ENABLE_WXWIDGETS | | Enable wxWidgets UI | ON | Currently required |
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ endif()
|
|||
option(ENABLE_HIDAPI "Build with HIDAPI" ON)
|
||||
option(ENABLE_SDL "Enables the SDLController backend" ON)
|
||||
option(ENABLE_LIBUSB "Build with libusb support" ON)
|
||||
option(ENABLE_CAMERA "Build with support for camera access" ON)
|
||||
|
||||
if (ENABLE_LIBUSB)
|
||||
add_compile_definitions(HAS_LIBUSB)
|
||||
|
|
|
|||
|
|
@ -242,3 +242,14 @@ MPTR makeCallableExport()
|
|||
}
|
||||
|
||||
void osLib_addVirtualPointer(const char* libraryName, const char* functionName, uint32 vPtr);
|
||||
|
||||
class CafeLockGuard
|
||||
{
|
||||
public:
|
||||
explicit CafeLockGuard(coreinit::OSMutex* mutex) : m_mutex(mutex) { coreinit::OSLockMutex(m_mutex); }
|
||||
~CafeLockGuard() { coreinit::OSUnlockMutex(m_mutex); }
|
||||
CafeLockGuard(const CafeLockGuard&) = delete;
|
||||
CafeLockGuard& operator=(const CafeLockGuard&) = delete;
|
||||
private:
|
||||
coreinit::OSMutex* m_mutex;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,278 +5,370 @@
|
|||
#include "Cafe/OS/libs/coreinit/coreinit_Alarm.h"
|
||||
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
|
||||
#include "Cafe/HW/Espresso/PPCCallback.h"
|
||||
#include "input/camera/CameraManager.h"
|
||||
#include "Common/CafeString.h"
|
||||
#include "OS/common/OSUtil.h"
|
||||
#include "OS/libs/coreinit/coreinit_Misc.h"
|
||||
#include "util/helpers/ringbuffer.h"
|
||||
|
||||
namespace camera
|
||||
{
|
||||
enum CAMStatus : sint32
|
||||
{
|
||||
CAM_STATUS_SUCCESS = 0,
|
||||
CAM_STATUS_INVALID_ARG = -1,
|
||||
CAM_STATUS_INVALID_HANDLE = -2,
|
||||
CAM_STATUS_SURFACE_QUEUE_FULL = -4,
|
||||
CAM_STATUS_INSUFFICIENT_MEMORY = -5,
|
||||
CAM_STATUS_NOT_READY = -6,
|
||||
CAM_STATUS_UNINITIALIZED = -8,
|
||||
CAM_STATUS_UVC_ERROR = -9,
|
||||
CAM_STATUS_DECODER_INIT_INIT_FAILED = -10,
|
||||
CAM_STATUS_DEVICE_IN_USE = -12,
|
||||
CAM_STATUS_DECODER_SESSION_FAILED = -13,
|
||||
CAM_STATUS_INVALID_PROPERTY = -14,
|
||||
CAM_STATUS_SEGMENT_VIOLATION = -15
|
||||
};
|
||||
|
||||
struct CAMInitInfo_t
|
||||
{
|
||||
/* +0x00 */ uint32be ukn00;
|
||||
/* +0x04 */ uint32be width;
|
||||
/* +0x08 */ uint32be height;
|
||||
|
||||
/* +0x0C */ uint32be workMemorySize;
|
||||
/* +0x10 */ MEMPTR<void> workMemory;
|
||||
enum class CAMFps : uint32
|
||||
{
|
||||
FPS_15 = 0,
|
||||
FPS_30 = 1
|
||||
};
|
||||
|
||||
/* +0x14 */ uint32be handlerFuncPtr;
|
||||
enum class CAMEventType : uint32
|
||||
{
|
||||
Decode = 0,
|
||||
// Implement when AVM*DRC* functions are added
|
||||
Detached [[maybe_unused]] = 1
|
||||
};
|
||||
|
||||
/* +0x18 */ uint32be ukn18;
|
||||
/* +0x1C */ uint32be fps;
|
||||
enum class CAMForceDisplay
|
||||
{
|
||||
None = 0,
|
||||
DRC = 1
|
||||
};
|
||||
|
||||
/* +0x20 */ uint32be ukn20;
|
||||
};
|
||||
enum class CAMImageType : uint32
|
||||
{
|
||||
Default = 0
|
||||
};
|
||||
|
||||
struct CAMTargetSurface
|
||||
{
|
||||
/* +0x00 */ uint32be surfaceSize;
|
||||
/* +0x04 */ MEMPTR<void> surfacePtr;
|
||||
/* +0x08 */ uint32be ukn08;
|
||||
/* +0x0C */ uint32be ukn0C;
|
||||
/* +0x10 */ uint32be ukn10;
|
||||
/* +0x14 */ uint32be ukn14;
|
||||
/* +0x18 */ uint32be ukn18;
|
||||
/* +0x1C */ uint32be ukn1C;
|
||||
};
|
||||
struct CAMImageInfo
|
||||
{
|
||||
betype<CAMImageType> type;
|
||||
uint32be height;
|
||||
uint32be width;
|
||||
};
|
||||
|
||||
struct CAMCallbackParam
|
||||
{
|
||||
// type 0 - frame decoded | field1 - imagePtr, field2 - imageSize, field3 - ukn (0)
|
||||
// type 1 - ???
|
||||
static_assert(sizeof(CAMImageInfo) == 0x0C);
|
||||
|
||||
struct CAMInitInfo_t
|
||||
{
|
||||
CAMImageInfo imageInfo;
|
||||
uint32be workMemorySize;
|
||||
MEMPTR<void> workMemoryData;
|
||||
MEMPTR<void> callback;
|
||||
betype<CAMForceDisplay> forceDisplay;
|
||||
betype<CAMFps> fps;
|
||||
uint32be threadFlags;
|
||||
uint8 unk[0x10];
|
||||
};
|
||||
|
||||
/* +0x0 */ uint32be type; // 0 -> Frame decoded
|
||||
/* +0x4 */ uint32be field1;
|
||||
/* +0x8 */ uint32be field2;
|
||||
/* +0xC */ uint32be field3;
|
||||
};
|
||||
static_assert(sizeof(CAMInitInfo_t) == 0x34);
|
||||
|
||||
struct CAMTargetSurface
|
||||
{
|
||||
sint32be size;
|
||||
MEMPTR<uint8> data;
|
||||
uint8 unused[0x18];
|
||||
};
|
||||
|
||||
#define CAM_ERROR_SUCCESS 0
|
||||
#define CAM_ERROR_INVALID_HANDLE -8
|
||||
static_assert(sizeof(CAMTargetSurface) == 0x20);
|
||||
|
||||
std::vector<struct CameraInstance*> g_table_cameraHandles;
|
||||
std::vector<struct CameraInstance*> g_activeCameraInstances;
|
||||
std::recursive_mutex g_mutex_camera;
|
||||
std::atomic_int g_cameraCounter{ 0 };
|
||||
SysAllocator<coreinit::OSAlarm_t, 1> g_alarm_camera;
|
||||
SysAllocator<CAMCallbackParam, 1> g_cameraHandlerParam;
|
||||
struct CAMDecodeEventParam
|
||||
{
|
||||
betype<CAMEventType> type;
|
||||
MEMPTR<void> data;
|
||||
uint32be channel;
|
||||
uint32be errored;
|
||||
};
|
||||
|
||||
CameraInstance* GetCameraInstanceByHandle(sint32 camHandle)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera);
|
||||
if (camHandle <= 0)
|
||||
return nullptr;
|
||||
camHandle -= 1;
|
||||
if (camHandle >= g_table_cameraHandles.size())
|
||||
return nullptr;
|
||||
return g_table_cameraHandles[camHandle];
|
||||
}
|
||||
static_assert(sizeof(CAMDecodeEventParam) == 0x10);
|
||||
|
||||
struct CameraInstance
|
||||
{
|
||||
CameraInstance(uint32 frameWidth, uint32 frameHeight, MPTR handlerFunc) : width(frameWidth), height(frameHeight), handlerFunc(handlerFunc) { AcquireHandle(); };
|
||||
~CameraInstance() { if (isOpen) { CloseCam(); } ReleaseHandle(); };
|
||||
constexpr static int32_t CAM_HANDLE = 0;
|
||||
|
||||
sint32 handle{ 0 };
|
||||
uint32 width;
|
||||
uint32 height;
|
||||
bool isOpen{false};
|
||||
std::queue<CAMTargetSurface> queue_targetSurfaces;
|
||||
MPTR handlerFunc;
|
||||
struct
|
||||
{
|
||||
bool previouslyInitialized = false;
|
||||
bool initialized = false;
|
||||
std::atomic_bool isOpen = false;
|
||||
std::atomic_bool isExiting = false;
|
||||
std::atomic_bool isWorkerExiting = false;
|
||||
unsigned fps = 30;
|
||||
MEMPTR<void> eventCallback = nullptr;
|
||||
RingBuffer<MEMPTR<uint8>, 20> inTargetBuffers{};
|
||||
} s_instance;
|
||||
|
||||
bool OpenCam()
|
||||
{
|
||||
if (isOpen)
|
||||
return false;
|
||||
isOpen = true;
|
||||
g_activeCameraInstances.push_back(this);
|
||||
return true;
|
||||
}
|
||||
SysAllocator<coreinit::OSMutex> s_cameraMutex;
|
||||
SysAllocator<CAMDecodeEventParam> s_cameraEventData;
|
||||
SysAllocator<OSThread_t> s_cameraWorkerThread;
|
||||
SysAllocator<uint8, 1024 * 64> s_cameraWorkerThreadStack;
|
||||
SysAllocator s_cameraWorkerThreadName("CameraWorkerThread");
|
||||
SysAllocator<coreinit::OSEvent> s_cameraOpenEvent;
|
||||
SysAllocator<coreinit::OSDriverInterface> s_driverInterface;
|
||||
SysAllocator s_driverName("CAM");
|
||||
|
||||
bool CloseCam()
|
||||
{
|
||||
if (!isOpen)
|
||||
return false;
|
||||
isOpen = false;
|
||||
vectorRemoveByValue(g_activeCameraInstances, this);
|
||||
return true;
|
||||
}
|
||||
static void WorkerThreadFunc(PPCInterpreter_t*)
|
||||
{
|
||||
s_cameraEventData->type = CAMEventType::Decode;
|
||||
s_cameraEventData->channel = 0;
|
||||
s_cameraEventData->data = nullptr;
|
||||
s_cameraEventData->errored = false;
|
||||
PPCCoreCallback(s_instance.eventCallback, s_cameraEventData.GetMPTR());
|
||||
|
||||
void QueueTargetSurface(CAMTargetSurface* targetSurface)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera);
|
||||
cemu_assert_debug(queue_targetSurfaces.size() < 100); // check for sane queue length
|
||||
queue_targetSurfaces.push(*targetSurface);
|
||||
}
|
||||
while (!s_instance.isExiting)
|
||||
{
|
||||
coreinit::OSWaitEvent(s_cameraOpenEvent);
|
||||
while (true)
|
||||
{
|
||||
const auto surfaceBuffer = s_instance.inTargetBuffers.Pop();
|
||||
if (!surfaceBuffer)
|
||||
{
|
||||
// Only exit when no buffers are left
|
||||
if (!s_instance.isOpen || s_instance.isExiting)
|
||||
break;
|
||||
s_cameraEventData->data = nullptr;
|
||||
s_cameraEventData->errored = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
CameraManager::FillNV12Buffer(
|
||||
std::span<uint8, CameraManager::CAMERA_NV12_BUFFER_SIZE>(
|
||||
surfaceBuffer.GetPtr(), CameraManager::CAMERA_NV12_BUFFER_SIZE));
|
||||
s_cameraEventData->data = surfaceBuffer;
|
||||
s_cameraEventData->errored = false;
|
||||
}
|
||||
s_cameraEventData->type = CAMEventType::Decode;
|
||||
s_cameraEventData->channel = 0;
|
||||
PPCCoreCallback(s_instance.eventCallback, s_cameraEventData.GetMPTR());
|
||||
coreinit::OSSleepTicks(coreinit::EspressoTime::ConvertMsToTimerTicks(1000 / s_instance.fps));
|
||||
}
|
||||
}
|
||||
s_instance.isWorkerExiting = false;
|
||||
coreinit::OSExitThread(0);
|
||||
}
|
||||
|
||||
private:
|
||||
void AcquireHandle()
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera);
|
||||
for (uint32 i = 0; i < g_table_cameraHandles.size(); i++)
|
||||
{
|
||||
if (g_table_cameraHandles[i] == nullptr)
|
||||
{
|
||||
g_table_cameraHandles[i] = this;
|
||||
this->handle = i + 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
this->handle = (sint32)(g_table_cameraHandles.size() + 1);
|
||||
g_table_cameraHandles.push_back(this);
|
||||
}
|
||||
sint32 CAMGetMemReq(const CAMImageInfo* info)
|
||||
{
|
||||
if (!info)
|
||||
return CAM_STATUS_INVALID_ARG;
|
||||
return 1 * 1024; // always return 1KB
|
||||
}
|
||||
|
||||
void ReleaseHandle()
|
||||
{
|
||||
for (uint32 i = 0; i < g_table_cameraHandles.size(); i++)
|
||||
{
|
||||
if (g_table_cameraHandles[i] == this)
|
||||
{
|
||||
g_table_cameraHandles[i] = nullptr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
cemu_assert_debug(false);
|
||||
}
|
||||
};
|
||||
CAMStatus CAMCheckMemSegmentation(const void* startAddr, uint32 size)
|
||||
{
|
||||
if (!startAddr || size == 0)
|
||||
return CAM_STATUS_INVALID_ARG;
|
||||
return CAM_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
sint32 CAMGetMemReq(void* ukn)
|
||||
{
|
||||
return 1 * 1024; // always return 1KB
|
||||
}
|
||||
sint32 CAMInit(sint32 channel, const CAMInitInfo_t* initInfo, betype<CAMStatus>* error)
|
||||
{
|
||||
*error = CAM_STATUS_SUCCESS;
|
||||
CafeLockGuard lock(s_cameraMutex);
|
||||
if (s_instance.initialized)
|
||||
{
|
||||
*error = CAM_STATUS_DEVICE_IN_USE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
sint32 CAMCheckMemSegmentation(void* base, uint32 size)
|
||||
{
|
||||
return CAM_ERROR_SUCCESS; // always return success
|
||||
}
|
||||
if (channel != 0 || !initInfo || !initInfo->workMemoryData ||
|
||||
!match_any_of(initInfo->forceDisplay, CAMForceDisplay::None, CAMForceDisplay::DRC) ||
|
||||
!match_any_of(initInfo->fps, CAMFps::FPS_15, CAMFps::FPS_30) ||
|
||||
initInfo->imageInfo.type != CAMImageType::Default)
|
||||
{
|
||||
*error = CAM_STATUS_INVALID_ARG;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ppcCAMUpdate60(PPCInterpreter_t* hCPU)
|
||||
{
|
||||
// update all open camera instances
|
||||
size_t numCamInstances = g_activeCameraInstances.size();
|
||||
//for (auto& itr : g_activeCameraInstances)
|
||||
for(size_t i=0; i<numCamInstances; i++)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera);
|
||||
if (i >= g_activeCameraInstances.size())
|
||||
break;
|
||||
CameraInstance* camInstance = g_activeCameraInstances[i];
|
||||
// todo - handle 30 / 60 FPS
|
||||
if (camInstance->queue_targetSurfaces.empty())
|
||||
continue;
|
||||
auto& targetSurface = camInstance->queue_targetSurfaces.front();
|
||||
g_cameraHandlerParam->type = 0;
|
||||
g_cameraHandlerParam->field1 = targetSurface.surfacePtr.GetMPTR();
|
||||
g_cameraHandlerParam->field2 = targetSurface.surfaceSize;
|
||||
g_cameraHandlerParam->field3 = 0;
|
||||
cemu_assert_debug(camInstance->handlerFunc != MPTR_NULL);
|
||||
camInstance->queue_targetSurfaces.pop();
|
||||
_lock.unlock();
|
||||
PPCCoreCallback(camInstance->handlerFunc, g_cameraHandlerParam.GetPtr());
|
||||
}
|
||||
osLib_returnFromFunction(hCPU, 0);
|
||||
}
|
||||
if (s_instance.previouslyInitialized)
|
||||
{
|
||||
while (s_instance.isWorkerExiting)
|
||||
{
|
||||
coreinit::OSSleepTicks(coreinit::EspressoTime::ConvertMsToTimerTicks(1000));
|
||||
}
|
||||
}
|
||||
|
||||
cemu_assert_debug(initInfo->forceDisplay != CAMForceDisplay::DRC);
|
||||
cemu_assert_debug(initInfo->workMemorySize != 0);
|
||||
cemu_assert_debug(initInfo->imageInfo.type == CAMImageType::Default);
|
||||
|
||||
sint32 CAMInit(uint32 cameraId, CAMInitInfo_t* camInitInfo, uint32be* error)
|
||||
{
|
||||
CameraInstance* camInstance = new CameraInstance(camInitInfo->width, camInitInfo->height, camInitInfo->handlerFuncPtr);
|
||||
*error = 0; // Hunter's Trophy 2 will fail to boot if we don't set this
|
||||
std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera);
|
||||
if (g_cameraCounter == 0)
|
||||
{
|
||||
coreinit::OSCreateAlarm(g_alarm_camera.GetPtr());
|
||||
coreinit::OSSetPeriodicAlarm(g_alarm_camera.GetPtr(), coreinit::OSGetTime(), (uint64)ESPRESSO_TIMER_CLOCK / 60ull, RPLLoader_MakePPCCallable(ppcCAMUpdate60));
|
||||
}
|
||||
g_cameraCounter++;
|
||||
s_instance.isExiting = false;
|
||||
s_instance.fps = initInfo->fps == CAMFps::FPS_15 ? 15 : 30;
|
||||
s_instance.initialized = true;
|
||||
s_instance.eventCallback = initInfo->callback;
|
||||
|
||||
return camInstance->handle;
|
||||
}
|
||||
coreinit::OSInitEvent(s_cameraOpenEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED,
|
||||
coreinit::OSEvent::EVENT_MODE::MODE_AUTO);
|
||||
|
||||
sint32 CAMExit(sint32 camHandle)
|
||||
{
|
||||
CameraInstance* camInstance = GetCameraInstanceByHandle(camHandle);
|
||||
if (!camInstance)
|
||||
return CAM_ERROR_INVALID_HANDLE;
|
||||
CAMClose(camHandle);
|
||||
delete camInstance;
|
||||
coreinit::__OSCreateThreadType(
|
||||
s_cameraWorkerThread, RPLLoader_MakePPCCallable(WorkerThreadFunc), 0, nullptr,
|
||||
s_cameraWorkerThreadStack.GetPtr() + s_cameraWorkerThreadStack.GetByteSize(),
|
||||
s_cameraWorkerThreadStack.GetByteSize(),
|
||||
0x10, initInfo->threadFlags & 7, OSThread_t::THREAD_TYPE::TYPE_DRIVER);
|
||||
coreinit::OSSetThreadName(s_cameraWorkerThread.GetPtr(), s_cameraWorkerThreadName.GetPtr());
|
||||
coreinit::OSResumeThread(s_cameraWorkerThread.GetPtr());
|
||||
|
||||
std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera);
|
||||
g_cameraCounter--;
|
||||
if (g_cameraCounter == 0)
|
||||
coreinit::OSCancelAlarm(g_alarm_camera.GetPtr());
|
||||
return CAM_ERROR_SUCCESS;
|
||||
}
|
||||
s_instance.previouslyInitialized = true;
|
||||
|
||||
sint32 CAMOpen(sint32 camHandle)
|
||||
{
|
||||
CameraInstance* camInstance = GetCameraInstanceByHandle(camHandle);
|
||||
if (!camInstance)
|
||||
return CAM_ERROR_INVALID_HANDLE;
|
||||
camInstance->OpenCam();
|
||||
return CAM_ERROR_SUCCESS;
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
sint32 CAMClose(sint32 camHandle)
|
||||
{
|
||||
CameraInstance* camInstance = GetCameraInstanceByHandle(camHandle);
|
||||
if (!camInstance)
|
||||
return CAM_ERROR_INVALID_HANDLE;
|
||||
camInstance->CloseCam();
|
||||
return CAM_ERROR_SUCCESS;
|
||||
}
|
||||
CAMStatus CAMClose(sint32 camHandle)
|
||||
{
|
||||
if (camHandle != CAM_HANDLE)
|
||||
return CAM_STATUS_INVALID_HANDLE;
|
||||
|
||||
sint32 CAMSubmitTargetSurface(sint32 camHandle, CAMTargetSurface* targetSurface)
|
||||
{
|
||||
CameraInstance* camInstance = GetCameraInstanceByHandle(camHandle);
|
||||
if (!camInstance)
|
||||
return CAM_ERROR_INVALID_HANDLE;
|
||||
|
||||
camInstance->QueueTargetSurface(targetSurface);
|
||||
// Check necessary because this function is called on unload, and OSMutex needs an active scheduler
|
||||
const auto schedulerActive = coreinit::OSIsSchedulerActive();
|
||||
if (schedulerActive)
|
||||
coreinit::OSLockMutex(s_cameraMutex);
|
||||
|
||||
return CAM_ERROR_SUCCESS;
|
||||
}
|
||||
if (!s_instance.initialized || !s_instance.isOpen)
|
||||
return CAM_STATUS_UNINITIALIZED;
|
||||
s_instance.isOpen = false;
|
||||
CameraManager::Close();
|
||||
if (schedulerActive)
|
||||
coreinit::OSUnlockMutex(s_cameraMutex);
|
||||
return CAM_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
g_cameraCounter = 0;
|
||||
}
|
||||
CAMStatus CAMOpen(sint32 camHandle)
|
||||
{
|
||||
if (camHandle != CAM_HANDLE)
|
||||
return CAM_STATUS_INVALID_HANDLE;
|
||||
CafeLockGuard lock(s_cameraMutex);
|
||||
if (!s_instance.initialized)
|
||||
return CAM_STATUS_UNINITIALIZED;
|
||||
if (s_instance.isOpen)
|
||||
return CAM_STATUS_DEVICE_IN_USE;
|
||||
CameraManager::Open();
|
||||
s_instance.isOpen = true;
|
||||
coreinit::OSSignalEvent(s_cameraOpenEvent);
|
||||
s_instance.inTargetBuffers.Clear();
|
||||
return CAM_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
class : public COSModule
|
||||
{
|
||||
public:
|
||||
std::string_view GetName() override
|
||||
{
|
||||
return "camera";
|
||||
}
|
||||
CAMStatus CAMSubmitTargetSurface(sint32 camHandle, CAMTargetSurface* targetSurface)
|
||||
{
|
||||
if (camHandle != CAM_HANDLE)
|
||||
return CAM_STATUS_INVALID_HANDLE;
|
||||
if (!targetSurface || targetSurface->data.IsNull() || targetSurface->size < 1)
|
||||
return CAM_STATUS_INVALID_ARG;
|
||||
cemu_assert_debug(targetSurface->size >= CameraManager::CAMERA_NV12_BUFFER_SIZE);
|
||||
CafeLockGuard lock(s_cameraMutex);
|
||||
if (!s_instance.initialized)
|
||||
return CAM_STATUS_UNINITIALIZED;
|
||||
if (!s_instance.inTargetBuffers.Push(targetSurface->data))
|
||||
return CAM_STATUS_SURFACE_QUEUE_FULL;
|
||||
return CAM_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void RPLMapped() override
|
||||
{
|
||||
cafeExportRegister("camera", CAMGetMemReq, LogType::Placeholder);
|
||||
cafeExportRegister("camera", CAMCheckMemSegmentation, LogType::Placeholder);
|
||||
cafeExportRegister("camera", CAMInit, LogType::Placeholder);
|
||||
cafeExportRegister("camera", CAMExit, LogType::Placeholder);
|
||||
cafeExportRegister("camera", CAMOpen, LogType::Placeholder);
|
||||
cafeExportRegister("camera", CAMClose, LogType::Placeholder);
|
||||
cafeExportRegister("camera", CAMSubmitTargetSurface, LogType::Placeholder);
|
||||
};
|
||||
void CAMExit(sint32 camHandle)
|
||||
{
|
||||
if (camHandle != CAM_HANDLE || !s_instance.previouslyInitialized)
|
||||
return;
|
||||
CafeLockGuard lock(s_cameraMutex);
|
||||
if (!s_instance.initialized)
|
||||
return;
|
||||
s_instance.isExiting = true;
|
||||
s_instance.isWorkerExiting = true;
|
||||
if (s_instance.isOpen)
|
||||
CAMClose(camHandle);
|
||||
coreinit::OSSignalEvent(s_cameraOpenEvent.GetPtr());
|
||||
s_instance.initialized = false;
|
||||
}
|
||||
|
||||
void rpl_entry(uint32 moduleHandle, coreinit::RplEntryReason reason) override
|
||||
{
|
||||
if (reason == coreinit::RplEntryReason::Loaded)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
else if (reason == coreinit::RplEntryReason::Unloaded)
|
||||
{
|
||||
// todo
|
||||
}
|
||||
}
|
||||
}s_COScameraModule;
|
||||
namespace Driver
|
||||
{
|
||||
MEMPTR<char> Name()
|
||||
{
|
||||
return s_driverName.GetPtr();
|
||||
}
|
||||
void Init() {}
|
||||
void Acquire() {}
|
||||
void Release()
|
||||
{
|
||||
CAMClose(CAM_HANDLE);
|
||||
}
|
||||
void Done()
|
||||
{
|
||||
CAMClose(CAM_HANDLE);
|
||||
}
|
||||
}
|
||||
|
||||
COSModule* GetModule()
|
||||
{
|
||||
return &s_COScameraModule;
|
||||
}
|
||||
class : public COSModule
|
||||
{
|
||||
public:
|
||||
std::string_view GetName() override
|
||||
{
|
||||
return "camera";
|
||||
}
|
||||
|
||||
void RPLMapped() override
|
||||
{
|
||||
cafeExportRegister("camera", CAMGetMemReq, LogType::InputAPI);
|
||||
cafeExportRegister("camera", CAMCheckMemSegmentation, LogType::InputAPI);
|
||||
cafeExportRegister("camera", CAMInit, LogType::InputAPI);
|
||||
cafeExportRegister("camera", CAMExit, LogType::InputAPI);
|
||||
cafeExportRegister("camera", CAMOpen, LogType::InputAPI);
|
||||
cafeExportRegister("camera", CAMClose, LogType::InputAPI);
|
||||
cafeExportRegister("camera", CAMSubmitTargetSurface, LogType::InputAPI);
|
||||
};
|
||||
|
||||
void rpl_entry(uint32 moduleHandle, coreinit::RplEntryReason reason) override
|
||||
{
|
||||
if (reason == coreinit::RplEntryReason::Loaded)
|
||||
{
|
||||
s_driverInterface->getDriverName = RPLLoader_MakePPCCallable(+[](PPCInterpreter_t* hCPU)
|
||||
{
|
||||
osLib_returnFromFunction(hCPU, Driver::Name().GetMPTR());
|
||||
});
|
||||
|
||||
s_driverInterface->init = RPLLoader_MakePPCCallable(+[](PPCInterpreter_t* hCPU)
|
||||
{
|
||||
Driver::Init();
|
||||
osLib_returnFromFunction(hCPU, 0);
|
||||
});
|
||||
s_driverInterface->onAcquireForeground = RPLLoader_MakePPCCallable(+[](PPCInterpreter_t* hCPU)
|
||||
{
|
||||
Driver::Acquire();
|
||||
osLib_returnFromFunction(hCPU, 0);
|
||||
});
|
||||
s_driverInterface->onReleaseForeground = RPLLoader_MakePPCCallable(+[](PPCInterpreter_t* hCPU)
|
||||
{
|
||||
Driver::Release();
|
||||
osLib_returnFromFunction(hCPU, 0);
|
||||
});
|
||||
s_driverInterface->done = RPLLoader_MakePPCCallable(+[](PPCInterpreter_t* hCPU)
|
||||
{
|
||||
Driver::Done();
|
||||
osLib_returnFromFunction(hCPU, 0);
|
||||
});
|
||||
coreinit::OSDriver_Register(moduleHandle, 0x226, s_driverInterface.GetPtr(), 0, nullptr, nullptr,
|
||||
nullptr);
|
||||
}
|
||||
else if (reason == coreinit::RplEntryReason::Unloaded)
|
||||
{
|
||||
Driver::Done();
|
||||
coreinit::OSDriver_Deregister(moduleHandle, 0);
|
||||
}
|
||||
}
|
||||
} s_COScameraModule;
|
||||
|
||||
COSModule* GetModule()
|
||||
{
|
||||
return &s_COScameraModule;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,5 @@
|
|||
|
||||
namespace camera
|
||||
{
|
||||
sint32 CAMOpen(sint32 camHandle);
|
||||
sint32 CAMClose(sint32 camHandle);
|
||||
|
||||
COSModule* GetModule();
|
||||
};
|
||||
|
|
@ -284,12 +284,17 @@ XMLConfigParser CemuConfig::Load(XMLConfigParser& parser)
|
|||
dsu_client.host = dsuc.get_attribute("host", dsu_client.host);
|
||||
dsu_client.port = dsuc.get_attribute("port", dsu_client.port);
|
||||
|
||||
|
||||
// emulatedusbdevices
|
||||
auto usbdevices = parser.get("EmulatedUsbDevices");
|
||||
emulated_usb_devices.emulate_skylander_portal = usbdevices.get("EmulateSkylanderPortal", emulated_usb_devices.emulate_skylander_portal);
|
||||
emulated_usb_devices.emulate_infinity_base = usbdevices.get("EmulateInfinityBase", emulated_usb_devices.emulate_infinity_base);
|
||||
emulated_usb_devices.emulate_dimensions_toypad = usbdevices.get("EmulateDimensionsToypad", emulated_usb_devices.emulate_dimensions_toypad);
|
||||
|
||||
|
||||
auto camera = parser.get("Camera");
|
||||
camera_id = camera.get("DeviceId", "");
|
||||
|
||||
return parser;
|
||||
}
|
||||
|
||||
|
|
@ -454,6 +459,9 @@ XMLConfigParser CemuConfig::Save(XMLConfigParser& parser)
|
|||
usbdevices.set("EmulateInfinityBase", emulated_usb_devices.emulate_infinity_base.GetValue());
|
||||
usbdevices.set("EmulateDimensionsToypad", emulated_usb_devices.emulate_dimensions_toypad.GetValue());
|
||||
|
||||
auto camera = config.set("Camera");
|
||||
camera.set("DeviceId", camera_id.GetValue());
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -512,6 +512,10 @@ struct CemuConfig
|
|||
// debug
|
||||
ConfigValueBounds<CrashDump> crash_dump{ CrashDump::Disabled };
|
||||
ConfigValue<uint16> gdb_port{ 1337 };
|
||||
|
||||
// camera
|
||||
ConfigValue<std::string> camera_id;
|
||||
|
||||
#ifdef ENABLE_METAL
|
||||
ConfigValue<std::string> gpu_capture_dir{ "" };
|
||||
ConfigValue<bool> framebuffer_fetch{ true };
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
add_library(CemuWxGui STATIC
|
||||
CameraSettingsWindow.cpp
|
||||
CameraSettingsWindow.h
|
||||
canvas/IRenderCanvas.h
|
||||
CemuApp.cpp
|
||||
CemuApp.h
|
||||
|
|
|
|||
179
src/gui/wxgui/CameraSettingsWindow.cpp
Normal file
179
src/gui/wxgui/CameraSettingsWindow.cpp
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
#include "CameraSettingsWindow.h"
|
||||
|
||||
#include "input/camera/CameraManager.h"
|
||||
#include <wx/button.h>
|
||||
#include <wx/combobox.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/dcbuffer.h>
|
||||
#include <wx/rawbmp.h>
|
||||
#include <wx/stattext.h>
|
||||
|
||||
struct wxCameraDeviceUniqueId : public wxClientData
|
||||
{
|
||||
std::string id;
|
||||
|
||||
explicit wxCameraDeviceUniqueId(std::string id) : id(std::move(id))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
CameraSettingsWindow::CameraSettingsWindow(wxWindow* parent)
|
||||
: wxDialog(parent, wxID_ANY, _("Camera settings"), wxDefaultPosition),
|
||||
m_imageBitmap(CameraManager::CAMERA_WIDTH, CameraManager::CAMERA_HEIGHT, 24),
|
||||
m_imageBuffer(CameraManager::CAMERA_RGB_BUFFER_SIZE)
|
||||
{
|
||||
CameraManager::Open();
|
||||
auto* rootSizer = new wxBoxSizer(wxVERTICAL);
|
||||
{
|
||||
auto* topSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
{
|
||||
m_cameraComboBox = new wxComboBox(this, wxID_ANY);
|
||||
m_cameraComboBox->Bind(wxEVT_COMBOBOX, &CameraSettingsWindow::OnSelectCameraChoice, this);
|
||||
m_cameraComboBox->SetToolTip(_("Cameras are only listed if they support 640x480"));
|
||||
|
||||
m_refreshButton = new wxButton(this, wxID_ANY, _("Refresh"));
|
||||
m_refreshButton->Bind(wxEVT_BUTTON, &CameraSettingsWindow::OnRefreshPressed, this);
|
||||
wxQueueEvent(m_refreshButton, new wxCommandEvent{wxEVT_BUTTON});
|
||||
|
||||
topSizer->Add(m_cameraComboBox);
|
||||
topSizer->Add(m_refreshButton);
|
||||
}
|
||||
|
||||
m_imageWindow = new wxWindow(this, wxID_ANY, wxDefaultPosition,
|
||||
{CameraManager::CAMERA_WIDTH, CameraManager::CAMERA_HEIGHT});
|
||||
m_imageWindow->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
|
||||
m_statusInfoText = new wxStaticText(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize,
|
||||
wxTE_READONLY);
|
||||
|
||||
rootSizer->Add(topSizer);
|
||||
rootSizer->Add(m_imageWindow, wxEXPAND);
|
||||
rootSizer->Add(m_statusInfoText);
|
||||
}
|
||||
SetSizerAndFit(rootSizer);
|
||||
m_imageUpdateTimer.Bind(wxEVT_TIMER, &CameraSettingsWindow::Update, this);
|
||||
m_imageWindow->Bind(wxEVT_PAINT, &CameraSettingsWindow::DrawImage, this);
|
||||
this->Bind(wxEVT_CLOSE_WINDOW, &CameraSettingsWindow::OnClose, this);
|
||||
|
||||
m_imageUpdateTimer.Start(33, wxTIMER_CONTINUOUS);
|
||||
}
|
||||
|
||||
void CameraSettingsWindow::OnSelectCameraChoice(wxCommandEvent&)
|
||||
{
|
||||
const auto selection = m_cameraComboBox->GetSelection();
|
||||
if (selection < 0)
|
||||
{
|
||||
m_cameraComboBox->Select(0);
|
||||
return;
|
||||
}
|
||||
if (selection == 0)
|
||||
CameraManager::ResetDevice();
|
||||
else
|
||||
{
|
||||
auto id = static_cast<wxCameraDeviceUniqueId*>(m_cameraComboBox->GetClientData(selection))->id;
|
||||
CameraManager::SetDevice(id);
|
||||
}
|
||||
}
|
||||
|
||||
void CameraSettingsWindow::OnRefreshPressed(wxCommandEvent&)
|
||||
{
|
||||
wxArrayString choices = {_("None")};
|
||||
const auto devices = CameraManager::EnumerateDevices();
|
||||
const auto currentDevice = CameraManager::GetCurrentDevice();
|
||||
auto currentIndex = 0;
|
||||
for (auto i = 0; i < devices.size(); ++i)
|
||||
{
|
||||
auto& device = devices[i];
|
||||
choices.push_back(fmt::format("{} ({})", device.name, device.uniqueId));
|
||||
if (currentDevice && device.uniqueId == *currentDevice)
|
||||
currentIndex = i + 1;
|
||||
}
|
||||
|
||||
m_cameraComboBox->Set(choices);
|
||||
m_cameraComboBox->SetSelection(currentIndex);
|
||||
// Client data can only be set when there are entries
|
||||
for (auto i = 0; i < devices.size(); ++i)
|
||||
{
|
||||
auto& device = devices[i];
|
||||
const auto choiceIndex = i + 1;
|
||||
m_cameraComboBox->SetClientData(choiceIndex, new wxCameraDeviceUniqueId{device.uniqueId});
|
||||
}
|
||||
}
|
||||
|
||||
void CameraSettingsWindow::Update(const wxTimerEvent&)
|
||||
{
|
||||
switch (CameraManager::GetState())
|
||||
{
|
||||
using enum CameraManager::State;
|
||||
case NoSupport:
|
||||
{
|
||||
m_statusInfoText->SetLabel(_("This build does not support camera capture"));
|
||||
return;
|
||||
}
|
||||
case NoDevice:
|
||||
{
|
||||
m_statusInfoText->SetLabel(_("No camera selected"));
|
||||
return;
|
||||
}
|
||||
case UnsupportedFormat:
|
||||
{
|
||||
m_statusInfoText->SetLabel(_("Camera does not support the required format"));
|
||||
}
|
||||
case NotOpen:
|
||||
{
|
||||
m_statusInfoText->SetLabel(_("Camera is unavailable, or was closed due to an error"));
|
||||
return;
|
||||
}
|
||||
case NeedsPermission:
|
||||
{
|
||||
m_statusInfoText->SetLabel(_("Waiting for permission to access camea"));
|
||||
return;
|
||||
}
|
||||
case NoPermission:
|
||||
{
|
||||
m_statusInfoText->SetLabel(_("Cemu was denied permission to access camera"));
|
||||
return;
|
||||
}
|
||||
case Capturing:
|
||||
{
|
||||
m_statusInfoText->SetLabel(_("Capturing"));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
cemu_assert(false);
|
||||
}
|
||||
CameraManager::FillRGBBuffer(std::span<uint8, CameraManager::CAMERA_RGB_BUFFER_SIZE>(m_imageBuffer));
|
||||
wxNativePixelData data{m_imageBitmap};
|
||||
if (!data)
|
||||
return;
|
||||
wxNativePixelData::Iterator p{data};
|
||||
for (auto row = 0u; row < CameraManager::CAMERA_HEIGHT; ++row)
|
||||
{
|
||||
const auto* rowPtr = m_imageBuffer.data() + row * CameraManager::CAMERA_WIDTH * 3;
|
||||
wxNativePixelData::Iterator rowStart = p;
|
||||
for (auto col = 0u; col < CameraManager::CAMERA_WIDTH; ++col, ++p)
|
||||
{
|
||||
auto* colour = rowPtr + col * 3;
|
||||
p.Red() = colour[0];
|
||||
p.Green() = colour[1];
|
||||
p.Blue() = colour[2];
|
||||
}
|
||||
p = rowStart;
|
||||
p.OffsetY(data, 1);
|
||||
}
|
||||
m_imageWindow->Refresh();
|
||||
}
|
||||
|
||||
void CameraSettingsWindow::DrawImage(const wxPaintEvent&)
|
||||
{
|
||||
wxAutoBufferedPaintDC dc{m_imageWindow};
|
||||
dc.DrawBitmap(m_imageBitmap, 0, 0);
|
||||
}
|
||||
|
||||
void CameraSettingsWindow::OnClose(wxCloseEvent& event)
|
||||
{
|
||||
m_imageUpdateTimer.Stop();
|
||||
CameraManager::SaveDevice();
|
||||
CameraManager::Close();
|
||||
event.Skip();
|
||||
}
|
||||
27
src/gui/wxgui/CameraSettingsWindow.h
Normal file
27
src/gui/wxgui/CameraSettingsWindow.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/timer.h>
|
||||
#include <wx/bitmap.h>
|
||||
|
||||
class wxButton;
|
||||
class wxComboBox;
|
||||
class wxStaticText;
|
||||
|
||||
class CameraSettingsWindow : public wxDialog
|
||||
{
|
||||
wxComboBox* m_cameraComboBox;
|
||||
wxButton* m_refreshButton;
|
||||
wxWindow* m_imageWindow;
|
||||
wxBitmap m_imageBitmap;
|
||||
wxStaticText* m_statusInfoText;
|
||||
wxTimer m_imageUpdateTimer;
|
||||
std::vector<uint8> m_imageBuffer;
|
||||
|
||||
public:
|
||||
explicit CameraSettingsWindow(wxWindow* parent);
|
||||
void OnSelectCameraChoice(wxCommandEvent&);
|
||||
void OnRefreshPressed(wxCommandEvent&);
|
||||
void Update(const wxTimerEvent&);
|
||||
void DrawImage(const wxPaintEvent&);
|
||||
void OnClose(wxCloseEvent& event);
|
||||
};
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
#include "CemuUpdateWindow.h"
|
||||
#include "GraphicPacksWindow2.h"
|
||||
#include "AudioDebuggerWindow.h"
|
||||
#include "CameraSettingsWindow.h"
|
||||
#include "input/InputSettings2.h"
|
||||
#include "input/HotkeySettings.h"
|
||||
#include "debugger/DebuggerWindow2.h"
|
||||
|
|
@ -91,6 +92,7 @@ enum
|
|||
MAINFRAME_MENU_ID_OPTIONS_AUDIO,
|
||||
MAINFRAME_MENU_ID_OPTIONS_INPUT,
|
||||
MAINFRAME_MENU_ID_OPTIONS_HOTKEY,
|
||||
MAINFRAME_MENU_ID_OPTIONS_CAMERA,
|
||||
MAINFRAME_MENU_ID_OPTIONS_MAC_SETTINGS,
|
||||
// options -> account
|
||||
MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_1 = 20350,
|
||||
|
|
@ -193,6 +195,7 @@ EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_GENERAL2, MainWindow::OnOptionsInput)
|
|||
EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_AUDIO, MainWindow::OnOptionsInput)
|
||||
EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_INPUT, MainWindow::OnOptionsInput)
|
||||
EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_HOTKEY, MainWindow::OnOptionsInput)
|
||||
EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_CAMERA, MainWindow::OnOptionsInput)
|
||||
EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_MAC_SETTINGS, MainWindow::OnOptionsInput)
|
||||
// tools menu
|
||||
EVT_MENU(MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER, MainWindow::OnToolsInput)
|
||||
|
|
@ -935,6 +938,14 @@ void MainWindow::OnOptionsInput(wxCommandEvent& event)
|
|||
frame->Show();
|
||||
break;
|
||||
}
|
||||
case MAINFRAME_MENU_ID_OPTIONS_CAMERA:
|
||||
{
|
||||
auto* frame = new CameraSettingsWindow(this);
|
||||
frame->ShowModal();
|
||||
frame->Destroy();
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2248,6 +2259,7 @@ void MainWindow::RecreateMenu()
|
|||
optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_GENERAL2, _("&General settings"));
|
||||
optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_INPUT, _("&Input settings"));
|
||||
optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_HOTKEY, _("&Hotkey settings"));
|
||||
optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_CAMERA, _("&Camera settings"));
|
||||
|
||||
optionsMenu->AppendSeparator();
|
||||
optionsMenu->AppendSubMenu(m_optionsAccountMenu, _("&Active account"));
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ add_library(CemuInput
|
|||
api/Keyboard/KeyboardControllerProvider.cpp
|
||||
api/Keyboard/KeyboardController.cpp
|
||||
api/Keyboard/KeyboardController.h
|
||||
camera/CameraManager.h
|
||||
emulated/ProController.cpp
|
||||
emulated/EmulatedController.h
|
||||
emulated/EmulatedController.cpp
|
||||
|
|
@ -98,6 +99,16 @@ if(ENABLE_SDL)
|
|||
target_link_libraries(CemuInput PRIVATE SDL3::SDL3)
|
||||
endif()
|
||||
|
||||
if (NOT ENABLE_CAMERA)
|
||||
target_sources(CemuInput PRIVATE
|
||||
camera/DummyCameraManager.cpp
|
||||
)
|
||||
elseif (ENABLE_SDL)
|
||||
target_sources(CemuInput PRIVATE
|
||||
camera/SDLCameraManager.cpp
|
||||
)
|
||||
endif ()
|
||||
|
||||
target_include_directories(CemuInput PUBLIC "../")
|
||||
|
||||
target_link_libraries(CemuInput PRIVATE
|
||||
|
|
|
|||
40
src/input/camera/CameraManager.h
Normal file
40
src/input/camera/CameraManager.h
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
namespace CameraManager
|
||||
{
|
||||
enum class State
|
||||
{
|
||||
Capturing,
|
||||
NoSupport,
|
||||
NoDevice,
|
||||
UnsupportedFormat,
|
||||
NotOpen,
|
||||
NeedsPermission,
|
||||
NoPermission
|
||||
};
|
||||
constexpr uint32 CAMERA_WIDTH = 640;
|
||||
constexpr uint32 CAMERA_HEIGHT = 480;
|
||||
constexpr uint32 CAMERA_PITCH = 768;
|
||||
|
||||
constexpr uint32 CAMERA_NV12_BUFFER_SIZE = (CAMERA_HEIGHT * CAMERA_PITCH * 3) >> 1;
|
||||
constexpr uint32 CAMERA_RGB_BUFFER_SIZE = CAMERA_HEIGHT * CAMERA_WIDTH * 3;
|
||||
|
||||
struct DeviceInfo
|
||||
{
|
||||
std::string uniqueId;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
void Open();
|
||||
void Close();
|
||||
|
||||
void FillNV12Buffer(std::span<uint8, CAMERA_NV12_BUFFER_SIZE> nv12Buffer);
|
||||
void FillRGBBuffer(std::span<uint8, CAMERA_RGB_BUFFER_SIZE> rgbBuffer);
|
||||
|
||||
void SetDevice(std::string_view deviceId);
|
||||
void ResetDevice();
|
||||
std::vector<DeviceInfo> EnumerateDevices();
|
||||
void SaveDevice();
|
||||
std::optional<std::string> GetCurrentDevice();
|
||||
State GetState();
|
||||
} // namespace CameraManager
|
||||
51
src/input/camera/DummyCameraManager.cpp
Normal file
51
src/input/camera/DummyCameraManager.cpp
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#include "CameraManager.h"
|
||||
|
||||
namespace CameraManager
|
||||
{
|
||||
void FillNV12Buffer(std::span<uint8, CAMERA_NV12_BUFFER_SIZE> nv12Buffer)
|
||||
{
|
||||
constexpr static auto PIXEL_COUNT = CAMERA_HEIGHT * CAMERA_PITCH;
|
||||
std::ranges::fill_n(nv12Buffer.data(), PIXEL_COUNT, 16);
|
||||
std::ranges::fill_n(nv12Buffer.data() + PIXEL_COUNT, (PIXEL_COUNT / 2), 128);
|
||||
}
|
||||
|
||||
void FillRGBBuffer(std::span<uint8, CAMERA_RGB_BUFFER_SIZE> rgbBuffer)
|
||||
{
|
||||
std::ranges::fill(rgbBuffer, 0);
|
||||
}
|
||||
|
||||
void SetDevice(std::string_view deviceId)
|
||||
{
|
||||
}
|
||||
|
||||
void ResetDevice()
|
||||
{
|
||||
}
|
||||
|
||||
void Open()
|
||||
{
|
||||
}
|
||||
|
||||
void Close()
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<DeviceInfo> EnumerateDevices()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
void SaveDevice()
|
||||
{
|
||||
}
|
||||
|
||||
std::optional<std::string> GetCurrentDevice()
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
State GetState()
|
||||
{
|
||||
return State::NoSupport;
|
||||
}
|
||||
} // namespace CameraManager
|
||||
315
src/input/camera/SDLCameraManager.cpp
Normal file
315
src/input/camera/SDLCameraManager.cpp
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
#include "CameraManager.h"
|
||||
|
||||
#include "config/CemuConfig.h"
|
||||
#include "util/helpers/helpers.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
|
||||
#include <SDL3/SDL_camera.h>
|
||||
#include <SDL3/SDL_init.h>
|
||||
|
||||
namespace CameraManager
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct InternalDeviceInfo
|
||||
{
|
||||
SDL_CameraID id;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
std::mutex s_deviceMutex;
|
||||
std::mutex s_bufferMutex;
|
||||
std::optional<SDL_CameraID> s_deviceId;
|
||||
std::atomic<SDL_Camera*> s_camera;
|
||||
std::unique_ptr<uint8[]> s_rgbBufferOut;
|
||||
std::unique_ptr<uint8[]> s_nv12BufferOut;
|
||||
int s_refCount = 0;
|
||||
std::thread s_captureThread;
|
||||
std::atomic_bool s_running = false;
|
||||
std::atomic_bool s_permissionWasDenied = false;
|
||||
std::atomic_bool s_unsupportedFormat = false;
|
||||
}
|
||||
|
||||
static void OpenStream()
|
||||
{
|
||||
s_unsupportedFormat = false;
|
||||
s_permissionWasDenied = false;
|
||||
SDL_CameraSpec cameraSpec;
|
||||
cameraSpec.format = SDL_PIXELFORMAT_NV12;
|
||||
cameraSpec.colorspace = SDL_COLORSPACE_BT601_LIMITED;
|
||||
cameraSpec.framerate_numerator = 30;
|
||||
cameraSpec.framerate_denominator = 1;
|
||||
cameraSpec.width = CAMERA_WIDTH;
|
||||
cameraSpec.height = CAMERA_HEIGHT;
|
||||
const auto camera = SDL_OpenCamera(*s_deviceId, &cameraSpec);
|
||||
if (camera == nullptr)
|
||||
{
|
||||
cemuLog_log(LogType::Force, "Failed to open camera: {}", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
const auto permission = SDL_GetCameraPermissionState(camera);
|
||||
if (permission == SDL_CAMERA_PERMISSION_STATE_PENDING)
|
||||
cemuLog_log(LogType::Force, "Cemu is waiting for permission to access camera");
|
||||
s_camera = camera;
|
||||
}
|
||||
|
||||
static void CloseStream()
|
||||
{
|
||||
if (s_camera)
|
||||
{
|
||||
SDL_CloseCamera(s_camera);
|
||||
s_camera = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void CaptureWorkerFunc()
|
||||
{
|
||||
SetThreadName("CameraManager");
|
||||
auto nv12Buffer = std::unique_ptr<uint8[]>(new uint8[CAMERA_NV12_BUFFER_SIZE]);
|
||||
while (s_running)
|
||||
{
|
||||
if (!s_camera)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
std::this_thread::yield();
|
||||
continue;
|
||||
}
|
||||
if (auto deviceLock = std::unique_lock(s_deviceMutex, std::try_to_lock))
|
||||
{
|
||||
const auto camera = s_camera.load();
|
||||
if (!camera)
|
||||
continue;
|
||||
if (auto frame = SDL_AcquireCameraFrame(camera, nullptr))
|
||||
{
|
||||
if (frame->format != SDL_PIXELFORMAT_NV12 || frame->w != CAMERA_WIDTH || frame->h != CAMERA_HEIGHT)
|
||||
{
|
||||
cemuLog_log(LogType::Force, "Camera format is not NV12 {}x{}", CAMERA_WIDTH, CAMERA_HEIGHT);
|
||||
s_unsupportedFormat = true;
|
||||
CloseStream();
|
||||
continue;
|
||||
}
|
||||
if (SDL_MUSTLOCK(frame))
|
||||
SDL_LockSurface(frame);
|
||||
const auto byteRowCount = (frame->h * 3) >> 1;
|
||||
for (auto row = 0; row < byteRowCount; ++row)
|
||||
{
|
||||
const auto lineIn = static_cast<const uint8*>(frame->pixels) + frame->pitch * row;
|
||||
const auto lineOut = nv12Buffer.get() + CAMERA_PITCH * row;
|
||||
std::memcpy(lineOut, lineIn, frame->w);
|
||||
}
|
||||
if (SDL_MUSTLOCK(frame))
|
||||
SDL_UnlockSurface(frame);
|
||||
SDL_ReleaseCameraFrame(s_camera, frame);
|
||||
std::scoped_lock lock(s_bufferMutex);
|
||||
std::swap(s_nv12BufferOut, nv12Buffer);
|
||||
}
|
||||
else if (auto permission = SDL_GetCameraPermissionState(s_camera); permission == SDL_CAMERA_PERMISSION_STATE_DENIED)
|
||||
{
|
||||
s_permissionWasDenied = true;
|
||||
CloseStream();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(30));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ResetBuffers()
|
||||
{
|
||||
std::scoped_lock lock(s_bufferMutex);
|
||||
std::fill_n(s_rgbBufferOut.get(), CAMERA_RGB_BUFFER_SIZE, 0);
|
||||
constexpr static auto PIXEL_COUNT = CAMERA_HEIGHT * CAMERA_PITCH;
|
||||
std::ranges::fill_n(s_nv12BufferOut.get(), PIXEL_COUNT, 16);
|
||||
std::ranges::fill_n(s_nv12BufferOut.get() + PIXEL_COUNT, (PIXEL_COUNT / 2), 128);
|
||||
}
|
||||
|
||||
static std::vector<InternalDeviceInfo> InternalEnumerateDevices()
|
||||
{
|
||||
int deviceCount = 0;
|
||||
const auto devices = SDL_GetCameras(&deviceCount);
|
||||
if (devices == nullptr)
|
||||
{
|
||||
cemuLog_log(LogType::Force, "Failed to list cameras: {}", SDL_GetError());
|
||||
return {};
|
||||
}
|
||||
if (deviceCount == 0)
|
||||
return {};
|
||||
std::vector<InternalDeviceInfo> infos;
|
||||
cemuLog_log(LogType::Force, "Available video capture devices:");
|
||||
for (auto cameraId : std::span(devices, deviceCount))
|
||||
{
|
||||
const auto name = SDL_GetCameraName(cameraId);
|
||||
std::string strName = name ? name : "";
|
||||
cemuLog_log(LogType::Force, "\t{}", strName);
|
||||
infos.push_back(InternalDeviceInfo{.id = cameraId, .name = std::move(strName)});
|
||||
}
|
||||
SDL_free(devices);
|
||||
return infos;
|
||||
}
|
||||
|
||||
static void Init()
|
||||
{
|
||||
SDL_InitSubSystem(SDL_INIT_CAMERA);
|
||||
s_running = true;
|
||||
s_rgbBufferOut.reset(new uint8[CAMERA_RGB_BUFFER_SIZE]);
|
||||
s_nv12BufferOut.reset(new uint8[CAMERA_NV12_BUFFER_SIZE]);
|
||||
|
||||
s_captureThread = std::thread(&CaptureWorkerFunc);
|
||||
|
||||
const auto deviceName = GetConfig().camera_id.GetValue();
|
||||
if (!deviceName.empty())
|
||||
{
|
||||
for (auto device : InternalEnumerateDevices())
|
||||
{
|
||||
if (device.name == deviceName)
|
||||
{
|
||||
s_deviceId = device.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
ResetBuffers();
|
||||
}
|
||||
|
||||
static void Deinit()
|
||||
{
|
||||
CloseStream();
|
||||
s_running = false;
|
||||
s_captureThread.join();
|
||||
s_nv12BufferOut.reset();
|
||||
s_rgbBufferOut.reset();
|
||||
SDL_QuitSubSystem(SDL_INIT_CAMERA);
|
||||
}
|
||||
|
||||
void FillNV12Buffer(std::span<uint8, CAMERA_NV12_BUFFER_SIZE> nv12Buffer)
|
||||
{
|
||||
std::scoped_lock lock(s_bufferMutex);
|
||||
std::ranges::copy_n(s_nv12BufferOut.get(), CAMERA_NV12_BUFFER_SIZE, nv12Buffer.data());
|
||||
}
|
||||
|
||||
static uint8 ClampU8(int v)
|
||||
{
|
||||
v = (v > 255) ? 255 : v;
|
||||
v = (v < 0) ? 0 : v;
|
||||
return v;
|
||||
}
|
||||
|
||||
void FillRGBBuffer(std::span<uint8, CAMERA_RGB_BUFFER_SIZE> rgbBuffer)
|
||||
{
|
||||
std::scoped_lock lock(s_bufferMutex);
|
||||
const auto* yPtr = s_nv12BufferOut.get();
|
||||
const auto* uvPtr = s_nv12BufferOut.get() + CAMERA_PITCH * CAMERA_HEIGHT;
|
||||
auto* rgbPtr = rgbBuffer.data();
|
||||
for (auto row = 0; row < CAMERA_HEIGHT; ++row)
|
||||
{
|
||||
const auto yRow = yPtr + row * CAMERA_PITCH;
|
||||
const auto uvRow = uvPtr + (row >> 1) * CAMERA_PITCH;
|
||||
const auto rgbRow = rgbPtr + row * CAMERA_WIDTH * 3;
|
||||
for (auto col = 0; col < CAMERA_WIDTH; ++col)
|
||||
{
|
||||
const auto _y = 19 * (yRow[col] - 16);
|
||||
const auto uvCol = col >> 1;
|
||||
const auto u = uvRow[(uvCol << 1) + 0];
|
||||
const auto v = uvRow[(uvCol << 1) + 1];
|
||||
rgbRow[col * 3 + 0] = ClampU8((_y + 26 * (v - 128)) >> 4);
|
||||
rgbRow[col * 3 + 1] = ClampU8((_y - 13 * (v - 128) - 6 * (u - 128)) >> 4);
|
||||
rgbRow[col * 3 + 2] = ClampU8((_y + 32 * (u - 128)) >> 4);;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetDevice(std::string_view deviceId)
|
||||
{
|
||||
std::scoped_lock lock(s_deviceMutex);
|
||||
CloseStream();
|
||||
SDL_CameraID id;
|
||||
auto [it, ec] = std::from_chars(deviceId.data(), deviceId.data() + deviceId.size(), id);
|
||||
if (ec != std::errc{})
|
||||
return;
|
||||
s_deviceId = id;
|
||||
if (s_refCount != 0)
|
||||
OpenStream();
|
||||
}
|
||||
|
||||
void ResetDevice()
|
||||
{
|
||||
std::scoped_lock lock(s_deviceMutex);
|
||||
CloseStream();
|
||||
s_deviceId = std::nullopt;
|
||||
ResetBuffers();
|
||||
}
|
||||
|
||||
|
||||
void Open()
|
||||
{
|
||||
std::scoped_lock lock(s_deviceMutex);
|
||||
if (!s_running)
|
||||
{
|
||||
Init();
|
||||
}
|
||||
if (s_deviceId && s_refCount == 0)
|
||||
{
|
||||
OpenStream();
|
||||
}
|
||||
s_refCount += 1;
|
||||
}
|
||||
|
||||
void Close()
|
||||
{
|
||||
std::scoped_lock lock(s_deviceMutex);
|
||||
if (s_refCount == 0)
|
||||
return;
|
||||
s_refCount -= 1;
|
||||
if (s_refCount != 0)
|
||||
return;
|
||||
CloseStream();
|
||||
Deinit();
|
||||
}
|
||||
|
||||
std::vector<DeviceInfo> EnumerateDevices()
|
||||
{
|
||||
std::scoped_lock lock(s_deviceMutex);
|
||||
std::vector<DeviceInfo> deviceInfos;
|
||||
for (const auto& [id, name] : InternalEnumerateDevices())
|
||||
{
|
||||
deviceInfos.push_back(DeviceInfo{.uniqueId = fmt::to_string(id), .name = name});
|
||||
}
|
||||
return deviceInfos;
|
||||
}
|
||||
|
||||
void SaveDevice()
|
||||
{
|
||||
std::scoped_lock lock(s_deviceMutex);
|
||||
const std::string cameraId = s_deviceId ? SDL_GetCameraName(*s_deviceId) : "";
|
||||
GetConfig().camera_id.SetValue(cameraId);
|
||||
GetConfigHandle().Save();
|
||||
}
|
||||
|
||||
std::optional<std::string> GetCurrentDevice()
|
||||
{
|
||||
if (s_deviceId)
|
||||
return std::to_string(*s_deviceId);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
State GetState()
|
||||
{
|
||||
if (!s_deviceId)
|
||||
return State::NoDevice;
|
||||
if (s_permissionWasDenied)
|
||||
return State::NoPermission;
|
||||
if (s_unsupportedFormat)
|
||||
return State::UnsupportedFormat;
|
||||
if (!s_camera)
|
||||
return State::NotOpen;
|
||||
const auto permission = SDL_GetCameraPermissionState(s_camera);
|
||||
if (permission == SDL_CAMERA_PERMISSION_STATE_PENDING)
|
||||
return State::NeedsPermission;
|
||||
return State::Capturing;
|
||||
}
|
||||
} // namespace CameraManager
|
||||
|
|
@ -65,5 +65,7 @@
|
|||
<string>Viewer</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>For Wii U Gamepad camera emulation</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -9,5 +9,7 @@
|
|||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.camera</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user