// **************************************************************************** // // Documentation // // **************************************************************************** /*! @mainpage @tableofcontents @section section_overview Overview Final Platform Layer is a Single-Header-File cross-platform C99/C++ development library, designed to abstract the underlying platform to a simple and easy-to-use API - providing low-level access to the operating system and hardware devices.

The main focus is rapid game/media/simulation development, so the default settings will create a window, set up an OpenGL rendering context, and initialize audio playback on any platform.

FPL supports the platforms Windows/Linux/Unix for the architectures x86/x64/arm.

The only dependencies are built-in operating system libraries and a C99 or C++/11 compliant compiler.

It is released under the @subpage page_license "MIT License". This license allows you to use FPL freely in any software, for private or commercial usages.
@section section_using_this_documentation How to use this documentation? Use the following links to learn more about FPL: - @subpage page_introduction - @subpage page_gettingstarted - @subpage page_categories - @subpage page_examples - @subpage page_faq - @subpage page_contribute - @subpage page_changelog - @subpage page_license I hope you find all the information you need. If not please drop an issue on the GitHub project page and it will be addressed as soon as possible! Thanks for using, have a great day! */ /*! @page page_introduction Introduction @tableofcontents @section section_intro Introduction to FPL @subsection subsection_intro_whatisfpl What is FPL? Final Platform Layer is a Single-Header-File cross-platform C99/C++ development library designed to abstract the underlying platform to a very simple and easy-to-use API - providing low level access to:
- Single Window creation and handling - Graphics Software or Hardware Rendering initialization. - Playback of raw Audio Samples - Accessing Input Devices (Keyboard, Mouse, Gamepad) - IO handling (Files, Directories, Paths) - Multithreading (Threads, Mutexes, Signals, Conditions) - String conversions UTF8 <-> WideString - Allocating Memory - Retrieve Hardware & OS Informations The main focus is game/media/simulation development, so the default settings will create a window, set up an OpenGL rendering context, and initialize audio playback on any platform.

Final Platform Layer is released under the @subpage page_license "MIT License". This license allows you to use FPL freely in any software.
@subsection subsection_intro_whatissingleheaderfilelibrary What is a single-header-file library and why is FPL based on that? A single header file library (as the name implies) is a development library designed to be one file only.
Such a file contains the header (API) and the body (Implementation) in one file but separated and controlled by compiler conditions.
Due to the nature of a single-header-file library, all the sources come with it - so it is most likely licensed by a public domain license.
Also, such libraries mostly do not require any dependencies at all, to making them more friendly to the user.
This makes it easy to use the library however you want (with source, Static-linked, As a DLL, Private use only).

So, why then is FPL based on that?

Because normal libraries typically have a lot of disadvantages: - Force you to link to it dynamically or statically - Force you to use build-systems just to compile it - Requires specific CRT versions which make linking a madness - Require or come with a ton of dependencies - Are closed source or use an incompatible license - Takes ages to compile because of its hundreds of translation units @subsection subsection_intro_supported_platforms Which platforms are supported by FPL? - Windows (x86/x64) - Linux (x86/x64/arm) - Unix/BSD (x86/x64, partially) For more details see the page: @subpage page_support_status @subsection subsection_intro_supported_compilers Which compilers are supported by FPL? FPL should compile on any C99 or C++/11 compliant compiler, such as MSVC, GCC, Clang, etc. @subsection section_intro_features What can FPL do? Read below a detailed feature overview of FPL: - Core - Compiler detection - Architecture detection - OS detection - Window - Create and handle a Single Window - Fullscreen toggling - Event handling - Clipboard string reading and writing - Input (Event-based or by Polling) - Text input (Event-based only) - Keyboard - Mouse - Game Controllers - Video: - Graphics initialization - Backends: - OpenGL 1.x (Legacy) - OpenGL 3.x or higher (Modern) - Software Backbuffer - Audio - Raw audio asyncronous playback - Device iteration and selection - Backends: - DirectSound - ALSA - Memory - Allocation and Deallocation - Custom Alignment functions - Fast Clear/Set and Copy - Atomics - Support for 8,16,32,64 bit signed and unsigned integer types - Support for void pointer type - Support for size type - Compare and Exchange (CAS) - Add / Exchange - Load / Store - Memory Barriers - Shared library - Loading of Shared Libraries (DLL on Windows, .so on Linux) - Function Pointer Lookup - IO - Path functions - Query User Home Directory - Query Executable Path - Extract Filename, Extension and Path - Combine Paths - Change File Extension - Files/Directories - Reading and Writing of Binary Files (32-bit and 64-bit) - Iterate over Files/Directories - Rename/Copy/Delete/Move operations - Hardware info retrievement - Query Processor Infos (Core count, Name) - Query Current Memory State (Physical size, Virtual Size, Page Size, etc.) - Query System Architecture - OS info retrievement - Query logged in Username - Query OS infos (Name, Version) - Timings - Get number of seconds (Low and High precision) used for profiling and delta calculations - Get number of milliseconds (Low and High precision) user for simple measurement - String conversion functions - UTF-8 <-> Widestring - Copy - Comparing - Matching - Formatting - Locales - Get keyboard/user/system locale - Console - Standard/Error out - Formatted out - Char input - Debug - DebugBreak for most compilers/platforms - DebugOut for limited compilers/platforms - Threading - Threads - Mutexes - Signals - Condition-Variables - Semaphores @subsection section_intro_getstarted Getting started with FPL - You download the latest "final_platform_layer.h" file. - Drop it into your C/C++ project and use it in any place you want. - Define **FPL_IMPLEMENTATION** in at least one translation-unit before including this header file! - Ready to go. For more details see the pages: @subpage page_gettingstarted / @subpage page_categories / @subpage page_examples. */ /*! @page page_gettingstarted Getting started @tableofcontents @section section_usage_setup Download & Setup @subsection subsection_usage_setup_download Download - You download the latest "final_platform_layer.h" file. - Drop it into your C/C++ project and use it in any place you want. @subsection subsection_usage_setup_setup Setup? - No setup required @section section_usage_requirements Requirements FPL uses built-in operating system libraries and requires a C99 or C++/11 compliant compiler.
Depending on the compiler and platform - linking to one system library may be needed: @subsection usage_requirements_win32 Win32 - Link against "kernel32.lib" @subsection usage_requirements_unix Unix/Linux - Link against "libld.so" - Compiler parameter "-pthread" (GCC Preprocessor Options) @subsection usage_requirements_crt C-Runtime optional By default, FPL uses the CRT (C-Runtime-Library) for functions such as vsnprintf(), but its usage is optional.
To learn how to use FPL without the CRT more details, see the page @subpage page_nocrt @section section_usage_howtouse How to use FPL? In one of your C/C++ translation-unit include this: @code{.c} #define FPL_IMPLEMENTATION #include "final_platform_layer.h" @endcode You can then include this file in any other C/C++ source or header file as you would with any other header file. Provide the typical main entry point with at least the initialization and release of the platform: @code{.c} int main(int argc, char **argv) { // Initialize the platform if (fplPlatformInit(fplInitFlags_All, fpl_null)) { // your code goes here // Release the platform fplPlatformRelease(); return 0; } else { return -1; } } @endcode @section section_usage_multiple_translation_units How to use FPL in multiple translation units? To use FPL in multiple translation units, it is recommended to create a separated "final_platform_layer.c" file in the same directory FPL is located and define the implementation there only:
final_platform_layer.c: @code{.c} #define FPL_IMPLEMENTATION #define FPL_NO_ENTRYPOINT // Disable the entry point inclusion #include "final_platform_layer.h" @endcode This way FPL implementation is compiled once and can be used everywhere.

In your main translation-unit, you just include the entry point only using the preprocessor define **FPL_ENTRYPOINT**. main_translation_unit.c: @code{.c} #define FPL_ENTRYPOINT // Integrate entrypoint only #include "final_platform_layer.h" @endcode @section section_usage_static_library How to use FPL in a static library? To use FPL as a/in static library you do the same steps required for using FPL in multiple translation units. See @ref section_usage_multiple_translation_units for more details. @section section_usage_codecomments How do I get the code-documentation to show up in the IDE? When FPL is compiled directly in your main translation-unit, some IDE's read the comments from the implementation bodies instead of from the header definitions.
This is wrong, but we can't help it. Those editors just are not designed for single-header-file libraries :-( But do not fear, you can get the comments to show up in your IDE properly, just compile the implementation into a separated translation-unit only.
This way the IDE will parse the comments from the API declaration only. See @ref section_usage_multiple_translation_units for more details. @section section_usage_options Options See @subpage page_compiler_options for all compile-time options. */ /*! @page page_categories Categories @tableofcontents @section section_category_general General @subpage page_category_initialization
@subpage page_category_errorhandling
@subpage page_category_logging
@section section_category_window Window @subpage page_category_window_basics
@subpage page_category_window_events
@subpage page_category_window_style
@section section_category_input Input @subpage page_category_input_events
@subpage page_category_input_polling
@subpage page_category_input_config
@section section_category_memory Memory @subpage page_category_memory_handling
@section section_category_platform Platform @subpage page_category_platform
@subpage page_category_hardware
@section section_category_dll Dynamic Library Loading @subpage page_category_dll
@section section_category_io File IO @subpage page_category_io_binaryfiles
@subpage page_category_io_paths
@section section_category_threading Multithreading @subpage page_category_threading_threads
@subpage page_category_threading_mutexes
@subpage page_category_threading_signals
@subpage page_category_threading_conditions
@subpage page_category_threading_semaphores
@subpage page_category_threading_atomics
@subpage page_category_threading_sync
@section section_category_video Video @subpage page_category_video_general
@subpage page_category_video_legacy_opengl
@subpage page_category_video_modern_opengl
@subpage page_category_video_vulkan
@subpage page_category_video_software
@section section_category_audio Audio @subpage page_category_audio_general
@subpage page_category_audio_writesamples
*/ /*! @page page_category_initialization Initialization & Release @tableofcontents @section section_category_initialization_include Include the Library In one of your C/C++ translation units include this: @code{.c} #define FPL_IMPLEMENTATION #include "final_platform_layer.h" @endcode You can then include this file in any other C/C++ source or header file as you would with any other header file. @section section_category_initialization_entrypoint Entry Point Simply provide the typical main entry point:
@code{.c} int main(int argc, char **argv) { // code goes here } @endcode @section section_category_initialization_simple Initialization with default settings Call @ref fplPlatformInit() (inside the main @ref section_category_initialization_entrypoint) and provide the desired @ref fplInitFlags and @ref fpl_null as arguments, to initialize FPL with default settings. @code{.c} int main(int argc, char **argv) { // With defaults (Window, Video, Audio) fplPlatformInit(fplInitFlags_All, fpl_null); // Only audio fplPlatformInit(fplInitFlags_Audio, fpl_null); // Only window and audio fplPlatformInit(fplInitFlags_Window | fplInitFlags_Audio, fpl_null); return 0; } @endcode It is recommended to always pass a pointer to a @ref fplSettings structure, but you can leave it as @ref fpl_null as well.
Call @ref fplSetDefaultSettings() with a pointer to your local settings container to initialize the settings container with default values. @section section_category_initialization_with_settings Initialization with custom settings Call @ref fplPlatformInit() (inside the main @ref section_category_initialization_entrypoint) and provide the desired @ref fplInitFlags and a pointer to @ref fplSettings argument to initialize FPL with custom settings. @code{.c} // Overwrite settings fplSettings settings; fplSetDefaultSettings(&settings); // or fplSettings settings = fplMakeDefaultSettings(); // change the settings here fplPlatformInit(fplInitFlags_All, &settings); @endcode @section section_category_initialization_release Releasing the Platform Call @ref fplPlatformRelease() when you are done. This will release all internal resources.
@code{.c} fplPlatformRelease(); @endcode @section section_category_initialization_result Result/Error checking There is no guarantee that @ref fplPlatformInit() will always work with the fplSettings you specified, maybe the audio device does not support a sample rate of 1337 kHz or your video card does not support OpenGL version 3.7 - who knows.

Therefore, you should always check the result using @ref fplGetPlatformResult() !
This returns a @ref fplPlatformResultType enum which is @ref fplPlatformResultType_Success when initialization succeeded.

Also, you should release the platform when the initialization was successful only!
If something goes wrong the remaining resources are already cleaned up by FPL automatically.

Also, you should use @ref fplGetLastError() to print out the actual error when the initialization fails!
Very bad: (But will work) @code{.c} fplPlatformInit(fplInitFlags_All, fpl_null); // your code here fplPlatformRelease(); @endcode Bad: (But will work) @code{.c} if (fplPlatformInit(fplInitFlags_All, fpl_null)) { // your code here } fplPlatformRelease(); @endcode Good: @code{.c} if (fplPlatformInit(fplInitFlags_All, fpl_null)) { // your code here fplPlatformRelease(); } @endcode Better: @code{.c} if (fplPlatformInit(fplInitFlags_All, fpl_null)) { // your code here fplPlatformRelease(); } else { const char *errStr = fplGetLastError(); fplConsoleFormatError("FPL-ERROR: %s\n", errStr); } @endcode Much better: @code{.c} if (fplPlatformInit(fplInitFlags_All, fpl_null)) { // your code here fplPlatformRelease(); } else { fplPlatformResultType initResult = fplGetPlatformResult(); const char *initResultStr = fplPlatformGetResultName(initResult); const char *errStr = fplGetLastError(); fplConsoleFormatError("FPL-ERROR[%s]: %s\n", initResultStr, errStr); } @endcode See the @subpage page_category_errorhandling page for more details about error handling. @section section_category_initialization_tips Tips After releasing FPL you can call @ref fplPlatformInit() again if needed - for example: Finding the proper audio device, Testing for OpenGL compatibility, etc. may require you to call @ref fplPlatformInit() and @ref fplPlatformRelease() multiple times.
For more details see @subpage page_gettingstarted

Optionally you can use @ref fplMakeDefaultSettings to create a default filled @ref fplSettings structure, which can be directly used for @ref fplPlatformInit()

If needed you can get the currently used @ref fplSettings configuration by calling @ref fplGetCurrentSettings() */ /*! @page page_category_errorhandling Error handling @tableofcontents @section section_category_errorhandling_getplatformresult Check platform result Use @ref fplGetPlatformResult() to query the current platform result.
This is useful to check if @ref fplPlatformInit() was already called or not. Also, this is useful to see which system failed in @ref fplPlatformInit() . Example: @code{.c} fplPlatformResultType resultType = fplGetPlatformResult(); switch (resultType) { case fplPlatformResultType_NotInitialized: printf("Platform is not initialized yet!"); break; case fplPlatformResultType_FailedAudio: printf("The audio system failed to initialize: %s", fplGetLastError()); break; } @endcode @note You may combine @ref fplGetPlatformResult() with @ref fplGetLastError, to get a more understanding of what exactly went wrong. @section section_category_errorhandling_getlatest Get latest error In case something goes wrong you can always call @ref fplGetLastError() - at any time, regardless if it is initialized or not.
This either returns an empty string indicating everything is fine or a formatted string with a valid error message.
Example: @code{.c} const char *errStr = fplGetLastError(); // Do something with the error string @endcode @section section_category_errorhandling_count Was there an error? If you just want to check if there was an error, you can call @ref fplGetErrorCount() to use the number of errors as a condition.
Example: @code{.c} if (fplGetErrorCount()) { // Print out the error message } @endcode @section section_category_errorhandling_getbyindex Get error by index Use @ref fplGetErrorByIndex() to get a error string for the given index in range @ref fplGetErrorCount() . Example: @code{.c} size_t errorCount = fplGetErrorCount(); if (errorCount > 0) { const char *lastError = fplGetErrorByIndex(errorCount - 1); // Do something with the last error } @endcode @section section_category_errorhandling_clear Clearing the errors Errors will never be cleared by FPL! You have to do this yourself using @ref fplClearErrors() .
Example: @code{.c} fplClearErrors(); @endcode */ /*! @page page_category_logging Debug & Logging @tableofcontents @section section_category_logging_logging Logging FPL has an internal logging system built-in.
By default this logging system is disabled, but you can enable it by defining the preprocessor definition **FPL_LOGGING** before including the FPL header file.

You can change the maximum log-level using @ref fplSetMaxLogLevel() . By default, this is set to @ref fplLogLevel_Critical which means that only system-wide critical errors will be reported.
When the logging system is enabled, you can get or change the configuration using @ref fplGetLogSettings() / @ref fplSetLogSettings() respectively.
This configuration has two modes, a mode in which you can define one writer for every log level and one mode in which you have a writer for each log-level separately. By default, it uses the first mode, which is one writer for all log-levels.
To change this behavior, you can set the preprocessor definition **FPL_LOG_MULTIPLE_WRITERS**. With this, you can change the logging in every detail.
A log writer can be configured to log to multiple logging-targets.

FPL supports up-to 4 log-targets: - Console standard output - Console error output - Debug console output - Custom callback See @ref fplLogWriterFlags and @ref fplLogSettings for more details. @subsection subsection_category_logging_logging_example_console_only Example: Log everything to the standard console only @code{.c} fplLogSettings logSettings = fplZeroInit; logSettings.maxLevel = fplLogLevel_All; logSettings.writers[0].flags = fplLogWriterFlags_StandardConsole; fplSetLogSettings(&logSettings); @endcode @subsection subsection_category_logging_logging_example_errors_only Example: Log all errors to the error console only Log all errors, warnings, and criticals to the default error console. @code{.c} fplLogSettings logSettings = fplZeroInit; logSettings.maxLevel = fplLogLevel_Error; logSettings.writers[0].flags = fplLogWriterFlags_ErrorConsole; fplSetLogSettings(&logSettings); @endcode @subsection subsection_category_logging_logging_example_custom Example: Log all errors to a custom logging function Log all errors, warnings, and criticals to a custom callback defined as @ref fpl_log_func_callback . @code{.c} static void MyLogFunction(const fplLogLevel level, const char *message) { // ... } fplLogSettings logSettings = fplZeroInit; logSettings.maxLevel = fplLogLevel_Error; logSettings.writers[0].flags = fplLogWriterFlags_Custom; logSettings.writers[0].custom.callback = MyLogFunction; fplSetLogSettings(&logSettings); @endcode @section section_category_logging_debug Debug @subsection subsection_category_logging_debug_break Forced Breakpoint You can force to stop on a specific line of code always by calling the function @ref fplDebugBreak() .
It works exactly like a breakpoint, but it will always break until you remove the call to it. Example: @code{.c} // ... code fplDebugBreak(); // Debugger will always stop here // ... code @endcode @subsection subsection_category_logging_debug_out Output to the Debug-Console On IDE's such as visual studio you can use @ref fplDebugOut() or @ref fplDebugFormatOut() to print out stuff in the console directly in your IDE.
Example: @code{.c} // Basic output fplDebugOut("Debug-Values:\n"); // or // Formatted output fplDebugFormatOut("Value of X: %f\n", xValue); @endcode @section section_category_assertions Assertions FPL contains runtime and compile-time assertion macros, which you can use to ensure application state consistency. @subsection subsection_category_assertions_runtime Runtime assertion You can use the macro function @ref fplAssert() to trigger an runtime assertion in a debug-build.
When the condition in your assertion is false, your application will crash immediately. Example: @code{.c} // When i is less than 5 this code will trigger a assertion that will crash your application immediately. fplAssert(i >= 5); // Will trigger a assertion when the "str" pointer is null. fplAssert(str != fpl_null); @endcode @warning When you are building in release-mode runtime-time assertions are compiled out entirely - unless you have specific **FPL_FORCE_ASSERTIONS**! @warning Do not call any important functions inside an assertion statement, because it will get compiled out eventually. @subsection subsection_category_assertions_static Compile-time / static assertion You can use the macro function @ref fplStaticAssert() to trigger a compile-time assertion in a debug-build.
Example: @code{.c} typedef struct myStruct { int a; float b; } myStruct; // Will throw a compile error when "myStruct" is not of total size of 8-bytes fplStaticAssert(sizeof(myStruct) == 8); @endcode @warning When you are building in release-mode compile-time assertions are compiled out entirely - unless you have specific **FPL_FORCE_ASSERTIONS**! @warning Do not call any important functions inside an assertion statement, because it will get compiled out eventually. @subsection subsection_category_assertions_enabledisable Enable/Disable assertions In Debug-Mode assertions are enabled, unless you have specified **FPL_NO_ASSERTIONS**.
In Release-Mode assertions are disabled, unless you have specified **FPL_FORCE_ASSERTIONS**.

You can use **FPL_DEBUG** or **FPL_RELEASE** to force either a "Release" or "Debug" mode.
See @subpage page_compiler_options for more details.
@note I highly recommend to never define this in your code, but rather outside in your build-configuration. */ /*! @page page_category_window_basics Window basics @tableofcontents @section section_window_basics_init Initialization To create a window, you add the @ref fplInitFlags_Window flag to the @ref fplInitFlags argument in the @ref fplPlatformInit() call.
It makes no sense to create a window alone, so we combine it at least with something else, like for example a video context or audio playback.
@code{.c} fplPlatformInit(fplInitFlags_Window | fplInitFlags_Video, fpl_null); @endcode @section section_window_basics_mainloop Main loop After you initialize FPL with a window, you have to create some sort of a loop to keep the window open until you close them.
This is required because the operating systems use an event-based system to communicate with the window and your application.
If no communication happens with your window and your app, the window will no longer be responsive - so make sure to communicate properly.

To solve this, you have to use @ref fplWindowUpdate() and @ref fplPollEvent() respectively.
First, you need to call @ref fplWindowUpdate() for every 'tick' for your application. This will clear the internal event queue and update input devices properly.
After that, you have to poll all events from the operating systems event queue using @ref fplPollEvent() .
@code{.c} while (fplWindowUpdate()) { fplEvent ev; while (fplPollEvent(&ev)) { // ... Handle event here } } @endcode See @subpage page_category_window_events for more details.
@attention All window-based calls are required to be executed from the main-thread only! @section section_window_basics_shutdown Shutdown the window Simply call @ref fplWindowShutdown to shutdown the window, while it is still running.

You can also query the running state of the window by calling @ref fplIsWindowRunning() .
@note The window is not immediately shut-down, one tick of window/input events may still be executed after that.
To forcefully terminate the window, use @ref fplPlatformRelease() instead. */ /*! @page page_category_window_events Window events @tableofcontents @section section_category_window_events_polling Polling the window events Call @ref fplPollEvent() in a while-loop inside your actual main-loop with a pointer to a @ref fplEvent argument to poll the next event from the OS event queue.
Each event is translated into the @ref fplEvent argument which you can handle or not. If there are no events left, the function returns false. @code{.c} fplEvent currentEvent; while (fplPollEvent(¤tEvent)) { // ... Handling the event } @endcode @section section_category_window_events_handling Handling the Events Each event has a @ref fplEvent.type field which you can check on to read the actual data (Keyboard, Mouse, Window, etc.). @code{.c} fplEvent currentEvent; while (fplPollEvent(¤tEvent)) { switch (currentEvent.type) { case fplEventType_Window: { // A window event, like resize, lost/got focus, etc. switch (currentEvent.window.type) { // ... } } break; case fplEventType_Keyboard: { // A keyboard event, like key down/up, pressed, etc. switch (currentEvent.keyboard.type) { // ... } } break; case fplEventType_Mouse: { // A mouse event, like mouse button down/up, mouse move, etc. switch (currentEvent.mouse.type) { // ... } } break; case fplEventType_Gamepad: { // A gamepad event, like connected/disconnected, state-updated etc. switch (currentEvent.gamepad.type) { // ... } } break; } } @endcode All available event types are stored in the @ref fplEventType enumeration. @section section_category_window_events_handle_event_type Handle the event data All relevant event data are stored in fields that match the lowercase @ref fplEventType name.
Each event structure has another type field to check for the actual type (Key-Down, Mouse-Move, Window-Resize, etc.).
@subsection subsection_category_window_events_handle_event_buttonstate Button states All mouse/keyboard or gamepad button states are descriped as a @ref fplButtonState.

To detect a non-repeatable button press, you simply compare against @ref fplButtonState_Press.
To detect a repeatable button press, you simply compare against @ref fplButtonState_Repeat.
To detect a non button press, you simply compare against @ref fplButtonState_Release.

If you just want to know if a key is pressed or hold-down, just do a greater equals comparison of @ref fplButtonState_Press and you are golden. @subsection subsection_category_window_events_handle_event_type_mouse Mouse events Mouse event data are stored in the @ref fplMouseEvent structure.
A mouse event contains either a single button click/release or a mouse wheel change or a position change.
@code{.c} switch (currentEvent.mouse.type) { case fplMouseEventType_Button: { fplMouseButtonType button = currentEvent.mouse.button; fplButtonState state = currentEvent.mouse.buttonState; bool buttonDown = state >= fplButtonState_Press; switch (button) { case fplMouseButtonType_Left: { // Left mouse button down/released } break; case fplMouseButtonType_Middle: { // Middle mouse button down/released } break; case fplMouseButtonType_Right: { // Right mouse button down/released } break; } } break; case fplMouseEventType_Wheel: { float wheelDelta = currentEvent.mouse.wheelDelta; if (wheelDelta < 0.0f) { // Mouse-wheel down } else if (wheelDelta > 0.0f) { // Mouse-wheel up } } break; case fplMouseEventType_Move: { int mouseX = currentEvent.mouse.mouseX; int mouseY = currentEvent.mouse.mouseY; // ... do something with the mouse position } break; } @endcode @note Any mouse button event contains the position of the click as well. @subsection subsection_category_window_events_handle_event_type_keyboard Keyboard events Keyboard event data are stored in the @ref fplKeyboardEvent structure.

You can either check for the original @ref fplKeyboardEvent.keyCode or use the fplKeyboardEvent.mappedKey field - which is much easier and less error-prone.
@code{.c} switch (currentEvent.keyboard.type) { case fplKeyboardEventType_KeyButton: { fplButtonState state = currentEvent.keyboard.buttonState; // ... Handle the key code uint64_t keyCode = currentEvent.keyboard.keyCode; if (state >= fplButtonState_Pressed) { if (keyCode == 65 || keyCode == 97) { // Letter A is held down } } // or // ... handle the mapped key fplKey mappedKey = currentEvent.keyboard.mappedKey; if (state == fplButtonState_Released) { if (mappedKey == fplKey_F1) { // F1 key pressed } } } break; case fplKeyboardEventType_CharInput: { if(currentEvent.keyboard.keyCode > 0 && event.keyboard.keyCode < 0x10000) { // Handle character input } } break; } @endcode @subsection subsection_category_window_events_handle_event_type_gamepad Gamepad events Gamepad event data are stored in the @ref fplGamepadEvent structure.
@code{.c} switch (currentEvent.gamepad.type) { case fplGamepadEventType_Connected: { // New gamepad device connected } break; case fplGamepadEventType_Disconnected: { // Lost connection to a gamepad device } break; case fplGamepadEventType_StateChanged: { // State of one controller updated (Buttons, Movement, etc.) if (absf(currentEvent.gamepad.leftStickX) > 0) { // ... Handle horizontal movement on left stick } if (currentEvent.gamepad.actionX.isDown) { // ... X-Button is held down } } break; } @endcode @subsection subsection_category_window_events_handle_event_type_window Window events Window event data are stored in the @ref fplWindowEvent structure.
@code{.c} switch (currentEvent.window.type) { case fplWindowEventType_Resized: { uint32_t newWidth = currentEvent.window.width; uint32_t newHeight = currentEvent.window.height; // ... Window was resized, handle it properly } break; case fplWindowEventType_GetFocus: case fplWindowEventType_LostFocus: { // ... Do something when window lost/got focus } break; case fplWindowEventType_Exposed: { // ... Do something when window was exposed } break; case fplWindowEventType_DropSingleFile: { const char *filePath = ev.window.dropFiles.single.filePath; // ... Do something when window got a single file dropped into it } break; case fplWindowEventType_Minimized: case fplWindowEventType_Maximized: case fplWindowEventType_Restored: { // ... Do something when window state was changed } break; } @endcode @section section_category_window_events_process Ignoring the events When you don't care about any events you can simply call @ref fplPollEvents() to process the events and be done with them.
@code{.c} while (fplWindowUpdate()) { fplPollEvents(); // ... Your code } @endcode @section section_category_window_events_inotes Important Notes @note FPL does not cache the events from the previous update. If you don't handle or cache the event - the data is lost! */ /*! @page page_category_window_style Window style & layout @tableofcontents @section section_category_window_style_style Reading/Setting the style @subsection subsection_category_window_style_style_decorated Decoration (Border, Titlebar) By default, a window is decorated. This means that it has a border, a title bar, and can be moved by the user.
If you enable window decorations, your window will have a border and a title bar always.

Use @ref fplIsWindowDecorated() to determine if the window has decoration enabled or not.
Use @ref fplSetWindowDecorated() to enable/disable the window decoration.
Example: @code{.c} // Enable window decoration fplSetWindowDecorated(true); // Disable window decoration fplSetWindowDecorated(false); // Toggle window decoration fplSetWindowDecorated(!fplIsWindowDecorated()); @endcode @note You can overwrite the default decoration behavior by changing the value in @ref fplWindowSettings.isDecorated from the @ref fplSettings.window field at startup. @subsection subsection_category_window_style_floating Floating (Always on top) By default, a window is not in floating mode. This means when another window gets focus, your window loses its focus.
If you set your window to floating instead, your window will always be on top of others.

Use @ref fplIsWindowFloating() to determine if the window is floating or not.
Use @ref fplSetWindowFloating() to enable/disable the window floating.
Example: @code{.c} // Enable window floating fplSetWindowFloating(true); // Disable window floating fplSetWindowFloating(false); // Toggle window floating fplSetWindowFloating(!fplIsWindowFloating()); @endcode @note You can overwrite the default floating behavior by changing the value in @ref fplWindowSettings.isFloating from the @ref fplSettings.window field at startup. @subsection subsection_category_window_style_resizable Resizeable By default, a window is resizeable. This means you can resize your window however you like.

Use @ref fplIsWindowResizable() to determine if the window is resizable or not.
Use @ref fplSetWindowResizeable() to enable/disable the window resizing.
Example: @code{.c} // Enable resizing of the window fplSetWindowResizeable(true); // Disable resizing of the window fplSetWindowResizeable(false); // Toggle window resizing mode fplSetWindowResizeable(!fplIsWindowResizable()); @endcode @note You can overwrite the default resizable behavior by changing the value in @ref fplWindowSettings.isResizable from the @ref fplSettings.window field at startup. @section section_category_window_style_size Getting/Changing the size/position @subsection subsection_category_window_style_size_pos Window position By default, a window uses either the default position from the platform or a fixed location of 0x0 screen units.

Use @ref fplSetWindowPosition() to change the position of the window Use @ref fplGetWindowPosition() to retrieve the current absolute window position from the top-left corner Example: @code{.c} // Retrieve absolute window position fplWindowPosition curPos; if (fplGetWindowPosition(&curPos)) { // Do something with the window position } // Change window absolute position to 0x0 fplSetWindowPosition(0, 0); @endcode @subsection subsection_category_window_style_size_window Window size By default, a window is created using either the default size of the platform or a fixed size of 400x400 screen units.

Use @ref fplSetWindowSize() to change the size of the area of the window.
Use @ref fplGetWindowSize() to get the size of the area of the window.
Example: @code{.c} // Retrieve window inner/client size fplWindowSize curSize; if (fplGetWindowSize(&curSize)) { // Do something with the client size } // Change window size to fit 800x600 screen units inside the client area fplSetWindowSize(800, 600); @endcode @note You can overwrite the default window size by changing the value in @ref fplWindowSettings.windowSize from the @ref fplSettings.window field at startup. @note Changing the window size changes the total size of the window - when decorations are enabled. @subsection subsection_category_window_style_size_fullscreen Fullscreen size By default, the window is created in non-fullscreen mode, using @ref fplWindowSettings.windowSize from the @ref fplSettings.window or the default window size. See @ref subsection_category_window_style_size_window for more details.

Use @ref fplSetWindowFullscreenSize() to enable/disable fullscreen for the window with a given **size** and a **color depth** with support of switching the screen-resolution if needed.
Use @ref fplSetWindowFullscreenRect() to enable/disable fullscreen for the window with a given **size** and a **position** without changing the screen-resolution.
Use @ref fplIsWindowFullscreen() to determine if the window is in fullscreen mode or not.
Enable fullscreen example: @code{.c} // Enable fullscreen on the nearest desktop fplEnableWindowFullscreen(); // or // Enable fullscreen on the nearest desktop fplSetWindowFullscreenSize(true, 0, 0, 0); // Enable 1080p fullscreen on the nearest desktop fplSetWindowFullscreenSize(true, 1920, 1080, 0); // Switch to 1080p screen resolution with a refresh rate of 60 Hz on the nearest desktop fplSetWindowFullscreenSize(true, 1920, 1080, 60); // Enable a 4k stretched resolution on two 1080p monitor left-right configuration fplSetWindowFullscreenRect(true, 0, 0, 3840, 1080); @endcode Disable fullscreen example: @code{.c} // Disable fullscreen size fplSetWindowFullscreenSize(false, 0, 0, 0); // or // Disable fullscreen rect fplSetWindowFullscreenRect(false, 0, 0, 0, 0); // or // Disable fullscreen fplDisableWindowFullscreen(); @endcode Query fullscreen state: @code{.c} if (fplIsWindowFullscreen()) { // Do something on fullscreen mode } // Toggle fullscreen fplSetWindowFullscreenSize(!fplIsWindowFullScreen(), 0, 0, 0); @endcode @note Set @ref fplWindowSettings.isFullscreen to true to enable fullscreen at startup. @note You can overwrite the fullscreen window size by changing the value in @ref fplWindowSettings.fullscreenSize from the @ref fplSettings.window field at startup. @note You can overwrite the fullscreen refresh rate by changing the value in @ref fplWindowSettings.fullscreenRefreshRate from the @ref fplSettings.window field at startup. @note If width or height in the fplWindowSettings.fullscreenSize field is zero, the current desktop rectangle is used from the nearest display. @section section_category_window_style_cursor Changing the window cursor By default, the window has cursors enabled. Use @ref fplSetWindowCursorEnabled to show/hide the cursor.
Example: @code{.c} // Enable the cursor fplSetWindowCursorEnabled(true); // Disable the cursor fplSetWindowCursorEnabled(false); @endcode @section section_category_window_style_title Get/Set the window title By default, the window has an unnamed title. Use @ref fplSetWindowTitle to set a window title on runtime.
Example: @code{.c} // Set window title fplSetWindowTitle("My awesome application"); // Get the window title char titleBuffer[FPL_MAX_NAME_LENGTH]; fplGetWindowTitle(titleBuffer, fplArrayCount(titleBuffer)); @endcode @note You can overwrite the window title by changing the value in @ref fplWindowSettings.title from the @ref fplSettings.window field at startup. @section section_category_window_style_state Get/Set the window state By default, the window has a normal default state. - Use @ref fplSetWindowState to change to state the window state to a different one. - Use @ref fplGetWindowState to retrieve the current state of the window Example: @code{.c} // Iconify the window fplSetWindowState(fplWindowState_Iconify); // Maximize the window fplSetWindowState(fplWindowState_Maximize); // Reset to the normal when not normal fplWindowState curState = fplGetWindowState(); if (curState != fplWindowState_Normal) { fplSetWindowState(fplWindowState_Normal); } @endcode @note When the window is in fullscreen mode, @ref fplWindowState_Fullscreen is returned from @ref fplGetWindowState() always. @warning The window state @ref fplWindowState_Fullscreen can never be set. @section section_category_window_style_notes Notes @note Use @ref fplSetDefaultWindowSettings() to initialize a @ref fplWindowSettings to a default state.
*/ /*! @page page_category_input_config Input configuration @tableofcontents @section section_catecory_input_config_param_controllerdetectionfrequency Controller detection frequency Use @ref fplInputSettings.controllerDetectionFrequency to change the frequency (in milliseconds) for detecting new or removed game controllers. If this is set to zero, new or removed game controllers are detected for every frame - which is not recommended due to performance issues. @section section_catecory_input_config_param_disabledevents Disable controller handling as events Set @ref fplInputSettings.disabledEvents to '1' to disable all game controller events, if needed. @section section_category_input_config_default Initialize to default settings If needed you can use @ref fplSetDefaultInputSettings to reset the @ref fplInputSettings structure to a default state.

You dont have to use this call, if you have already initialized the @ref fplSettings structure by @ref fplMakeDefaultSettings() or @ref fplSetDefaultSettings() */ /*! @page page_category_input_events Input events @tableofcontents @section section_page_category_input_events_overview Overview Input events are triggered by your window event loop. To detect any key/button presses you simply handle the specific event type in your event-loop.
See the @subpage page_category_window_events page for more details.

If you don't want to handle input this way, you can use polling instead - see @subpage page_category_input_polling for more details. */ /*! @page page_category_input_polling Polling of input states @tableofcontents @section section_category_input_polling_overview Overview FPL supports manual polling of input states for Keyboard/Gamepad/Mouse as well.
@subsection subsection_category_input_polling_disableevents Disable Input-Events If you are only use polling to get your input states, you should disable the input-events entirely.
This is done by simply setting the @ref fplInputSettings.disabledEvents to "1" in your @ref fplSettings structure. @subsection subsection_category_input_polling_keyboard Keyboard Use @ref fplPollKeyboardState to poll the current keyboard state.
This state contains all the raw/mapped button states and the modifier states.
@code{.c} fplKeyboardState keyboardState; if (fplPollKeyboardState(&keyboardState)) { if (keyboardState.buttonStatesMapped[fplKey_Space] >= fplButtonState_Press) { // Spacebar pressed } } @endcode See @ref fplKeyboardState for more details. @subsection subsection_category_input_polling_gamepad Gamepad Use @ref fplPollGamepadStates to poll the current states for all connected game controllers.
This state contains all the buttons, digital-pad, the left/right stick position + trigger, etc.
@code{.c} fplGamepadStates gamepadStates; if (fplPollGamepadStates(&gamepadStates)) { for (int index = 0; index < fplArrayCount(gamepadStates.deviceStates); ++index) { fplGamepadState *gamepadState = gamepadStates.deviceStates + index; // ... do something with the gamepad state } } @endcode See @ref fplGamepadStates for more details. @note use @ref fplGamepadState.isConnected to check if the gamepad is connected or not. @subsection subsection_category_input_polling_mouse Mouse Use @ref fplPollMouseState to poll the current mouse state.
This state contains the state of all the buttons (Left, Right, Middle) and the position in pixels coordinates.
@code{.c} fplMouseState mouseState; if (fplPollMouseState(&mouseState)) { int mousePosX = mouseState.x; int mousePosY = mouseState.y; if (mouseState.buttonStates[fplMouseButtonType_Left] >= fplButtonState_Pressed) { // Left mouse button down } } @endcode See @ref fplMouseState for more details. @subsection subsection_category_input_polling_tips Tips @note You can always use all those polling functions, regardless of the @ref fplInputSettings.disabledEvents field! */ /*! @page page_category_video_general Initialization & Overview @tableofcontents @section category_video_general_init Initialize a video context To initialize either software or hardware video output, you have to set the @ref fplInitFlags_Video flag in the @ref fplPlatformInit() call and ensure that video is not disabled by a preprocessor directive **FPL_NO_VIDEO**.
Also setting the @ref fplInitFlags_Video flag ensures that the @ref fplInitFlags_Window flag is appended automatically.
@subsection category_video_general_init_default Default video output If you don't specify any settings then the video backend is automatically detected, depending on your operating system and supported hardware and software.
By default, this is most likely be legacy OpenGL - which is supported on almost every video card on any OS.

But this is not the recommended way to initialize video output, because you are responsible for handling any video output yourself.
@code{.c} if (fplPlatformInit(fplInitFlags_Video)) { // ... your code here } @endcode @subsection category_video_general_init_setting_driver Setting the video backend It is recommended to set at least the video backend manually, to ensure that you get either initialized with that backend properly or an error when your configuration is not supported.

You do that by simply setting the @ref fplVideoBackendType field in your @ref fplVideoSettings structure which is included in the @ref fplSettings structure to the @ref fplPlatformInit() call. @code{.c} fplSettings settings; fplSetDefaultSettings(&settings); fplVideoSettings &videoSettings = settings.video; // Forcing the video backend to be OpenGL videoSettings.backend = fplVideoBackendType_OpenGL; if (fplPlatformInit(fplInitFlags_Video, &settings)) { // ... your code here } @endcode @section category_video_general_vsync Enable/Disable Vertical Synchronisation If you want to enable/disable vertical synchronization you simply set the fplVideoSettings.isVSync field respectively.
@note There is no guarantee that vertical synchronization is supported by your video device or selected backend. @note Software video output does not support vertical synchronisation! @section category_video_general_disable Disable unneeded video backends To compile out certain video backends, you simply specify the **FPL_NO_VIDEO_[Name of the video backend]** preprocessor directive.
But the @ref fplVideoBackendType in question is never removed from the enumeration - keep that in mind!
Example (Disable OpenGL video backend): @code{.c} #define FPL_NO_VIDEO_OPENGL #define FPL_IMPLEMENTATION #include @endcode @section category_video_general_disable_all Disable all Video Output To compile out all video output code you define the **FPL_NO_VIDEO** preprocessor directive. @code{.c} #define FPL_NO_VIDEO #define FPL_IMPLEMENTATION #include @endcode @note Keep in mind that this is not useful for window-based applications! @note If you writing a console application and don't want any video output whatsoever you set the **FPL_NO_WINDOW** which automatically disables any video devices as well. @section category_video_general_notes Notes Backend types stored in the @ref fplVideoBackendType enumeration are not filtered away, even when you disable it by a preprocessor directive!
Keep that in mind when you initialize the video backend.

If needed you can use @ref fplSetDefaultVideoSettings() to fill in just the default video settings inside the @ref fplVideoSettings structure.
*/ /*! @page page_category_video_legacy_opengl Legacy OpenGL @tableofcontents @section section_category_video_legacy_opengl_init Initialize Legacy OpenGL To initialize a legacy OpenGL (up to GL version 2.1) rendering context you simply set the @ref fplInitFlags_Video flag in the @ref fplPlatformInit() call and change the video backend type to @ref fplVideoBackendType_OpenGL and set the @ref fplOpenGLSettings.compabilityFlags to @ref fplOpenGLCompabilityFlags_Legacy .

This will work in ~99% on all supported platforms - if not please post an issue for that platform/configuration/video-card ;-) @code{.c} fplSettings settings; fplSetDefaultSettings(&settings); fplVideoSettings &videoSettings = settings.video; // Forcing the video backend to be legacy OpenGL videoSettings.backend = fplVideoBackendType_OpenGL; videoSettings.opengl.compabilityFlags = fplOpenGLCompabilityFlags_Legacy; if (fplPlatformInit(fplInitFlags_Video, &settings)) { // ... your code here } @endcode @section section_category_video_legacy_opengl_usage Usage @subsection subsection_category_video_legacy_opengl_usage_extensions Extensions loader To use features of OpenGL 1.2 or later you need some sort of an OpenGL extension loader which gives you access to the constants and functions like glMultiTexCoord2f().
For more details please check the modern OpenGL @ref subsection_category_video_modern_opengl_usage_extensions section. @subsection subsection_category_video_legacy_opengl_usage_present Presenting your frame Call @ref fplVideoFlip() to present the frame to the screen.
Its recommend to call this after each draw call of your frame at the end of the main-loop. */ /*! @page page_category_video_modern_opengl Modern OpenGL @tableofcontents @section section_category_video_modern_opengl_init Initialize a modern OpenGL Rendering Context To initialize a modern OpenGL (3.0+) rendering context, you simply set the @ref fplInitFlags_Video flag in the @ref fplPlatformInit() call and change the video backend type to @ref fplVideoBackendType_OpenGL and setup the following parameters:
- Set the @ref fplOpenGLSettings.majorVersion to 3 or higher - Set the @ref fplOpenGLSettings.minorVersion to 0 or higher - Set the @ref fplOpenGLSettings.compabilityFlags to either a @ref fplOpenGLCompabilityFlags_Core or @ref fplOpenGLCompabilityFlags_Compability - Optionally add the @ref fplOpenGLCompabilityFlags_Forward flag for removing obsolete functions @code{.c} fplSettings settings; fplSetDefaultSettings(&settings); fplVideoSettings &videoSettings = settings.video; // Forcing the video backend to be modern OpenGL with Core profile and for GL version 3.3 videoSettings.backend = fplVideoBackendType_OpenGL; videoSettings.graphics.opengl.compabilityFlags = fplOpenGLCompabilityFlags_Core; videoSettings.graphics.opengl.majorVersion = 3; videoSettings.graphics.opengl.minorVersion = 3; if (fplPlatformInit(fplInitFlags_Video, &settings)) { // ... modern context is ready } @endcode @section section_category_video_modern_opengl_usage Usage @subsection subsection_category_video_modern_opengl_usage_extensions Extensions loader To use modern OpenGL, you need some sort of a OpenGL extension loader which gives you access to the constants and functions like glCreateProgram().
You can either use a third-party C/C++ Library for doing that for you or use/write your own OpenGL loader. FPL should work in both ways.

List of tested OpenGL loaders: - Final Dynamic OpenGL - Glew @subsection subsection_category_video_modern_opengl_present Presenting your frame Call @ref fplVideoFlip() to present the frame to the screen.
Its recommend to call this after each draw call of your frame at the end of the main-loop. @subsection subsection_category_video_modern_opengl_multisampling Multisample anti-aliasing (MSAA) In a modern OpenGL context, you can activate multi-sampling-antialiasing (MSAA) to get smooth edges with few performance costs out-of-the-box.
By default, multisampling is disabled. To enable it, you simply set your desired multi-sampling count in @ref fplOpenGLSettings.multiSamplingCount .
@code{.c} fplSettings settings; fplSetDefaultSettings(&settings); fplVideoSettings &videoSettings = settings.video; // Forcing the video backend to be modern OpenGL with Core profile and for GL version 3.3 videoSettings.backend = fplVideoBackendType_OpenGL; videoSettings.graphics.opengl.compabilityFlags = fplOpenGLCompabilityFlags_Core; videoSettings.graphics.opengl.majorVersion = 3; videoSettings.graphics.opengl.minorVersion = 3; // Use 4x MSAA videoSettings.opengl.multiSamplingCount = 4; if (fplPlatformInit(fplInitFlags_Video, &settings)) { // ... modern context is ready } @endcode @section section_category_video_modern_opengl_notes Notes FPL does not provide any OpenGL types, prototypes, or functions - it's fully up to the caller how to handle this.
Keep in mind that FPL does not work with platform abstraction libraries like GLFW or GLUT, but GLEW for example will work just fine. */ /*! @page page_category_video_vulkan Vulkan @tableofcontents @section section_category_video_vulkan_init Initialize Vulkan Initializing Vulkan is different than initializing other graphics APIs such as OpenGL, but FPL helps you in the first steps. The very first thing you need is a Vulkan Instance (VkInstance). Secondly you require a Vulkan Surface (VkSurfaceKHR) to present the graphical result to the screen. FPL can create the Instance and the Surface for you. The surface creation is mandatory, but an exiting instance can optionally be passed to the @ref fplSettings structure if needed. - @ref section_category_video_vulkan_init_full - @ref section_category_video_vulkan_init_surfaceonly @section section_category_video_vulkan_init_full Create a Vulkan Instance and a Surface First you have to create a @ref fplSettings structure and initialize it with default settings using @ref fplSetDefaultSettings(). Second you set the @ref fplVideoSettings.backend to @ref fplVideoBackendType_Vulkan . We can reference the @ref fplVulkanSettings type stored in @ref fplGraphicsApiSettings.vulkan for convenience usage later. @code{.c} fplSettings settings; fplSetDefaultSettings(&settings); // Reference of the video settings fplVideoSettings *videoSettings = &settings.video; // Forcing the video backend to Vulkan videoSettings->backend = fplVideoBackendType_Vulkan; // Reference of the vulkan settings for later usage fplVulkanSettings *vulkanSettings = &videoSettings.graphics.vulkan; @endcode @subsection subsection_category_video_vulkan_init_full_versions Fill in the application, engine and api versions @code{.c} fplVulkanSettings *vulkanSettings = &videoSettings.graphics.vulkan; vulkanSettings->appName = "The name of your application"; vulkanSettings->appVersion = fplStructInit(fplVersionInfo, "1.0.0", "1", "0", "0"); // Version of your application vulkanSettings->engineName = "The name of your engine"; vulkanSettings->engineVersion = fplStructInit(fplVersionInfo, "1.0.0", "1", "0", "0"); // Version of your engine vulkanSettings->apiVersion = fplStructInit(fplVersionInfo, "1.1.0", "1", "1", "0"); // Preferred Vulkan API version (should be greater or equal than 1.1.0) @endcode @subsection subsection_category_video_vulkan_init_full_validationmode Choose a Vulkan validation mode @code{.c} static void VulkanValidationLayerCallback(void *userData, const char *message, const VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, const VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT *debugUtilsMessengerCallbackData) { fplConsoleFormatOut("Vulkan Validation: %s\n", message); } fplVulkanSettings *vulkanSettings = &videoSettings.graphics.vulkan; vulkanSettings->validationLayerMode = fplVulkanValidationLayerMode_Disabled; // Do not use the Vulkan Validation Layer as an instance extension // or vulkanSettings->validationLayerMode = fplVulkanValidationLayerMode_Logging; // Enable Vulkan Validation Layer as an instance extension and log everything to FPL directly, using the FPL logging system // or vulkanSettings->validationLayerMode = fplVulkanValidationLayerMode_User; // Enable Vulkan Validation Layer as an instance extension and let the user handle the logging vulkanSettings->validationLayerCallback = VulkanValidationLayerCallback; // The user callback vulkanSettings->userData = (void *)YourUserData; // The user data passed to the callback @endcode Continue with @ref section_category_video_vulkan_usage @section section_category_video_vulkan_init_surfaceonly Create a Vulkan Surface from an existing Instance If you already have a Vulkan Instance (VkInstance), you can simply pass that to the @ref fplVulkanSettings.instanceHandle field and initialize FPL with @ref fplPlatformInit(): @code{.c} VkInstance yourVulkanInstance; fplVulkanSettings *vulkanSettings = &videoSettings.graphics.vulkan; vulkanSettings->instance = yourVulkanInstance; // Pass in your vulkan instance to FPL if (fplPlatformInit(fplInitFlags_Video, &settings)) { // ... Vulkan Surface is created now and are ready to use } @endcode Continue with @ref section_category_video_vulkan_usage @section section_category_video_vulkan_usage Vulkan Usage After you have created a Vulkan Instance and a Surface, you can query it by calling @ref fplGetVideoSurface() directly after @ref fplPlatformInit(): @code{.c} if (fplPlatformInit(fplInitFlags_All, &settings)) { // Get video surface const fplVideoSurface *surface = fplGetVideoSurface(); fplAssert(surface != fpl_null); // Get the VkInstance and VkSurfaceKHR VkInstance instanceHandle = (VkInstance)surface->vulkan.instanceHandle; VkSurfaceKHR surfaceHandle = (VkSurfaceKHR)surface->vulkan.surfaceKHR; fplAssert(instanceHandle != VK_NULL_HANDLE); fplAssert(surfaceHandle != VK_NULL_HANDLE); // ... continue the Vulkan initialization process } @endcode After that, the typical Vulkan process continues: - Finding the physical device (VkPhysicalDevice) - Creating a logical device (VkDevice) - Find the right queue families, surface formats, frame buffer formats, etc. - Creating a VkSwapChainKHR with VkImageView, etc. - Creating the full pipeline with VkCommandPool, VkCommandBuffer, etc. See the FPL_Vulkan demo for more details or learn more about Vulkan: Vulkan-Tutorials @section section_category_video_vulkan_allocator Memory Allocator If you have an existing memory allocator for Vulkan, you can pass that to FPL before calling @ref fplPlatformInit(): @code{.c} VkAllocationCallbacks *yourMemoryAllocator; fplVulkanSettings *vulkanSettings = &videoSettings.graphics.vulkan; vulkanSettings->allocator = (void *)yourMemoryAllocator; // Pass in your memory allocator @endcode */ /*! @page page_category_video_software Software Output @tableofcontents @section section_category_video_software_init Initialize a software backbuffer To initialize software rendering output, you simply set the @ref fplInitFlags_Video flag in the @ref fplPlatformInit() call and change the video backend type to @ref fplVideoBackendType_Software.
Call @ref fplGetVideoBackBuffer() to get access to the pixel data. @code{.c} fplSettings settings; fplSetDefaultSettings(&settings); fplVideoSettings &videoSettings = settings.video; // Forcing the video backend to be software videoSettings.backend = fplVideoBackendType_Software; if (fplPlatformInit(fplInitFlags_Video, &settings)) { // Video software backbuffer is ready fplVideoBackBuffer *videoBackBuffer = fplGetVideoBackBuffer(); } @endcode @section section_category_video_software_usage Usage To use the software backbuffer you simply access the @ref fplVideoBackBuffer from the @ref fplGetVideoBackBuffer() function and update the pixels as needed. @subsection subsection_category_video_software_present Presenting your frame Call @ref fplVideoFlip() to draw the pixels from the backbuffer to the window.
Its recommend to call this after each draw call of your frame at the end of the main-loop. @subsection subsection_category_video_software_drawing Drawing Drawing is done by manually changing the pixels in the @ref fplVideoBackBuffer.pixels field.
Each pixel is stored as 32-bit with 4 RGBA components in little-endian (AA BB GG RR).
Lines are stored in top-down order - meaning that position "0" in the @ref fplVideoBackBuffer.pixels field is always the top-left corner of the bitmap!
To calculate the actual position for the current line, you simply multiply your Y-Index with the @ref fplVideoBackBuffer.lineWidth field. Example (Filling all pixels to purple): @code{.c} fplVideoBackBuffer *backBuffer = fplGetVideoBackBuffer(); for (uint32_t y = 0; y < backBuffer->height; ++y) { uint32_t *p = (uint32_t *)((uint8_t *)backBuffer->pixels + y * backBuffer->lineWidth); for (uint32_t x = 0; x < backBuffer->width; ++x) { uint32_t color = 0xFFFF00FF; *p++ = color; } } @endcode @subsection subsection_category_video_software_outrect Limiting the output rectangle (Stretching vs non-stretched) To force the pixels to be shown in a fixed rectangle you simply enable the @ref fplVideoBackBuffer.useOutputRect field and update the @ref fplVideoBackBuffer.outputRect as needed.
This mimics a "viewport" which is similar to OpenGLs glViewport().

If you don't use this feature all pixels are fully stretched to the current window area always! @note This viewport should not be greater than the actual window area dimension! Example (Resize window event): @code{.c} static fplVideoRect ComputeLetterbox(int windowWidth, int windowHeight, int backWidth, int backHeight) { return {}; } fplVideoBackBuffer *backBuffer = fplGetVideoBackBuffer(); backBuffer->useOutputRect = true; while (fplWindowUpdate()) { fplEvent ev; while (fplPollEvent(&ev)) { if (ev.type == fplEventType_Window) { if (ev.window.type == fplWindowEventType_Resized) { fplVideoRect newRect = ComputeLetterbox(ev.window.width, ev.window.height, backBuffer->width, backBuffer->height); // ... Compute new rectangle here (Letterbox or something) backBuffer->outputRect = newRect; } } } // ... Modify the pixels here (Draw call) fplVideoFlip(); } @endcode Example (Always before the draw call): @code{.c} fplVideoBackBuffer *backBuffer = fplGetVideoBackBuffer(); backBuffer->useOutputRect = true; while (fplWindowUpdate()) { fplEvent ev; while (fplPollEvent(ev)) {} fplWindowSize windowArea = fplGetWindowSize(); fplVideoRect newRect = ComputeLetterbox(windowArea, backBuffer->width, backBuffer->height); // ... Compute new rectangle here (Letterbox or something) backBuffer->outputRect = newRect; // ... Modify the pixels here (Draw call) fplVideoFlip(); } @endcode @subsection subsection_category_video_software_resize Resizing the backbuffer By default, the video backbuffer is automatically resized when the dimension of the window area changes.
If you want to manually do this, you disable this feature in the @ref fplVideoSettings.isAutoSize field - in the @ref fplSettings.video configuration section.

Call @ref fplResizeVideoBackBuffer() with a new width and height as an argument, to force the backbuffer to be resized to the new dimension.
@warning Do not call this method while you are modifying pixels! @section section_category_video_software_notes Notes - There is no software rendering functions built-in! If you want to draw for example a circle, you have to roll out your drawCircle() function - which may use Bresenham as its base or something.
- Vertical synchronization is not supported for software video backends! */ /*! @page page_category_audio_general Initialization & Usage @tableofcontents @section section_category_audio_general_default_init Default initialization To initialize audio playback with default settings (Interleaved, 48 kHz, 2 Channels, signed 16-bit integer format), you have to set the @ref fplInitFlags_Audio flag in the @ref fplPlatformInit() call and ensure that audio is not disabled by a preprocessor directive **FPL_NO_AUDIO**.
@code{.c} if (fplPlatformInit(fplInitFlags_Audio, fpl_null)) { // ... your code here } @endcode @subsection subsection_category_audio_general_default_init_clientcallback Setting the client callback Next is to specify the client user callback which gets invoked regularly when the audio device requires new samples to play.
This @ref fpl_audio_client_read_callback "client callback" can be set up in the @ref fplAudioSettings.clientReadCallback field from the @ref fplSettings.audio or changed by calling @ref fplSetAudioClientReadCallback() :
@code{.c} static uint32_t MyAudioPlaybackCallback(const fplAudioDeviceFormat *nativeFormat, const uint32_t frameCount, void *outputSamples, void *userData) { // ... Fill audio frames here } fplSettings settings; fplSetDefaultSettings(&settings); fplAudioSettings &audioSettings = settings->audio; audioSettings.clientReadCallback = MyAudioPlaybackCallback; audioSettings.userData = // ... pointer to some user data if (fplPlatformInit(fplInitFlags_Audio, &settings)) { // ... your code here } @endcode @note This step must be done before you can start playing the audio! @note You can specify a user data pointer that gets passed to the client callback as well. @section section_category_audio_general_custom_init Custom initialization You can change several audio settings (Sample rate, Number of Channels, Format, etc.) before initializing the audio playback like this:
@code{.c} fplSettings settings; fplSetDefaultSettings(&settings); fplAudioSettings &audioSettings = settings.audio; audioSettings.clientReadCallback = MyAudioPlaybackCallback; audioSettings.userData = // ... pointer to some user data fplAudioDeviceFormat &audioDeviceFormat = audioSettings.deviceFormat; audioDeviceFormat.sampleRate = 48000; audioDeviceFormat.channels = 2; audioDeviceFormat.type = fplAudioFormatType_F32; if (fplPlatformInit(fplInitFlags_Audio, &settings)) { // ... your code here } @endcode @note Please see the @ref section_category_audio_general_notes for possible limitations! @section section_category_audio_general_choosing_driver Choosing the audio backend By default, FPL uses the first available audio backend which is supported on your platform.
If you want to force FPL to use a certain audio backend, you can do this by changing the @ref fplAudioSettings.backend field in the @ref fplAudioSettings structure:
@code{.c} fplSettings settings; fplSetDefaultSettings(&settings); fplAudioSettings &audioSettings = settings.audio; // Forcing to use the DirectSound audio backend audioSettings.backend = fplAudioBackendType_DirectSound; if (fplPlatformInit(fplInitFlags_Audio, &settings)) { // ... your code here } @endcode It is recommended to use the default @ref fplAudioBackendType_Auto which uses the first supported audio backend. @warning If your platform/system does not support the desired backend, the audio and platform initialization will fail! @section section_category_audio_general_autoplay Automatic play/stop of audio samples playback By default, FPL starts the playback of audio samples automatically, but only when @ref fplAudioSettings.clientReadCallback was set!
Also audio playback will be stopped when @ref fplPlatformRelease() is called as well.

You can disable this behavior by changing the fields @ref fplAudioSettings.startAuto and fplAudioSettings.stopAuto in the @ref fplAudioSettings configuration respectively.
@section section_category_audio_general_manualplay Manual play/stop of audio samples playback For manual control of start/stop playback of audio samples use the following functions: - @ref fplPlayAudio() to start requesting and play audio samples. - @ref fplStopAudio() to stop the audio playback. @warning Please ensure that @ref fplStopAudio() is called before @ref fplPlatformRelease() always! @section section_category_audio_general_notes Notes There is no guarantee that you get the desired audio format you specified back!
You should always check the @ref fplAudioDeviceFormat "nativeAudioFormat" in your client callback and convert/write the correct samples the audio device expects!

For more details see the page: @subpage page_category_audio_writesamples

If needed you can use @ref fplSetDefaultAudioSettings() to fill in just the default video settings inside the @ref fplAudioSettings structure.
*/ /*! @page page_category_audio_writesamples Writing audio samples @tableofcontents @section section_category_audio_writesamples_overview Overview To write audio samples in the audio client callback, you have to know at least four things: - Target format (S16, S24, F32, etc.) - Target number of channels (1 = Mono, 2 = Stereo, etc.) - Target sample rate (44 kHz, 48 kHz, etc.) - Number of frames required All these informations are provided by the @ref fpl_audio_client_read_callback callback function.

Call @ref fplGetAudioHardwareFormat(), to get this information before the audio playback is started. @section section_category_audio_writesamples_functions Useful functions To help with sample computation there are several functions available: - @ref fplGetAudioHardwareFormat() - @ref fplGetAudioBufferSizeInFrames() - @ref fplGetAudioBackendName() - @ref fplGetAudioFormatName() - @ref fplGetAudioSampleSizeInBytes() - @ref fplGetAudioFrameSizeInBytes() - @ref fplGetAudioBufferSizeInBytes() @section section_category_audio_writesamples_s16 Writing 16-bit signed integer samples @code{.c} static uint32_t MyAudioPlaybackCallback(const fplAudioDeviceFormat *nativeFormat, const uint32_t frameCount, void *outputSamples, void *userData) { uint32_t result = 0; if (nativeFormat->type == fplAudioFormatType_S16) { int16_t *outSamples = (int16_t *)outputSamples; for (uint32_t frameIndex = 0; frameIndex < frameCount; ++frameIndex) { for (uint32_t channelIndex = 0; channelIndex < nativeFormat->channels; ++channelIndex) { *outSamples++ = // ... Getting a sample for the current frame/channel ++result; } } } else { // ... handle other formats here } return result; } @endcode @section section_category_audio_writesamples_notes Notes - FPL does not provide any functionality for doing any kind of DSP or format conversion!
- You are responsible for filling out the samples in the correct format your audio device expects! */ /*! @page page_category_memory_handling Memory handling @tableofcontents FPL provides a couple of functions to allocate/set/clear memory for you.
You are free to use it as you like, but you can use malloc(), free() as well if you want to. @section section_category_memory_handling_normal Default/Unaligned memory allocation @subsection subsection_category_memory_handling_normal_allocate Allocate (n)-bytes of memory To allocate a block of contiguous memory, you simply call @ref fplMemoryAllocate() with the number of bytes you want to allocate.
This will return a pointer you can start to write into. @code{.c} // Allocate 4MB of memory void *myMemory = fplMemoryAllocate(fplMegaBytes(4)); // ... // or you can allocate the type directly // Allocate a bunch of int's int *intArray = (int *)fplMemoryAllocate(sizeof(int) * 100000); intArray[0] = 42; // ... @endcode @note This function does nothing more than calling the proper operating-system function such as **VirtualAlloc()** or **mmap()** . @attention The memory is not guaranteed to be aligned, but depending on the OS it may be aligned to something. @warning Do not write before this pointer, otherwise, you may overwrite meta-informations required for releasing the memory later on. @subsection section_category_memory_normal_free Free memory To free memory, you simply call @ref fplMemoryFree() with the base pointer of your memory, and that's it. @code{.c} // Free a memory block allocated by fplMemoryAllocate fplMemoryFree(myMemory); @endcode @warning Do not call this function with a pointer allocated from @ref fplMemoryAlignedAllocate() , otherwise, you will corrupt memory! @subsection subsection_category_memory_handling_normal_datalayout Data layout of default/unaligned memory Here you can see the full data-layout for a default/unaligned memory block: | 0 or 4/8 bytes for x86/x64 | 0 or 4/8 bytes for x86/x64 | n-bytes | |----------------------------------------------|--------------------------------|-------------------------| | **Optional size of the entire memory block** | **Optional padding** | **Memory of the data** | @note On Linux/Unix the size and small padding are stored before the actual data, because **munmap()** requires a size as a parameter as well. @section section_category_memory_handling_aligned Custom aligned memory allocation @subsection subsection_category_memory_handling_aligned_allocate Allocate custom aligned (n)-bytes of memory To allocate a **custom aligned** block of contiguous memory, you simply call @ref fplMemoryAlignedAllocate() with the number of bytes you want to allocate and the custom alignment.
This will return a guaranteed aligned pointer you can start to write into. @code{.c} // Allocate a bunch of 4x4 matrices aligned by 16-bytes typedef struct Mat4f { float m[16]; } Vec4f; Mat4f *matrices = (Mat4f *)fplMemoryAlignedAllocate(sizeof(Mat4f) * 128, 16); // ... @endcode @note The size of the memory block may be greater than the size you requested, due to alignment padding and meta-informations. @attention The alignment parameter must be a power-of-two based value, otherwise, the function will fail. @warning Do not write before this pointer, otherwise you will overwrite meta-informations required for releasing the memory later on. @subsection subsection_category_memory_handling_aligned_free Free aligned memory To free aligned memory you simply call @ref fplMemoryAlignedFree() with the base pointer of your aligned memory and that's it. @warning Do not call this function with a pointer allocated from @ref fplMemoryAllocate() , otherwise, you will corrupt memory! @subsection subsection_category_memory_handling_aligned_datalayout Data layout of aligned memory Here you can see the full data-layout for a aligned memory block: | 4/8 bytes for x86/x64 | Alignment * 2 bytes | n-bytes | |---------------------------------|-----------------------|-------------------------------------------------------------------------------| | **Address of the base-pointer** | **Alignment padding** | \ref subsection_category_memory_handling_normal_datalayout "Data layout" | @section section_category_memory_handling_ops Memory operations @subsection subsection_category_memory_handling_ops_clear Clear (n)-bytes of memory Call @ref fplMemoryClear() to clear a memory block starting from the base-pointer with a given size. @code{.c} // Clear 4-KB of memory to zero, starting by the given memory address fplMemoryClear(myMemory, 4096); // or // Clear a struct fplMemoryClear(myStructPtr, sizeof(*myStructPtr)); // or // Clear a fixed sized array fplMemoryClear(myArray, sizeof(myArray)); @endcode @note This operation is executed in 8-bytes, 4-bytes, 2-bytes sized chunks to improve performance. @subsection subsection_category_memory_handling_ops_set Overwrite (n)-bytes of memory with a given value Call @ref fplMemorySet() to overwrite a memory block starting from the base-pointer with a given size and the given value. @code{.c} // Overwrites 1000 bytes of memory with a 8-bit value fplMemorySet(myMemory, 0xAB, 1000); @endcode @note This operation is executed in 8-bytes, 4-bytes, 2-bytes sized chunks to improve performance. @subsection subsection_category_memory_handling_ops_copy Copy (n)-bytes of memory to another memory location To copy (n)-bytes of memory you simply call @ref fplMemoryCopy() with the source and destination pointer and the number of bytes you want to copy. @code{.c} // Copy parts of memory into another memory // void *destMemory was already allocated before size_t numberOfBytesToCopy = fplKiloBytes(10); fplMemoryCopy(sourceMemory, numberOfBytesToCopy, destMemory); @endcode @note This operation is executed in 8-bytes, 4-bytes, 2-bytes sized chunks to improve performance. @section section_category_memory_handling_macrofuncs Useful macro functions FPL provides a few macro functions useful for dealing with memory:
| Name | What it does | |---------------------|------------------------------------------------------------| | @ref fplZeroInit | Initializes a struct with zero | | @ref fplStructInit | Initializes a struct with values | | @ref fplStructSet | Overwrites a struct with the given value | | @ref fplClearStruct | Clears the given struct pointer to zero | | @ref fplCopyStruct | Copies the source struct data into the destination pointer | | @ref fplArrayCount | Returns the number of elements in fixed sized array | | @ref fplOffsetOf | Returns the byte-offset to a field in a struct | | @ref fplKiloBytes | Converts the given kilobytes value into bytes | | @ref fplMegaBytes | Converts the given megabytes value into bytes | | @ref fplGigaBytes | Converts the given gigabytes value into bytes | | @ref fplTeraBytes | Converts the given terabytes value into bytes | */ /*! @page page_category_hardware Query Hardware information @tableofcontents FPL provides a couple of functions for getting hardware functions, such as CPU & memory Infos, etc.
@section section_category_hardware_corecount Get number of CPU-Cores You can retrieve the number of processor cores by calling @ref fplCPUGetCoreCount() .
@code{.c} size_t coreCount = fplCPUGetCoreCount(); // Do something with the core count @endcode @note Hyperthreads will be included as well, so on a typical 4-core HT CPU, this function will return eight cores. @section section_category_hardware_cpuarch Get CPU architecture You can query the CPU architecture type by calling @ref fplCPUGetArchitecture() . This will return a @ref fplCPUArchType value, which you can use test for different architecture types.

Use @ref fplCPUGetArchName() to convert a @ref fplCPUArchType into a string.
@code{.c} fplCPUArchType cpuArch = fplCPUGetArchitecture(); if (cpuArch == fplCPUArchType_Arm64) { // Do something on a ARM-64 CPU } // or just print it out const char *cpuArchString = fplCPUGetArchName(cpuArch); fplConsoleFormatOut("CPU Architecture: %s\n", cpuArchString); @endcode @section section_category_hardware_cpuname Get the CPU Name You can query the CPU name by calling @ref fplCPUGetName() . @code{.c} // Print out the CPU-Name char nameBuffer[1024]; fplCPUGetName(nameBuffer, fplArrayCount(nameBuffer)); fplConsoleFormatOut("CPU Name: %s\n", nameBuffer); @endcode @section section_category_hardware_memstate Query memory state With @ref fplMemoryGetInfos() you can query the current memory state.
This includes the size of the physical memory, the current memory usage, and more.
See @ref fplMemoryInfos for more details.
@code{.c} fplMemoryInfos memInfos = fplZeroInit; if (fplMemoryGetInfos(&memInfos)) { fplConsoleFormatOut("%llu of %llu physical memory is available.\n", memInfos.freePhysicalSize, memInfos.totalPhysicalSize); } @endcode @section section_category_hardware_cpucaps Query CPU Capabilities Use the @ref fplCPUGetCapabilities() to retrieve a full set of available processor capabilities, like MMX/SSE/AVX support, etc. @code{.c} fplCPUCapabilities caps = fplZeroInit; if (fplCPUGetCapabilities(&caps)) { if (caps.x86.hasSSE2) { // Do something when the x86 CPU supports SSE2 - this should be true, on most modern x86 CPUs. } } @endcode */ /*! @page page_category_platform Query Platform/OS information @tableofcontents FPL provides a couple of functions for query operating system or platform information. @section section_category_platform_type Get Current Platform Type/Name Use @ref fplGetPlatformType() to get the current @ref fplPlatformType .
This can be useful to do different operations on different platforms.

Use @ref fplGetPlatformName() to get a string representation for the given @ref fplPlatformType .
@code{.c} fplPlatformType currentPlatform = fplGetPlatformType(); const char *platformName = fplGetPlatformName(currentPlatform); fplConsoleFormatOut("Platform: %s\n", platformName); @endcode @section section_category_platform_os_version Query OS Version, Kernel, Distribution etc. You can retrieve the version/name of your operating system by calling @ref fplOSGetVersionInfos() .
See @ref fplOSVersionInfos for more details.
@code{.c} fplOSVersionInfos infos = fplZeroInit; if (fplOSGetVersionInfos(&infos)) { fplConsoleFormatOut("Operating system name: %s\n", infos.osName); fplConsoleFormatOut("Operating system version: %s.%s.%s.%s\n", infos.osName.major, infos.osName.minor, infos.osName.fix, infos.osName.build); } @endcode @section section_category_platform_os_username Get current username Use @ref fplSessionGetUsername() to get the username for the current session. @code{.c} char usernameBuffer[256]; if (fplSessionGetUsername(usernameBuffer, fplArrayCount(usernameBuffer))) { fplConsoleFormatOut("Current username: %s\n", usernameBuffer); } @endcode */ /*! @page page_category_dll Using Dynamic Libraries @tableofcontents FPL has built-in support for loading dynamic libraries (.dll/.so).
@section section_category_dll_load Loading a dynamic library To load a dynamic library, such as .dll or .so you simply call @ref fplDynamicLibraryLoad() with the name of the library or the full path.
@code{.c} fplDynamicLibraryHandle libHandle; if (fplDynamicLibraryLoad("mylibrary.dll", &libHandle)) { // ... using the library // Unload it when you are done fplDynamicLibraryUnload(&libHandle); } @endcode @note Call @ref fplDynamicLibraryUnload() to unload a loaded library when you are done. @section section_category_dll_getprocaddr Getting a procedure address To get a procedure address from a function inside a library, simply call @ref fplGetDynamicLibraryProc() .
@code{.c} typedef int(fn_LengthSquared)(int a, int b); fplDynamicLibraryHandle libHandle; if (fplDynamicLibraryLoad("mymathy.dll", &libHandle)) { // Get "LengthSquared" raw procedure address void *lenSqFuncRaw = fplGetDynamicLibraryProc(&libHandle, "LengthSquared"); // Get "LengthSquared" casted procedure address fn_LengthSquared *lenSqFunc = (fn_LengthSquared *)fplGetDynamicLibraryProc(&libHandle, "LengthSquared"); // Use the procedure like any function pointer int len = lenSqFunc(3, 6); // Unload it when you are done fplDynamicLibraryUnload(&libHandle); } @endcode */ /*! @page page_category_io_binaryfiles Using Binary Files @tableofcontents @section section_category_io_binaryfiles_read Reading Files @subsection subsection_category_io_binaryfiles_read_open Opening a Binary File To open a binary file, simply declare a @ref fplFileHandle somewhere and call fplFileOpenBinary() with a valid UTF-8 path to open it.
@code{.c} fplFileHandle fileHandle; if (fplFileOpenBinary("myimage.jpg", &fileHandle)) { // ... read data fplFileClose(&fileHandle); } @endcode @note Call @ref fplFileClose() to close a file when you are done. @subsection subsection_category_io_binaryfiles_read_readblock Reading from the file To read a block of n-bytes from the file starting from the current file position, simply call @ref fplFileReadBlock() with your filehandle and a buffer to read into.
@code{.c} struct jpeg_header; // defined somewhere fplFileHandle fileHandle; if (fplFileOpenBinary("myimage.jpg", &fileHandle)) { size_t read; // Read the first 4 characters char fourCC[4]; read = fplFileReadBlock(&fileHandle, 4, fourCC, sizeof(fourCC)); fplAssert(read == 4); // or // Read a JPEG-Header jpeg_header jpegHeader; size_t read; read = fplFileReadBlock(&fileHandle, sizeof(jpegHeader), &jpegHeader, sizeof(jpegHeader)); fplAssert(read == sizeof(jpegHeader)); fplFileClose(&fileHandle); } @endcode @section section_category_io_binaryfiles_write Writing Files @subsection subsection_category_io_binaryfiles_write_create Creating a Binary File To create a binary file, simply declare a @ref fplFileHandle somewhere and call fplFileCreateBinary() with a valid UTF-8 path to open it.
If the file already exists, it will be overridden automatically.
@code{.c} fplFileHandle fileHandle; if (fplFileCreateBinary("myimage.jpg", &fileHandle)) { // ... write data fplFileClose(&fileHandle); } @endcode @note Call @ref fplFileClose() to close a file when you are done. @subsection subsection_category_io_binaryfiles_write_writeblock Writing to the file To write n-bytes of memory into an opened file beginning at the current file position, simply call @ref fplFileWriteBlock() with your filehandle and the buffer you want to write.
@code{.c} fplFileHandle fileHandle; char *saveGameFilePath = ... if (fplFileCreateBinary(saveGameFilePath, &fileHandle)) { char savegameFourCC[4] = {'S', 'A', 'V', 'E'}; uint8_t *savegameDataPtr = ...; size_t savegameDataSize = ...; // Write a savegame fplFileWriteBlock(&fileHandle, savegameFourCC, 4); fplFileWriteBlock(&fileHandle, savegameDataPtr, savegameDataSize); fplFileClose(&fileHandle); } @endcode @section section_category_io_binaryfiles_pos File Position @subsection subsection_category_io_binaryfiles_pos_get Getting the current file position Call @ref fplFileGetPosition() to get the current file position from a opened @ref fplFileHandle . @code{.c} fplFileHandle fileHandle = ... size_t curPos = fplFileGetPosition(&fileHandle); @endcode @subsection subsection_category_io_binaryfiles_pos_set Setting the current file position (Seeking) Call @ref fplFileSetPosition() to set the current file position on a opened @ref fplFileHandle .
@code{.c} fplFileHandle fileHandle = ... // Seek 4-bytes forwards from the current position fplFileSetPosition(&fileHandle, 4, fplFilePositionMode_Current); // Seek 8-bytes backwards from the current position fplFileSetPosition(&fileHandle, -4, fplFilePositionMode_Current); // Seek to the 128-byte from the beginning (Absolute seeking) fplFileSetPosition(&fileHandle, 128, fplFilePositionMode_Beginning); // Seek to the start of the file fplFileSetPosition(&fileHandle, 0, fplFilePositionMode_Beginning); // Seek to the end of the file (Get file length) size_t fileLength = fplFileSetPosition(&fileHandle, 0, fplFilePositionMode_End); @endcode @section section_category_io_binaryfiles_32vs64 Default vs 32-bit vs 64-bit There are three versions for file reading/writing and seekings:
- A version which uses uint32_t or int32_t (32-bit only, Limited to 2 GB of file = 2^31).
- A version which uses uint64_t or int64_t (64-bit only, Limited to 2 TB of file = 2^63).
- A default version which uses size_t or intptr_t always and use either the 32-bit or the 64-bit, depending on the compiled CPU-Architecture.

See the next sections for more details. @subsection section_category_io_binaryfiles_32vs64_default Default Uses either 32-bit or the 64-bit, depending on the compiled CPU architecture:
- @ref fplFileSetPosition() maps to @ref fplFileSetPosition32() or fplFileSetPosition64() - @ref fplFileGetPosition() maps to @ref fplFileGetPosition32() or fplFileGetPosition64() - @ref fplFileReadBlock() maps to @ref fplFileReadBlock32() or fplFileReadBlock64() - @ref fplFileWriteBlock() maps to @ref fplFileWriteBlock32() or fplFileWriteBlock64() - @ref fplFileGetSizeFromPath() maps to @ref fplFileGetSizeFromPath32() or fplFileGetSizeFromPath64() - @ref fplFileGetSizeFromHandle() maps to @ref fplFileGetSizeFromHandle32() or fplFileGetSizeFromHandle64() @subsection section_category_io_binaryfiles_32vs64_32 32-bit Limited to 32-bit:
- @ref fplFileSetPosition32() - @ref fplFileSetPosition32() - @ref fplFileReadBlock32() - @ref fplFileWriteBlock32() - @ref fplFileGetSizeFromPath32() - @ref fplFileGetSizeFromHandle32() @subsection section_category_io_binaryfiles_32vs64_64 64-bit Up to 2-TB (64-bit):
- @ref fplFileSetPosition64() - @ref fplFileSetPosition64() - @ref fplFileReadBlock64() - @ref fplFileWriteBlock64() - @ref fplFileGetSizeFromPath64() - @ref fplFileGetSizeFromHandle64() */ /*! @page page_category_io_paths Working with Paths/Files @tableofcontents @section section_category_io_paths_constants Path constants FPL provides a couple of path constants you can use, if needed:
| Name | Description | |-|-| | @ref FPL_MAX_PATH_LENGTH | Defines the maximum length of any path| | @ref FPL_MAX_FILENAME_LENGTH | Defines the maximum length of a single filename without path| | @ref FPL_PATH_SEPARATOR | Defines the path separator character| | @ref FPL_FILE_EXT_SEPARATOR | Defines the file extension separator character| @note All length constants include the null-terminator character as well, so you don't have to add one to the length when using! @section section_category_io_paths_utils Utilities @subsection subsection_category_io_paths_utils_pathcombine Combining paths Use @ref fplPathCombine() to combine multiple parts of a path to build one single path from it.
Unfortunately, this function requires you to pass the number of parts you want to combine :-(
If you know how to get the count of the variadic arguments (va_list) in C99, please contact me or write a GitHub issue! Thanks.
Example: @code{.c} const char *assetsRootPath = ... char textureAssetFilePath[FPL_MAX_PATH_LENGTH]; if (fplPathCombine(textureAssetFilePath, fplArrayCount(textureAssetFilePath), 3, assetsRootPath, "textures", "mytexture.png") != fpl_null) { // ... Do something with the textureAssetFilePath } char tempPath[1024]; fplPathCombine(tempPath, fplArrayCount(tempPath), 3, "One", "Another", "Last"); // POSIX tempPath = One/Another/Last // Win32 tempPath = One\Another\Last @endcode @note This is the recommended way to construct any kind of path because it will handle path separators correctly. @note This function is designed to be compatible with .NET Path.Combine(). @warning Do not pass the wrong number of maximum arguments as a third-argument, otherwise, your application may end crashing horribly. @subsection subsection_category_io_paths_utils_extractfilepath Extracting the file path Use @ref fplExtractFilePath() to extract the directory from the given path.

Using this on a path with a file will extract the path where the file is stored.
Using this on a directory path will extract the path of the parent directory.
Example: @code{.c} const char *inputPath = ... char newPath[FPL_MAX_PATH_LENGTH]; if (fplExtractFilePath(inputPath, &newPath, fplArrayCount(newPath)) != fpl_null) { // ... Do something with the newPath } @endcode @note This function is designed to be compatible with .NET Path.GetDirectoryName() or ExtractFilePath() on Delphi/FreePascal.
@note The original path is not altered in any way, therefore the source argument is defined as const. @warning Do not use manually constructed source paths with fixed path separators such as / or \\, otherwise you will lose platform independence! Use @ref fplPathCombine() to build new paths from multiple paths if needed. @warning The return value of @ref fplExtractFilePath returns the pointer to the last-written character and NOT the pointer to the first character! @subsection subsection_category_io_paths_utils_extractfilename Extracting the file name Use @ref fplExtractFileName() to extract the filename from any path.
Using this on a full or relative file-path will extract the name of the file.
Using this on a directory path will extract the name of the last directory in the path.
Example: @code{.c} const char *inputFilePath = ... const char *fileName = fplExtractFileName(inputFilePath); // ... Do something with the fileName // Results: // /my/path/to/the/file.ico -> file.ico // /my/path/to/the/starting.file.ico -> starting.file.ico // c:\Program Files (x86)\Xenorate\Xenorate.exe -> Xenorate.exe // myAwesomeFilename.executable -> myAwesomeFilename.executable // passthrough -> passthrough // /my/path/to/the/ -> @endcode @note This function is designed to be compatible with .NET Path.GetFileName() or ExtractFileName() on Delphi/FreePascal.
@note The original path is not altered in any way, therefore the source argument is defined as const. @warning Do not use manually constructed source paths with fixed path separators such as / or \\, otherwise you will lose platform independence! Use @ref fplPathCombine() to build new paths from multiple paths if needed. @subsection subsection_category_io_paths_utils_extractfileext Extracting the file extension Use @ref fplExtractFileExtension() to extract the file extension from a path.
This will return anything starting from the last found file extension separator @ref FPL_FILE_EXT_SEPARATOR to the very end of the path - containing the starting dot.
Using this on a path or filename will get you just the file extension if there is any.
Example: @code{.c} const char *inputFilePath = ... const char *fileExt = fplExtractFileExtension(inputFilePath); // ... Do something with the fileExt // Results: // /my/path/to/the/file.ico -> .ico // /my/path/to/the/starting.file.ico -> .file.ico // c:\Program Files (x86)\Xenorate\Xenorate.exe -> .exe // myAwesomeFilename.executable -> .executable // /my/path/what.ever/libMyAwesome.so -> .so // i_dont_have_any_dots_in_the_name -> @endcode @note This function is designed to be compatible with .NET Path.GetExtension() or ExtractFileExt() on Delphi/FreePascal.
@note The original path is not altered in any way, therefore the source argument is defined as const. @warning Do not use manually constructed source paths with fixed path separators such as / or \\, otherwise you will lose platform independence! Use @ref fplPathCombine() to build new paths from multiple paths if needed. @subsection subsection_category_io_paths_utils_changefileext Changing the file extension Use @ref fplChangeFileExtension() to change/add a file extensions on a file path.
Example: @code{.c} const char *inputFilePath = ... char changedFileExt[FPL_MAX_PATH_LENGTH]; if (fplChangeFileExtension(inputFilePath, ".log", changedFileExt, fplArrayCount(changedFileExt)) != fpl_null) { // ... Do something with the changedFileExt } @endcode @note This function is designed to be compatible with .NET Path.ChangeFileExtension().
@note The original path is not altered in any way, therefore the source arguments are defined as const. @warning Do not use manually constructed source paths with fixed path separators such as / or \\, otherwise you will lose platform independence! Use @ref fplPathCombine() to build new paths from multiple paths if needed. @section section_category_io_paths_get Retrievement of paths @subsection subsection_category_io_paths_get_home Getting the home directory path Use @ref fplGetHomePath() to retrieve the home directory path for the current user.

On a POSIX based system this will most likely return something like "/usr/home/the_username_in_question"
On a Win32 based system this will most likely return something like "c:\\Users\\TheUsernameInQuestion"
Example: @code{.c} char homePath[FPL_MAX_PATH_LENGTH]; fplGetHomePath(&homePath, fplArrayCount(homePath)); // ... Do something with the homePath @endcode @note There is no guarantee that this path ends with a trailing path separator, such as / on POSIX or \\ on Win32! If needed use @ref fplEnforcePathSeparator to force a path separator on always. @warning The return value of @ref fplGetHomePath returns the pointer to the last-written character and NOT the pointer to the first character! @subsection subsection_category_io_paths_get_exepath Get executable file path Use @ref fplGetExecutableFilePath() to retrieve the full-path to your current executable.

On a POSIX based system this will most likely return something like "/some/path/../my_application"
On a Win32 based system this will most likely return something like "c:\\Some\\Path\\..\\MyApplication.exe"
Example: @code{.c} // Get executable file path char exeFilePath[FPL_MAX_PATH_LENGTH]; fplGetExecutableFilePath(&exeFilePath, fplArrayCount(exeFilePath)); // Get executable directory path char appPath[FPL_MAX_PATH_LENGTH]; if (fplExtractFilePath(exeFilePath, &appPath, fplArrayCount(appPath)) != fpl_null) { // ... Do something with appPath } @endcode @note There is no guarantee that this path ends with a trailing path separator, such as / on POSIX or \\ on Win32! If needed use @ref fplEnforcePathSeparator to force a path separator on always. @note The name of the executable is always included in the path and will contain the file extension if there is one. @warning The return value of @ref fplExtractFilePath returns the pointer to the last-written character and NOT the pointer to the first character! @section section_category_io_paths_traversing Directory Traversing FPL provides a powerful and easy directory traversal API, to find either files, directories, or both using a wildcard matching system. @subsection subsection_category_io_paths_traversing_findfiles Finding files by wildcard Use @ref fplDirectoryListBegin() to start finding files in a path.
If this function succeeds, the result for the first match is written into the @ref fplFileEntry structure.

After this you repeatly call @ref fplDirectoryListNext(), to get the next match into @ref fplFileEntry structure - until the function returns false.
Example: @code{.c} const char *sourcePath = ... fplFileEntry fileEntry; size_t fileCount = 0; // Find all .so files (For-loop style) fileCount = 0; for(bool hasEntry = fplDirectoryListBegin(sourcePath, "*.so", &entry); hasEntry; hasEntry = fplDirectoryListNext(&entry)) { // ... do something with entry.name ++fileCount; } // Find all .so files (While style) fileCount = 0; if (fplDirectoryListBegin(sourcePath, "*.so", &entry)) { // The first file was found // ... do something with entry.name ++fileCount; // Loop until no file is found while (fplDirectoryListNext(&entry)) { // ... do something with entry.name ++fileCount; } } // Find all .so files (For-loop style, break out after reaching a limit of maximum allowed files) size_t maxFileCount = ... fileCount = 0; for(bool hasEntry = fplDirectoryListBegin(sourcePath, "*.so", &entry); hasEntry; hasEntry = fplDirectoryListNext(&entry)) { if (fileCount < maxFileCount) { // ... do something with entry.name } else { // Release resources, when we exit out the iteration fplDirectoryListEnd(&entry); break; } ++fileCount; } @endcode @note When @ref fplDirectoryListBegin() or @ref fplDirectoryListNext() does not find any match, the internal resources are released automatically. @warning If you manually stop the iteration, please call @ref fplDirectoryListEnd() to release its internal resources - otherwise you may leak memory! @subsection subsection_category_io_paths_traversing_traversefiles Recursively get all files in a directory There is no recursion support built-in in FPL, but you can easily make one using @ref fplDirectoryListBegin() .
Just iterate through the file entries, which are already explained here: @ref subsection_category_io_paths_traversing_findfiles
You can check @ref fplFileEntry.type ,to check for either a @ref fplFileEntryType_Directory or a @ref fplFileEntryType_File type to start a recursion or not.
Example: @code{.c} // Some existing awesome file-list container api typedef struct file_list_t { size_t capacity; size_t count; char **items; } file_list_t; void init_file_list(file_list_t *outList); void free_file_list(file_list_t *outList); void push_file_list_item(file_list_t *outList, const char *item); static void GetFilesFromDirectory(const char *rootPath, const bool recursive, file_list_t *outList) { fplEntry entry; for(bool hasEntry = fplDirectoryListBegin(rootPath, "*", &entry); hasEntry; hasEntry = fplDirectoryListNext(&entry)) { char path[FPL_MAX_PATH_LENGTH]; fplPathCombine(path, fplArrayCount(path), 2, rootPath, entry.name); if (entry.type == fplFileEntryType_Directory && recursive) { GetFilesFromDirectory(path, true, outList); } else if (entry.type == fplFileEntryType_File) { push_file_list_item(outList, path); } } return(result); } int main(int argc, char **argv) { if (argc < 2) { return 1; } const char *rootPath = argv[1]; file_list_t outFileList; init_file_list(&outFileList); GetFilesFromDirectory(rootPath, true, &outFileList); // ... do someting with out file list free_file_list(&outFileList); return 0; } @endcode */ /*! @page page_category_threading_threads Threads @tableofcontents @section section_category_threading_threads_overview Overview In this section, you will learn the Basics about creating and handling of Threads.
With Threads, you can run multiple pieces of code in parallel.
Threads require the use of @subpage page_category_threading_sync to prevent race conditions. @section section_category_threading_threads_create Creating a Thread Call @ref fplThreadCreate() with a pointer to @ref fpl_run_thread_callback "callback" and a user pointer argument, to create and immediately run a Thread.
@code{.c} void MyThreadProc(const fplThreadHandle *context, void *data) { // ... do something here } void RunMyThread() { int myData = 42; fplThreadHandle *thread = fplThreadCreate(MyThreadProc, &myData); fplThreadWaitForOne(thread, FPL_TIMEOUT_INFINITE); } @endcode @note The internal Thread resources will be cleaned up automatically after your code has finished running.
@warning When a Thread has finished running, you cannot use the same @ref fplThreadHandle anymore -> It may be reassigned to another Thread in the future.
@section section_category_threading_threads_destroy Destroying a Thread? You don't have to manually release the Thread resources, this will be cleaned up automatically when either the Thread ends naturally or when it was terminated forcefully using @ref fplThreadTerminate() .
@warning Do not call @ref fplThreadTerminate() to stop or release a Thread! Let the thread exit naturally. @section section_category_threading_threads_wait Waiting/Joining for Threads to Exit @subsection subsection_category_threading_threads_wait_single Wait for a single Thread to Exit Call @ref fplThreadWaitForOne() with a pointer to @ref fplThreadHandle and a @ref fplTimeoutValue argument, to wait for a single Thread to finish. @code{.c} // Wait until the thread is finished fplThreadWaitForOne(thread, FPL_TIMEOUT_INFINITE); // ... or // Wait at max for 5 seconds to finish the thread fplThreadWaitForOne(thread, 5000); @endcode @subsection subsection_category_threading_threads_wait_any Wait for any Thread to Exit Call @ref fplThreadWaitForAny() to wait until at least for one Thread to finish. Example, default thread handle array:
@code{.c} fplThreadHandle **threads; // Thread handle array already initialized // Wait until one of the threads is finished fplThreadWaitForAny(threads, numOfThreads, sizeof(fplThreadHandle *), FPL_TIMEOUT_INFINITE); // ... or // Wait at max for 5 seconds to finish at least one of the threads -> default stride fplThreadWaitForAny(threads, numOfThreads, sizeof(fplThreadHandle *), 5000); @endcode Example, custom struct with stored thread handle: @code{.c} typedef struct MyCustomStruct { int a; void *ptr; fplThreadHandle *thread; short b; } MyCustomStruct; MyCustomStruct *customThreads; // Struct array already initialized fplThreadHandle **firstPointerToThread = &customThreads[0].thread; // Wait until one of the threads is finished fplThreadWaitForAny(firstPointerToThread, numOfThreads, sizeof(MyCustomStruct), FPL_TIMEOUT_INFINITE); // ... or // Wait at max for 5 seconds to finish at least one of the threads -> default stride fplThreadWaitForAny(firstPointerToThread, numOfThreads, sizeof(MyCustomStruct), 5000); @endcode @subsection subsection_category_threading_threads_wait_all Wait for all Threads to Exit Call @ref fplThreadWaitForAll() to wait for all Threads to be finished. Example, default thread handle array:
@code{.c} fplThreadHandle **threads; // Thread handle array already initialized // Wait until all of the threads are finished fplThreadWaitForAll(threads, numOfThreads, sizeof(fplThreadHandle *), FPL_TIMEOUT_INFINITE); // ... or // Wait at max for 5 seconds to finish all threads fplThreadWaitForAll(threads, numOfThreads, sizeof(fplThreadHandle *), 5000); @endcode Example, custom struct with stored thread handle: @code{.c} typedef struct MyCustomStruct { int a; void *ptr; fplThreadHandle *thread; short b; } MyCustomStruct; MyCustomStruct *customThreads; // Struct array already initialized fplThreadHandle **firstPointerToThread = &customThreads[0].thread; // Wait until all of the threads are finished fplThreadWaitForAll(threads, numOfThreads, sizeof(MyCustomStruct), FPL_TIMEOUT_INFINITE); // ... or // Wait at max for 5 seconds to finish all threads fplThreadWaitForAll(threads, numOfThreads, sizeof(MyCustomStruct), 5000); @endcode @section section_category_threading_threads_terminate Terminate a thread Call @ref fplThreadTerminate() with a pointer to @ref fplThreadHandle as an argument, to forcefully terminate a thread. @note Using this will almost immediately terminate the thread and releases its resources. @note It is safe to call this when a thread is already terminated. @section section_category_threading_threads_states Query the Thread State Call @ref fplGetThreadState() with a pointer to @ref fplThreadHandle as an argument, to query the current state.
If a thread is stopped or in the process of getting stopped, it will return @ref fplThreadState_Stopped or @ref fplThreadState_Stopping respectively.
If a thread is started or in the process of getting started, it will return @ref fplThreadState_Running or @ref fplThreadState_Starting respectively.
@section section_category_threading_threads_notes Notes @note Your code should always ensure that it will exit eventually. You can use @subpage page_category_threading_sync to achieve this.
@note It is bad practice to "Terminate" a thread, you should design your code to let threads end naturally. */ /*! @page page_category_threading_mutexes Mutexes @tableofcontents @section section_category_threading_mutexes_overview Overview This section explains how to create and handling of Mutexes.
A mutex is a Kernel-Level object used to prevent race conditions when multiple Threads access the same code section.
With a Mutex, you can ensure that only one thread at a time can access a code section. @section section_category_threading_mutexes_init Initialize a Mutex Call @ref fplMutexInit() with a pointer to @ref fplMutexHandle argument, to initialize a Mutex.
After the initialization is done, you can start locking and unlocking it.
When you are done with the Mutex call @ref fplMutexDestroy() to release its internal resources. @code{.c} fplMutexHandle mutex; if (!fplMutexInit(&mutex)) { // Error: Mutex failed initializing -> Too many active mutexes for this process or already initialized } // ... Mutex is not required anymore fplMutexDestroy(&mutex); @endcode @section section_category_threading_mutexes_locking Locking/Unlocking a Mutex @subsection subsection_category_threading_mutexes_locking_lock Locking a Mutex Call @ref fplMutexLock() with a pointer to @ref fplMutexHandle as an argument, to lock a Mutex.
It this Mutex is already locked, the current thread will wait forever until it gets unlocked by the owner Thread.
If this Mutex is not locked yet, it will be locked by the calling Thread.
@note You should always unlock the @ref fplMutexHandle as soon as possible - on the same call stack.
@subsection subsection_category_threading_mutexes_locking_unlock Unlocking a Mutex Call @ref fplMutexUnlock() with a pointer to @ref fplMutexHandle as an argument, to unlock a Mutex.
The Mutex will be unlocked only when this function is called from the owner Thread.
If this Mutex was not locked or the owner Thread does not match it will fail. @subsection subsection_category_threading_mutexes_locking_probe Trying to lock a Mutex Call @ref fplMutexTryLock() with a pointer to @ref fplMutexHandle as an argument, to try to lock a Mutex.
If the Mutex is already locked, the current thread will not be blocked and the function returns false.
If this Mutex is not locked yet, it will be locked by the calling Thread.
@subsection subsection_category_threading_mutexes_locking_example Example @code{.c} // Lock down the execution of a code section to one Thread at a time fplMutexLock(&mutex); { // Do something in this critical section here } fplMutexUnlock(&mutex); // Locking a mutex can fail, so ensure that you check the result if (fplMutexLock(&mutex)) { // Do something in this critical section here fplMutexUnlock(&mutex); } // Dont wait for the mutex to be unlocked, just try to lock it if (fplMutexTryLock(&mutex)) { // Do something in this critical section here fplMutexUnlock(&mutex); } @endcode @note It is recommended to put an opening/closing brace to mark the code inside the lock as critical code. */ /*! @page page_category_threading_signals Signals @tableofcontents @section section_category_threading_signals_overview Overview This section explains how to create and manage Signals.
A Signal is a Kernel-Level object used to notify one or multiple waiting Threads.
It internally contains a Value which is either @ref fplSignalValue_Set or @ref fplSignalValue_Unset.
When this value gets changed, all Threads which waits on that Signal will wakeup.
They can be shared across process boundaries and may be used as standalone locks to shared data, but the number of Signals is limited by the OS that can be allocated at a time. @section section_category_threading_signals_init Initialize a Signal Call @ref fplSignalInit() with a pointer to @ref fplSignalHandle as an argument, to initialize a Signal.
Also, you need to specify if the Signal starts as @ref fplSignalValue_Set or @ref fplSignalValue_Unset as a second argument.
When you are done with that Signal, you need to call @ref fplSignalDestroy() to release its internal resources. @code{.c} fplSignalHandle mutex; // Initialize a Signal as "unset" if (!fplSignalInit(&mutex, fplSignalValue_Unset)) { // Error: Signal failed initializing -> Too many active Signals } // ... Signal is not required anymore fplSignalDestroy(&mutex); @endcode @section section_category_threading_signals_wait Waiting for Signal @subsection subsection_category_threading_signals_wait_single Wait for a single Signal to be set Call @ref fplSignalWaitForOne() with a pointer to @ref fplSignalHandle and a @ref fplTimeoutValue argument, to let the current thread wait until the Signal is set. @code{.c} // Wait until the Signal is set fplSignalWaitForOne(thread, FPL_TIMEOUT_INFINITE); // ... or // Wait at max for 5 seconds or until the Signal is set fplSignalWaitForOne(thread, 5000); @endcode @subsection subsection_category_threading_signals_wait_any Wait for any Signal to be set Call @ref fplSignalWaitForAny() to let the current thread wait until at least one Signal is set. Example, default signal handle array:
@code{.c} fplSignalHandle **signals; // Signals array already initialized // Wait until one of the Signal is set fplSignalWaitForAny(signals, numOfSignals, sizeof(fplSignalHandle *), FPL_TIMEOUT_INFINITE); // ... or // Wait at max for 5 seconds or until one of the Signal is set fplSignalWaitForAny(signals, numOfSignals, sizeof(fplSignalHandle *), 5000); @endcode Example, custom struct with stored signal handle: @code{.c} typedef struct MyCustomStruct { int a; void *ptr; fplSignalHandle *signal; short b; } MyCustomStruct; MyCustomStruct *customSignals; // Struct array already initialized fplSignalHandle *firstPointerToSignal = &customSignals[0].signal; // Wait until one of the Signal is set fplSignalWaitForAny(firstPointerToSignal, numOfSignals, sizeof(MyCustomStruct), FPL_TIMEOUT_INFINITE); // ... or // Wait at max for 5 seconds or until one of the Signal is set fplSignalWaitForAny(firstPointerToSignal, numOfSignals, sizeof(MyCustomStruct), 5000); @endcode @subsection subsection_category_threading_signals_wait_all Wait for all Signals to be set Call @ref fplSignalWaitForAll() to let the current thread wait until all Signals are set. Example, default signal handle array:
@code{.c} fplSignalHandle **signals; // Signals array already initialized // Wait until all of the Signals are set fplSignalWaitForAll(signals, numOfSignals, sizeof(fplSignalHandle *), FPL_TIMEOUT_INFINITE); // ... or // Wait at max for 5 seconds or until all of the Signal are set fplSignalWaitForAll(signals, numOfSignals, sizeof(fplSignalHandle *), 5000); @endcode Example, custom struct with stored signal handle: @code{.c} typedef struct MyCustomStruct { int a; void *ptr; fplSignalHandle *signal; short b; } MyCustomStruct; MyCustomStruct *customSignals; // Struct array already initialized fplSignalHandle *firstPointerToSignal = &customSignals[0].signal; // Wait until all of the Signals are set fplSignalWaitForAll(firstPointerToSignal, numOfSignals, sizeof(MyCustomStruct), FPL_TIMEOUT_INFINITE); // ... or // Wait at max for 5 seconds or until all of the Signal are set fplSignalWaitForAll(firstPointerToSignal, numOfSignals, sizeof(MyCustomStruct), 5000); @endcode @section section_category_threading_signals_set Setting a Signal Call @ref fplSignalSet() with a pointer to @ref fplSignalHandle as an argument, to set a Signal and wakeup all waiting Threads.
@note Unlike @ref fplConditionVariable , setting a Signal is not Thread-Safe so you should ensure that only one thread at a time will set it! @code{.c} // Use a mutex or another synchronization method to ensure that only one thread can set the Signal fplMutexLock(&mutex); fplSignalSet(signal); fplMutexUnlock(&mutex); @endcode @section section_category_threading_signals_reset Resetting a Signal Call @ref fplSignalReset() with a pointer to @ref fplSignalHandle as an argument, to reset a Signal.
@note Unlike @ref fplConditionVariable , resetting a Signal is not Thread-Safe so you should ensure that only one thread at a time will set it! @code{.c} // Use a mutex or another synchronization method to ensure that only one thread can reset the Signal fplMutexLock(&mutex); fplSignalReset(signal); fplMutexUnlock(&mutex); @endcode */ /*! @page page_category_threading_conditions Condition-Variables @tableofcontents @section section_category_threading_conditions_overview Overview This section explains how to create and handling of Condition-Variables.
A Condition-Variable is a User-Level Object used to let Threads wait until a Condition-Variable is signaled or broadcasted.
Unlike @subpage page_category_threading_signals, Condition-Variables requires you to use a @ref fplMutexHandle as a locking mechanism.
They cannot be shared across process boundaries but the number of Condition-Variables is only limited by the amount of memory you have. @section category_threading_conditions_init Initialize a Condition-Variable Call @ref fplConditionInit() with a pointer to @ref fplConditionVariable as an argument, to initialize a Condition-Variable.
When you are done with that Condition-Variable, you need to call @ref fplConditionDestroy() to release its internal resources. @code{.c} fplConditionVariable condition; if (!fplConditionInit(&condition)) { // Error: Condition-Variable failed to initialize -> This will most likely never happen } // ... Condition-Variable is not required anymore fplConditionDestroy(&condition); @endcode @section category_threading_conditions_wait_single Waiting on a Condition-Variable Call @ref fplConditionWait() with a pointer to @ref fplConditionVariable and a pointer to @ref fplMutexHandle as an argument, to let a thread wait on that Condition-Variable.
Also, you need to specify the number of milliseconds you want the Thread to wait. If **FPL_TIMEOUT_INFINITE** is passed, it will wait forever.
@note Conditions must be called inside a locked critical-section or undefined behavior may happen. @code{.c} fplMutexLock(&mutex); { // Wait until the Condition-Variable is signaled or broadcasted fplConditionWait(&condition, &mutex, FPL_TIMEOUT_INFINITE); // ... or // Wait at max for 5 seconds for a signal or broadcast on the Condition-Variable fplConditionWait(&condition, &mutex, 5000); } fplMutexUnlock(&mutex); @endcode @section category_threading_conditions_wait_multiple Waiting on multiple Condition-Variables? A Thread can only wait on one single Condition-Variable at a time - if you need to wait on multiple Condition-Variables, you should consider using @subpage page_category_threading_signals . @section category_threading_conditions_signal Send a Signal to a Condition-Variable to one waiting Thread Call @ref fplConditionSignal() with a pointer to @ref fplConditionVariable, to signal any waiting Threads.
If you want to let multiple Threads waits on the same Condition-Variable, use @ref fplConditionBroadcast() instead.
@note Unlike Signals Condition-Variables does not need a Mutex for signaling. @code{.c} // Send a Signal to the Condition-Variable to one waiting Thread fplConditionSignal(&cond); @endcode @section category_threading_conditions_broadcast Send a Condition-Variable Broadcast to all waiting Threads Call @ref fplConditionBroadcast() with a pointer to @ref fplConditionVariable, to signal all waiting Threads.
If you just need a single Thread to wait on the Condition-Variable, use @ref fplConditionSignal() instead.
@note Unlike Signals Condition-Variables does not need a Mutex for signaling a Condition-Variable. @code{.c} // Broadcast a Signal to the Condition-Variable to all waiting Threads fplConditionBroadcast(&cond); @endcode */ /*! @page page_category_threading_semaphores Semaphores @tableofcontents @section section_category_threading_semaphores_overview Overview This section explains how to create and manage semaphores.
Semaphores are similar to @subpage page_category_threading_mutexes "Mutexes" but have one major difference: Any Thread can release it!
It internally uses an atomic counter which gets incremented and decremented. @section category_threading_semaphores_init Initialize a Semaphore Call @ref fplSemaphoreInit() to initialize a Semaphore with a @ref fplSemaphoreHandle as an argument. Also, you need to specify the initial value for the Semaphore to start with.
Call @ref fplSemaphoreDestroy() when you are done with that Semaphore to let it releases its internal resources. @code{.c} fplSemaphoreHandle semaphore; if (!fplSemaphoreInit(&semaphore, 1)) { // Error: Semaphore failed to initialize -> This will most likely never happen } // ... Semaphore is not required anymore and no threads are waiting on it fplSemaphoreDestroy(&semaphore); @endcode @section category_threading_semaphores_wait Waiting/Locking a Semaphore Call @ref fplSemaphoreWait() to let a Thread wait and decrement the Semaphores value with a @ref fplSemaphoreHandle as an argument.
If the Semaphores value is greater than zero, then the decrement will happen and the function returns immediately.
If the Semaphores value is zero then the Thread will wait until it is possible to decrement the value.
Also, you need to specify the number of milliseconds you want the thread to wait. If FPL_TIMEOUT_INFINITE is passed, it will wait forever.
@code{.c} // Wait until the Semaphores value is > 0 fplSemaphoreWait(&semaphore, FPL_TIMEOUT_INFINITE); // ... or // Wait at max for 5 seconds to the Semaphores value to be > 0 fplSemaphoreWait(&semaphore, 5000); @endcode @section category_threading_semaphores_wait_multiple Waiting on multiple Semaphores? A thread can only wait on one single Semaphore at a time - if you need to wait on multiple Semaphores, you should consider using @subpage page_category_threading_signals . @section subsection_category_threading_semaphores_trywait Trying to wait on a Semaphore @ref fplSemaphoreTryWait() is the same as @ref fplSemaphoreWait() , except that if the decrement cannot immediately perform, the current thread will not be _blocked_. @section category_threading_semaphores_post Releasing the Semaphore Call @ref fplSemaphoreRelease() with the @ref fplSemaphoreHandle in question, to release a Semaphore and signal all waiting Threads.
@code{.c} // Release a Semaphore and signal all waiting Threads fplSemaphoreRelease(&semaphore); @endcode @section category_threading_semaphores_getvalue Reading the Value from the Semaphore Call @ref fplSemaphoreValue() to get the current value from the Semaphore. @code{.c} int32_t currentValue = fplSemaphoreValue(&semaphore); // ... do something with the value here @endcode */ /*! @page page_category_threading_atomics Atomics @tableofcontents @section category_threading_atomics_overview Overview This section explains what atomics is and how they are used.
Since decades CPUs have hardware CPU instructions for doing thread-safe math and compare operations built-in.
These instructions are available as built-in intrinsics in any modern C/C++ compiler.
In FPL these are called Atomics and are wrapper functions around those intrinsics.

Atomics functions generate memory barriers (or fences) to ensure that memory operations are completed in order. @section category_threading_atomics_add Fetch-And-Add (Add) With a Fetch-And-Add instruction, you can do an atomic addition and get the previous value **before** the add back as an atomic operation.
Pseudo code: @code oldValue = *storageValue; *storageValue = oldValue + inc; return oldValue; @endcode FPL have several versions for that for various datatypes: - @ref fplAtomicFetchAndAddU32() - @ref fplAtomicFetchAndAddU64() - @ref fplAtomicFetchAndAddS32() - @ref fplAtomicFetchAndAddS64() - @ref fplAtomicFetchAndAddSize() - @ref fplAtomicFetchAndAddPtr() Usage: @code{.c} volatile uint32_t storageValue; uint32_t oldValue = fplAtomicFetchAndAddU32(&storageValue, 7); @endcode @section category_threading_atomics_inc Add-And-Fetch (Increment) With an Add-And-Fetch instruction, you can do an atomic addition by **one** and get the new value back **after** the increment as an atomic operation.
Pseudo code: @code *StorageValue++; NewValue = *StorageValue; return newValue; @endcode FPL have several versions for that for various datatypes: - @ref fplAtomicAddAndFetchU32() - @ref fplAtomicAddAndFetchU64() - @ref fplAtomicAddAndFetchS32() - @ref fplAtomicAddAndFetchS64() - @ref fplAtomicAddAndFetchSize() - @ref fplAtomicAddAndFetchPtr() Usage: @code{.c} volatile uint32_t storageValue; uint32_t newValue = fplAtomicAddAndFetchU32(&storageValue); @endcode @section category_threading_atomics_exchange Exchange With a Exchange instruction, you can do an atomic exchange and get the previous value back **before** the change as an atomic operation.
Pseudo code: @code oldValue = *storageValue; *storageValue = exchangeValue; return oldValue; @endcode FPL have several versions for that for various datatypes: - @ref fplAtomicExchangeU32() - @ref fplAtomicExchangeU64() - @ref fplAtomicExchangeS32() - @ref fplAtomicExchangeS64() - @ref fplAtomicExchangeSize() - @ref fplAtomicExchangePtr() Usage: @code{.c} volatile uint32_t storageValue; uint32_t newValue = fplAtomicStoreU32(&storageValue, 1337); @endcode @note The exchange atomics is identical to @ref category_threading_atomics_store, except that the exchange atomics will return the previous value before the change. @section category_threading_atomics_store Store With a Store instruction, you can do a memory write as an atomic operation.
Pseudo code: @code *storageValue = newValue; @endcode FPL have several versions for that for various datatypes: - @ref fplAtomicStoreU32() - @ref fplAtomicStoreU64() - @ref fplAtomicStoreS32() - @ref fplAtomicStoreS64() - @ref fplAtomicStoreSize() - @ref fplAtomicStorePtr() Usage: @code{.c} volatile uint32_t storageValue; fplAtomicStoreU32(&storageValue, 1337); @endcode @note The store atomics is identical to @ref category_threading_atomics_exchange, except that the store atomics won't return any value. @section category_threading_atomics_load Load With a Load instruction, you can do a memory read as an atomic operation.
Pseudo code: @code return *storageValue; @endcode FPL have several versions for that for various datatypes: - @ref fplAtomicLoadU32() - @ref fplAtomicLoadU64() - @ref fplAtomicLoadS32() - @ref fplAtomicLoadS64() - @ref fplAtomicLoadSize() - @ref fplAtomicLoadPtr() Usage: @code{.c} volatile uint32_t storageValue; uint32_t value = fplAtomicLoadU32(&storageValue); @endcode @section category_threading_atomics_cas Compare-And-Swap With a Compare-And-Swap instruction, you can check a value against another and exchange them as an atomic operation.
The previous/current value will always be returned regardless of the result.
Pseudo code: @code oldValue = *storageValue; if (oldValue == comparend) { *storageValue = exchangeValue; } return oldValue; @endcode FPL have several versions for that for various datatypes: - @ref fplAtomicCompareAndSwapU32() - @ref fplAtomicCompareAndSwapU64() - @ref fplAtomicCompareAndSwapS32() - @ref fplAtomicCompareAndSwapS64() - @ref fplAtomicCompareAndSwapSize() - @ref fplAtomicCompareAndSwapPtr() - @ref fplAtomicIsCompareAndSwapU32() - @ref fplAtomicIsCompareAndSwapU64() - @ref fplAtomicIsCompareAndSwapS32() - @ref fplAtomicIsCompareAndSwapS64() - @ref fplAtomicIsCompareAndSwapSize() - @ref fplAtomicIsCompareAndSwapPtr() Usage: @code{.c} volatile uint32_t storageValue = 0; uint32_t oldValue = fplAtomicCompareAndSwapU32(&storageValue, 0, 1337); // The first thread executing this will see 0 as the oldValue, all others will see 1337 // or volatile uint32_t storageValue = 0; if (fplAtomicIsCompareAndSwapU32(&storageValue, 0, 1337)) { // The first thread executing this to 1337 will get here } else { // All other threads gets here } @endcode @section section_category_threading_atomics_barriers Memory Barriers With memory barriers you can prevent the compiler from reordering memory reads/writes.
In FPL additional CPU fences may be added to ensure that the previous memory operations are completed before the future ones.
@subsection subsection_category_threading_atomics_barriers_without Without fences @code{.c} int theValue; int valueIsPublished = 0; void updateValue(int newValue) { // The compiler may move the write above the the value set theValue = newValue; valueIsPublished = 1; } int getValue() { // The compiler may move the read of value before the publish check if (valueIsPublished) { return theValue; } return -1; } @endcode @warning This example may break even on a single-threaded environment! @subsection subsection_category_threading_atomics_barriers_full With a read/write barrier @code{.c} int theValue; int valueIsPublished = 0; void updateValue(int newValue) { theValue = newValue; // The value is written before the publish is set fplAtomicReadWriteFence(); valueIsPublished = 1; } int getValue() { // The publish is read before the value is being returned if (valueIsPublished) { fplAtomicReadWriteFence(); return theValue; } return -1; } @endcode @subsection subsection_category_threading_atomics_barriers_read_or_write Read or Write In some special cases you don't have to issue a full memory barrier like @ref fplAtomicReadWriteFence(), so you can just use a read or write barrier only:
- You can use @ref fplAtomicReadFence() to just ensure memory writes are completed before future ones only (Plus preventing from compiler reordering memory writes).
- You can use @ref fplAtomicWriteFence() to just ensure memory reads are completed before future ones only (Plus preventing from compiler reordering memory reads).
@subsection subsection_category_threading_atomics_barriers_atomics Atomics vs Barriers? Atomic functions may include memory barriers to ensure that CPU memory read/write instructions are executed in order and to prevent the compiler from reordering memory read/writes.
This means you don't have to use any kind of memory barriers around atomic functions ;-) */ /*! @page page_category_threading_sync Synchronization methods @tableofcontents @todo(final): Explain why we need this @todo(final): Add examples of solving race conditions (Mutexes, Signals, Condition-Variables, Semaphores, Atomics) @todo(final): Add a comparison table to show the difference between the different types. */ /*! @page page_faq FAQ @tableofcontents @section section_faq FAQ @subsection subsection_faq_license What are the license requirements for FPL? Final Platform Layer is released under the @subpage page_license "MIT License".
This license allows you to use FPL freely in any software.
@subsection subsection_faq_costs I did pay for FPL, did I get ripped-of? Yes, you are! FPL is fully open source and costs nothing. @subsection subsection_faq_inwhatlanguage In what language is FPL written? Final Platform Layer is written in C99 for simplicity and best portability, but is C++/11 compatible as well.
It uses standard types such as uint32_t or intptr_t from and to ensure correctness on all platforms.
For certain features such as printf(), getchar() the CRT (C-Runtime-Library) are used, but you can disable it if needed.
@subsection subsection_faq_whyshouldiuse What makes it different from other platform abstraction libraries, such as SDL/SFML, etc. ? - FPL is designed to require bare minimum linking to the OS (kernel32.lib / libld.so) only. - It does not require any dependencies or build-systems to get it running. - It has a lightweight feature set. - No data hiding -> everything is accessible. - It uses a fixed and small memory footprint and handles memory very gracefully. - FPL can be controlled by configuration in very detail at startup. - You decide how to integrate it; not the library. You can statically link it, you can dynamically link it, or you can just include the full source. @subsection subsection_faq_whyisfplsobig Why is FPL so big? Final Platform Layer contains headers and implementations for all supported platforms with all the function prototypes for linking operating system functions dynamically.
Also the entire API definition is documented inline and it comes with a full detailed changelog.
@subsection subsection_faq_howdoesitwork How does it work? FPL relies heavy on preprocessor defines, used for detecting the current architecture / compiler / operating system.
This makes it possible to enable/disable certain code-paths for certain platform/compiler combinations.
Only built-in OS operating system functions are used in every situation and the usage of standard library functions is prevented by all means.

To prevent code duplication, FPL implements either platform sets or just subsets which can be used for other platforms For example, Linux is a POSIX-based platform so it uses POSIX libraries, such as pthread / X11, etc. The pthread and X11 implementation are separated, so other Unix-based platforms can use that implementation without any problem.
On top of that FPL implements multiple audio and video backends, so that all platforms can playback audio and render using a supported graphics API.
@subsection subsection_faq_limitations Does FPL have some limitations? Yes, it does have some limitations, because it cannot do everything for you: - No audio DSP is going on, so you have to convert the samples into the proper format (Sample rate, Channels, Format type) FPL expects. - There are no rendering functions included, except for presenting the current frame. So you have to load and call the correct API functions yourself. - FPL has a lightweight feature set, so it may not have all the features you may need. @subsection subsection_faq_strings How does FPL handle strings? On every platform, all strings/paths are expected to be UTF-8 always!
If you need to convert Unicode/UTF-16 based strings there are conversion functions built-in. @subsection subsection_faq_thirdparty_libs Can FPL handle third-party libraries? Yes it works very well with other libraries. There are several demo projects in the repository which show that.

Here is a short list of tested third-party libraries with FPL: - C-Standard Library - C++ Standard Template Library - STB Libraries - Glew/Glad - PhysX - Box2D - GLM - ImGUI - FFMPEG But it won't work with other platform abstraction libraries such as SDL, SFML, GLUT, GLFW, etc.
All these other platform abstraction libraries have their way of providing the main entry point and may not compatibility with each other. */ /*! @page page_contribute How to contribute to FPL @tableofcontents @section section_contribute_time Donate time You can contribute your time by helping with: - **programming**: Writing demos, helping me fixing bugs, implement new platforms, etc. - **testing and filing bug reports**: Testing FPL on different platforms/compilers (https://github.com/f1nalspace/final_game_tech/issues) - **improving the documentation**: Improving the documentation, by fixing spelling errors or fill-out missing pages/sections. - **Send in feedback**: Write feedback, answer questions, or post ideas (https://fpl.handmade.network/forums) I have always welcomed users whose only contribution is simply using FPL, giving me feedback on how to improve it and telling others about it. Thank you for supporting FPL. @section section_contribute_donate Donate money If you don't have time to help but do find FPL useful, then please consider making a financial donation.
This will help to pay the bills and motivate me to continue working on FPL. Thanks. @subsection subsection_contribute_donate_needs What are the financial needs of FPL? - Maintaining the official webpage (https://libfpl.org) - Extending the Jetbrains CLion license - Buying hardware for developing and testing purposes (Computers, Soundcards, etc.) @subsection subsection_contribute_donate_links Donation Links @htmlonly
@endhtmlonly */ /*! @page page_examples Examples @tableofcontents @section section_console_examples Console examples - @subpage page_example_helloworld_console - @subpage page_example_simple_audio @section section_windowed_examples Window examples - @subpage page_example_opengl1x - @subpage page_example_opengl33 */ /*! @page page_nocrt How to disable the use of the CRT in FPL @tableofcontents @section section_nocrt_consequences Consequences of disabling the CRT If you disable the CRT, you lose a lot of functionality which we today take for granted:
- No functions from the entire C standard library stack, such as "stdio.h" / "stdlib", / "math.h" / "string.h", etc. - No or limited integer mul/div operations on some platforms - No console input/output - No C++ exceptions - Limited security checks - No automatic entry point mapping to main() or WinMain() - Limited floating point operations on some platforms - Limited stack size Some limitations can be corrected by setting up the compiler properly, such as stack size, security checks, etc.

So if you are fine with that, the next section describes how to do disable the CRT usage in FPL. @section section_nocrt_requirements Requirements for disabling the CRT in FPL - You have read the @ref section_nocrt_consequences - You know what you are doing - You already disabled the CRT by compiler settings - You fixed the security checks and stack size by compiler settings @section bsection_nocrt_disableit Disable the CRT in FPL To disable the use of the C-Runtime Library, you need to set certain preprocessor definitions: - Define **FPL_NO_CRT** in all translation units where FPL implementation is included - In your main translation-unit, you need to define the application type **FPL_APPTYPE_CONSOLE** or **FPL_APPTYPE_WINDOW** explicitly @code{.c} #define FPL_NO_CRT // Disable CRT support in FPL #define FPL_APPTYPE_CONSOLE // or FPL_APPTYPE_WINDOW #define FPL_IMPLEMENTATION // FPL_NO_CRT must be set set in the implementation only #include @endcode @section section_nocrt_provfunccrt Providing intrinsics (Mini-CRT) Some compilers such as MSVC require certain stuff to compile properly.
You are responsible to provide that stuff if you get compile errors.

A few errors you may get: Runtime check: - unresolved external symbol __RTC_Shutdown - unresolved external symbol __RTC_InitBase - unresolved external symbol __RTC_CheckEsp - unresolved external symbol __RTC_CheckStackVars Float: - unresolved external symbol __fltused -> Global used in MSVC - unresolved external symbol __ltod3 -> Double division intrinsic Integer: - unresolved external symbol __allmul -> Long-Long integer multiplication intrinsic Other: - unresolved external symbol __memset -> memset intrinsic (May use fplMemorySet) - unresolved external symbol __memcpy -> memcpy intrinsic (May use fplMemoryCopy) To solve those compile errors it is recommended to use a mini-crt library or write all the functions yourself.
@section section_nocrt_notes Notes Right now FPL has No-CRT support for Win32 only! If you need this for other platforms let me know. */ /*! @page page_compiler_options Compiler Options @tableofcontents @section section_compiler_options_preprocessor_opts Preprocessor options
CategoryMacro definitionDescriptionDefault
System FPL_IMPLEMENTATION Define this to include the actual implementation code.
Set this only once per translation-unit, otherwise you will get linking errors.
Not set by default
System FPL_NO_ENTRYPOINT Define this to disable the entry point inclusion.
This is useful when you use FPL in multiple translation units, but want your main entry point to be included only once.
Not set by default
System FPL_ENTRYPOINT Force the inclusion of the main entry point.
If you use FPL as a static library, you need to set this in your main translation-unit only.
Automatically set when FPL_IMPLEMENTATION is defined, but only when FPL_NO_ENTRYPOINT is _not_ defined
System FPL_API_AS_PRIVATE Define this to make all functions be private ( static ).
This means that all function calls can be called from one translation-unit only.
By default all FPL functions are defined as ( extern )
System FPL_DEBUG Define this to enable the "Debug" configuration.
When set assertions and some debug related features are enabled.
By default this is auto-detected by compiler settings.
System FPL_RELEASE Define this to enable the "Release" configuration.
When set all DEBUG features are compiled out entirely.
By default this is auto-detected by compiler settings.
System FPL_NO_RUNTIME_LINKING Define this to disable runtime linking of libraries.
Not set by default
System FPL_NO_PLATFORM_INCLUDES Define this to disable the includes for the specific platform in the API.
Not set by default
System FPL_OPAQUE_HANDLES Define this to force the use of opaque handles in the API.
Not set by default
Assertions FPL_NO_ASSERTIONS Define this to disable all internal assertions. Not set by default
Assertions FPL_FORCE_ASSERTIONS Define this to enable internal assertions always, even in release builds. Not set by default.
@note When enabled all assertions will use a simple null-pointer assertion macro always!
Assertions FPL_NO_C_ASSERT Define this to disable C runtime assert. Not set by default.
@note Has no effect when FPL_FORCE_ASSERTIONS is set!
Window FPL_NO_WINDOW Define this to disable Window Support entirely. Not set by default
Video FPL_NO_VIDEO Define this to disable Video Support entirely. Not set by default
Video FPL_NO_VIDEO_OPENGL Define this to disable the OpenGL video backend. Not set by default
Video FPL_NO_VIDEO_SOFTWARE Define this to disable the Software video backend. Not set by default
Audio FPL_NO_AUDIO Define this to disable Audio Support entirely. Not set by default
Audio FPL_NO_AUDIO_DIRECTSOUND Define this to disable DirectSound support entirely. Not set by default
Audio FPL_NO_AUDIO_ALSA Define this to disable ALSA support entirely. Not set by default
Logging FPL_LOGGING Define this to enable logging. Not set by default
Logging FPL_LOG_MULTIPLE_WRITERS Define this to support multiple log writers, so you can have a writer for each log level. Not set by default
Logging FPL_CRASH_ON_ERROR Define this to crash the application on any error logged by FPL. Not set by default
Logging FPL_CRASH_ON_WARNING Define this to crash the application on any warning logged by FPL. Not set by default
C-Runtime FPL_NO_CRT Define this to disable the usage of functions from the CRT. This is not set by default.
Application FPL_NO_APPTYPE Define this to disable the application type detection. By default this are not set and the application type is detected automatically.
Application FPL_APPTYPE_CONSOLE Define this to force your application to be a console application.
When this is set the window support will be compiled out!
By default this are set when FPL_NO_WINDOW is defined.
Application FPL_APPTYPE_WINDOW Define this to force your application to be a windowed application. By default this is set when FPL_NO_WINDOW is _not_ defined.
User functions FPL_USERFUNC_vsnprintf Define this to provide a replacement for vsnprintf() when FPL_NO_CRT is set. By default this is not set.
@section section_compiler_options_detection Platform/Compiler detection Like every other C/C++ program, FPL uses compiler defines to detect the proper platform/architecture and used compiler.
If you need this information for whatever reason you can simply compare the defines: - **FPL_PLATFORM_WIN32** or **FPL_PLATFORM_LINUX** or **FPL_PLATFORM_UNIX** ... - **FPL_ARCH_X64** or **FPL_ARCH_X86** or **FPL_ARCH_ARM64* ... All these defines have no values set, there are just "defined" - use always defined() for checking them. @code{.c} #if defined(FPL_PLATFORM_WINDOWS) // ... Any win32 code you may need #endif // FPL_PLATFORM_WIN32 #if defined(FPL_ARCH_X64) // ... Any x64 code you may need #endif // FPL_ARCH_X64 #if defined(FPL_COMPILER_MSVC) // ... MSVC compiler specific code you may need #endif // FPL_COMPILER_MSVC @endcode @section section_compiler_options_subplatform Sub-Platforms To prevent code duplication for platform combinations, FPL uses sub-platforms - which are no real platforms, but rather principles or standards the actual operating system is built-on.
For example "Linux" is detected as **FPL_PLATFORM_LINUX** but uses POSIX as a sub-platform **FPL_SUBPLATFORM_POSIX**.
Another example is "FreeBSD" which is a UNIX-based operation system that uses POSIX standards as well, so it uses the same POSIX sub-platform.
*/ /*! @page page_example_helloworld_console Hello World Console Application! @tableofcontents @section section_maincpp1 main.cpp @code{.c} #define FPL_IMPLEMENTATION #include "final_platform_layer.h" int main(int argc, char **argv) { int result; if (fplPlatformInit(fplInitFlags_Console)) { fplConsoleOut("Hello World!"); fplPlatformRelease(); result = 0; } else { result = -1; } return(result); } @endcode */ /*! @page page_example_opengl1x Simple OpenGL 1.x @tableofcontents @section section_maincpp2 main.cpp @code{.c} #define FPL_IMPLEMENTATION #include "final_platform_layer.h" // You have to include GL.h yourself or use any other OpenGL loader you want. // FPL just creates an OpenGL rendering context for you, but nothing more. #include int main(int argc, char **argv) { int result = 0; if (fplPlatformInit(fplInitFlags_Video, fpl_null)) { glClearColor(0.39f, 0.58f, 0.93f, 1.0f); while (fplWindowUpdate()) { fplEvent ev; while (fplPollEvent(&ev)) {} fplWindowSize windowArea = fplGetWindowSize(); glViewport(0, 0, windowArea.width, windowArea.height); glClear(GL_COLOR_BUFFER_BIT); glBegin(GL_TRIANGLES); glVertex2f(0.0f, 0.5f); glVertex2f(-0.5f, -0.5f); glVertex2f(0.5f, -0.5f); glEnd(); fplVideoFlip(); } fplPlatformRelease(); result = 0; } else { result = -1; } return(result); } @endcode */ /*! @page page_example_opengl33 Modern OpenGL 3.3+ @tableofcontents @section section_maincpp3 main.cpp @code{.c} #define FPL_IMPLEMENTATION #define FPL_NO_VIDEO_SOFTWARE #define FPL_NO_AUDIO #include // You have to include GL.h yourself or use any other OpenGL loader you want. // FPL just creates an OpenGL rendering context for you, but nothing more. #include #ifndef APIENTRY #define APIENTRY #endif #define APIENTRYP APIENTRY * #include typedef ptrdiff_t GLsizeiptr; typedef ptrdiff_t GLintptr; typedef char GLchar; #ifndef GL_CONTEXT_PROFILE_MASK #define GL_CONTEXT_PROFILE_MASK 0x9126 #define GL_CONTEXT_FLAGS 0x821E #define GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT 0x0001 #define GL_CONTEXT_FLAG_DEBUG_BIT 0x00000002 #define GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT 0x00000004 #define GL_CONTEXT_FLAG_NO_ERROR_BIT 0x00000008 #define GL_CONTEXT_CORE_PROFILE_BIT 0x00000001 #define GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002 #endif // GL_CONTEXT_PROFILE_MASK #define GL_COMPILE_STATUS 0x8B81 #define GL_INFO_LOG_LENGTH 0x8B84 #define GL_FRAGMENT_SHADER 0x8B30 #define GL_VERTEX_SHADER 0x8B31 #define GL_SHADING_LANGUAGE_VERSION 0x8B8C #define GL_LINK_STATUS 0x8B82 #define GL_STATIC_DRAW 0x88E4 typedef GLuint(APIENTRYP PFNGLCREATESHADERPROC) (GLenum type); typedef void (APIENTRYP PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); typedef void (APIENTRYP PFNGLCOMPILESHADERPROC) (GLuint shader); typedef void (APIENTRYP PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader); typedef GLuint(APIENTRYP PFNGLCREATEPROGRAMPROC) (void); typedef void (APIENTRYP PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); typedef void (APIENTRYP PFNGLLINKPROGRAMPROC) (GLuint program); typedef void (APIENTRYP PFNGLVALIDATEPROGRAMPROC) (GLuint program); typedef void (APIENTRYP PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); typedef void (APIENTRYP PFNGLDELETESHADERPROC) (GLuint shader); typedef void (APIENTRYP PFNGLUSEPROGRAMPROC) (GLuint program); static PFNGLCREATESHADERPROC glCreateShader = NULL; static PFNGLSHADERSOURCEPROC glShaderSource = NULL; static PFNGLCOMPILESHADERPROC glCompileShader = NULL; static PFNGLGETSHADERIVPROC glGetShaderiv = NULL; static PFNGLATTACHSHADERPROC glAttachShader = NULL; static PFNGLCREATEPROGRAMPROC glCreateProgram = NULL; static PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog = NULL; static PFNGLLINKPROGRAMPROC glLinkProgram = NULL; static PFNGLVALIDATEPROGRAMPROC glValidateProgram = NULL; static PFNGLGETPROGRAMIVPROC glGetProgramiv = NULL; static PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog = NULL; static PFNGLDELETESHADERPROC glDeleteShader = NULL; static PFNGLUSEPROGRAMPROC glUseProgram = NULL; #define GL_ARRAY_BUFFER 0x8892 typedef void (APIENTRYP PFNGLGENVERTEXARRAYSPROC) (GLsizei n, GLuint *arrays); typedef void (APIENTRYP PFNGLBINDVERTEXARRAYPROC) (GLuint array); typedef void (APIENTRYP PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers); typedef void (APIENTRYP PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer); typedef void (APIENTRYP PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const void *data, GLenum usage); typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index); typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYPROC) (GLuint index); typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSPROC) (GLsizei n, const GLuint *arrays); static PFNGLGENVERTEXARRAYSPROC glGenVertexArrays = NULL; static PFNGLBINDVERTEXARRAYPROC glBindVertexArray = NULL; static PFNGLGENBUFFERSPROC glGenBuffers = NULL; static PFNGLBINDBUFFERPROC glBindBuffer = NULL; static PFNGLBUFFERDATAPROC glBufferData = NULL; static PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray = NULL; static PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray = NULL; static PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer = NULL; static PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays = NULL; #if defined(FPL_PLATFORM_WINDOWS) static void *GLProcAddress(const char *name) { fpl__VideoState *videoState = (fpl__VideoState *)fpl__global__AppState->video.mem; fplAssert(videoState != NULL); fplAssert(videoState->win32.opengl.api.wglGetProcAddress != NULL); void *result = videoState->win32.opengl.api.wglGetProcAddress(name); return(result); } #else static void *GLProcAddress(const char *name) { fpl__VideoState *videoState = (fpl__VideoState *)fpl__global__AppState->video.mem; fplAssert(videoState != NULL); fplAssert(videoState->x11.opengl.api.glXGetProcAddress != NULL); void *result = videoState->x11.opengl.api.glXGetProcAddress((const GLubyte *)name); return(result); } #endif static void LoadGLExtensions() { glCreateShader = (PFNGLCREATESHADERPROC)GLProcAddress("glCreateShader"); glShaderSource = (PFNGLSHADERSOURCEPROC)GLProcAddress("glShaderSource"); glCompileShader = (PFNGLCOMPILESHADERPROC)GLProcAddress("glCompileShader"); glGetShaderiv = (PFNGLGETSHADERIVPROC)GLProcAddress("glGetShaderiv"); glAttachShader = (PFNGLATTACHSHADERPROC)GLProcAddress("glAttachShader"); glCreateProgram = (PFNGLCREATEPROGRAMPROC)GLProcAddress("glCreateProgram"); glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)GLProcAddress("glGetShaderInfoLog"); glLinkProgram = (PFNGLLINKPROGRAMPROC)GLProcAddress("glLinkProgram"); glValidateProgram = (PFNGLVALIDATEPROGRAMPROC)GLProcAddress("glValidateProgram"); glGetProgramiv = (PFNGLGETPROGRAMIVPROC)GLProcAddress("glGetProgramiv"); glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)GLProcAddress("glGetProgramInfoLog"); glDeleteShader = (PFNGLDELETESHADERPROC)GLProcAddress("glDeleteShader"); glUseProgram = (PFNGLUSEPROGRAMPROC)GLProcAddress("glUseProgram"); glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)GLProcAddress("glGenVertexArrays"); glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)GLProcAddress("glBindVertexArray"); glGenBuffers = (PFNGLGENBUFFERSPROC)GLProcAddress("glGenBuffers"); glBindBuffer = (PFNGLBINDBUFFERPROC)GLProcAddress("glBindBuffer"); glBufferData = (PFNGLBUFFERDATAPROC)GLProcAddress("glBufferData"); glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)GLProcAddress("glEnableVertexAttribArray"); glDisableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)GLProcAddress("glDisableVertexAttribArray"); glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)GLProcAddress("glVertexAttribPointer"); glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC)GLProcAddress("glDeleteVertexArrays"); } #define MODERN_OPENGL 1 static GLuint CreateShaderType(GLenum type, const char *source) { GLuint shaderId = glCreateShader(type); glShaderSource(shaderId, 1, &source, NULL); glCompileShader(shaderId); char info[1024 * 10] = fplZeroInit; GLint compileResult; glGetShaderiv(shaderId, GL_COMPILE_STATUS, &compileResult); if(!compileResult) { GLint infoLen; glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &infoLen); fplAssert(infoLen <= fplArrayCount(info)); glGetShaderInfoLog(shaderId, infoLen, &infoLen, info); fplConsoleFormatError("Failed compiling %s shader!\n", (type == GL_VERTEX_SHADER ? "vertex" : "fragment")); fplConsoleFormatError("%s\n", info); } return(shaderId); } static GLuint CreateShaderProgram(const char *name, const char *vertexSource, const char *fragmentSource) { GLuint programId = glCreateProgram(); GLuint vertexShader = CreateShaderType(GL_VERTEX_SHADER, vertexSource); GLuint fragmentShader = CreateShaderType(GL_FRAGMENT_SHADER, fragmentSource); glAttachShader(programId, vertexShader); glAttachShader(programId, fragmentShader); glLinkProgram(programId); glValidateProgram(programId); char info[1024 * 10] = fplZeroInit; GLint linkResult; glGetProgramiv(programId, GL_LINK_STATUS, &linkResult); if(!linkResult) { GLint infoLen; glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &infoLen); fplAssert(infoLen <= fplArrayCount(info)); glGetProgramInfoLog(programId, infoLen, &infoLen, info); fplConsoleFormatError("Failed linking '%s' shader!\n", name); fplConsoleFormatError("%s\n", info); } glDeleteShader(fragmentShader); glDeleteShader(vertexShader); return(programId); } static bool RunModern() { LoadGLExtensions(); GLuint vertexArrayID; glGenVertexArrays(1, &vertexArrayID); glBindVertexArray(vertexArrayID); const char *glslVersion = (const char *)glGetString(GL_SHADING_LANGUAGE_VERSION); fplConsoleFormatOut("OpenGL GLSL Version %s:\n", glslVersion); int profileMask; int contextFlags; glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &profileMask); glGetIntegerv(GL_CONTEXT_FLAGS, &contextFlags); fplConsoleFormatOut("OpenGL supported profiles:\n"); fplConsoleFormatOut("\tCore: %s\n", ((profileMask & GL_CONTEXT_CORE_PROFILE_BIT) ? "yes" : "no")); fplConsoleFormatOut("\tForward: %s\n", ((contextFlags & GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT) ? "yes" : "no")); fplConsoleOut("Running modern OpenGL\n"); const char vertexSource[] = { "#version 330 core\n" "\n" "layout(location = 0) in vec4 inPosition;\n" "\n" "void main() {\n" "\tgl_Position = inPosition;\n" "}\n" }; const char fragmentSource[] = { "#version 330 core\n" "\n" "layout(location = 0) out vec4 outColor;\n" "\n" "void main() {\n" "\toutColor = vec4(1.0, 0.0, 0.0, 1.0);\n" "}\n" }; GLuint shaderProgram = CreateShaderProgram("Test", vertexSource, fragmentSource); float vertices[] = { 0.0f, 0.5f, -0.5f, -0.5f, 0.5f, -0.5f }; GLuint buffer; glGenBuffers(1, &buffer); glBindBuffer(GL_ARRAY_BUFFER, buffer); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); glUseProgram(shaderProgram); glBindBuffer(GL_ARRAY_BUFFER, buffer); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, NULL); glClearColor(0.39f, 0.58f, 0.93f, 1.0f); while(fplWindowUpdate()) { fplEvent ev; while(fplPollEvent(&ev)) {} fplWindowSize windowArea; fplGetWindowSize(&windowArea); glViewport(0, 0, windowArea.width, windowArea.height); glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_TRIANGLES, 0, 3); fplVideoFlip(); } glDisableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); glDeleteVertexArrays(1, &vertexArrayID); return true; } int main(int argc, char **args) { int result = 0; fplSettings settings = fplMakeDefaultSettings(); settings.video.backend = fplVideoBackendType_OpenGL; fplCopyString("FPL Modern OpenGL", settings.window.title, fplArrayCount(settings.window.title)); settings.video.graphics.opengl.compabilityFlags = fplOpenGLCompabilityFlags_Core; settings.video.graphics.opengl.majorVersion = 3; settings.video.graphics.opengl.minorVersion = 3; settings.video.graphics.opengl.multiSamplingCount = 4; if(fplPlatformInit(fplInitFlags_Video, &settings)) { const char *version = (const char *)glGetString(GL_VERSION); const char *vendor = (const char *)glGetString(GL_VENDOR); const char *renderer = (const char *)glGetString(GL_RENDERER); fplConsoleFormatOut("OpenGL version: %s\n", version); fplConsoleFormatOut("OpenGL vendor: %s\n", vendor); fplConsoleFormatOut("OpenGL renderer: %s\n", renderer); RunModern(); fplPlatformRelease(); result = 0; } else { result = -1; } return(result); } @endcode */ /*! @page page_example_simple_audio Simple audio playback @tableofcontents @section section_maincpp4 main.cpp @code{.c} #define FPL_IMPLEMENTATION #define FPL_NO_WINDOW #include #include // sinf struct AudioTest { uint32_t toneHz; uint32_t toneVolume; uint32_t runningSampleIndex; uint32_t wavePeriod; bool useSquareWave; }; static const float PI32 = 3.14159265359f; static uint32_t FillAudioBuffer(const fplAudioDeviceFormat *nativeFormat, const uint32_t frameCount, void *outputSamples, void *userData) { AudioTest *audioTest = (AudioTest *)userData; fplAssert(audioTest != nullptr); fplAssert(nativeFormat->type == fplAudioFormatType_S16); uint32_t result = 0; int16_t *outSamples = (int16_t *)outputSamples; uint32_t halfWavePeriod = audioTest->wavePeriod / 2; for (uint32_t frameIndex = 0; frameIndex < frameCount; ++frameIndex) { int16_t sampleValue; if (audioTest->useSquareWave) { sampleValue = ((audioTest->runningSampleIndex++ / halfWavePeriod) % 2) ? (int16_t)audioTest->toneVolume : -(int16_t)audioTest->toneVolume; } else { float t = 2.0f * PI32 * (float)audioTest->runningSampleIndex++ / (float)audioTest->wavePeriod; sampleValue = (int16_t)(sinf(t) * audioTest->toneVolume); } for (uint32_t channelIndex = 0; channelIndex < nativeFormat->channels; ++channelIndex) { *outSamples++ = sampleValue; ++result; } } return result; } int main(int argc, char **argv) { int result = -1; // Initialize to default settings which is 48 kHz and 2 Channels fplSettings settings; fplSetDefaultSettings(&settings); // Optionally overwrite audio settings if needed // Setup some state for the sine/square wave generation AudioTest audioTest = {}; audioTest.toneHz = 256; audioTest.toneVolume = 1000; audioTest.wavePeriod = settings.audio.deviceFormat.sampleRate / audioTest.toneHz; audioTest.useSquareWave = false; // Provide client read callback and optionally user data settings.audio.clientReadCallback = FillAudioBuffer; settings.audio.userData = &audioTest; settings.audio.deviceFormat.type = fplAudioFormatType_S16; settings.audio.deviceFormat.channels = 2; settings.audio.deviceFormat.sampleRate = 48000; // Disable auto start/stop of audio playback settings.audio.startAuto = false; settings.audio.stopAuto = false; // Find audio device if (fplPlatformInit(fplInitFlags_Audio, &settings)) { fplAudioDeviceID audioDevices[16] = {}; uint32_t deviceCount = fplGetAudioDevices(audioDevices, fplArrayCount(audioDevices)); if (deviceCount > 0) { settings.audio.deviceID = audioDevices[0]; fplConsoleFormatOut("Using audio device: %s\n", settings.audio.deviceID.name); } fplPlatformRelease(); } // Initialize the platform with audio enabled and the settings if (fplPlatformInit(fplInitFlags_Audio, &settings)) { // You can overwrite the client read callback and user data if you want to fplSetAudioClientReadCallback(FillAudioBuffer, &audioTest); // Start audio playback (This will start calling clientReadCallback regulary) if (fplPlayAudio() == fplAudioResult_Success) { // Print out the native audio format fplAudioDeviceFormat nativeFormat = fplGetAudioHardwareFormat(); fplConsoleFormatOut("Audio with %lu kHz and %lu channels is playing, press any key to stop playback...\n", nativeFormat.sampleRate, nativeFormat.channels); // Wait for any key presses fplConsoleWaitForCharInput(); // Stop audio playback fplStopAudio(); } // Release the platform fplPlatformRelease(); result = 0; } return(result); } @endcode */ /*! @page page_license License @tableofcontents @section section_mit_license MIT License Copyright (c) 2017-2023 Torsten Spaete

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/