diff --git a/port/android/host/host_sdl.c b/port/android/host/host_sdl.c index ea9ddbbc..a7a8654f 100644 --- a/port/android/host/host_sdl.c +++ b/port/android/host/host_sdl.c @@ -239,10 +239,20 @@ int host_sdl_rumble_gamepad(uint32_t gamepad, uint32_t low, uint32_t high, uint3 /* SDL calls audio_callback on its own audio thread, which cannot run guest code; it passes each request to the stream's thread (audio_thread), which -can, and waits for it to be done */ +can, and waits for it to be done. SDL holds the stream's lock through the +callback, so the data the game puts into the stream meanwhile, from the +other thread, is gathered here and put in by SDL's thread once the game is +done (SDL_PutAudioStreamData from audio_thread would wait for that lock +for ever, and the game would never be heard). */ struct audio_binding { uint32_t handle; + SDL_AudioStream *stream; + pthread_t thread; + int thread_known; + unsigned char *gathered; + size_t gathered_size; + size_t gathered_capacity; uint32_t callback; uint32_t userdata; pthread_mutex_t lock; @@ -258,6 +268,8 @@ static void *audio_thread(void *context) struct audio_binding *binding = context; pthread_mutex_lock(&binding->lock); + binding->thread = pthread_self(); + binding->thread_known = 1; for (;;) { int additional, total; @@ -287,9 +299,18 @@ static void SDLCALL audio_callback(void *userdata, SDL_AudioStream *stream, int pthread_cond_signal(&binding->requested); while (binding->pending) pthread_cond_wait(&binding->done, &binding->lock); + if (binding->gathered_size) + { + SDL_PutAudioStreamData(stream, binding->gathered, (int)binding->gathered_size); + binding->gathered_size = 0; + } pthread_mutex_unlock(&binding->lock); } +#define AUDIO_BINDINGS 4 + +static struct audio_binding *audio_bindings[AUDIO_BINDINGS]; + uint32_t host_sdl_open_audio_stream(uint32_t device, const void *spec, uint32_t callback, uint32_t userdata) { struct audio_binding *binding = SDL_calloc(1, sizeof(*binding)); @@ -309,16 +330,55 @@ uint32_t host_sdl_open_audio_stream(uint32_t device, const void *spec, uint32_t } /* the device starts paused, so no callback can run before this */ binding->handle = handle_new(_handle_audio, stream); + binding->stream = stream; if (callback && host_native_thread_create(audio_thread, binding, 256 * 1024) != 0) host_fatal("cannot start the audio thread"); + { + int index; + + for (index = 0; index < AUDIO_BINDINGS; index++) + { + if (!audio_bindings[index]) + { + audio_bindings[index] = binding; + break; + } + } + } return binding->handle; } int host_sdl_put_audio_stream_data(uint32_t stream, const void *data, int length) { SDL_AudioStream *object = handle_get(stream, _handle_audio); + int index; - return object ? SDL_PutAudioStreamData(object, data, length) : 0; + if (!object || length < 0) + return 0; + /* from inside the stream's callback: gathered (struct audio_binding) */ + for (index = 0; index < AUDIO_BINDINGS; index++) + { + struct audio_binding *binding = audio_bindings[index]; + + if (binding && binding->stream == object && binding->thread_known && + pthread_equal(binding->thread, pthread_self())) + { + if (binding->gathered_size + (size_t)length > binding->gathered_capacity) + { + size_t capacity = (binding->gathered_size + (size_t)length) * 2; + unsigned char *grown = SDL_realloc(binding->gathered, capacity); + + if (!grown) + return 0; + binding->gathered = grown; + binding->gathered_capacity = capacity; + } + memcpy(binding->gathered + binding->gathered_size, data, (size_t)length); + binding->gathered_size += (size_t)length; + return 1; + } + } + return SDL_PutAudioStreamData(object, data, length); } int host_sdl_resume_audio_stream_device(uint32_t stream) diff --git a/port/linux/include/halo_vr.h b/port/linux/include/halo_vr.h index 24762480..678d874c 100644 --- a/port/linux/include/halo_vr.h +++ b/port/linux/include/halo_vr.h @@ -62,6 +62,10 @@ int halo_vr_aim(short local_player_index, float current_yaw, float current_pitch /* where the head looks, in the game's axes; 0 when not in VR play */ int halo_vr_look_vector(short local_player_index, float vector[3]); +/* vr.log_performance: logs the unit's looking vector against the head's +every second (players.c) */ +void halo_vr_debug_look(short local_player_index, const float unit_looking[3], int in_vehicle); + /* how far the view has turned since this was last asked, once past a few degrees on an axis (the other axis 0); nonzero if it has. The game counts it as looking around (player_control.c's action tests). */ diff --git a/port/linux/src/dsound_sdl.c b/port/linux/src/dsound_sdl.c index 98dc1226..28c91607 100644 --- a/port/linux/src/dsound_sdl.c +++ b/port/linux/src/dsound_sdl.c @@ -429,6 +429,33 @@ static void mix(float *output, unsigned long frames) static SDL_AudioStream *audio_stream; static BOOL audio_started = FALSE; +static BOOL audio_log = FALSE; + +/* debug.audio_log: the level of what is mixed, every five seconds */ +static void audio_log_level(const float *samples, unsigned long count) +{ + static double sum; + static float peak; + static unsigned long total; + unsigned long index; + + for (index = 0; index < count; index++) + { + float value = samples[index] < 0.0f ? -samples[index] : samples[index]; + + sum += (double)value * value; + if (value > peak) + peak = value; + } + total += count; + if (total >= (unsigned long)OUTPUT_RATE * OUTPUT_CHANNELS * 5) + { + platform_log("audio: mixed peak %.4f, rms %.5f over %lu samples", peak, sqrt(sum / total), total); + sum = 0.0; + peak = 0.0f; + total = 0; + } +} static void SDLCALL audio_callback(void *userdata, SDL_AudioStream *stream, int additional_amount, int total_amount) { @@ -445,6 +472,8 @@ static void SDLCALL audio_callback(void *userdata, SDL_AudioStream *stream, int if (!frames) frames = 1; mix(buffer, frames); + if (audio_log) + audio_log_level(buffer, frames * OUTPUT_CHANNELS); SDL_PutAudioStreamData(stream, buffer, (int)(frames * OUTPUT_CHANNELS * sizeof(float))); additional_amount -= (int)(frames * OUTPUT_CHANNELS * sizeof(float)); } @@ -480,6 +509,7 @@ static void audio_start(void) return; audio_started = TRUE; master_volume = (float)config_real("audio.volume"); + audio_log = config_boolean("debug.audio_log"); if (config_boolean("audio.enabled") && platform_sdl_initialize()) { @@ -491,6 +521,8 @@ static void audio_start(void) if (audio_stream) { SDL_ResumeAudioStreamDevice(audio_stream); + platform_log("audio: device stream open, %d Hz, %d channels, volume %.2f", OUTPUT_RATE, OUTPUT_CHANNELS, + master_volume); return; } platform_log("cannot open an audio device (%s); sound is silent", SDL_GetError()); diff --git a/port/linux/src/port_config.c b/port/linux/src/port_config.c index 12df4d97..0196358d 100644 --- a/port/linux/src/port_config.c +++ b/port/linux/src/port_config.c @@ -220,6 +220,8 @@ static const struct config_setting config_settings[] = "Log texture uploads." }, { "debug.texture_no_cache", _config_boolean, "false", "HALO_TEXTURE_NO_CACHE", _environment_set_is_true, _platform_all, "Upload textures again every time they are used." }, + { "debug.audio_log", _config_boolean, "false", "HALO_AUDIO_LOG", _environment_set_is_true, _platform_all, + "Log the level of the mixed sound every five seconds." }, { "debug.sample_seconds", _config_real, "0.0", "HALO_SAMPLE", _environment_value, _platform_android, "Log where every game thread is this often, in seconds (read by the\n" "app, port/android/host/host_debug.c); 0 never." }, diff --git a/port/linux/src/vr.c b/port/linux/src/vr.c index 8c4b4c6d..9457f1e3 100644 --- a/port/linux/src/vr.c +++ b/port/linux/src/vr.c @@ -613,6 +613,21 @@ int halo_vr_look_vector(short local_player_index, float vector[3]) return 1; } +void halo_vr_debug_look(short local_player_index, const float unit_looking[3], int in_vehicle) +{ + static unsigned long long last_us; + float head[3] = { 0.0f, 0.0f, 0.0f }; + int valid; + unsigned long long now = now_us(); + + if (!vr.log_performance || local_player_index != 0 || now - last_us < 1000000) + return; + last_us = now; + valid = halo_vr_look_vector(local_player_index, head); + platform_log("VR look: head %s (%.2f %.2f %.2f), unit looking (%.2f %.2f %.2f)%s", valid ? "valid" : "INVALID", + head[0], head[1], head[2], unit_looking[0], unit_looking[1], unit_looking[2], in_vehicle ? ", in a vehicle" : ""); +} + int halo_vr_look_delta(float *yaw, float *pitch) { /* past the head's tremble */ @@ -683,6 +698,10 @@ int halo_vr_laser(short local_player_index, float start[3], float direction[3]) return 0; } int halo_vr_look_vector(short local_player_index, float vector[3]) { (void)local_player_index; (void)vector; return 0; } +void halo_vr_debug_look(short local_player_index, const float unit_looking[3], int in_vehicle) +{ + (void)local_player_index; (void)unit_looking; (void)in_vehicle; +} int halo_vr_look_delta(float *yaw, float *pitch) { *yaw = *pitch = 0.0f; return 0; } int halo_vr_hide_crosshair(void) { return 0; } diff --git a/source/game/players.c b/source/game/players.c index 469ecfb8..05e55526 100644 --- a/source/game/players.c +++ b/source/game/players.c @@ -3511,6 +3511,15 @@ void players_update_before_game( control_data.aiming_vector.k = vr_aim[2]; } } + { + real unit_look[3]; + + unit_look[0] = unit->unit.looking_vector.i; + unit_look[1] = unit->unit.looking_vector.j; + unit_look[2] = unit->unit.looking_vector.k; + halo_vr_debug_look(player->local_player_index, unit_look, + unit->object.parent_object_index != NONE); + } #endif control_data.throttle.i = action->throttle.i; control_data.throttle.j = action->throttle.j; diff --git a/source/units/units.c b/source/units/units.c index 060a21fe..92e17a60 100644 --- a/source/units/units.c +++ b/source/units/units.c @@ -688,6 +688,10 @@ symbols in this file: #include "sound/game_sound.h" #include "vehicles.h" +#ifdef HALO_ANDROID +#include "halo_vr.h" +#endif + /* ---------- constants */ enum @@ -2971,6 +2975,22 @@ boolean unit_can_see_point( { can_see_point = TRUE; } +#ifdef HALO_ANDROID + else if (unit->unit.player_index != NONE && halo_vr_active()) + { + /* VR: the head only points roughly where the eyes look, so it + sees within a wider angle; and the weapon's laser targets too + (the training's "target each of the flashing lights" asks for + 5 degrees) */ + real head_field_of_view = MAX(field_of_view, DEGREES_TO_RADIANS(15.f)); + + if (dot_product3d(&direction, &unit->unit.looking_vector)>cosine(head_field_of_view) || + dot_product3d(&direction, &unit->unit.aiming_vector)>cosine(field_of_view)) + { + can_see_point = TRUE; + } + } +#endif } return can_see_point;