Merge branch 'main' into dusk-tphd

This commit is contained in:
Luke Street
2026-05-29 21:22:43 -06:00
20 changed files with 886 additions and 55 deletions
+10
View File
@@ -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
+2 -2
View File
@@ -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)
+1
View File
@@ -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;
+25
View File
@@ -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
+7
View File
@@ -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,
+5
View File
@@ -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
View File
@@ -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
+15
View File
@@ -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
+182
View File
@@ -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
View File
@@ -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) {
+5 -3
View File
@@ -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
View File
@@ -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
+8 -5
View File
@@ -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
View File
@@ -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
+3
View File
@@ -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
View File
@@ -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;
+3 -1
View File
@@ -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;
}
+78
View File
@@ -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
+12
View File
@@ -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
+28
View File
@@ -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) {