6 changed files with 320 additions and 7 deletions
-
1src/core/CMakeLists.txt
-
2src/core/hle/service/hid/irs.cpp
-
47src/core/hle/service/hid/irs_ring_lifo.h
-
237src/core/hle/service/hid/irsensor/clustering_processor.cpp
-
38src/core/hle/service/hid/irsensor/clustering_processor.h
-
2src/yuzu/bootmanager.cpp
@ -0,0 +1,47 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <array> |
||||
|
|
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace Service::IRS { |
||||
|
|
||||
|
template <typename State, std::size_t max_buffer_size> |
||||
|
struct Lifo { |
||||
|
s64 sampling_number{}; |
||||
|
s64 buffer_count{}; |
||||
|
std::array<State, max_buffer_size> entries{}; |
||||
|
|
||||
|
const State& ReadCurrentEntry() const { |
||||
|
return entries[GetBufferTail()]; |
||||
|
} |
||||
|
|
||||
|
const State& ReadPreviousEntry() const { |
||||
|
return entries[GetPreviousEntryIndex()]; |
||||
|
} |
||||
|
|
||||
|
s64 GetBufferTail() const { |
||||
|
return sampling_number % max_buffer_size; |
||||
|
} |
||||
|
|
||||
|
std::size_t GetPreviousEntryIndex() const { |
||||
|
return static_cast<size_t>((GetBufferTail() + max_buffer_size - 1) % max_buffer_size); |
||||
|
} |
||||
|
|
||||
|
std::size_t GetNextEntryIndex() const { |
||||
|
return static_cast<size_t>((GetBufferTail() + 1) % max_buffer_size); |
||||
|
} |
||||
|
|
||||
|
void WriteNextEntry(const State& new_state) { |
||||
|
if (buffer_count < max_buffer_size) { |
||||
|
buffer_count++; |
||||
|
} |
||||
|
sampling_number++; |
||||
|
entries[GetBufferTail()] = new_state; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
} // namespace Service::IRS |
||||
@ -1,34 +1,263 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
|
||||
|
#include <queue>
|
||||
|
|
||||
|
#include "core/hid/emulated_controller.h"
|
||||
|
#include "core/hid/hid_core.h"
|
||||
#include "core/hle/service/hid/irsensor/clustering_processor.h"
|
#include "core/hle/service/hid/irsensor/clustering_processor.h"
|
||||
|
|
||||
namespace Service::IRS { |
namespace Service::IRS { |
||||
ClusteringProcessor::ClusteringProcessor(Core::IrSensor::DeviceFormat& device_format) |
|
||||
: device(device_format) { |
|
||||
|
ClusteringProcessor::ClusteringProcessor(Core::HID::HIDCore& hid_core_, |
||||
|
Core::IrSensor::DeviceFormat& device_format, |
||||
|
std::size_t npad_index) |
||||
|
: device{device_format} { |
||||
|
npad_device = hid_core_.GetEmulatedControllerByIndex(npad_index); |
||||
|
|
||||
device.mode = Core::IrSensor::IrSensorMode::ClusteringProcessor; |
device.mode = Core::IrSensor::IrSensorMode::ClusteringProcessor; |
||||
device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected; |
device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected; |
||||
device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped; |
device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped; |
||||
|
SetDefaultConfig(); |
||||
|
|
||||
|
shared_memory = std::construct_at( |
||||
|
reinterpret_cast<ClusteringSharedMemory*>(&device_format.state.processor_raw_data)); |
||||
|
|
||||
|
Core::HID::ControllerUpdateCallback engine_callback{ |
||||
|
.on_change = [this](Core::HID::ControllerTriggerType type) { OnControllerUpdate(type); }, |
||||
|
.is_npad_service = true, |
||||
|
}; |
||||
|
callback_key = npad_device->SetCallback(engine_callback); |
||||
} |
} |
||||
|
|
||||
ClusteringProcessor::~ClusteringProcessor() = default; |
|
||||
|
ClusteringProcessor::~ClusteringProcessor() { |
||||
|
npad_device->DeleteCallback(callback_key); |
||||
|
}; |
||||
|
|
||||
void ClusteringProcessor::StartProcessor() {} |
|
||||
|
void ClusteringProcessor::StartProcessor() { |
||||
|
device.camera_status = Core::IrSensor::IrCameraStatus::Available; |
||||
|
device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Ready; |
||||
|
} |
||||
|
|
||||
void ClusteringProcessor::SuspendProcessor() {} |
void ClusteringProcessor::SuspendProcessor() {} |
||||
|
|
||||
void ClusteringProcessor::StopProcessor() {} |
void ClusteringProcessor::StopProcessor() {} |
||||
|
|
||||
|
void ClusteringProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType type) { |
||||
|
if (type != Core::HID::ControllerTriggerType::IrSensor) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
next_state = {}; |
||||
|
const auto camera_data = npad_device->GetCamera(); |
||||
|
auto filtered_image = camera_data.data; |
||||
|
|
||||
|
RemoveLowIntensityData(filtered_image); |
||||
|
|
||||
|
const std::size_t window_start_x = |
||||
|
static_cast<std::size_t>(current_config.window_of_interest.x); |
||||
|
const std::size_t window_start_y = |
||||
|
static_cast<std::size_t>(current_config.window_of_interest.y); |
||||
|
const std::size_t window_end_x = |
||||
|
window_start_x + static_cast<std::size_t>(current_config.window_of_interest.width); |
||||
|
const std::size_t window_end_y = |
||||
|
window_start_y + static_cast<std::size_t>(current_config.window_of_interest.height); |
||||
|
|
||||
|
for (std::size_t y = window_start_y; y < window_end_y; y++) { |
||||
|
for (std::size_t x = window_start_x; x < window_end_x; x++) { |
||||
|
u8 pixel = GetPixel(filtered_image, x, y); |
||||
|
if (pixel == 0) { |
||||
|
continue; |
||||
|
} |
||||
|
const auto cluster = GetClusterProperties(filtered_image, x, y); |
||||
|
if (cluster.pixel_count > current_config.pixel_count_max) { |
||||
|
continue; |
||||
|
} |
||||
|
if (cluster.pixel_count < current_config.pixel_count_min) { |
||||
|
continue; |
||||
|
} |
||||
|
// Cluster object limit reached
|
||||
|
if (next_state.object_count >= 0x10) { |
||||
|
continue; |
||||
|
} |
||||
|
next_state.data[next_state.object_count] = cluster; |
||||
|
next_state.object_count++; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
next_state.sampling_number = camera_data.sample; |
||||
|
next_state.timestamp = next_state.timestamp + 131; |
||||
|
next_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low; |
||||
|
shared_memory->clustering_lifo.WriteNextEntry(next_state); |
||||
|
|
||||
|
if (!IsProcessorActive()) { |
||||
|
StartProcessor(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void ClusteringProcessor::RemoveLowIntensityData(std::vector<u8>& data) { |
||||
|
for (u8& pixel : data) { |
||||
|
if (pixel < current_config.pixel_count_min) { |
||||
|
pixel = 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
ClusteringProcessor::ClusteringData ClusteringProcessor::GetClusterProperties(std::vector<u8>& data, |
||||
|
std::size_t x, |
||||
|
std::size_t y) { |
||||
|
std::queue<Common::Point<std::size_t>> search_points{}; |
||||
|
ClusteringData current_cluster = GetPixelProperties(data, x, y); |
||||
|
SetPixel(data, x, y, 0); |
||||
|
search_points.push({x, y}); |
||||
|
|
||||
|
while (!search_points.empty()) { |
||||
|
const auto point = search_points.front(); |
||||
|
search_points.pop(); |
||||
|
|
||||
|
// Avoid negative numbers
|
||||
|
if (point.x == 0 || point.y == 0) { |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
std::array<Common::Point<std::size_t>, 4> new_points{ |
||||
|
Common::Point<std::size_t>{point.x - 1, point.y}, |
||||
|
{point.x, point.y - 1}, |
||||
|
{point.x + 1, point.y}, |
||||
|
{point.x, point.y + 1}, |
||||
|
}; |
||||
|
|
||||
|
for (const auto new_point : new_points) { |
||||
|
if (new_point.x < 0 || new_point.x >= width) { |
||||
|
continue; |
||||
|
} |
||||
|
if (new_point.y < 0 || new_point.y >= height) { |
||||
|
continue; |
||||
|
} |
||||
|
if (GetPixel(data, new_point.x, new_point.y) < current_config.object_intensity_min) { |
||||
|
continue; |
||||
|
} |
||||
|
const ClusteringData cluster = GetPixelProperties(data, new_point.x, new_point.y); |
||||
|
current_cluster = MergeCluster(current_cluster, cluster); |
||||
|
SetPixel(data, new_point.x, new_point.y, 0); |
||||
|
search_points.push({new_point.x, new_point.y}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return current_cluster; |
||||
|
} |
||||
|
|
||||
|
ClusteringProcessor::ClusteringData ClusteringProcessor::GetPixelProperties( |
||||
|
const std::vector<u8>& data, std::size_t x, std::size_t y) const { |
||||
|
return { |
||||
|
.average_intensity = GetPixel(data, x, y) / 255.0f, |
||||
|
.centroid = |
||||
|
{ |
||||
|
.x = static_cast<f32>(x), |
||||
|
.y = static_cast<f32>(y), |
||||
|
|
||||
|
}, |
||||
|
.pixel_count = 1, |
||||
|
.bound = |
||||
|
{ |
||||
|
.x = static_cast<s16>(x), |
||||
|
.y = static_cast<s16>(y), |
||||
|
.width = 1, |
||||
|
.height = 1, |
||||
|
}, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
ClusteringProcessor::ClusteringData ClusteringProcessor::MergeCluster( |
||||
|
const ClusteringData a, const ClusteringData b) const { |
||||
|
const u32 pixel_count = a.pixel_count + b.pixel_count; |
||||
|
const f32 average_intensitiy = |
||||
|
(a.average_intensity * a.pixel_count + b.average_intensity * b.pixel_count) / pixel_count; |
||||
|
const Core::IrSensor::IrsCentroid centroid = { |
||||
|
.x = (a.centroid.x * a.pixel_count + b.centroid.x * b.pixel_count) / pixel_count, |
||||
|
.y = (a.centroid.y * a.pixel_count + b.centroid.y * b.pixel_count) / pixel_count, |
||||
|
}; |
||||
|
s16 bound_start_x = a.bound.x < b.bound.x ? a.bound.x : b.bound.x; |
||||
|
s16 bound_start_y = a.bound.y < b.bound.y ? a.bound.y : b.bound.y; |
||||
|
s16 a_bound_end_x = a.bound.x + a.bound.width; |
||||
|
s16 a_bound_end_y = a.bound.y + a.bound.height; |
||||
|
s16 b_bound_end_x = b.bound.x + b.bound.width; |
||||
|
s16 b_bound_end_y = b.bound.y + b.bound.height; |
||||
|
|
||||
|
const Core::IrSensor::IrsRect bound = { |
||||
|
.x = bound_start_x, |
||||
|
.y = bound_start_y, |
||||
|
.width = a_bound_end_x > b_bound_end_x ? a_bound_end_x - bound_start_x |
||||
|
: b_bound_end_x - bound_start_x, |
||||
|
.height = a_bound_end_y > b_bound_end_y ? a_bound_end_y - bound_start_y |
||||
|
: b_bound_end_y - bound_start_y, |
||||
|
}; |
||||
|
|
||||
|
return { |
||||
|
.average_intensity = average_intensitiy, |
||||
|
.centroid = centroid, |
||||
|
.pixel_count = pixel_count, |
||||
|
.bound = bound, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
u8 ClusteringProcessor::GetPixel(const std::vector<u8>& data, std::size_t x, std::size_t y) const { |
||||
|
if ((y * width) + x > data.size()) { |
||||
|
return 0; |
||||
|
} |
||||
|
return data[(y * width) + x]; |
||||
|
} |
||||
|
|
||||
|
void ClusteringProcessor::SetPixel(std::vector<u8>& data, std::size_t x, std::size_t y, u8 value) { |
||||
|
if ((y * width) + x > data.size()) { |
||||
|
return; |
||||
|
} |
||||
|
data[(y * width) + x] = value; |
||||
|
} |
||||
|
|
||||
|
void ClusteringProcessor::SetDefaultConfig() { |
||||
|
current_config.camera_config.exposure_time = 200000; |
||||
|
current_config.camera_config.gain = 2; |
||||
|
current_config.camera_config.is_negative_used = false; |
||||
|
current_config.camera_config.light_target = Core::IrSensor::CameraLightTarget::BrightLeds; |
||||
|
current_config.window_of_interest = { |
||||
|
.x = 0, |
||||
|
.y = 0, |
||||
|
.width = width, |
||||
|
.height = height, |
||||
|
}; |
||||
|
current_config.pixel_count_min = 3; |
||||
|
current_config.pixel_count_max = 0x12C00; |
||||
|
current_config.is_external_light_filter_enabled = true; |
||||
|
current_config.object_intensity_min = 150; |
||||
|
|
||||
|
npad_device->SetCameraFormat(format); |
||||
|
} |
||||
|
|
||||
void ClusteringProcessor::SetConfig(Core::IrSensor::PackedClusteringProcessorConfig config) { |
void ClusteringProcessor::SetConfig(Core::IrSensor::PackedClusteringProcessorConfig config) { |
||||
current_config.camera_config.exposure_time = config.camera_config.exposure_time; |
current_config.camera_config.exposure_time = config.camera_config.exposure_time; |
||||
current_config.camera_config.gain = config.camera_config.gain; |
current_config.camera_config.gain = config.camera_config.gain; |
||||
current_config.camera_config.is_negative_used = config.camera_config.is_negative_used; |
current_config.camera_config.is_negative_used = config.camera_config.is_negative_used; |
||||
current_config.camera_config.light_target = |
current_config.camera_config.light_target = |
||||
static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target); |
static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target); |
||||
|
current_config.window_of_interest = config.window_of_interest; |
||||
current_config.pixel_count_min = config.pixel_count_min; |
current_config.pixel_count_min = config.pixel_count_min; |
||||
current_config.pixel_count_max = config.pixel_count_max; |
current_config.pixel_count_max = config.pixel_count_max; |
||||
current_config.is_external_light_filter_enabled = config.is_external_light_filter_enabled; |
current_config.is_external_light_filter_enabled = config.is_external_light_filter_enabled; |
||||
current_config.object_intensity_min = config.object_intensity_min; |
current_config.object_intensity_min = config.object_intensity_min; |
||||
|
|
||||
|
LOG_INFO(Service_IRS, |
||||
|
"Processor config, exposure_time={}, gain={}, is_negative_used={}, " |
||||
|
"light_target={}, window_of_interest=({}, {}, {}, {}), pixel_count_min={}, " |
||||
|
"pixel_count_max={}, is_external_light_filter_enabled={}, object_intensity_min={}", |
||||
|
current_config.camera_config.exposure_time, current_config.camera_config.gain, |
||||
|
current_config.camera_config.is_negative_used, |
||||
|
current_config.camera_config.light_target, current_config.window_of_interest.x, |
||||
|
current_config.window_of_interest.y, current_config.window_of_interest.width, |
||||
|
current_config.window_of_interest.height, current_config.pixel_count_min, |
||||
|
current_config.pixel_count_max, current_config.is_external_light_filter_enabled, |
||||
|
current_config.object_intensity_min); |
||||
|
|
||||
|
npad_device->SetCameraFormat(format); |
||||
} |
} |
||||
|
|
||||
} // namespace Service::IRS
|
} // namespace Service::IRS
|
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue