From e7b5768b0c3588e682eb7e6325219b1dbe45f95b Mon Sep 17 00:00:00 2001 From: Manuel Lauss <123231678+mlauss2@users.noreply.github.com> Date: Wed, 27 Sep 2023 22:47:02 +0200 Subject: [PATCH 1/4] Use SDL2 Mutexes and Threads (#335) * TFE: Use SDL2 Mutexes and Threads Replace the explicit Windows and Linux Mutex and Thread code with SDL2 equivalents. * Remove unused (Win32) signal code * systemMidiDevice: serialize access to the port. When switching MIDI devices (System Midi<->internal soft synth), there's a short timespan when both the midi player and the midi thread write data to the port at the same time. This confuses the linux ALSA midi parser into overshooting buffers causing crashes. This patch adds a mutex around the port send function to serialize access. Apart from the switching scenario above, the mutex is always uncontended, so perf impact should not be noticeable. --- TheForceEngine/TFE_Audio/audioDevice.h | 27 ----- TheForceEngine/TFE_Audio/audioSystem.cpp | 64 ++++++------ TheForceEngine/TFE_Audio/midiPlayer.cpp | 90 ++++++++--------- TheForceEngine/TFE_Audio/midiPlayer.h | 3 - TheForceEngine/TFE_Audio/systemMidiDevice.cpp | 32 ++++-- TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp | 4 - TheForceEngine/TFE_Jedi/IMuse/imuse.cpp | 2 +- TheForceEngine/TFE_System/CMakeLists.txt | 2 - .../TFE_System/Threads/Linux/mutexLinux.cpp | 29 ------ .../TFE_System/Threads/Linux/mutexLinux.h | 16 --- .../TFE_System/Threads/Linux/threadLinux.cpp | 55 ----------- .../TFE_System/Threads/Linux/threadLinux.h | 18 ---- .../TFE_System/Threads/Win32/mutexWin32.cpp | 29 ------ .../TFE_System/Threads/Win32/mutexWin32.h | 16 --- .../TFE_System/Threads/Win32/signalWin32.cpp | 39 -------- .../TFE_System/Threads/Win32/signalWin32.h | 16 --- .../TFE_System/Threads/Win32/threadWin32.cpp | 99 ------------------- .../TFE_System/Threads/Win32/threadWin32.h | 18 ---- TheForceEngine/TFE_System/Threads/mutex.h | 18 ---- TheForceEngine/TFE_System/Threads/signal.h | 21 ---- TheForceEngine/TFE_System/Threads/thread.h | 46 --------- 21 files changed, 102 insertions(+), 542 deletions(-) delete mode 100644 TheForceEngine/TFE_System/Threads/Linux/mutexLinux.cpp delete mode 100644 TheForceEngine/TFE_System/Threads/Linux/mutexLinux.h delete mode 100644 TheForceEngine/TFE_System/Threads/Linux/threadLinux.cpp delete mode 100644 TheForceEngine/TFE_System/Threads/Linux/threadLinux.h delete mode 100644 TheForceEngine/TFE_System/Threads/Win32/mutexWin32.cpp delete mode 100644 TheForceEngine/TFE_System/Threads/Win32/mutexWin32.h delete mode 100644 TheForceEngine/TFE_System/Threads/Win32/signalWin32.cpp delete mode 100644 TheForceEngine/TFE_System/Threads/Win32/signalWin32.h delete mode 100644 TheForceEngine/TFE_System/Threads/Win32/threadWin32.cpp delete mode 100644 TheForceEngine/TFE_System/Threads/Win32/threadWin32.h delete mode 100644 TheForceEngine/TFE_System/Threads/mutex.h delete mode 100644 TheForceEngine/TFE_System/Threads/signal.h delete mode 100644 TheForceEngine/TFE_System/Threads/thread.h diff --git a/TheForceEngine/TFE_Audio/audioDevice.h b/TheForceEngine/TFE_Audio/audioDevice.h index ffede6569..29bbe9488 100644 --- a/TheForceEngine/TFE_Audio/audioDevice.h +++ b/TheForceEngine/TFE_Audio/audioDevice.h @@ -4,33 +4,6 @@ #include "audioOutput.h" #include -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN 1 -#include - -#define MUTEX_INITIALIZE(A) InitializeCriticalSection(A) -#define MUTEX_DESTROY(A) DeleteCriticalSection(A) -#define MUTEX_LOCK(A) EnterCriticalSection(A) -#define MUTEX_TRYLOCK(A) TryEnterCriticalSection(A) -#define MUTEX_UNLOCK(A) LeaveCriticalSection(A) - -typedef CRITICAL_SECTION Mutex; -#undef min -#undef max -#else -#include - -#include - -#define MUTEX_INITIALIZE(A) pthread_mutex_init(A, NULL) -#define MUTEX_DESTROY(A) pthread_mutex_destroy(A) -#define MUTEX_LOCK(A) pthread_mutex_lock(A) -#define MUTEX_TRYLOCK(A) pthread_mutex_trylock(A) -#define MUTEX_UNLOCK(A) pthread_mutex_unlock(A) - -typedef pthread_mutex_t Mutex; -#endif - static const u32 AUDIO_STATUS_INPUT_OVERFLOW = 0x1; // Input data was discarded because of an overflow condition at the driver. static const u32 AUDIO_STATUS_OUTPUT_UNDERFLOW = 0x2; // The output buffer ran low, likely causing a gap in the output sound. diff --git a/TheForceEngine/TFE_Audio/audioSystem.cpp b/TheForceEngine/TFE_Audio/audioSystem.cpp index ee809630f..8d3654c8f 100644 --- a/TheForceEngine/TFE_Audio/audioSystem.cpp +++ b/TheForceEngine/TFE_Audio/audioSystem.cpp @@ -1,8 +1,8 @@ #include - #include "audioSystem.h" #include "audioDevice.h" #include "midiPlayer.h" +#include #include #include #include @@ -67,7 +67,7 @@ namespace TFE_Audio static u32 s_sourceCount; static SoundSource s_sources[MAX_SOUND_SOURCES]; - static Mutex s_mutex; + static SDL_mutex* s_mutex; static bool s_paused = false; static bool s_nullDevice = false; static volatile s32 s_silentAudioFrames = 0; @@ -124,7 +124,15 @@ namespace TFE_Audio return false; } - MUTEX_INITIALIZE(&s_mutex); + s_mutex = SDL_CreateMutex(); + if (!s_mutex) + { + TFE_System::logWrite(LOG_ERROR, "Audio", "Cannot init SDL_mutex."); + TFE_AudioDevice::destroy(); + s_nullDevice = true; + return false; + } + s_nullDevice = false; return true; } @@ -137,21 +145,21 @@ namespace TFE_Audio stopAllSounds(); TFE_AudioDevice::destroy(); - MUTEX_DESTROY(&s_mutex); + SDL_DestroyMutex(s_mutex); } void stopAllSounds() { if (s_nullDevice) { return; } - MUTEX_LOCK(&s_mutex); + SDL_LockMutex(s_mutex); s_sourceCount = 0u; memset(s_sources, 0, sizeof(SoundSource) * MAX_SOUND_SOURCES); for (s32 i = 0; i < MAX_SOUND_SOURCES; i++) { s_sources[i].slot = i; } - MUTEX_UNLOCK(&s_mutex); + SDL_UnlockMutex(s_mutex); } void selectDevice(s32 id) @@ -190,16 +198,16 @@ namespace TFE_Audio void pause() { - MUTEX_LOCK(&s_mutex); + SDL_LockMutex(s_mutex); s_paused = true; - MUTEX_UNLOCK(&s_mutex); + SDL_UnlockMutex(s_mutex); } void resume() { - MUTEX_LOCK(&s_mutex); + SDL_LockMutex(s_mutex); s_paused = false; - MUTEX_UNLOCK(&s_mutex); + SDL_UnlockMutex(s_mutex); } // Really the buffered audio will continue to process so time advances properly. @@ -214,9 +222,9 @@ namespace TFE_Audio { if (s_nullDevice) { return; } - MUTEX_LOCK(&s_mutex); + SDL_LockMutex(s_mutex); s_audioThreadCallback = callback; - MUTEX_UNLOCK(&s_mutex); + SDL_UnlockMutex(s_mutex); } const OutputDeviceInfo* getOutputDeviceList(s32& count, s32& curOutput) @@ -227,13 +235,13 @@ namespace TFE_Audio void lock() { if (s_nullDevice) { return; } - MUTEX_LOCK(&s_mutex); + SDL_LockMutex(s_mutex); } void unlock() { if (s_nullDevice) { return; } - MUTEX_UNLOCK(&s_mutex); + SDL_UnlockMutex(s_mutex); } // One shot, play and forget. Only do this if the client needs no control until stopAllSounds() is called. @@ -242,7 +250,7 @@ namespace TFE_Audio { if (!buffer || s_nullDevice) { return false; } - MUTEX_LOCK(&s_mutex); + SDL_LockMutex(s_mutex); // Find the first inactive source. SoundSource* snd = s_sources; SoundSource* newSource = nullptr; @@ -275,7 +283,7 @@ namespace TFE_Audio newSource->finishedUserData = cbUserData; newSource->finishedArg = cbArg; } - MUTEX_UNLOCK(&s_mutex); + SDL_UnlockMutex(s_mutex); return newSource != nullptr; } @@ -286,7 +294,7 @@ namespace TFE_Audio if (!buffer || s_nullDevice) { return nullptr; } assert(volume >= 0.0f && volume <= 1.0f); - MUTEX_LOCK(&s_mutex); + SDL_LockMutex(s_mutex); // Find the first inactive source. SoundSource* snd = s_sources; SoundSource* newSource = nullptr; @@ -314,7 +322,7 @@ namespace TFE_Audio newSource->finishedCallback = callback; newSource->finishedUserData = userData; } - MUTEX_UNLOCK(&s_mutex); + SDL_UnlockMutex(s_mutex); return newSource; } @@ -348,29 +356,29 @@ namespace TFE_Audio return; } - MUTEX_LOCK(&s_mutex); + SDL_LockMutex(s_mutex); source->flags |= SND_FLAG_PLAYING; if (looping) { source->flags |= SND_FLAG_LOOPING; } source->sampleIndex = 0u; - MUTEX_UNLOCK(&s_mutex); + SDL_UnlockMutex(s_mutex); } void stopSource(SoundSource* source) { if (!source || s_nullDevice) { return; } - MUTEX_LOCK(&s_mutex); + SDL_LockMutex(s_mutex); source->flags &= ~SND_FLAG_PLAYING; - MUTEX_UNLOCK(&s_mutex); + SDL_UnlockMutex(s_mutex); } void freeSource(SoundSource* source) { if (!source || s_nullDevice) { return; } - MUTEX_LOCK(&s_mutex); + SDL_LockMutex(s_mutex); source->flags &= ~SND_FLAG_PLAYING; source->flags &= ~SND_FLAG_ACTIVE; source->buffer = nullptr; - MUTEX_UNLOCK(&s_mutex); + SDL_UnlockMutex(s_mutex); } void setSourceVolume(SoundSource* source, f32 volume) @@ -383,10 +391,10 @@ namespace TFE_Audio void setSourceBuffer(SoundSource* source, const SoundBuffer* buffer) { if (s_nullDevice) { return; } - MUTEX_LOCK(&s_mutex); + SDL_LockMutex(s_mutex); source->sampleIndex = 0u; source->buffer = buffer; - MUTEX_UNLOCK(&s_mutex); + SDL_UnlockMutex(s_mutex); } bool isSourcePlaying(SoundSource* source) @@ -458,7 +466,7 @@ namespace TFE_Audio // First clear samples memset(buffer, 0, sizeof(f32)*bufferSize*AUDIO_CHANNEL_COUNT); - MUTEX_LOCK(&s_mutex); + SDL_LockMutex(s_mutex); // Then call the audio thread callback if (s_audioThreadCallback && !s_paused) { @@ -553,7 +561,7 @@ namespace TFE_Audio TFE_MidiPlayer::synthesizeMidi((f32*)outputBuffer, bufferSize, !s_silentAudioFrames); } if (s_silentAudioFrames > 0) { s_silentAudioFrames--; } - MUTEX_UNLOCK(&s_mutex); + SDL_UnlockMutex(s_mutex); // Handle out of range audio samples. buffer = (f32*)outputBuffer; diff --git a/TheForceEngine/TFE_Audio/midiPlayer.cpp b/TheForceEngine/TFE_Audio/midiPlayer.cpp index 018969303..7abaf8f15 100644 --- a/TheForceEngine/TFE_Audio/midiPlayer.cpp +++ b/TheForceEngine/TFE_Audio/midiPlayer.cpp @@ -2,9 +2,10 @@ #include "midiDevice.h" #include "audioDevice.h" #include "systemMidiDevice.h" +#include +#include #include #include -#include #include #include #include @@ -53,11 +54,11 @@ namespace TFE_MidiPlayer static const f32 c_musicVolumeScale = 0.75f; static f32 s_masterVolume = 1.0f; static f32 s_masterVolumeScaled = s_masterVolume * c_musicVolumeScale; - static Thread* s_thread = nullptr; + static SDL_Thread* s_thread = nullptr; static atomic_bool s_runMusicThread; static u8 s_channelSrcVolume[MIDI_CHANNEL_COUNT] = { 0 }; - static Mutex s_mutex; + static SDL_mutex* s_mutex; static MidiDevice* s_midiDevice = nullptr; static MidiCallback s_midiCallback = {}; @@ -74,7 +75,7 @@ namespace TFE_MidiPlayer static Instrument s_instrOn[MIDI_INSTRUMENT_COUNT] = { 0 }; static f64 s_curNoteTime = 0.0; - TFE_THREADRET midiUpdateFunc(void* userData); + int midiUpdateFunc(void* userData); void stopAllNotes(); void changeVolume(); void allocateMidiDevice(MidiDeviceType type); @@ -93,8 +94,15 @@ namespace TFE_MidiPlayer bool init(s32 midiDeviceIndex, MidiDeviceType type) { TFE_System::logWrite(LOG_MSG, "Startup", "TFE_MidiPlayer::init"); - bool res = false; + + s_mutex = SDL_CreateMutex(); + if (!s_mutex) + { + TFE_System::logWrite(LOG_ERROR, "Midi", "cannot initialize SDL mutex"); + return false; + } + allocateMidiDevice(type); if (s_midiDevice) @@ -111,12 +119,13 @@ namespace TFE_MidiPlayer } s_runMusicThread.store(true); - MUTEX_INITIALIZE(&s_mutex); - s_thread = Thread::create("MidiThread", midiUpdateFunc, nullptr); - if (s_thread) + + s_thread = SDL_CreateThread(midiUpdateFunc, "TFE_MidiThread", nullptr); + if (!s_thread) { - s_thread->run(); + TFE_System::logWrite(LOG_ERROR, "Midi", "cannot create Midi Thread!"); + res = false; } CCMD("setMusicVolume", setMusicVolumeConsole, 1, "Sets the music volume, range is 0.0 to 1.0"); @@ -131,19 +140,16 @@ namespace TFE_MidiPlayer void destroy() { + int i; + TFE_System::logWrite(LOG_MSG, "MidiPlayer", "Shutdown"); // Destroy the thread before shutting down the Midi Device. s_runMusicThread.store(false); - if (s_thread->isPaused()) - { - s_thread->resume(); - } - s_thread->waitOnExit(); + SDL_WaitThread(s_thread, &i); - delete s_thread; delete s_midiDevice; - MUTEX_DESTROY(&s_mutex); + SDL_DestroyMutex(s_mutex); } MidiDevice* getMidiDevice() @@ -198,14 +204,14 @@ namespace TFE_MidiPlayer ////////////////////////////////////////////////// void setVolume(f32 volume) { - MUTEX_LOCK(&s_mutex); + SDL_LockMutex(s_mutex); MidiCmd* midiCmd = midiAllocCmd(); if (midiCmd) { midiCmd->cmd = MIDI_CHANGE_VOL; midiCmd->newVolume = volume; } - MUTEX_UNLOCK(&s_mutex); + SDL_UnlockMutex(s_mutex); } // Set the length in seconds that a note is allowed to play for in seconds. @@ -214,53 +220,37 @@ namespace TFE_MidiPlayer s_maxNoteLength = f64(dt); } - void pauseThread() - { - if (s_thread) - { - s_thread->pause(); - } - } - - void resumeThread() - { - if (s_thread) - { - s_thread->resume(); - } - } - void pause() { - MUTEX_LOCK(&s_mutex); + SDL_LockMutex(s_mutex); MidiCmd* midiCmd = midiAllocCmd(); if (midiCmd) { midiCmd->cmd = MIDI_PAUSE; } - MUTEX_UNLOCK(&s_mutex); + SDL_UnlockMutex(s_mutex); } void resume() { - MUTEX_LOCK(&s_mutex); + SDL_LockMutex(s_mutex); MidiCmd* midiCmd = midiAllocCmd(); if (midiCmd) { midiCmd->cmd = MIDI_RESUME; } - MUTEX_UNLOCK(&s_mutex); + SDL_UnlockMutex(s_mutex); } void stopMidiSound() { - MUTEX_LOCK(&s_mutex); + SDL_LockMutex(s_mutex); MidiCmd* midiCmd = midiAllocCmd(); if (midiCmd) { midiCmd->cmd = MIDI_STOP_NOTES; } - MUTEX_UNLOCK(&s_mutex); + SDL_UnlockMutex(s_mutex); } void synthesizeMidi(f32* buffer, u32 stereoSampleCount, bool updateBuffer) @@ -278,7 +268,7 @@ namespace TFE_MidiPlayer s_sampleBufferPtr = s_sampleBuffer.data(); } - MUTEX_LOCK(&s_mutex); + SDL_LockMutex(s_mutex); // This is checked again, in case it was immediately changed in another thread; such as when changing midi devices or outputs. if (s_midiDevice && s_midiDevice->canRender()) { @@ -293,7 +283,7 @@ namespace TFE_MidiPlayer } } } - MUTEX_UNLOCK(&s_mutex); + SDL_UnlockMutex(s_mutex); } } @@ -304,7 +294,7 @@ namespace TFE_MidiPlayer void midiSetCallback(void(*callback)(void), f64 timeStep) { - MUTEX_LOCK(&s_mutex); + SDL_LockMutex(s_mutex); s_midiCallback.callback = callback; s_midiCallback.timeStep = timeStep; s_midiCallback.accumulator = 0.0; @@ -314,16 +304,16 @@ namespace TFE_MidiPlayer s_channelSrcVolume[i] = CHANNEL_MAX_VOLUME; } changeVolume(); - MUTEX_UNLOCK(&s_mutex); + SDL_UnlockMutex(s_mutex); } void midiClearCallback() { - MUTEX_LOCK(&s_mutex); + SDL_LockMutex(s_mutex); s_midiCallback.callback = nullptr; s_midiCallback.timeStep = 0.0; s_midiCallback.accumulator = 0.0; - MUTEX_UNLOCK(&s_mutex); + SDL_UnlockMutex(s_mutex); } ////////////////////////////////////////////////// @@ -432,7 +422,7 @@ namespace TFE_MidiPlayer } // Thread Function - TFE_THREADRET midiUpdateFunc(void* userData) + int midiUpdateFunc(void* userData) { bool runThread = true; bool wasPlaying = false; @@ -444,7 +434,7 @@ namespace TFE_MidiPlayer f64 dt = 0.0; while (runThread) { - MUTEX_LOCK(&s_mutex); + SDL_LockMutex(s_mutex); // Read from the command buffer. MidiCmd* midiCmd = s_midiCmdBuffer; @@ -494,11 +484,11 @@ namespace TFE_MidiPlayer detectHangingNotes(); } - MUTEX_UNLOCK(&s_mutex); + SDL_UnlockMutex(s_mutex); runThread = s_runMusicThread.load(); }; - return (TFE_THREADRET)0; + return 0; } // Console Functions diff --git a/TheForceEngine/TFE_Audio/midiPlayer.h b/TheForceEngine/TFE_Audio/midiPlayer.h index bf97a52a7..741f8b498 100644 --- a/TheForceEngine/TFE_Audio/midiPlayer.h +++ b/TheForceEngine/TFE_Audio/midiPlayer.h @@ -31,9 +31,6 @@ namespace TFE_MidiPlayer void midiSetCallback(void(*callback)(void) = nullptr, f64 timeStep = 0.0); void midiClearCallback(); - void pauseThread(); - void resumeThread(); - // Pause the midi player, which also stops all sound channels. void pause(); // Resume midi playback from where it left off. diff --git a/TheForceEngine/TFE_Audio/systemMidiDevice.cpp b/TheForceEngine/TFE_Audio/systemMidiDevice.cpp index cf04d2e13..4aa9e6ba7 100644 --- a/TheForceEngine/TFE_Audio/systemMidiDevice.cpp +++ b/TheForceEngine/TFE_Audio/systemMidiDevice.cpp @@ -1,4 +1,6 @@ +#include #include +#include #include "midi.h" #include "systemMidiDevice.h" @@ -8,7 +10,6 @@ #include "RtMidi.h" #endif #include -#include #ifdef _WIN32 // Windows library required to access the midi device(s). @@ -27,9 +28,13 @@ namespace TFE_Audio }; void midiErrorCallback(RtMidiError::Type, const std::string&, void*); + // serialize access to the physical MIDI port, to at least + // prevent a buffer overrun in the Linux ALSA MIDI parser. + static SDL_mutex* serializer = nullptr; SystemMidiDevice::SystemMidiDevice() { + serializer = SDL_CreateMutex(); m_outputId = -1; m_midiout = new RtMidiOut(); m_midiout->setErrorCallback(midiErrorCallback); @@ -40,6 +45,11 @@ namespace TFE_Audio SystemMidiDevice::~SystemMidiDevice() { exit(); + if (serializer) + { + SDL_DestroyMutex(serializer); + serializer = nullptr; + } } void SystemMidiDevice::exit() @@ -59,17 +69,25 @@ namespace TFE_Audio return c_SystemMidi_Name; } - void SystemMidiDevice::message(u8 type, u8 arg1, u8 arg2) + void SystemMidiDevice::message(const u8* msg, u32 len) { - const u8 msg[3] = { type, arg1, arg2 }; - if (m_midiout) { m_midiout->sendMessage(msg, 3); } + if (m_outputId >= 0 && serializer) + { + // this mutex is uncontended except for a short time + // after a midi device switch is done in the settings. + SDL_LockMutex(serializer); + m_midiout->sendMessage(msg, (size_t)len); + SDL_UnlockMutex(serializer); + } } - - void SystemMidiDevice::message(const u8* msg, u32 len) + + void SystemMidiDevice::message(u8 type, u8 arg1, u8 arg2) { - if (m_midiout) { m_midiout->sendMessage(msg, (size_t)len); } + const u8 msg[3] = { type, arg1, arg2 }; + message(msg, 3); } + void SystemMidiDevice::noteAllOff() { for (u32 c = 0; c < MIDI_CHANNEL_COUNT; c++) diff --git a/TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp b/TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp index 1f2ac821a..3453034c9 100644 --- a/TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp +++ b/TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp @@ -2505,7 +2505,6 @@ namespace TFE_FrontEndUI if (ImGui::Combo("##Midi Type", &curType, (const char*)outputMidiTypeNames, typeCount)) { TFE_Audio::pause(); - TFE_MidiPlayer::pauseThread(); TFE_MidiPlayer::setDeviceType(MidiDeviceType(curType)); device = TFE_MidiPlayer::getMidiDevice(); @@ -2515,7 +2514,6 @@ namespace TFE_FrontEndUI sound->midiOutput = device->getActiveOutput(); } - TFE_MidiPlayer::resumeThread(); TFE_Audio::resume(); if (s_game) { s_game->restartMusic(); } @@ -2548,13 +2546,11 @@ namespace TFE_FrontEndUI if (hasChanged) { TFE_Audio::pause(); - TFE_MidiPlayer::pauseThread(); device->selectOutput(curOutput); sound->midiType = (s32)device->getType(); sound->midiOutput = device->getActiveOutput(); - TFE_MidiPlayer::resumeThread(); TFE_Audio::resume(); if (s_game) { s_game->restartMusic(); } diff --git a/TheForceEngine/TFE_Jedi/IMuse/imuse.cpp b/TheForceEngine/TFE_Jedi/IMuse/imuse.cpp index 49e5b38b5..80c0cea5a 100644 --- a/TheForceEngine/TFE_Jedi/IMuse/imuse.cpp +++ b/TheForceEngine/TFE_Jedi/IMuse/imuse.cpp @@ -2,12 +2,12 @@ #include #include #include -#include #include #include #include #include #include +#include #include #include "imList.h" #include "imTrigger.h" diff --git a/TheForceEngine/TFE_System/CMakeLists.txt b/TheForceEngine/TFE_System/CMakeLists.txt index 6b1e1f043..3a0baebb7 100644 --- a/TheForceEngine/TFE_System/CMakeLists.txt +++ b/TheForceEngine/TFE_System/CMakeLists.txt @@ -2,12 +2,10 @@ file(GLOB SOURCES "*.cpp") target_sources(tfe PRIVATE ${SOURCES}) if(WIN32) - file(GLOB SRCWINTHRD "${CMAKE_CURRENT_SOURCE_DIR}/Threads/Win32/*.cpp") target_sources(tfe PRIVATE ${SRCWINTHRD} "${CMAKE_CURRENT_SOURCE_DIR}/CrashHandler/crashHandlerWin32.cpp" ) elseif(LINUX) - file(GLOB SRCLNXTHRD "${CMAKE_CURRENT_SOURCE_DIR}/Threads/Linux/*.cpp") target_sources(tfe PRIVATE ${SRCLNXTHRD} "${CMAKE_CURRENT_SOURCE_DIR}/CrashHandler/crashHandlerLinux.cpp" ) diff --git a/TheForceEngine/TFE_System/Threads/Linux/mutexLinux.cpp b/TheForceEngine/TFE_System/Threads/Linux/mutexLinux.cpp deleted file mode 100644 index 2061cb75f..000000000 --- a/TheForceEngine/TFE_System/Threads/Linux/mutexLinux.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "mutexLinux.h" - -MutexLinux::MutexLinux() : Mutex() -{ - pthread_mutex_init(&m_handle, NULL); -} - -MutexLinux::~MutexLinux() -{ - pthread_mutex_destroy(&m_handle); -} - -s32 MutexLinux::lock() -{ - pthread_mutex_lock(&m_handle); - return 0; -} - -s32 MutexLinux::unlock() -{ - pthread_mutex_unlock(&m_handle); - return 0; -} - -//factory -Mutex* Mutex::create() -{ - return new MutexLinux(); -} diff --git a/TheForceEngine/TFE_System/Threads/Linux/mutexLinux.h b/TheForceEngine/TFE_System/Threads/Linux/mutexLinux.h deleted file mode 100644 index 7e228a596..000000000 --- a/TheForceEngine/TFE_System/Threads/Linux/mutexLinux.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once -#include -#include "../mutex.h" - -class MutexLinux : public Mutex -{ -public: - MutexLinux(); - virtual ~MutexLinux(); - - virtual s32 lock(); - virtual s32 unlock(); - -private: - pthread_mutex_t m_handle; -}; diff --git a/TheForceEngine/TFE_System/Threads/Linux/threadLinux.cpp b/TheForceEngine/TFE_System/Threads/Linux/threadLinux.cpp deleted file mode 100644 index 323e64377..000000000 --- a/TheForceEngine/TFE_System/Threads/Linux/threadLinux.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include -#include -#include "threadLinux.h" - -ThreadLinux::ThreadLinux(const char* name, ThreadFunc func, void* userData) : Thread(name, func, userData) -{ - m_handle = 0; -} - -ThreadLinux::~ThreadLinux() -{ - if (m_handle) - { - pthread_cancel(m_handle); - m_handle = 0; - } -} - -bool ThreadLinux::run(void) -{ - int res = pthread_create(&m_handle, NULL, m_func, m_userData); - //create the thread. - if (res != 0) - { - m_handle = 0; - TFE_System::logWrite( LOG_ERROR, "Thread \"%s\" cannot be run", m_name ); - } - - return (res == 0); -} - -void ThreadLinux::pause(void) -{ - if (!m_handle) { return; } - //to-do. -} - -void ThreadLinux::resume(void) -{ - if (!m_handle) { return; } - //to-do. -} - -void ThreadLinux::waitOnExit(void) -{ - pthread_cancel(m_handle); - m_handle = 0; - m_isRunning = false; -} - -//factory -Thread* Thread::create(const char* name, ThreadFunc func, void* userData) -{ - return new ThreadLinux(name, func, userData); -} diff --git a/TheForceEngine/TFE_System/Threads/Linux/threadLinux.h b/TheForceEngine/TFE_System/Threads/Linux/threadLinux.h deleted file mode 100644 index 6c7fcca77..000000000 --- a/TheForceEngine/TFE_System/Threads/Linux/threadLinux.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once -#include -#include "../thread.h" - -class ThreadLinux : public Thread -{ -public: - ThreadLinux(const char* name, ThreadFunc func, void* userData); - virtual ~ThreadLinux(); - - virtual bool run(); - virtual void pause(); - virtual void resume(); - virtual void waitOnExit(void); - -protected: - pthread_t m_handle; -}; diff --git a/TheForceEngine/TFE_System/Threads/Win32/mutexWin32.cpp b/TheForceEngine/TFE_System/Threads/Win32/mutexWin32.cpp deleted file mode 100644 index 9ac0fe4f5..000000000 --- a/TheForceEngine/TFE_System/Threads/Win32/mutexWin32.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "mutexWin32.h" - -MutexWin32::MutexWin32() : Mutex() -{ - InitializeCriticalSection(&C); -} - -MutexWin32::~MutexWin32() -{ - DeleteCriticalSection(&C); -} - -s32 MutexWin32::lock() -{ - EnterCriticalSection(&C); - return 0; -} - -s32 MutexWin32::unlock() -{ - LeaveCriticalSection(&C); - return 0; -} - -//factory -Mutex* Mutex::create() -{ - return new MutexWin32(); -} diff --git a/TheForceEngine/TFE_System/Threads/Win32/mutexWin32.h b/TheForceEngine/TFE_System/Threads/Win32/mutexWin32.h deleted file mode 100644 index 0be077b3d..000000000 --- a/TheForceEngine/TFE_System/Threads/Win32/mutexWin32.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once -#include -#include "../mutex.h" - -class MutexWin32 : public Mutex -{ -public: - MutexWin32(); - virtual ~MutexWin32(); - - virtual s32 lock(); - virtual s32 unlock(); - -private: - mutable CRITICAL_SECTION C; -}; diff --git a/TheForceEngine/TFE_System/Threads/Win32/signalWin32.cpp b/TheForceEngine/TFE_System/Threads/Win32/signalWin32.cpp deleted file mode 100644 index f23cd2e77..000000000 --- a/TheForceEngine/TFE_System/Threads/Win32/signalWin32.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "signalWin32.h" -#include - -SignalWin32::SignalWin32() -{ - m_win32Handle = CreateEvent(0, TRUE, FALSE, 0); -} - -SignalWin32::~SignalWin32() -{ - if (m_win32Handle) - { - CloseHandle(m_win32Handle); - } -} - -void SignalWin32::fire() -{ - SetEvent(m_win32Handle); -} - -bool SignalWin32::wait(u32 timeOutInMS, bool reset) -{ - DWORD res = WaitForSingleObject(m_win32Handle, timeOutInMS); - - //reset the event so it can be used again but only if the event was signaled. - if (res == WAIT_OBJECT_0 && reset) - { - ResetEvent(m_win32Handle); - } - - return (res!=WAIT_TIMEOUT); -} - -//factory -Signal* Signal::create() -{ - return new SignalWin32(); -} diff --git a/TheForceEngine/TFE_System/Threads/Win32/signalWin32.h b/TheForceEngine/TFE_System/Threads/Win32/signalWin32.h deleted file mode 100644 index f03c36aa5..000000000 --- a/TheForceEngine/TFE_System/Threads/Win32/signalWin32.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once -#include -#include "../signal.h" - -class SignalWin32 : public Signal -{ -public: - SignalWin32(); - virtual ~SignalWin32(); - - virtual void fire(); - virtual bool wait(u32 timeOutInMS=TIMEOUT_INFINITE, bool reset=true); - -protected: - HANDLE m_win32Handle; -}; diff --git a/TheForceEngine/TFE_System/Threads/Win32/threadWin32.cpp b/TheForceEngine/TFE_System/Threads/Win32/threadWin32.cpp deleted file mode 100644 index e2571e01b..000000000 --- a/TheForceEngine/TFE_System/Threads/Win32/threadWin32.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "threadWin32.h" -#include - -void setThreadName(HANDLE threadHandle, const char* threadName); - -ThreadWin32::ThreadWin32(const char* name, ThreadFunc func, void* userData) : Thread(name, func, userData) -{ - m_win32Handle = 0; -} - -ThreadWin32::~ThreadWin32() -{ - if (m_win32Handle) - { - TerminateThread(m_win32Handle, 0); // Dangerous source of errors! TO-DO: non-crappy solution. :D- - CloseHandle(m_win32Handle); - } -} - -bool ThreadWin32::run() -{ - m_win32Handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)m_func, m_userData, 0, NULL); - if (m_win32Handle) - { - setThreadName(m_win32Handle, m_name); - m_isRunning = true; - m_isPaused = false; - } - else - { - TFE_System::logWrite(LOG_ERROR, "Thread", "Thread \"%s\" cannot be run", m_name); - } - - return m_win32Handle!=0; -} - -void ThreadWin32::pause() -{ - if (!m_win32Handle) { return; } - SuspendThread(m_win32Handle); - m_isPaused = true; -} - -void ThreadWin32::resume() -{ - if (!m_win32Handle) { return; } - ResumeThread(m_win32Handle); - m_isPaused = false; -} - -void ThreadWin32::waitOnExit() -{ - WaitForSingleObject(m_win32Handle, INFINITE); - m_isRunning = false; -} - -//factory -Thread* Thread::create(const char* name, ThreadFunc func, void* userData) -{ - return new ThreadWin32(name, func, userData); -} - -///////////////////////////////////////////////////////////// -// Wonky Windows specific method for setting the thread name. -///////////////////////////////////////////////////////////// -#define MS_VC_EXCEPTION 0x406D1388 - -#pragma pack(push,8) -typedef struct tagTHREADNAME_INFO -{ - DWORD dwType; // Must be 0x1000. - LPCSTR szName; // Pointer to name (in user addr space). - DWORD dwThreadID; // Thread ID (-1=caller thread). - DWORD dwFlags; // Reserved for future use, must be zero. - } THREADNAME_INFO; -#pragma pack(pop) - -void setThreadName(HANDLE threadHandle, const char* threadName) -{ - u32 threadID = GetThreadId( threadHandle ); - - THREADNAME_INFO info; - info.dwType = 0x1000; - info.szName = threadName; - info.dwThreadID = threadID; - info.dwFlags = 0; - - //Wonky Windows stuff... (see https://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx) -#pragma warning(push) -#pragma warning(disable: 6320 6322) - __try - { - RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info); - } - __except (EXCEPTION_EXECUTE_HANDLER) - { - } -#pragma warning(pop) -} diff --git a/TheForceEngine/TFE_System/Threads/Win32/threadWin32.h b/TheForceEngine/TFE_System/Threads/Win32/threadWin32.h deleted file mode 100644 index 684abdc68..000000000 --- a/TheForceEngine/TFE_System/Threads/Win32/threadWin32.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once -#include -#include "../thread.h" - -class ThreadWin32 : public Thread -{ -public: - ThreadWin32(const char* name, ThreadFunc func, void* userData); - virtual ~ThreadWin32(); - - virtual bool run(); - virtual void pause(); - virtual void resume(); - virtual void waitOnExit(); - -protected: - HANDLE m_win32Handle; -}; diff --git a/TheForceEngine/TFE_System/Threads/mutex.h b/TheForceEngine/TFE_System/Threads/mutex.h deleted file mode 100644 index 92f53b164..000000000 --- a/TheForceEngine/TFE_System/Threads/mutex.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once -#include - -class Mutex -{ -public: - virtual ~Mutex() {}; - - virtual s32 lock() = 0; - virtual s32 unlock() = 0; - -protected: - Mutex() {}; - -public: - //static factory function - creates the correct platform dependent version - static Mutex* create(); -}; diff --git a/TheForceEngine/TFE_System/Threads/signal.h b/TheForceEngine/TFE_System/Threads/signal.h deleted file mode 100644 index 72ba8c48b..000000000 --- a/TheForceEngine/TFE_System/Threads/signal.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once -#include -#include - -#define TIMEOUT_INFINITE 0xFFFFFFFF - -class Signal -{ -public: - ~Signal() {}; - - virtual void fire() = 0; - //returns true if signaled, false if the timeout was hit instead. - virtual bool wait(u32 timeOutInMS=TIMEOUT_INFINITE, bool reset=true) = 0; - -public: - //static factory function - creates the correct platform dependent version - static Signal* create(); - -protected: -}; diff --git a/TheForceEngine/TFE_System/Threads/thread.h b/TheForceEngine/TFE_System/Threads/thread.h deleted file mode 100644 index 30d8d0c1e..000000000 --- a/TheForceEngine/TFE_System/Threads/thread.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once -#include -#include - -#ifdef _WIN32 - #define TFE_THREADRET u32 -#else - #define TFE_THREADRET void* -#endif - -typedef TFE_THREADRET (TFE_STDCALL* ThreadFunc)(void*); - -class Thread -{ -public: - virtual ~Thread() {}; - - virtual bool run() = 0; - virtual void pause() = 0; - virtual void resume() = 0; - virtual void waitOnExit() = 0; - - bool isRunning() { return m_isRunning; } - bool isPaused() { return m_isPaused; } - -protected: - Thread(const char* name, ThreadFunc func, void* userData) - { - m_func = func; - m_userData = userData; - strcpy(m_name, name); - m_isRunning = false; - m_isPaused = false; - } - -public: - //static factory function - creates the correct platform dependent version - static Thread* create(const char* name, ThreadFunc func, void* userData); - -protected: - ThreadFunc m_func; - void* m_userData; - bool m_isRunning; - bool m_isPaused; - char m_name[256]; -}; From 78cc33897099c29666df16d299c64e717082a34c Mon Sep 17 00:00:00 2001 From: Kevin Foley Date: Wed, 27 Sep 2023 13:48:06 -0700 Subject: [PATCH 2/4] A11y: Don't init captions if they are disabled; improved settings UI (#336) * accessibility.cpp: replace `delete` with `free()` to address compiler warning * accessibility.cpp: For cutscene captions, restore previous behavior of allowing more lines of text at a time when using a smaller font size. Minor code cleanup and comments. * A11y: when TFE launches, don't init captions if they are disabled. * A11y: Tweaks to A11y settings UI layout; in particular, fixed issue with small window sizes where captions would overlap the bottom of the window and cover some settings. * frontEndUi.cpp: fix typo 'Sickess' -> 'Sickness' --- TheForceEngine/TFE_A11y/accessibility.cpp | 61 ++++++++----- TheForceEngine/TFE_A11y/accessibility.h | 12 ++- TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp | 96 +++++++++++++------- TheForceEngine/TFE_Settings/settings.h | 5 + 4 files changed, 113 insertions(+), 61 deletions(-) diff --git a/TheForceEngine/TFE_A11y/accessibility.cpp b/TheForceEngine/TFE_A11y/accessibility.cpp index e623a37d3..02b34347a 100644 --- a/TheForceEngine/TFE_A11y/accessibility.cpp +++ b/TheForceEngine/TFE_A11y/accessibility.cpp @@ -33,7 +33,7 @@ namespace TFE_A11Y // a11y is industry slang for accessibility bool filterCaptionFile(const string fileName); void onFileError(const string path); void enqueueCaption(const ConsoleArgList& args); - void drawCaptions(std::vector* captions); + Vec2f drawCaptions(std::vector* captions); string toUpper(string input); string toLower(string input); string toFileName(string language); @@ -47,7 +47,6 @@ namespace TFE_A11Y // a11y is industry slang for accessibility /////////////////////////////////////////// const char* DEFAULT_FONT = "Fonts/NotoSans-Regular.ttf"; const f32 MAX_CAPTION_WIDTH = 1200; - const f32 DEFAULT_LINE_HEIGHT = 20; const f32 LINE_PADDING = 5; // Base duration of a caption const s64 BASE_DURATION_MICROSECONDS = secondsToMicroseconds(0.9f); @@ -65,7 +64,7 @@ namespace TFE_A11Y // a11y is industry slang for accessibility /////////////////////////////////////////// // Static vars /////////////////////////////////////////// - static A11yStatus s_status = CC_NOT_LOADED; + static A11yStatus s_captionsStatus = CC_NOT_LOADED; static FilePathList s_captionFileList; static FilePathList s_fontFileList; static FilePath s_currentCaptionFile; @@ -74,7 +73,7 @@ namespace TFE_A11Y // a11y is industry slang for accessibility static DisplayInfo s_display; static u32 s_screenWidth; static u32 s_screenHeight; - static bool s_active = true; + static bool s_active = true; // Used by ImGui static bool s_logSFXNames = false; static system_clock::duration s_lastTime; @@ -88,10 +87,19 @@ namespace TFE_A11Y // a11y is industry slang for accessibility static ImFont* s_currentCaptionFont; static string s_pendingFontPath; - // Initialize the Accessibility system. Only call this once on application launch. void init() { - assert(s_status == CC_NOT_LOADED); + if (TFE_Settings::getA11ySettings()->captionSystemEnabled()) + { + initCaptions(); + } + } + + void initCaptions() + { + TFE_System::logWrite(LOG_MSG, "a11y", "Initializing caption system..."); + + assert(s_captionsStatus == CC_NOT_LOADED); CCMD("showCaption", enqueueCaption, 1, "Display a test caption. Example: showCaption \"Hello, world\""); CVAR_BOOL(s_logSFXNames, "d_logSFXNames", CVFLAG_DO_NOT_SERIALIZE, "If enabled, log the name of each sound effect that plays."); @@ -146,7 +154,9 @@ namespace TFE_A11Y // a11y is industry slang for accessibility // Try to load the previously selected font. string lastFontPath = TFE_Settings::getA11ySettings()->lastFontPath; - tryLoadFont(lastFontPath, false); + + if (lastFontPath, ImGui::GetIO().Fonts->Locked) { setPendingFont(lastFontPath); } + else { tryLoadFont(lastFontPath, false); } } // Specify a font to load after ImGui finishes rendering. @@ -226,14 +236,14 @@ namespace TFE_A11Y // a11y is industry slang for accessibility // Captions ////////////////////////////////////////////////////// + A11yStatus getCaptionSystemStatus() { return s_captionsStatus; } + // Get the list of all caption files we detect in the Captions directories. FilePathList getCaptionFiles() { return s_captionFileList; } // The name and path of the currently selected Caption file FilePath getCurrentCaptionFile() { return s_currentCaptionFile; } - A11yStatus getStatus() { return s_status; } - // Get all caption file names from the Captions directories; we will use this to populate the // dropdown in the Accessibility settings menu. void findCaptionFiles() @@ -267,7 +277,7 @@ namespace TFE_A11Y // a11y is industry slang for accessibility } // If the language didn't load, default to English. - if (s_status != CC_LOADED) + if (s_captionsStatus != CC_LOADED) { string fileName = programCaptionsDir + toFileName("en"); loadCaptions(fileName); @@ -353,15 +363,15 @@ namespace TFE_A11Y // a11y is industry slang for accessibility s_captionMap[name] = caption; }; - s_status = CC_LOADED; - delete s_captionsBuffer; + s_captionsStatus = CC_LOADED; + free(s_captionsBuffer); } void onFileError(const string path) { string error = "Couldn't find caption file at " + path; TFE_System::logWrite(LOG_ERROR, "a11y", error.c_str()); - s_status = CC_ERROR; + s_captionsStatus = CC_ERROR; // TODO: display an error dialog } @@ -403,11 +413,11 @@ namespace TFE_A11Y // a11y is industry slang for accessibility f32 fontScale; auto windowSize = calcWindowSize(&fontScale, env); assert(fontScale > 0); - s32 count = 1; - size_t idx = 0; + + size_t idx = 0; // Index of current character in string. + string line = ""; // Current line of the current chunk. string chunk; s32 chunkLineCount = 0; - string line = ""; while (idx < caption.text.length()) { // Extend the line one character at a time until we exceed the width of the panel @@ -431,8 +441,8 @@ namespace TFE_A11Y // a11y is industry slang for accessibility } else { break; } - // If the chunk has three lines, add it as a new caption. - if (chunkLineCount >= 3) + // If the chunk has reached the maximum number of lines, add it as a new caption. + if (chunkLineCount >= maxLines) { s32 length = (s32)chunk.length(); f32 ratio = length / (f32)caption.text.length(); @@ -450,7 +460,6 @@ namespace TFE_A11Y // a11y is industry slang for accessibility else { break; } caption.microsecondsRemaining -= next.microsecondsRemaining; - count++; idx = 0; chunkLineCount = 0; chunk = ""; @@ -491,15 +500,15 @@ namespace TFE_A11Y // a11y is industry slang for accessibility //TFE_System::logWrite(LOG_ERROR, "a11y", std::to_string(active.size()).c_str()); } - void drawCaptions() + Vec2f drawCaptions() { if (s_activeCaptions.size() > 0) { - drawCaptions(&s_activeCaptions); + return drawCaptions(&s_activeCaptions); } } - void drawCaptions(std::vector* captions) + Vec2f drawCaptions(std::vector* captions) { if (isFontLoaded()) { ImGui::PushFont(s_currentCaptionFont); } @@ -604,15 +613,19 @@ namespace TFE_A11Y // a11y is industry slang for accessibility } } } + + ImVec2 finalWindowSize = ImGui::GetWindowSize(); ImGui::PopStyleColor(); ImGui::End(); if (isFontLoaded()) { ImGui::PopFont(); } s_lastTime = time; + + return { finalWindowSize.x, finalWindowSize.y }; } - void drawExampleCaptions() + Vec2f drawExampleCaptions() { if (s_exampleCaptions.size() == 0) { @@ -620,7 +633,7 @@ namespace TFE_A11Y // a11y is industry slang for accessibility caption.env = CC_CUTSCENE; s_exampleCaptions.push_back(caption); } - drawCaptions(&s_exampleCaptions); + return drawCaptions(&s_exampleCaptions); } bool cutsceneCaptionsEnabled() diff --git a/TheForceEngine/TFE_A11y/accessibility.h b/TheForceEngine/TFE_A11y/accessibility.h index 1ac56ef38..033697a2a 100644 --- a/TheForceEngine/TFE_A11y/accessibility.h +++ b/TheForceEngine/TFE_A11y/accessibility.h @@ -37,12 +37,18 @@ namespace TFE_A11Y { /////////////////////////////////////////// const string FILE_NAME_START = "subtitles-"; const string FILE_NAME_EXT = ".txt"; + const f32 DEFAULT_LINE_HEIGHT = 20; /////////////////////////////////////////// // Functions /////////////////////////////////////////// + + // Initialize the A11y system. void init(); + // Initialize the caption system. Only call this if the current status is CC_NOT_LOADED. + void initCaptions(); + // Fonts FilePathList getFontFiles(); FilePath getCurrentFontFile(); @@ -53,11 +59,11 @@ namespace TFE_A11Y { // Captions FilePathList getCaptionFiles(); FilePath getCurrentCaptionFile(); - A11yStatus getStatus(); + A11yStatus getCaptionSystemStatus(); void loadCaptions(const string path); void clearActiveCaptions(); - void drawCaptions(); - void drawExampleCaptions(); + Vec2f drawCaptions(); + Vec2f drawExampleCaptions(); void focusCaptions(); void enqueueCaption(Caption caption); void onSoundPlay(char* name, CaptionEnv env); diff --git a/TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp b/TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp index 3453034c9..f92c24e78 100644 --- a/TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp +++ b/TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp @@ -249,7 +249,7 @@ namespace TFE_FrontEndUI void configHud(); void configSound(); void configSystem(); - void configA11y(); + void configA11y(s32 tabWidth, u32 height); void pickCurrentResolution(); void manual(); void credits(); @@ -882,7 +882,7 @@ namespace TFE_FrontEndUI } ImGui::SetNextWindowPos(ImVec2(160.0f*s_uiScale, 0.0f)); - ImGui::SetNextWindowSize(ImVec2(f32(tabWidth), f32(h))); + if (s_configTab != CONFIG_A11Y) { ImGui::SetNextWindowSize(ImVec2(f32(tabWidth), f32(h))); } ImGui::SetNextWindowBgAlpha(0.95f); ImGui::Begin("##Settings", &active, window_flags); @@ -920,7 +920,7 @@ namespace TFE_FrontEndUI configSystem(); break; case CONFIG_A11Y: - configA11y(); + configA11y(tabWidth, h); break; }; renderBackground(); @@ -2680,13 +2680,40 @@ namespace TFE_FrontEndUI } // Accessibility - void configA11y() + void configA11y(s32 tabWidth, u32 height) { - TFE_Settings_A11y* a11y = TFE_Settings::getA11ySettings(); - float labelW = 140 * s_uiScale; - float valueW = 260 * s_uiScale; + // WINDOW -------------------------------------------- + TFE_Settings_A11y* a11ySettings = TFE_Settings::getA11ySettings(); + f32 labelW = 140 * s_uiScale; + f32 valueW = 260 * s_uiScale - 10; - if (TFE_A11Y::getStatus() == TFE_A11Y::CC_ERROR) + // Draw the example caption if captions are enabled, and resize the settings window accordingly. + if (a11ySettings->captionSystemEnabled()) + { + Vec2f captionWindowSize = TFE_A11Y::drawExampleCaptions(); + + // Resize the settings window so it isn't covered by the example caption + ImVec2 windowSize; + windowSize.x = tabWidth; + windowSize.y = height - captionWindowSize.z - TFE_A11Y::DEFAULT_LINE_HEIGHT * 2; + ImGui::SetWindowSize(windowSize); + } + else // Captions not enabled + { + ImGui::SetWindowSize(ImVec2(tabWidth, height)); + } + + // CAPTIONS ------------------------------------------- + ImGui::PushFont(s_dialogFont); + ImGui::LabelText("##ConfigLabel", "Captions"); + ImGui::PopFont(); + + // Check status, and init the caption system if necessary. + if (TFE_A11Y::getCaptionSystemStatus() == TFE_A11Y::CC_NOT_LOADED) + { + TFE_A11Y::initCaptions(); + } + if (TFE_A11Y::getCaptionSystemStatus() == TFE_A11Y::CC_ERROR) { ImGui::LabelText("##ConfigLabel", "Error: Caption file could not be loaded!"); } @@ -2695,7 +2722,7 @@ namespace TFE_FrontEndUI ImGui::LabelText("##ConfigLabel", "Subtitle/caption file:"); TFE_A11Y::FilePath currentCaptionFile = TFE_A11Y::getCurrentCaptionFile(); string currentCaptionFilePath = DrawFileListCombo("##ccfile", currentCaptionFile.name, currentCaptionFile.path, TFE_A11Y::getCaptionFiles()); - // If user changed the selected caption file, reload captions + // If user changed the selected caption file, reload captions. if (currentCaptionFilePath != currentCaptionFile.path) { TFE_A11Y::clearActiveCaptions(); @@ -2714,48 +2741,49 @@ namespace TFE_FrontEndUI } // CUTSCENES ----------------------------------------- - ImGui::PushFont(s_dialogFont); + ImGui::Dummy(ImVec2(0.0f, 10.0f)); + ImGui::PushFont(s_versionFont); ImGui::LabelText("##ConfigLabel", "Cutscenes"); ImGui::PopFont(); - ImGui::Checkbox("Subtitles (voice)##Cutscenes", &a11y->showCutsceneSubtitles); + ImGui::Checkbox("Subtitles (voice)##Cutscenes", &a11ySettings->showCutsceneSubtitles); ImGui::SameLine(0, 22); - ImGui::Checkbox("Captions (SFX)##Cutscenes", &a11y->showCutsceneCaptions); + ImGui::Checkbox("Captions (SFX)##Cutscenes", &a11ySettings->showCutsceneCaptions); - DrawFontSizeCombo(labelW, valueW, "Font Size##Cutscenes", "##CFS", (s32*)&a11y->cutsceneFontSize); - DrawRGBFields(labelW, valueW, "Font Color##Cutscenes", &a11y->cutsceneFontColor); - DrawLabelledFloatSlider(labelW, valueW * 0.5f - 2, "Background Opacity", "##CBO", &a11y->cutsceneTextBackgroundAlpha, 0.0f, 1.0f); + DrawFontSizeCombo(labelW, valueW, "Font Size##Cutscenes", "##CFS", (s32*)&a11ySettings->cutsceneFontSize); + DrawRGBFields(labelW, valueW, "Font Color##Cutscenes", &a11ySettings->cutsceneFontColor); + DrawLabelledFloatSlider(labelW, valueW * 0.5f - 2, "Background Opacity", "##CBO", &a11ySettings->cutsceneTextBackgroundAlpha, 0.0f, 1.0f); ImGui::SameLine(0, 40); - ImGui::Checkbox("Border##Cutscenes", &a11y->showCutsceneTextBorder); - DrawLabelledFloatSlider(labelW, valueW, "Text speed", "##CTS", &a11y->cutsceneTextSpeed, 0.5f, 2.0f); - - ImGui::Separator(); + ImGui::Checkbox("Border##Cutscenes", &a11ySettings->showCutsceneTextBorder); + DrawLabelledFloatSlider(labelW, valueW, "Text speed", "##CTS", &a11ySettings->cutsceneTextSpeed, 0.5f, 2.0f); - // GAMEPLAY------------------------------------------ - ImGui::PushFont(s_dialogFont); + // GAMEPLAY ------------------------------------------ + ImGui::Dummy(ImVec2(0.0f, 10.0f)); + ImGui::PushFont(s_versionFont); ImGui::LabelText("##ConfigLabel3", "Gameplay"); ImGui::PopFont(); - ImGui::Checkbox("Subtitles (voice)##Gameplay", &a11y->showGameplaySubtitles); + ImGui::Checkbox("Subtitles (voice)##Gameplay", &a11ySettings->showGameplaySubtitles); ImGui::SameLine(0, 22); - ImGui::Checkbox("Captions (SFX)##Gameplay", &a11y->showGameplayCaptions); + ImGui::Checkbox("Captions (SFX)##Gameplay", &a11ySettings->showGameplayCaptions); - DrawFontSizeCombo(labelW, valueW, "Font Size##Gameplay", "##GFS", (s32*)&a11y->gameplayFontSize); - DrawRGBFields(labelW, valueW, "Font Color##Gameplay", &a11y->gameplayFontColor); - DrawLabelledFloatSlider(labelW, valueW * 0.5f - 2, "Background Opacity", "##GBO", &a11y->gameplayTextBackgroundAlpha, 0.0f, 1.0f); + DrawFontSizeCombo(labelW, valueW, "Font Size##Gameplay", "##GFS", (s32*)&a11ySettings->gameplayFontSize); + DrawRGBFields(labelW, valueW, "Font Color##Gameplay", &a11ySettings->gameplayFontColor); + DrawLabelledFloatSlider(labelW, valueW * 0.5f - 2, "Background Opacity", "##GBO", &a11ySettings->gameplayTextBackgroundAlpha, 0.0f, 1.0f); ImGui::SameLine(0, 40); - ImGui::Checkbox("Border##Gameplay", &a11y->showGameplayTextBorder); - DrawLabelledFloatSlider(labelW, valueW, "Text speed", "##GTS", &a11y->gameplayTextSpeed, 0.5f, 2.0f); + ImGui::Checkbox("Border##Gameplay", &a11ySettings->showGameplayTextBorder); + DrawLabelledFloatSlider(labelW, valueW, "Text speed", "##GTS", &a11ySettings->gameplayTextSpeed, 0.5f, 2.0f); - DrawLabelledIntSlider(labelW, valueW, "Max Lines", "##CML", &a11y->gameplayMaxTextLines, 2, 7); - DrawLabelledIntSlider(labelW, valueW, "Min. Volume", "##CMV", &a11y->gameplayCaptionMinVolume, 0, 127); + DrawLabelledIntSlider(labelW, valueW, "Max Lines", "##CML", &a11ySettings->gameplayMaxTextLines, 2, 7); + DrawLabelledIntSlider(labelW, valueW, "Min. Volume", "##CMV", &a11ySettings->gameplayCaptionMinVolume, 0, 127); + // MOTION SICKNESS ------------------------------------- + ImGui::Dummy(ImVec2(0.0f, 10.0f)); + ImGui::Separator(); ImGui::PushFont(s_dialogFont); - ImGui::LabelText("##ConfigLabel4", "Motion Sickess"); + ImGui::LabelText("##ConfigLabel4", "Motion Sickness"); ImGui::PopFont(); - ImGui::Checkbox("Enable headwave", &a11y->enableHeadwave); - - TFE_A11Y::drawExampleCaptions(); + ImGui::Checkbox("Enable headwave", &a11ySettings->enableHeadwave); } void pickCurrentResolution() diff --git a/TheForceEngine/TFE_Settings/settings.h b/TheForceEngine/TFE_Settings/settings.h index fe0ba9fed..e8033fe07 100644 --- a/TheForceEngine/TFE_Settings/settings.h +++ b/TheForceEngine/TFE_Settings/settings.h @@ -278,6 +278,11 @@ struct TFE_Settings_A11y f32 gameplayTextSpeed = 1.0f; s32 gameplayCaptionMinVolume = 32; // In range 0 - 127 + bool captionSystemEnabled() + { + return showCutsceneSubtitles || showCutsceneCaptions || showGameplaySubtitles || showGameplayCaptions; + } + // Motion sickness settings bool enableHeadwave = true; }; From 9fe5652e556c96471197ba864f0f9077c6456185 Mon Sep 17 00:00:00 2001 From: Kevin Foley Date: Wed, 27 Sep 2023 13:48:40 -0700 Subject: [PATCH 3/4] frontEndUi.cpp: On Game Settings screen, fixed green text getting cut off at smaller window sizes (#338) --- TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp b/TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp index f92c24e78..373ee0dbf 100644 --- a/TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp +++ b/TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp @@ -1019,7 +1019,7 @@ namespace TFE_FrontEndUI ImGui::PopFont(); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.25f, 1.0f, 0.25f, 1.0f)); - ImGui::LabelText("##ConfigLabel", "The path should contain the source game exe or gob/lab files."); + ImGui::TextWrapped("The path should contain the source game exe or gob/lab files."); ImGui::PopStyleColor(); ImGui::Spacing(); From 0cd3b48410e48681e6ac2e08f991305e0a6b4780 Mon Sep 17 00:00:00 2001 From: Manuel Lauss <123231678+mlauss2@users.noreply.github.com> Date: Wed, 27 Sep 2023 22:49:15 +0200 Subject: [PATCH 4/4] Fix tiny mistakes in german subs (#339) "MIENE" (countenance) to "MINE" (mine/pit), this irritated me a lot in Dark Tide 2. I also changed "Unterbruch" since this is (I think) a Swiss German expression, not commonly heard in Germany or Austria. --- TheForceEngine/Captions/subtitles-de.txt | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/TheForceEngine/Captions/subtitles-de.txt b/TheForceEngine/Captions/subtitles-de.txt index 24d8e9d43..b6e678ad9 100644 --- a/TheForceEngine/Captions/subtitles-de.txt +++ b/TheForceEngine/Captions/subtitles-de.txt @@ -20,9 +20,9 @@ m16moc01.voc "Offizier Mohc: Es ist lange her, seit ich einen Mann zum Kampf her //SFX boba-1.voc "[Fett lacht]" boba-3.voc "[Fett grunzt]" -boba-4.voc "[Fett Todesschrei]" +boba-4.voc "[Fetts Todesschrei]" beep-01.voc "[Alarm]" -beep-10.voc "[MIENE PIPST]" +beep-10.voc "[MINE PIEPST]" fall.voc "[Kyle schreit]" door.voc "[Türe zischt]" door-04.voc "[Türe bewegt sich]" @@ -75,11 +75,11 @@ m05jan01.voc "Jan: Alles klar Kyle, bin unterwegs." m05kyl03.voc "Kyle: Wenn das Ding einen Hinweis darauf ist, womit wir es zu tun bekommen, dann brauchen wir mehr Feuerkraft." //M06 (Detention Center) m06kyl01.voc "Kyle: Okay Jan, ich habe Madine befreit." -m06jan01.voc "Jan: Beil dich, wir müssen zusehen das wir von hier verschwinden, bevor noch mehr Sturmtruppen eintreffen " -//M07 (Ramsees Head) +m06jan01.voc "Jan: Beeil dich, wir müssen zusehen dass wir von hier verschwinden, bevor noch mehr Sturmtruppen eintreffen " +//M07 (Ramsees Hed) m07kyl01.voc "Kyle: Der Signalgeber ist platziert." m07jan01.voc "Jan: Ich empfange das Signal. Sieht so aus, als wären wir hier fertig." -m07jan02.voc "Jan: OK dann lass uns mal sehen, wohin diese Schmuggler hinfliegen." +m07jan02.voc "Jan: OK dann lass uns mal sehen, wo diese Schmuggler hinfliegen." //M08 (Robotics Facility) m08kyl01.voc "Kyle: Erste Ladung scharf gemacht." m08kyl02.voc "Kyle: Zweite Ladung scharf gemacht." @@ -119,8 +119,8 @@ m13kyl01.voc "Kyle: Es geht los!" m16kyl01.voc "Kyle: Das war die Erste..." m16kyl02.voc "Kyle: Das ist die Zweite..." m16kyl03.voc "Kyle: Jetzt noch die Letzte." -m16kyl04.voc "Kyle: Jan würde stolz auf mich sein." -m16kyl05.voc "Kyle: Es gibt keine Ehre im Krieg Mhoc." +m16kyl04.voc "Kyle: Jan wäre stolz auf mich." +m16kyl05.voc "Kyle: Es gibt keine Ehre im Krieg, Mohc." m16kyl06.voc "Kyle: Für die Freiheit." //----- CUTSCENES ----------------------------------------------- @@ -149,10 +149,10 @@ m01mma02 "Vor fünf Tagen hat das Imperium einen unserer geheimen Stützpunkte a m01mma03 "Tak Base wurde innerhalb von Minuten zerstört. Viele unschuldige Stadtbewohner hat es genauso getroffen wie unsere eigenen Leute. Der Geheimdienst vermutet, dass es sich dabei um eine Art Vergeltung für die Vernichtung des Todesstern handelte." 12.5 m01reb01 "Ist da draussen jemand der uns hört? Dies ist Tak Base. Bitte wir werden angegriffen! Sie sind überall! Wir wurde vollkommen überrascht!" //unused m01reb02 "Total Zerstörung! Sie brechen durch unsere Schilde... Sie brechen durch unsere Schilde!" //unused -distress "Ist da jemand draussen der uns hört? Dies ist Tak Base. Bitte, [Unterbruch] wir werden angegriffen! Sie sind überall! Wir wurden [Unterbruch] vollkommen überrascht! Total Zerstörung! Sie brechen durch unsere [Unterbruch] Schilde... Sie brechen durch unsere Schilde!" +distress "Ist da jemand draussen der uns hört? Dies ist Tak Base. Bitte, [Störung] wir werden angegriffen! Sie sind überall! Wir wurden [Störung] vollkommen überrascht! Totale Zerstörung! Sie brechen durch unsere [Störung] Schilde... Sie brechen durch unsere Schilde!" m01kyl02 "Interessant. Abgesehen von diesen Geräuschen macht es den Anschein eines normalen imperialen Angriffs." -m01mma04 "Sehr richtig Commander. Ich hoffe Sie werden verstehen was wir hier besprechen vertraulich ist." 5.0 -m01mma05 "Dieser imperiale Offizier, Crix Madine, möchte zur Allianz überlaufen. Er hat uns mit Informationen über die Entwicklung einer neuen imperialen Waffe versorgt. Wir glauben, dass diese Geräusche die Sie gerade gehört haben, von dieser Waffe stammen. Einen neuen Typ von Sturmtruppler, dem Dark Trooper." 17.3 +m01mma04 "Sehr richtig Commander. Ich hoffe Sie werden verstehen dass alles was wir hier besprechen vertraulich ist." 5.0 +m01mma05 "Dieser imperiale Offizier, Crix Madine, möchte zur Allianz überlaufen. Er hat uns mit Informationen über die Entwicklung einer neuen imperialen Waffe versorgt. Wir glauben, dass diese Geräusche die Sie gerade gehört haben, von dieser Waffe stammen. Einem neuen Typ von Sturmtruppler, dem Dark Trooper." 17.3 m01kyl03 "Ein neuer Sturmtruppler der eine Rebellenbasis derartig schnell ausschalten kann? Ich hätte besser weiter für das Imperium arbeiten sollen." m01mma06 "Die Rebellenführung nimmt diese Angelegenheit nicht auf die leichte Schulter. Sie hat veranlasst Sie zu beauftragen herauszufinden ob eine Bedrohung besteht und gegebenenfalls auszuschalten. Das heisst falls Sie noch auf unserer Seite stehen." 12.5 m01kyl04 "Klingt interessant... [lange Pause] Okay ich bin dabei. Aber ich brauche dennoch Unterstützung bei diesem Auftrag. Ich möchte das Jan Ors mein Missionsoffizier ist."