mirror of
https://github.com/encounter/aurora.git
synced 2026-07-09 18:19:33 -07:00
Merge branch 'main' into dusk-tphd
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
add_library(aurora_core STATIC
|
||||
lib/aurora.cpp
|
||||
lib/device.cpp
|
||||
lib/device.hpp
|
||||
lib/input.cpp
|
||||
lib/window.cpp
|
||||
lib/logging.cpp
|
||||
@@ -25,6 +27,13 @@ elseif (APPLE)
|
||||
target_sources(aurora_core PRIVATE lib/system_info_mac.mm)
|
||||
endif ()
|
||||
|
||||
if (IOS)
|
||||
find_library(COREHAPTICS_FRAMEWORK CoreHaptics REQUIRED)
|
||||
target_sources(aurora_core PRIVATE lib/device_ios.mm)
|
||||
set_source_files_properties(lib/device_ios.mm PROPERTIES COMPILE_FLAGS -fobjc-arc)
|
||||
target_link_libraries(aurora_core PUBLIC ${COREHAPTICS_FRAMEWORK})
|
||||
endif ()
|
||||
|
||||
if (AURORA_ENABLE_GX)
|
||||
target_sources(aurora_core PRIVATE lib/imgui.cpp)
|
||||
target_link_libraries(aurora_core PUBLIC imgui)
|
||||
@@ -35,6 +44,7 @@ if(AURORA_ENABLE_RMLUI)
|
||||
|
||||
target_sources(aurora_core PRIVATE
|
||||
lib/rmlui.cpp
|
||||
lib/rmlui/RuntimeTextureProvider.cpp
|
||||
lib/rmlui/RmlUi_Backend_Aurora.cpp
|
||||
lib/rmlui/WebGPURenderInterface.cpp
|
||||
lib/rmlui/SystemInterface_Aurora.cpp
|
||||
|
||||
@@ -40,6 +40,6 @@ add_library(aurora_gx STATIC
|
||||
add_library(aurora::gx ALIAS aurora_gx)
|
||||
set_target_properties(aurora_gx PROPERTIES FOLDER "aurora")
|
||||
|
||||
target_link_libraries(aurora_gx PUBLIC aurora::core xxhash)
|
||||
target_link_libraries(aurora_gx PRIVATE absl::btree absl::flat_hash_map dawn::webgpu_dawn sqlite3 TracyClient PNG::PNG)
|
||||
target_link_libraries(aurora_gx PUBLIC aurora::core dawn::webgpu_dawn xxhash)
|
||||
target_link_libraries(aurora_gx PRIVATE absl::btree absl::flat_hash_map sqlite3 TracyClient PNG::PNG)
|
||||
target_compile_definitions(aurora_gx PRIVATE WEBGPU_DAWN)
|
||||
|
||||
@@ -83,6 +83,7 @@ typedef struct {
|
||||
const char* appName;
|
||||
const char* userPath;
|
||||
const char* cachePath;
|
||||
const char* resourcesPath;
|
||||
AuroraBackend desiredBackend;
|
||||
uint32_t msaa;
|
||||
uint16_t maxTextureAnisotropy;
|
||||
|
||||
@@ -4,6 +4,14 @@
|
||||
|
||||
#include <RmlUi/Core/Context.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace aurora::rmlui {
|
||||
|
||||
enum class InputType {
|
||||
@@ -11,12 +19,29 @@ enum class InputType {
|
||||
Number,
|
||||
};
|
||||
|
||||
inline constexpr const char* TouchStartEvent = "touchstart";
|
||||
inline constexpr const char* TouchMoveEvent = "touchmove";
|
||||
inline constexpr const char* TouchEndEvent = "touchend";
|
||||
inline constexpr const char* TouchCancelEvent = "touchcancel";
|
||||
|
||||
Rml::Context* get_context() noexcept;
|
||||
bool is_initialized() noexcept;
|
||||
void set_input_type(InputType type) noexcept;
|
||||
void set_ui_scale(float scale) noexcept;
|
||||
float get_ui_scale() noexcept;
|
||||
|
||||
struct RuntimeTexture {
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
std::span<const std::byte> rgba8;
|
||||
bool premultipliedAlpha = false;
|
||||
};
|
||||
|
||||
using TextureProvider = std::function<std::optional<RuntimeTexture>(std::string_view)>;
|
||||
|
||||
void register_texture_provider(std::string scheme, TextureProvider provider);
|
||||
void unregister_texture_provider(std::string_view scheme) noexcept;
|
||||
|
||||
} // namespace aurora::rmlui
|
||||
|
||||
#endif
|
||||
|
||||
@@ -222,6 +222,10 @@ PADSignedNativeAxis PADGetNativeAxisPulled(u32 port);
|
||||
void PADRestoreDefaultMapping(u32 port);
|
||||
void PADBlockInput(bool block);
|
||||
|
||||
void PADSetVirtualStatus(u32 port, const PADStatus* status);
|
||||
void PADClearVirtualStatus(u32 port);
|
||||
void PADClearAllVirtualStatus();
|
||||
|
||||
/**
|
||||
* Set the default controller mapping used.
|
||||
*
|
||||
@@ -270,6 +274,9 @@ BOOL PADHasLED(u32 port);
|
||||
BOOL PADSetRumbleIntensity(u32 port, u16 low, u16 high);
|
||||
BOOL PADGetRumbleIntensity(u32 port, u16* low, u16* high);
|
||||
BOOL PADSupportsRumbleIntensity(u32 port);
|
||||
BOOL PADCanForceDeviceRumble(u32 port);
|
||||
BOOL PADGetForceDeviceRumble(u32 port);
|
||||
BOOL PADSetForceDeviceRumble(u32 port, BOOL force);
|
||||
|
||||
typedef enum PADBatteryState {
|
||||
PAD_BATTERYSTATE_ERROR = -1,
|
||||
|
||||
@@ -89,6 +89,11 @@ AuroraInfo initialize(int argc, char* argv[], const AuroraConfig& config) noexce
|
||||
} else {
|
||||
g_config.cachePath = strdup(g_config.cachePath);
|
||||
}
|
||||
if (g_config.resourcesPath == nullptr) {
|
||||
g_config.resourcesPath = SDL_GetBasePath();
|
||||
} else {
|
||||
g_config.resourcesPath = strdup(g_config.resourcesPath);
|
||||
}
|
||||
if (g_config.msaa == 0) {
|
||||
g_config.msaa = 1;
|
||||
}
|
||||
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
#include "device.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string_view>
|
||||
|
||||
#include <SDL3/SDL_haptic.h>
|
||||
#include <SDL3/SDL_sensor.h>
|
||||
#include <SDL3/SDL_video.h>
|
||||
|
||||
namespace aurora::device {
|
||||
namespace detail {
|
||||
void shutdown_rumble() noexcept;
|
||||
}
|
||||
|
||||
namespace {
|
||||
SDL_Sensor* g_gyro = nullptr;
|
||||
#if defined(SDL_PLATFORM_ANDROID)
|
||||
SDL_Haptic* g_haptic = nullptr;
|
||||
|
||||
constexpr std::string_view kAndroidDeviceHapticName = "VIBRATOR_SERVICE";
|
||||
constexpr uint32_t kIndefiniteRumbleDurationMs = 30000;
|
||||
|
||||
float rumble_strength(const uint16_t low, const uint16_t high) noexcept {
|
||||
const float mixed = static_cast<float>(low) * 0.6f + static_cast<float>(high) * 0.4f;
|
||||
return std::clamp(mixed / 65535.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
bool open_haptic() noexcept {
|
||||
if (g_haptic != nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
SDL_HapticID* haptics = SDL_GetHaptics(&count);
|
||||
if (haptics == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_HapticID selected = 0;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const char* name = SDL_GetHapticNameForID(haptics[i]);
|
||||
if (name != nullptr && std::string_view{name} == kAndroidDeviceHapticName) {
|
||||
selected = haptics[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
SDL_free(haptics);
|
||||
|
||||
if (selected == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
g_haptic = SDL_OpenHaptic(selected);
|
||||
if (g_haptic == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (!SDL_HapticRumbleSupported(g_haptic) || !SDL_InitHapticRumble(g_haptic)) {
|
||||
SDL_CloseHaptic(g_haptic);
|
||||
g_haptic = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
SDL_Sensor* open_sensor(const SDL_SensorType type, SDL_Sensor*& sensor) noexcept {
|
||||
if (sensor != nullptr) {
|
||||
return sensor;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
SDL_SensorID* sensors = SDL_GetSensors(&count);
|
||||
if (sensors == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SDL_SensorID selected = 0;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (SDL_GetSensorTypeForID(sensors[i]) == type) {
|
||||
selected = sensors[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
SDL_free(sensors);
|
||||
|
||||
if (selected == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
sensor = SDL_OpenSensor(selected);
|
||||
return sensor;
|
||||
}
|
||||
|
||||
int orientation_quarter_turns(const SDL_DisplayOrientation orientation) noexcept {
|
||||
switch (orientation) {
|
||||
case SDL_ORIENTATION_PORTRAIT:
|
||||
return 0;
|
||||
case SDL_ORIENTATION_LANDSCAPE:
|
||||
return 1;
|
||||
case SDL_ORIENTATION_PORTRAIT_FLIPPED:
|
||||
return 2;
|
||||
case SDL_ORIENTATION_LANDSCAPE_FLIPPED:
|
||||
return 3;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void rotate_sensor_to_display(float* data, const int n_values) noexcept {
|
||||
if (n_values < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
const SDL_DisplayID display = SDL_GetPrimaryDisplay();
|
||||
if (display == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int natural = orientation_quarter_turns(SDL_GetNaturalDisplayOrientation(display));
|
||||
const int current = orientation_quarter_turns(SDL_GetCurrentDisplayOrientation(display));
|
||||
if (natural < 0 || current < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float x = data[0];
|
||||
const float y = data[1];
|
||||
switch ((current - natural + 4) % 4) {
|
||||
case 1:
|
||||
data[0] = -y;
|
||||
data[1] = x;
|
||||
break;
|
||||
case 2:
|
||||
data[0] = -x;
|
||||
data[1] = -y;
|
||||
break;
|
||||
case 3:
|
||||
data[0] = y;
|
||||
data[1] = -x;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
#if defined(SDL_PLATFORM_ANDROID)
|
||||
bool rumble_available() noexcept { return open_haptic(); }
|
||||
|
||||
void rumble(const uint16_t low_freq_intensity, const uint16_t high_freq_intensity,
|
||||
const uint16_t duration_ms) noexcept {
|
||||
if (!open_haptic()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float strength = rumble_strength(low_freq_intensity, high_freq_intensity);
|
||||
if (strength <= 0.0f) {
|
||||
SDL_StopHapticRumble(g_haptic);
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_PlayHapticRumble(g_haptic, strength,
|
||||
duration_ms == 0 ? kIndefiniteRumbleDurationMs : static_cast<uint32_t>(duration_ms));
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
void shutdown_rumble() noexcept {
|
||||
if (g_haptic != nullptr) {
|
||||
SDL_StopHapticRumble(g_haptic);
|
||||
SDL_CloseHaptic(g_haptic);
|
||||
g_haptic = nullptr;
|
||||
}
|
||||
}
|
||||
} // namespace detail
|
||||
#elif !defined(SDL_PLATFORM_IOS)
|
||||
bool rumble_available() noexcept { return false; }
|
||||
|
||||
void rumble(const uint16_t lowFreq, const uint16_t highFreq,
|
||||
const uint16_t durationMs) noexcept {
|
||||
static_cast<void>(lowFreq);
|
||||
static_cast<void>(highFreq);
|
||||
static_cast<void>(durationMs);
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
void shutdown_rumble() noexcept {}
|
||||
} // namespace detail
|
||||
#endif
|
||||
|
||||
bool gyro_available() noexcept { return open_sensor(SDL_SENSOR_GYRO, g_gyro) != nullptr; }
|
||||
|
||||
bool gyro(float* data, const int n_values) noexcept {
|
||||
if (data == nullptr || n_values <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_Sensor* sensor = open_sensor(SDL_SENSOR_GYRO, g_gyro);
|
||||
if (sensor == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_UpdateSensors();
|
||||
if (!SDL_GetSensorData(sensor, data, n_values)) {
|
||||
return false;
|
||||
}
|
||||
rotate_sensor_to_display(data, n_values);
|
||||
return true;
|
||||
}
|
||||
|
||||
void shutdown() noexcept {
|
||||
detail::shutdown_rumble();
|
||||
if (g_gyro != nullptr) {
|
||||
SDL_CloseSensor(g_gyro);
|
||||
g_gyro = nullptr;
|
||||
}
|
||||
}
|
||||
} // namespace aurora::device
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace aurora::device {
|
||||
|
||||
bool rumble_available() noexcept;
|
||||
void rumble(uint16_t lowFreq, uint16_t highFreq, uint16_t durationMs) noexcept;
|
||||
|
||||
bool gyro_available() noexcept;
|
||||
bool gyro(float* data, int n_values) noexcept;
|
||||
|
||||
void shutdown() noexcept;
|
||||
|
||||
} // namespace aurora::device
|
||||
@@ -0,0 +1,182 @@
|
||||
#include "device.hpp"
|
||||
|
||||
#import <CoreHaptics/CoreHaptics.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
|
||||
@interface AuroraDeviceHaptics : NSObject
|
||||
{
|
||||
std::atomic<uint64_t> _generation;
|
||||
}
|
||||
@property(nonatomic, strong) CHHapticEngine* engine;
|
||||
@property(nonatomic, strong) id<CHHapticAdvancedPatternPlayer> player;
|
||||
@property(nonatomic) BOOL active;
|
||||
- (BOOL)available;
|
||||
- (void)rumbleLow:(uint16_t)low high:(uint16_t)high duration:(uint16_t)durationMs;
|
||||
- (void)shutdown;
|
||||
@end
|
||||
|
||||
@implementation AuroraDeviceHaptics
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_generation.store(0);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)available {
|
||||
return CHHapticEngine.capabilitiesForHardware.supportsHaptics;
|
||||
}
|
||||
|
||||
- (BOOL)ensurePlayer {
|
||||
if (![self available]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSError* error = nil;
|
||||
if (self.engine == nil) {
|
||||
self.engine = [[CHHapticEngine alloc] initAndReturnError:&error];
|
||||
if (error != nil || self.engine == nil) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
__weak AuroraDeviceHaptics* weakSelf = self;
|
||||
self.engine.stoppedHandler = ^(CHHapticEngineStoppedReason) {
|
||||
AuroraDeviceHaptics* strongSelf = weakSelf;
|
||||
if (strongSelf == nil) {
|
||||
return;
|
||||
}
|
||||
strongSelf.player = nil;
|
||||
strongSelf.engine = nil;
|
||||
strongSelf.active = NO;
|
||||
};
|
||||
self.engine.resetHandler = ^{
|
||||
AuroraDeviceHaptics* strongSelf = weakSelf;
|
||||
if (strongSelf == nil) {
|
||||
return;
|
||||
}
|
||||
strongSelf.player = nil;
|
||||
strongSelf.active = NO;
|
||||
[strongSelf.engine startAndReturnError:nil];
|
||||
};
|
||||
|
||||
[self.engine startAndReturnError:&error];
|
||||
if (error != nil) {
|
||||
self.engine = nil;
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
if (self.player == nil) {
|
||||
CHHapticEventParameter* intensity =
|
||||
[[CHHapticEventParameter alloc] initWithParameterID:CHHapticEventParameterIDHapticIntensity value:1.0f];
|
||||
CHHapticEvent* event = [[CHHapticEvent alloc] initWithEventType:CHHapticEventTypeHapticContinuous
|
||||
parameters:@[ intensity ]
|
||||
relativeTime:0
|
||||
duration:1.0];
|
||||
CHHapticPattern* pattern = [[CHHapticPattern alloc] initWithEvents:@[ event ] parameters:@[] error:&error];
|
||||
if (error != nil || pattern == nil) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
self.player = [self.engine createAdvancedPlayerWithPattern:pattern error:&error];
|
||||
if (error != nil || self.player == nil) {
|
||||
return NO;
|
||||
}
|
||||
self.player.loopEnabled = YES;
|
||||
self.player.loopEnd = 1.0;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
++_generation;
|
||||
if (self.player != nil && self.active) {
|
||||
[self.player stopAtTime:CHHapticTimeImmediate error:nil];
|
||||
}
|
||||
self.active = NO;
|
||||
}
|
||||
|
||||
- (void)rumbleLow:(uint16_t)low high:(uint16_t)high duration:(uint16_t)durationMs {
|
||||
const float lowValue = static_cast<float>(low) / 65535.0f;
|
||||
const float highValue = static_cast<float>(high) / 65535.0f;
|
||||
const float intensity = std::clamp(lowValue * 0.6f + highValue * 0.4f, 0.0f, 1.0f);
|
||||
if (intensity <= 0.0f) {
|
||||
[self stop];
|
||||
return;
|
||||
}
|
||||
|
||||
if (![self ensurePlayer]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float sharpness = std::clamp(highValue * 0.75f + lowValue * 0.25f, 0.0f, 1.0f);
|
||||
CHHapticDynamicParameter* intensityParam =
|
||||
[[CHHapticDynamicParameter alloc] initWithParameterID:CHHapticDynamicParameterIDHapticIntensityControl
|
||||
value:intensity
|
||||
relativeTime:0];
|
||||
CHHapticDynamicParameter* sharpnessParam =
|
||||
[[CHHapticDynamicParameter alloc] initWithParameterID:CHHapticDynamicParameterIDHapticSharpnessControl
|
||||
value:sharpness
|
||||
relativeTime:0];
|
||||
if (![self.player sendParameters:@[ intensityParam, sharpnessParam ] atTime:CHHapticTimeImmediate error:nil]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self.active) {
|
||||
if (![self.player startAtTime:CHHapticTimeImmediate error:nil]) {
|
||||
return;
|
||||
}
|
||||
self.active = YES;
|
||||
}
|
||||
|
||||
const uint64_t generation = ++_generation;
|
||||
if (durationMs != 0) {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, static_cast<int64_t>(durationMs) * NSEC_PER_MSEC),
|
||||
dispatch_get_main_queue(), ^{
|
||||
if (_generation.load() == generation) {
|
||||
[self stop];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)shutdown {
|
||||
[self stop];
|
||||
if (self.player != nil) {
|
||||
[self.player cancelAndReturnError:nil];
|
||||
self.player = nil;
|
||||
}
|
||||
if (self.engine != nil) {
|
||||
[self.engine stopWithCompletionHandler:nil];
|
||||
self.engine = nil;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
namespace aurora::device {
|
||||
namespace {
|
||||
AuroraDeviceHaptics* haptics() {
|
||||
static AuroraDeviceHaptics* s_haptics = [[AuroraDeviceHaptics alloc] init];
|
||||
return s_haptics;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool rumble_available() noexcept { return [haptics() available]; }
|
||||
|
||||
void rumble(const uint16_t loqFreq, const uint16_t highFreq, const uint16_t durationMs) noexcept {
|
||||
[haptics() rumbleLow:loqFreq high:highFreq duration:durationMs];
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
void shutdown_rumble() noexcept {
|
||||
[haptics() shutdown];
|
||||
}
|
||||
} // namespace detail
|
||||
} // namespace aurora::device
|
||||
+177
-27
@@ -1,4 +1,5 @@
|
||||
#include "../../input.hpp"
|
||||
#include "../../device.hpp"
|
||||
#include "../../internal.hpp"
|
||||
#include <dolphin/pad.h>
|
||||
#include <dolphin/si.h>
|
||||
@@ -9,7 +10,8 @@
|
||||
#include <ranges>
|
||||
|
||||
namespace {
|
||||
constexpr int32_t k_mappingsFileVersion = 3;
|
||||
constexpr int32_t k_mappingsFileVersion = 4;
|
||||
constexpr int32_t k_minMappingsFileVersion = 3;
|
||||
|
||||
std::array<PADButtonMapping, PAD_BUTTON_COUNT> g_defaultButtonsStandard{{
|
||||
{SDL_GAMEPAD_BUTTON_SOUTH, PAD_BUTTON_A},
|
||||
@@ -26,6 +28,9 @@ std::array<PADButtonMapping, PAD_BUTTON_COUNT> g_defaultButtonsStandard{{
|
||||
{SDL_GAMEPAD_BUTTON_DPAD_RIGHT, PAD_BUTTON_RIGHT},
|
||||
}};
|
||||
|
||||
std::array<PADStatus, PAD_CHANMAX> g_virtualPadStatus{};
|
||||
std::array<bool, PAD_CHANMAX> g_virtualPadActive{};
|
||||
|
||||
std::array<PADButtonMapping, PAD_BUTTON_COUNT> g_defaultButtonsXBox360{{
|
||||
{SDL_GAMEPAD_BUTTON_SOUTH, PAD_BUTTON_A},
|
||||
{SDL_GAMEPAD_BUTTON_EAST, PAD_BUTTON_B},
|
||||
@@ -306,6 +311,30 @@ void PADSetSpec(u32 spec [[maybe_unused]]) {}
|
||||
static void load_keyboard_bindings();
|
||||
static void save_keyboard_bindings();
|
||||
|
||||
static bool device_rumble_available_for_port(const u32 port) {
|
||||
return port == PAD_CHAN0 && aurora::device::rumble_available();
|
||||
}
|
||||
|
||||
static bool should_use_device_rumble(const u32 port, const aurora::input::GameController* controller) {
|
||||
return device_rumble_available_for_port(port) &&
|
||||
(controller == nullptr ||
|
||||
(!controller->m_isGameCube && (!controller->m_hasRumble || controller->m_forceDeviceRumble)));
|
||||
}
|
||||
|
||||
static bool device_gyro_available_for_port(const u32 port) {
|
||||
return port == PAD_CHAN0 && aurora::device::gyro_available();
|
||||
}
|
||||
|
||||
static bool controller_has_sensor(const aurora::input::GameController* controller, const PADSensorType sensor) {
|
||||
return controller != nullptr && SDL_GamepadHasSensor(controller->m_controller, static_cast<SDL_SensorType>(sensor));
|
||||
}
|
||||
|
||||
static bool should_use_device_sensor(const u32 port, const aurora::input::GameController* controller,
|
||||
const PADSensorType sensor) {
|
||||
return sensor == PAD_SENSOR_GYRO && !controller_has_sensor(controller, sensor) &&
|
||||
device_gyro_available_for_port(port);
|
||||
}
|
||||
|
||||
// ReSharper disable once CppDFAConstantFunctionResult
|
||||
BOOL PADInit() {
|
||||
if (g_initialized) {
|
||||
@@ -472,9 +501,9 @@ void __PADLoadMapping(aurora::input::GameController* controller) /* NOLINT(*-re
|
||||
|
||||
uint32_t version = 0;
|
||||
SDL_ReadU32LE(file, &version);
|
||||
if (version != k_mappingsFileVersion) {
|
||||
aurora::input::Log.warn("Invalid controller mapping version! (Expected {0}, found {1})", k_mappingsFileVersion,
|
||||
version);
|
||||
if (version < k_minMappingsFileVersion || version > k_mappingsFileVersion) {
|
||||
aurora::input::Log.warn("Invalid controller mapping version! (Expected {0}..{1}, found {2})",
|
||||
k_minMappingsFileVersion, k_mappingsFileVersion, version);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -499,6 +528,9 @@ void __PADLoadMapping(aurora::input::GameController* controller) /* NOLINT(*-re
|
||||
if (!isGameCube) {
|
||||
SDL_ReadIO(file, &controller->m_rumbleIntensityLow, sizeof(u16));
|
||||
SDL_ReadIO(file, &controller->m_rumbleIntensityHigh, sizeof(u16));
|
||||
if (version >= k_mappingsFileVersion) {
|
||||
SDL_ReadIO(file, &controller->m_forceDeviceRumble, sizeof(bool));
|
||||
}
|
||||
}
|
||||
SDL_CloseIO(file);
|
||||
|
||||
@@ -597,6 +629,23 @@ static void apply_unblock_suppression(PADStatus& status, const u32 port, const b
|
||||
}
|
||||
}
|
||||
|
||||
static int dominant_axis_value(const int physical, const int virtualValue, const int min, const int max) {
|
||||
return std::clamp(std::abs(virtualValue) > std::abs(physical) ? virtualValue : physical, min, max);
|
||||
}
|
||||
|
||||
static void merge_virtual_status(PADStatus& status, const PADStatus& virtualStatus) {
|
||||
status.button |= virtualStatus.button;
|
||||
status.extButton |= virtualStatus.extButton;
|
||||
status.stickX = static_cast<s8>(dominant_axis_value(status.stickX, virtualStatus.stickX, -127, 127));
|
||||
status.stickY = static_cast<s8>(dominant_axis_value(status.stickY, virtualStatus.stickY, -127, 127));
|
||||
status.substickX = static_cast<s8>(dominant_axis_value(status.substickX, virtualStatus.substickX, -127, 127));
|
||||
status.substickY = static_cast<s8>(dominant_axis_value(status.substickY, virtualStatus.substickY, -127, 127));
|
||||
status.triggerLeft = std::max(status.triggerLeft, virtualStatus.triggerLeft);
|
||||
status.triggerRight = std::max(status.triggerRight, virtualStatus.triggerRight);
|
||||
status.analogA = std::max(status.analogA, virtualStatus.analogA);
|
||||
status.analogB = std::max(status.analogB, virtualStatus.analogB);
|
||||
}
|
||||
|
||||
u32 PADRead(PADStatus* status) {
|
||||
if (!g_keyboardBindingsLoaded) {
|
||||
g_keyboardBindingsLoaded = true;
|
||||
@@ -611,8 +660,12 @@ u32 PADRead(PADStatus* status) {
|
||||
uint32_t rumbleSupport = 0;
|
||||
for (uint32_t i = 0; i < PAD_CHANMAX; ++i) {
|
||||
memset(&status[i], 0, sizeof(PADStatus));
|
||||
// Support device rumble on port 0 regardless of whether a controller is connected.
|
||||
if (device_rumble_available_for_port(i)) {
|
||||
rumbleSupport |= PAD_CHAN0_BIT;
|
||||
}
|
||||
auto controller = aurora::input::get_controller_for_player(i);
|
||||
if (controller == nullptr && !g_keyboardBindings[i].m_mappingsSet) {
|
||||
if (controller == nullptr && !g_keyboardBindings[i].m_mappingsSet && !g_virtualPadActive[i]) {
|
||||
status[i].err = PAD_ERR_NO_CONTROLLER;
|
||||
g_suppressedButtons[i] = 0;
|
||||
g_suppressLeftTrigger[i] = false;
|
||||
@@ -825,18 +878,63 @@ u32 PADRead(PADStatus* status) {
|
||||
neutralize_status(status[i]);
|
||||
} else {
|
||||
apply_unblock_suppression(status[i], i, captureHeldInput);
|
||||
if (g_virtualPadActive[i]) {
|
||||
merge_virtual_status(status[i], g_virtualPadStatus[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return rumbleSupport;
|
||||
}
|
||||
|
||||
void PADSetVirtualStatus(const u32 port, const PADStatus* virtualStatus) {
|
||||
if (port >= PAD_CHANMAX || virtualStatus == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_virtualPadStatus[port] = *virtualStatus;
|
||||
g_virtualPadStatus[port].err = PAD_ERR_NONE;
|
||||
g_virtualPadActive[port] = true;
|
||||
}
|
||||
|
||||
void PADClearVirtualStatus(const u32 port) {
|
||||
if (port >= PAD_CHANMAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_virtualPadStatus[port] = {};
|
||||
g_virtualPadActive[port] = false;
|
||||
}
|
||||
|
||||
void PADClearAllVirtualStatus() {
|
||||
g_virtualPadStatus.fill({});
|
||||
g_virtualPadActive.fill(false);
|
||||
}
|
||||
|
||||
void PADControlMotor(const u32 chan, const u32 cmd) {
|
||||
const auto controller = aurora::input::get_controller_for_player(chan);
|
||||
const auto instance = aurora::input::get_instance_for_player(chan);
|
||||
if (should_use_device_rumble(chan, controller)) {
|
||||
u16 low = 0;
|
||||
u16 high = 0;
|
||||
if (controller != nullptr) {
|
||||
EnsureMappingLoaded(controller);
|
||||
low = controller->m_rumbleIntensityLow;
|
||||
high = controller->m_rumbleIntensityHigh;
|
||||
} else {
|
||||
aurora::input::get_device_rumble_intensity(&low, &high);
|
||||
}
|
||||
if (cmd == PAD_MOTOR_STOP || cmd == PAD_MOTOR_STOP_HARD) {
|
||||
aurora::device::rumble(0, 0, 0);
|
||||
} else if (cmd == PAD_MOTOR_RUMBLE) {
|
||||
aurora::device::rumble(low, high, 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (controller == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto instance = aurora::input::get_instance_for_player(chan);
|
||||
if (controller->m_isGameCube) {
|
||||
if (cmd == PAD_MOTOR_STOP) {
|
||||
aurora::input::controller_rumble(instance, 0, 1, 0);
|
||||
@@ -1302,6 +1400,7 @@ void PADSerializeMappings() {
|
||||
if (!controller.m_isGameCube) {
|
||||
SDL_WriteIO(file, &controller.m_rumbleIntensityLow, sizeof(u16));
|
||||
SDL_WriteIO(file, &controller.m_rumbleIntensityHigh, sizeof(u16));
|
||||
SDL_WriteIO(file, &controller.m_forceDeviceRumble, sizeof(bool));
|
||||
}
|
||||
SDL_CloseIO(file);
|
||||
}
|
||||
@@ -1533,31 +1632,35 @@ BOOL PADGetColor(const u32 port, u8* red, u8* green, u8* blue) {
|
||||
BOOL PADSetSensorEnabled(const u32 port, const PADSensorType sensor, const BOOL enabled) {
|
||||
const auto* ctrl = aurora::input::get_controller_for_player(port);
|
||||
|
||||
if (ctrl == nullptr) {
|
||||
return FALSE;
|
||||
if (controller_has_sensor(ctrl, sensor)) {
|
||||
return SDL_SetGamepadSensorEnabled(ctrl->m_controller, static_cast<SDL_SensorType>(sensor), enabled ? true : false)
|
||||
? TRUE
|
||||
: FALSE;
|
||||
}
|
||||
|
||||
return SDL_SetGamepadSensorEnabled(ctrl->m_controller, static_cast<SDL_SensorType>(sensor), enabled ? true : false)
|
||||
? TRUE
|
||||
: FALSE;
|
||||
return should_use_device_sensor(port, ctrl, sensor) ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
BOOL PADHasSensor(const u32 port, const PADSensorType sensor) {
|
||||
const auto* ctrl = aurora::input::get_controller_for_player(port);
|
||||
if (ctrl == nullptr) {
|
||||
return FALSE;
|
||||
if (controller_has_sensor(ctrl, sensor)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return SDL_GamepadHasSensor(ctrl->m_controller, static_cast<SDL_SensorType>(sensor)) ? TRUE : FALSE;
|
||||
return should_use_device_sensor(port, ctrl, sensor) ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
BOOL PADGetSensorData(const u32 port, const PADSensorType sensor, f32* data, const int nValues) {
|
||||
const auto* ctrl = aurora::input::get_controller_for_player(port);
|
||||
if (ctrl == nullptr) {
|
||||
return FALSE;
|
||||
if (controller_has_sensor(ctrl, sensor)) {
|
||||
return SDL_GetGamepadSensorData(ctrl->m_controller, static_cast<SDL_SensorType>(sensor), data, nValues);
|
||||
}
|
||||
|
||||
return SDL_GetGamepadSensorData(ctrl->m_controller, static_cast<SDL_SensorType>(sensor), data, nValues);
|
||||
if (should_use_device_sensor(port, ctrl, sensor)) {
|
||||
return aurora::device::gyro(data, nValues) ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BOOL PADHasLED(const u32 port) {
|
||||
@@ -1572,35 +1675,82 @@ BOOL PADHasLED(const u32 port) {
|
||||
|
||||
BOOL PADSetRumbleIntensity(const u32 port, const u16 low, const u16 high) {
|
||||
auto* ctrl = aurora::input::get_controller_for_player(port);
|
||||
if (ctrl == nullptr || ctrl->m_isGameCube || !ctrl->m_hasRumble) {
|
||||
if (ctrl != nullptr) {
|
||||
if (ctrl->m_isGameCube || (!ctrl->m_hasRumble && !should_use_device_rumble(port, ctrl))) {
|
||||
return FALSE;
|
||||
}
|
||||
EnsureMappingLoaded(ctrl);
|
||||
ctrl->m_rumbleIntensityLow = low;
|
||||
ctrl->m_rumbleIntensityHigh = high;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (!should_use_device_rumble(port, nullptr)) {
|
||||
return FALSE;
|
||||
}
|
||||
ctrl->m_rumbleIntensityLow = low;
|
||||
ctrl->m_rumbleIntensityHigh = high;
|
||||
|
||||
aurora::input::set_device_rumble_intensity(low, high);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL PADGetRumbleIntensity(const u32 port, u16* low, u16* high) {
|
||||
const auto* ctrl = aurora::input::get_controller_for_player(port);
|
||||
if (ctrl == nullptr || ctrl->m_isGameCube || !ctrl->m_hasRumble) {
|
||||
auto* ctrl = aurora::input::get_controller_for_player(port);
|
||||
if (ctrl != nullptr) {
|
||||
if (ctrl->m_isGameCube || (!ctrl->m_hasRumble && !should_use_device_rumble(port, ctrl))) {
|
||||
*low = 0;
|
||||
*high = 0;
|
||||
return FALSE;
|
||||
}
|
||||
EnsureMappingLoaded(ctrl);
|
||||
*low = ctrl->m_rumbleIntensityLow;
|
||||
*high = ctrl->m_rumbleIntensityHigh;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (!should_use_device_rumble(port, nullptr)) {
|
||||
*low = 0;
|
||||
*high = 0;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
*low = ctrl->m_rumbleIntensityLow;
|
||||
*high = ctrl->m_rumbleIntensityHigh;
|
||||
aurora::input::get_device_rumble_intensity(low, high);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL PADSupportsRumbleIntensity(const u32 port) {
|
||||
if (const auto* ctrl = aurora::input::get_controller_for_player(port)) {
|
||||
if (!ctrl->m_isGameCube && (ctrl->m_hasRumble || should_use_device_rumble(port, ctrl))) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
return should_use_device_rumble(port, nullptr) ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
BOOL PADCanForceDeviceRumble(const u32 port) {
|
||||
const auto* ctrl = aurora::input::get_controller_for_player(port);
|
||||
if (ctrl == nullptr) {
|
||||
return ctrl != nullptr && !ctrl->m_isGameCube && ctrl->m_hasRumble && device_rumble_available_for_port(port) ? TRUE
|
||||
: FALSE;
|
||||
}
|
||||
|
||||
BOOL PADGetForceDeviceRumble(const u32 port) {
|
||||
auto* ctrl = aurora::input::get_controller_for_player(port);
|
||||
if (ctrl == nullptr || !PADCanForceDeviceRumble(port)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return !ctrl->m_isGameCube && ctrl->m_hasRumble;
|
||||
EnsureMappingLoaded(ctrl);
|
||||
return ctrl->m_forceDeviceRumble ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
BOOL PADSetForceDeviceRumble(const u32 port, const BOOL force) {
|
||||
auto* ctrl = aurora::input::get_controller_for_player(port);
|
||||
if (ctrl == nullptr || !PADCanForceDeviceRumble(port)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
EnsureMappingLoaded(ctrl);
|
||||
ctrl->m_forceDeviceRumble = force != FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL PADIsGCAdapter(const u32 port) {
|
||||
|
||||
@@ -571,8 +571,10 @@ ConvertedTexture convert_texture_palette(u32 textureFormat, uint32_t width, uint
|
||||
|
||||
const auto* indexData = reinterpret_cast<const u16*>(indices.data.data());
|
||||
size_t offset = 0;
|
||||
uint32_t mipWidth = width;
|
||||
uint32_t mipHeight = height;
|
||||
for (u32 mip = 0; mip < mips; ++mip) {
|
||||
const size_t pixelCount = static_cast<size_t>(width) * height;
|
||||
const size_t pixelCount = static_cast<size_t>(mipWidth) * mipHeight;
|
||||
for (size_t i = 0; i < pixelCount; ++i) {
|
||||
const u32 index = indexData[offset + i];
|
||||
if (index >= tlutEntries) {
|
||||
@@ -591,8 +593,8 @@ ConvertedTexture convert_texture_palette(u32 textureFormat, uint32_t width, uint
|
||||
}
|
||||
}
|
||||
offset += pixelCount;
|
||||
width = std::max(width >> 1, 1u);
|
||||
height = std::max(height >> 1, 1u);
|
||||
mipWidth = std::max(mipWidth >> 1, 1u);
|
||||
mipHeight = std::max(mipHeight >> 1, 1u);
|
||||
}
|
||||
|
||||
bool hasArbitraryMips = arb_mip_check(width, height, mips, pixels);
|
||||
|
||||
+15
-5
@@ -1018,18 +1018,28 @@ static u16 wgpu_aniso(GXAnisotropy aniso) {
|
||||
|
||||
wgpu::SamplerDescriptor aurora::gfx::TextureBind::get_descriptor() const noexcept {
|
||||
auto [minFilter, mipFilter] = wgpu_filter_mode(texObj.min_filter());
|
||||
const auto [magFilter, _] = wgpu_filter_mode(texObj.mag_filter());
|
||||
auto [magFilter, _] = wgpu_filter_mode(texObj.mag_filter());
|
||||
const bool mipsEnabled = mipFilter != wgpu::MipmapFilterMode::Undefined;
|
||||
float minLod = texObj.min_lod();
|
||||
float maxLod = texObj.max_lod();
|
||||
u16 maxAnisotropy = wgpu_aniso(texObj.max_aniso());
|
||||
if (ref && ref->isReplacement) {
|
||||
minFilter = wgpu::FilterMode::Linear;
|
||||
mipFilter = wgpu::MipmapFilterMode::Linear;
|
||||
minLod = 0.f;
|
||||
maxLod = static_cast<float>(std::max(ref->mipCount, 1u) - 1u);
|
||||
maxLod = 1000.f;
|
||||
if (!mipsEnabled) {
|
||||
mipFilter = wgpu::MipmapFilterMode::Nearest;
|
||||
}
|
||||
} else if (mipFilter == wgpu::MipmapFilterMode::Undefined) {
|
||||
minLod = 0.f;
|
||||
maxLod = 0.f;
|
||||
}
|
||||
if ((ref && ref->hasArbitraryMips) || !mipsEnabled) {
|
||||
maxAnisotropy = 1;
|
||||
} else if (maxAnisotropy > 1) {
|
||||
magFilter = wgpu::FilterMode::Linear;
|
||||
minFilter = wgpu::FilterMode::Linear;
|
||||
mipFilter = wgpu::MipmapFilterMode::Linear;
|
||||
}
|
||||
return {
|
||||
.label = "Generated Filtering Sampler",
|
||||
.addressModeU = wgpu_address_mode(texObj.wrap_s()),
|
||||
@@ -1040,6 +1050,6 @@ wgpu::SamplerDescriptor aurora::gfx::TextureBind::get_descriptor() const noexcep
|
||||
.mipmapFilter = mipFilter,
|
||||
.lodMinClamp = minLod,
|
||||
.lodMaxClamp = maxLod,
|
||||
.maxAnisotropy = wgpu_aniso(texObj.max_aniso()),
|
||||
.maxAnisotropy = maxAnisotropy,
|
||||
};
|
||||
} // namespace aurora::gx
|
||||
|
||||
@@ -16,11 +16,14 @@ bool is_alpha_bump_channel(GXChannelID id) { return id == GX_ALPHA_BUMP || id ==
|
||||
Vec4<float> texture_size_bias(const gfx::TextureBind& tex) {
|
||||
auto width = static_cast<float>(tex.texObj.width());
|
||||
auto height = static_cast<float>(tex.texObj.height());
|
||||
const auto vpBias =
|
||||
enableLodBias && tex.ref && tex.ref->hasArbitraryMips
|
||||
? log2(std::min(g_gxState.renderViewport.width / std::max(g_gxState.logicalViewport.width, 1.f),
|
||||
g_gxState.renderViewport.height / std::max(g_gxState.logicalViewport.height, 1.f)))
|
||||
: 0.f;
|
||||
float vpBias = 0.f;
|
||||
if (enableLodBias && tex.ref && tex.ref->hasArbitraryMips) {
|
||||
const float viewportScale =
|
||||
std::min(g_gxState.renderViewport.width / std::max(g_gxState.logicalViewport.width, 1.f),
|
||||
g_gxState.renderViewport.height / std::max(g_gxState.logicalViewport.height, 1.f));
|
||||
const float replacementScale = static_cast<float>(tex.ref->size.width) / std::max(width, 1.f);
|
||||
vpBias = std::log2(viewportScale / std::max(replacementScale, 0.001f));
|
||||
}
|
||||
return {width, height, tex.texObj.lod_bias() + vpBias, 0.0f};
|
||||
}
|
||||
|
||||
|
||||
+33
-4
@@ -1,4 +1,5 @@
|
||||
#include "input.hpp"
|
||||
#include "device.hpp"
|
||||
#include "internal.hpp"
|
||||
|
||||
#include "magic_enum.hpp"
|
||||
@@ -22,8 +23,10 @@ absl::flat_hash_map<Uint32, GameController> g_GameControllers;
|
||||
|
||||
namespace {
|
||||
constexpr uint32_t kPortPreferencesMagic = SBIG('CPRT');
|
||||
constexpr uint32_t kPortPreferencesVersion = 2;
|
||||
constexpr uint32_t kPortPreferencesVersion = 3;
|
||||
constexpr uint32_t kMaxPersistedStringLength = 256;
|
||||
constexpr uint16_t kDefaultDeviceRumbleIntensityLow = 0x8000;
|
||||
constexpr uint16_t kDefaultDeviceRumbleIntensityHigh = 0x8000;
|
||||
|
||||
enum class PortPreferenceState : uint8_t {
|
||||
Unset = 0,
|
||||
@@ -41,7 +44,13 @@ struct PortPreference {
|
||||
ControllerIdentity identity;
|
||||
};
|
||||
|
||||
struct DevicePreference {
|
||||
uint16_t rumbleIntensityLow = kDefaultDeviceRumbleIntensityLow;
|
||||
uint16_t rumbleIntensityHigh = kDefaultDeviceRumbleIntensityHigh;
|
||||
};
|
||||
|
||||
std::array<PortPreference, PAD_MAX_CONTROLLERS> g_portPreferences;
|
||||
DevicePreference g_devicePreference;
|
||||
bool g_portPreferencesLoaded = false;
|
||||
|
||||
std::string port_preferences_path() {
|
||||
@@ -158,7 +167,7 @@ bool read_port_preferences_file(std::array<PortPreference, PAD_MAX_CONTROLLERS>&
|
||||
uint32_t magic = 0;
|
||||
uint32_t version = 0;
|
||||
bool ok = read_value(file, magic) && read_value(file, version) && magic == kPortPreferencesMagic &&
|
||||
version == kPortPreferencesVersion;
|
||||
(version == 2 || version == kPortPreferencesVersion);
|
||||
|
||||
for (auto& preference : preferences) {
|
||||
uint8_t state = 0;
|
||||
@@ -170,6 +179,10 @@ bool read_port_preferences_file(std::array<PortPreference, PAD_MAX_CONTROLLERS>&
|
||||
preference.identity = std::move(identity);
|
||||
}
|
||||
}
|
||||
if (ok && version >= 3) {
|
||||
ok = read_value(file, g_devicePreference.rumbleIntensityLow) &&
|
||||
read_value(file, g_devicePreference.rumbleIntensityHigh);
|
||||
}
|
||||
|
||||
SDL_CloseIO(file);
|
||||
if (!ok) {
|
||||
@@ -212,6 +225,8 @@ void save_port_preferences() {
|
||||
const auto state = static_cast<uint8_t>(preference.state);
|
||||
ok = ok && write_value(file, state) && write_identity(file, preference.identity);
|
||||
}
|
||||
ok = ok && write_value(file, g_devicePreference.rumbleIntensityLow) &&
|
||||
write_value(file, g_devicePreference.rumbleIntensityHigh);
|
||||
|
||||
if (!SDL_FlushIO(file)) {
|
||||
ok = false;
|
||||
@@ -437,6 +452,19 @@ void controller_rumble(uint32_t instance, uint16_t low_freq_intensity, uint16_t
|
||||
}
|
||||
}
|
||||
|
||||
void get_device_rumble_intensity(uint16_t* low_freq_intensity, uint16_t* high_freq_intensity) noexcept {
|
||||
ensure_port_preferences_loaded();
|
||||
*low_freq_intensity = g_devicePreference.rumbleIntensityLow;
|
||||
*high_freq_intensity = g_devicePreference.rumbleIntensityHigh;
|
||||
}
|
||||
|
||||
void set_device_rumble_intensity(const uint16_t low_freq_intensity, const uint16_t high_freq_intensity) noexcept {
|
||||
ensure_port_preferences_loaded();
|
||||
g_devicePreference.rumbleIntensityLow = low_freq_intensity;
|
||||
g_devicePreference.rumbleIntensityHigh = high_freq_intensity;
|
||||
save_port_preferences();
|
||||
}
|
||||
|
||||
uint32_t controller_count() noexcept { return g_GameControllers.size(); }
|
||||
|
||||
void persist_controller_for_player(uint32_t player, const GameController* controller) noexcept {
|
||||
@@ -458,8 +486,8 @@ void persist_controller_for_player(uint32_t player, const GameController* contro
|
||||
void initialize() noexcept {
|
||||
/* Make sure we initialize everything input related now, this will automatically add all of the connected controllers
|
||||
* as expected */
|
||||
ASSERT(SDL_Init(SDL_INIT_HAPTIC | SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD), "Failed to initialize SDL subsystems: {}",
|
||||
SDL_GetError());
|
||||
ASSERT(SDL_Init(SDL_INIT_HAPTIC | SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD | SDL_INIT_SENSOR),
|
||||
"Failed to initialize SDL subsystems: {}", SDL_GetError());
|
||||
}
|
||||
|
||||
struct MouseScrollStatus {
|
||||
@@ -488,5 +516,6 @@ void shutdown() noexcept {
|
||||
}
|
||||
controller_rumble(controller.first, 0, 0, 0);
|
||||
}
|
||||
device::shutdown();
|
||||
}
|
||||
} // namespace aurora::input
|
||||
|
||||
@@ -32,6 +32,7 @@ struct GameController {
|
||||
std::array<PADAxisMapping, PAD_AXIS_COUNT> m_axisMapping{};
|
||||
uint16_t m_rumbleIntensityLow = 32767;
|
||||
uint16_t m_rumbleIntensityHigh = 32767;
|
||||
bool m_forceDeviceRumble = false;
|
||||
bool m_mappingLoaded = false;
|
||||
constexpr bool operator==(const GameController& other) const {
|
||||
return m_controller == other.m_controller && m_index == other.m_index;
|
||||
@@ -54,6 +55,8 @@ bool is_gamecube(Uint32 instance) noexcept;
|
||||
bool controller_has_rumble(Uint32 instance) noexcept;
|
||||
void controller_rumble(uint32_t instance, uint16_t low_freq_intensity, uint16_t high_freq_intensity,
|
||||
uint16_t duration_ms) noexcept;
|
||||
void get_device_rumble_intensity(uint16_t* low_freq_intensity, uint16_t* high_freq_intensity) noexcept;
|
||||
void set_device_rumble_intensity(uint16_t low_freq_intensity, uint16_t high_freq_intensity) noexcept;
|
||||
uint32_t controller_count() noexcept;
|
||||
void initialize() noexcept;
|
||||
void persist_controller_for_player(uint32_t player, const GameController* controller) noexcept;
|
||||
|
||||
+60
-8
@@ -26,6 +26,9 @@ constexpr size_t MaxTrackedTouches = 16;
|
||||
struct TrackedTouch {
|
||||
SDL_FingerID id = 0;
|
||||
Rml::Vector2f position;
|
||||
Rml::Vector2f rmlPosition;
|
||||
Rml::Vector2f startPosition;
|
||||
Rml::Element* target = nullptr;
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
@@ -80,6 +83,7 @@ void ensure_render_target(Rml::Vector2i dimensions) noexcept {
|
||||
|
||||
struct MappedPoint {
|
||||
Rml::Vector2f position;
|
||||
bool valid = false;
|
||||
bool inside = false;
|
||||
};
|
||||
|
||||
@@ -99,8 +103,7 @@ MappedPoint map_native_point_to_content(float nativeX, float nativeY) noexcept {
|
||||
static_cast<uint32_t>(contentSize.x), static_cast<uint32_t>(contentSize.y));
|
||||
const float right = viewport.left + viewport.width;
|
||||
const float bottom = viewport.top + viewport.height;
|
||||
if (viewport.width <= 0.f || viewport.height <= 0.f || nativeX < viewport.left || nativeY < viewport.top ||
|
||||
nativeX >= right || nativeY >= bottom) {
|
||||
if (viewport.width <= 0.f || viewport.height <= 0.f) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -110,7 +113,8 @@ MappedPoint map_native_point_to_content(float nativeX, float nativeY) noexcept {
|
||||
(nativeX - viewport.left) * static_cast<float>(contentSize.x) / viewport.width,
|
||||
(nativeY - viewport.top) * static_cast<float>(contentSize.y) / viewport.height,
|
||||
},
|
||||
.inside = true,
|
||||
.valid = true,
|
||||
.inside = nativeX >= viewport.left && nativeY >= viewport.top && nativeX < right && nativeY < bottom,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -170,6 +174,24 @@ Rml::TouchList touch_list(SDL_FingerID id, Rml::Vector2f position) {
|
||||
return {Rml::Touch{static_cast<Rml::TouchId>(id), position}};
|
||||
}
|
||||
|
||||
void dispatch_touch_event(
|
||||
TrackedTouch& touch, const char* type, Rml::Vector2f position, bool inside, Rml::Vector2f delta = {}) noexcept {
|
||||
if (touch.target == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
Rml::Dictionary parameters;
|
||||
parameters["finger_id"] = touch.id;
|
||||
parameters["x"] = position.x;
|
||||
parameters["y"] = position.y;
|
||||
parameters["dx"] = delta.x;
|
||||
parameters["dy"] = delta.y;
|
||||
parameters["start_x"] = touch.startPosition.x;
|
||||
parameters["start_y"] = touch.startPosition.y;
|
||||
parameters["inside"] = inside;
|
||||
touch.target->DispatchEvent(type, parameters, true, true);
|
||||
}
|
||||
|
||||
void handle_mouse_motion(const SDL_MouseMotionEvent& motion) noexcept {
|
||||
const MappedPoint mapped = map_window_point_to_content(motion.x, motion.y);
|
||||
if (!mapped.inside) {
|
||||
@@ -234,11 +256,19 @@ void handle_touch_down(const SDL_TouchFingerEvent& finger) noexcept {
|
||||
if (tracked == nullptr) {
|
||||
return;
|
||||
}
|
||||
auto* target = g_context->GetElementAtPoint(mapped.position);
|
||||
if (target == nullptr) {
|
||||
target = g_context->GetRootElement();
|
||||
}
|
||||
*tracked = {
|
||||
.id = finger.fingerID,
|
||||
.position = mapped.position,
|
||||
.rmlPosition = mapped.position,
|
||||
.startPosition = mapped.position,
|
||||
.target = target,
|
||||
.active = true,
|
||||
};
|
||||
dispatch_touch_event(*tracked, TouchStartEvent, mapped.position, true);
|
||||
g_context->ProcessTouchStart(touch_list(finger.fingerID, mapped.position), RmlSDL::GetKeyModifierState());
|
||||
}
|
||||
|
||||
@@ -248,11 +278,16 @@ void handle_touch_motion(const SDL_TouchFingerEvent& finger) noexcept {
|
||||
return;
|
||||
}
|
||||
const auto mapped = map_touch_point_to_content(finger);
|
||||
if (!mapped.inside) {
|
||||
if (!mapped.valid) {
|
||||
return;
|
||||
}
|
||||
const Rml::Vector2f delta = mapped.position - tracked->position;
|
||||
tracked->position = mapped.position;
|
||||
g_context->ProcessTouchMove(touch_list(finger.fingerID, mapped.position), RmlSDL::GetKeyModifierState());
|
||||
dispatch_touch_event(*tracked, TouchMoveEvent, mapped.position, mapped.inside, delta);
|
||||
if (mapped.inside) {
|
||||
tracked->rmlPosition = mapped.position;
|
||||
g_context->ProcessTouchMove(touch_list(finger.fingerID, mapped.position), RmlSDL::GetKeyModifierState());
|
||||
}
|
||||
}
|
||||
|
||||
void handle_touch_up(const SDL_TouchFingerEvent& finger) noexcept {
|
||||
@@ -260,9 +295,24 @@ void handle_touch_up(const SDL_TouchFingerEvent& finger) noexcept {
|
||||
if (tracked == nullptr) {
|
||||
return;
|
||||
}
|
||||
const auto position = tracked->position;
|
||||
const auto mapped = map_touch_point_to_content(finger);
|
||||
const Rml::Vector2f position = mapped.valid ? mapped.position : tracked->position;
|
||||
const Rml::Vector2f delta = position - tracked->position;
|
||||
dispatch_touch_event(*tracked, TouchEndEvent, position, mapped.valid && mapped.inside, delta);
|
||||
const auto rmlPosition = tracked->rmlPosition;
|
||||
*tracked = {};
|
||||
g_context->ProcessTouchEnd(touch_list(finger.fingerID, position), RmlSDL::GetKeyModifierState());
|
||||
g_context->ProcessTouchEnd(touch_list(finger.fingerID, rmlPosition), RmlSDL::GetKeyModifierState());
|
||||
}
|
||||
|
||||
void handle_touch_cancel(const SDL_TouchFingerEvent& finger) noexcept {
|
||||
auto* tracked = find_tracked_touch(finger.fingerID);
|
||||
if (tracked == nullptr) {
|
||||
return;
|
||||
}
|
||||
dispatch_touch_event(*tracked, TouchCancelEvent, tracked->position, false);
|
||||
const auto rmlPosition = tracked->rmlPosition;
|
||||
*tracked = {};
|
||||
g_context->ProcessTouchCancel(touch_list(finger.fingerID, rmlPosition));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -338,9 +388,11 @@ void handle_event(SDL_Event& event) noexcept {
|
||||
handle_touch_motion(event.tfinger);
|
||||
return;
|
||||
case SDL_EVENT_FINGER_UP:
|
||||
case SDL_EVENT_FINGER_CANCELED:
|
||||
handle_touch_up(event.tfinger);
|
||||
return;
|
||||
case SDL_EVENT_FINGER_CANCELED:
|
||||
handle_touch_cancel(event.tfinger);
|
||||
return;
|
||||
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
|
||||
sync_context_metrics(presentation_dimensions_from_window_size(window::get_window_size()));
|
||||
return;
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "../logging.hpp"
|
||||
|
||||
#include <aurora/aurora.h>
|
||||
|
||||
#include <SDL3/SDL_filesystem.h>
|
||||
#include <SDL3/SDL_iostream.h>
|
||||
|
||||
@@ -33,7 +35,7 @@ std::string resolve_path(const Rml::String& source) {
|
||||
if (path.empty() || is_absolute_path(path)) {
|
||||
return path;
|
||||
}
|
||||
const char* basePath = SDL_GetBasePath();
|
||||
const char* basePath = g_config.resourcesPath;
|
||||
if (basePath == nullptr || basePath[0] == '\0') {
|
||||
return path;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
#include "RuntimeTextureProvider.hpp"
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
namespace aurora::rmlui {
|
||||
namespace {
|
||||
|
||||
struct ProviderRegistry {
|
||||
std::mutex mutex;
|
||||
std::unordered_map<std::string, TextureProvider> providers;
|
||||
};
|
||||
|
||||
ProviderRegistry& registry() {
|
||||
static auto* instance = new ProviderRegistry();
|
||||
return *instance;
|
||||
}
|
||||
|
||||
std::string_view source_scheme(std::string_view source) noexcept {
|
||||
const std::string_view delimiter = "://";
|
||||
const auto delimiterPos = source.find(delimiter);
|
||||
if (delimiterPos == std::string_view::npos || delimiterPos == 0) {
|
||||
return {};
|
||||
}
|
||||
return source.substr(0, delimiterPos);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void register_texture_provider(std::string scheme, TextureProvider provider) {
|
||||
if (scheme.empty() || !provider) {
|
||||
return;
|
||||
}
|
||||
if (const auto delimiterPos = scheme.find("://"); delimiterPos != std::string::npos) {
|
||||
scheme.erase(delimiterPos);
|
||||
}
|
||||
|
||||
auto& providers = registry();
|
||||
const std::lock_guard lock(providers.mutex);
|
||||
providers.providers[std::move(scheme)] = std::move(provider);
|
||||
}
|
||||
|
||||
void unregister_texture_provider(std::string_view scheme) noexcept {
|
||||
if (scheme.empty()) {
|
||||
return;
|
||||
}
|
||||
if (const auto delimiterPos = scheme.find("://"); delimiterPos != std::string_view::npos) {
|
||||
scheme = scheme.substr(0, delimiterPos);
|
||||
}
|
||||
|
||||
auto& providers = registry();
|
||||
const std::lock_guard lock(providers.mutex);
|
||||
providers.providers.erase(std::string(scheme));
|
||||
}
|
||||
|
||||
std::optional<RuntimeTexture> load_runtime_texture(std::string_view source) {
|
||||
const auto scheme = source_scheme(source);
|
||||
if (scheme.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
TextureProvider provider;
|
||||
{
|
||||
auto& providers = registry();
|
||||
const std::lock_guard lock(providers.mutex);
|
||||
const auto it = providers.providers.find(std::string(scheme));
|
||||
if (it == providers.providers.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
provider = it->second;
|
||||
}
|
||||
|
||||
return provider(source);
|
||||
}
|
||||
|
||||
} // namespace aurora::rmlui
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <aurora/rmlui.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
namespace aurora::rmlui {
|
||||
|
||||
std::optional<RuntimeTexture> load_runtime_texture(std::string_view source);
|
||||
|
||||
} // namespace aurora::rmlui
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "WebGPURenderInterface.hpp"
|
||||
|
||||
#include "FileInterface_SDL.h"
|
||||
#include "RuntimeTextureProvider.hpp"
|
||||
|
||||
#include <RmlUi/Core/Core.h>
|
||||
#include <RmlUi/Core/DecorationTypes.h>
|
||||
@@ -15,6 +16,7 @@
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "../logging.hpp"
|
||||
#include "../webgpu/gpu.hpp"
|
||||
@@ -625,6 +627,32 @@ void WebGPURenderInterface::ReleaseGeometry(Rml::CompiledGeometryHandle geometry
|
||||
}
|
||||
|
||||
Rml::TextureHandle WebGPURenderInterface::LoadTexture(Rml::Vector2i& dimensions, const Rml::String& source) {
|
||||
if (const auto runtimeTexture = load_runtime_texture(source)) {
|
||||
const size_t size = static_cast<size_t>(runtimeTexture->width) * static_cast<size_t>(runtimeTexture->height) * 4;
|
||||
if (runtimeTexture->width == 0 || runtimeTexture->height == 0 || runtimeTexture->rgba8.size() < size) {
|
||||
Log.error("Runtime texture provider returned invalid texture! Path: {}", source);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto* texels = reinterpret_cast<const Rml::byte*>(runtimeTexture->rgba8.data());
|
||||
std::vector<Rml::byte> premultiplied;
|
||||
if (!runtimeTexture->premultipliedAlpha) {
|
||||
premultiplied.assign(texels, texels + size);
|
||||
for (size_t offset = 0; offset < premultiplied.size(); offset += 4) {
|
||||
const uint8_t alpha = premultiplied[offset + 3];
|
||||
for (size_t channel = 0; channel < 3; ++channel) {
|
||||
premultiplied[offset + channel] = static_cast<uint8_t>(
|
||||
(static_cast<uint32_t>(premultiplied[offset + channel]) * static_cast<uint32_t>(alpha)) / 255);
|
||||
}
|
||||
}
|
||||
texels = premultiplied.data();
|
||||
}
|
||||
|
||||
dimensions.x = static_cast<int>(runtimeTexture->width);
|
||||
dimensions.y = static_cast<int>(runtimeTexture->height);
|
||||
return GenerateTexture({texels, size}, dimensions);
|
||||
}
|
||||
|
||||
// load texels from image source
|
||||
const auto image = get_image(source);
|
||||
if (image.size == 0) {
|
||||
|
||||
Reference in New Issue
Block a user