FPL - A C99 Single-Header-File Platform Abstraction Library

Hi

i wanted to present you my recent project i am working on which is a platform abstraction library.
This library is designed to be simple, non-bloated, can be included however you want and do not require any thirdparty libraries at all. It will simply abstract away all relevant platform specific things in a simple to use api without hiding any data. The main focus is game/media/simulation development, so you get a window, audio playback and a rendering context.

Mindset:

- Its written in C99 but is C++ compatible
- There is just a single header file you have to include and thats it.
- C-Runtime support can be disabled
- The code style is simple and clean
- Its full open source, so you can use and extend it however you want
- Uses just the built-in operating system functions/libraries
- Works well with other libraries as well (ImGUI, Box2D, Glew, STL, etc.)
- Some features can be compiled out as needed (Window, OpenGL, etc.)
- Fixed and small memory footprint
- Easy to use api
- Can be debugged

Why:

- Moving away from object orienation to handmade programming
- Most platform abstraction libraries are bloated, have a bad api, cannot be debugged, are slow, has bad memory management
- I dont want to write platform specific code anymore, just once for each platform with a fixed set of features and thats it.
- Learning C99 low level programming

Features:

- Window creation and handling
- OpenGL legacy and modern rendering context creation
- Memory allocation and deallocation
- Timing operations
- Threading and syncronisation
- File/Path IO
- String conversion functions
- Atomic operations
- Dynamic library loading
- Console out
- Gamepad input
- Audio playback

History:

Initially the project was fully written with 100% C/89 compability in mind without needing the c-runtime library, but as i see myself always fighting against C or missing any features like console output, i decided to move to a minimal C++ version.

After a while i upgraded to C++/11, but felt there was no need for C++ at all...

Now its written in C99 and it was the best choice ever.

Current state:

- Win32 support is complete
- Linux support is almost complete

Planned:

- More video drivers (Direct2D/3D/Vulcan)
- More audio drivers (XAudio, Wasapi)
- More platforms (MacOSX, maybe Android not sure)
- More docs & tutorials
- More tests

Source:

https://github.com/f1nalspace/final_game_tech

Feedback/Comments:

I would like to know what you think about, what is missing, how can i improve it, what do you want from it, etc.

Edited by Finalspace on
Update v0.4.0 alpha:

Changed: All FPL_ENABLE_ defines are internal now, the caller must use FPL_NO_ or FPL_YES_ respectivily.
Changed: AtomicCompareExchange is now AtomicCompareAndExchange
Changed: InitFlags::VideoOpenGL is now InitFlags::Video
Added: Software rendering support
Added: VideoDriverType enumeration for selecting the active video driver
Added: video::GetVideoBackBuffer with [Win32] implementation
Added: video::ResizeVideoBackBuffer with [Win32] implementation
Added: FPL_PETABYTES macro
Added: FPL_EXABYTES macro
Added: FPL_ZETTABYTES macro
Added: FPL_YOTTABYTES macro
Added: FPL_MIN macro
Added: FPL_MAX macro
Added: MutexCreate with [Win32] implementation
Added: MutexDestroy with [Win32] implementation
Added: MutexLock with [Win32] implementation
Added: MutexUnlock with [Win32] implementation
Added: SignalCreate with [Win32] implementation
Added: SignalDestroy with [Win32] implementation
Added: SignalWait with [Win32] implementation
Added: SignalWakeUp with [Win32] implementation
Added: GetClipboardAnsiText with [Win32] implementation
Added: GetClipboardWideText with [Win32] implementation
Added: SetClipboardText with [Win32] implementation for ansi and wide strings
Added [MSVC]: AtomicExchangeS32 (Signed integer)
Added [MSVC]: AtomicExchangeS64 (Signed integer)
Added [MSVC]: AtomicAddS32 (Signed integer)
Added [MSVC]: AtomicAddS64 (Signed integer)
Added [MSVC]: AtomicCompareExchangeS32 (Signed integer)
Added [MSVC]: AtomicCompareExchangeS64 (Signed integer)
Fixed [MSVC]: AtomicExchangeU32 was not using unsigned intrinsic
Fixed [MSVC]: AtomicExchangeU64 was not using unsigned intrinsic
Fixed [MSVC]: AtomicAddU32 was not using unsigned intrinsic
Fixed [MSVC]: AtomicAddU64 was not using unsigned intrinsic
Fixed [MSVC]: AtomicCompareExchangeU32 was not using unsigned intrinsic
Fixed [MSVC]: AtomicCompareExchangeU64 was not using unsigned intrinsic
Implemented [Win32]: GetProcessorCoreCount
Implemented [Win32]: Main thread infos
Performance [Win32]: GetProcessorName (3 loop iterations at max)

State:

Its almost feature complete for Win32, only audio output is missing.

Next i will clean up all the ifdef madness to get a more clean code structure - so i have a single ifdef block for each platform/backend. Also i am thinking about removing the sub namespaces, just because its so much useless typing for no reason.
Update v0.4.1 alpha:

Cleanup: Internal cleanup
Changed: All the settings constructors removed and replaced by a simple inline function.
Added: Added native C++ unit test project to demos solution
Fixed: FPL_OFFSETOF was not working
Fixed: All file size macros like FPL_MEGABYTES was returning invalid results.
Removed: FPL_PETABYTES and higher are removed, just because its useless.

State:

I cleaned up all the internals so i have a single block per platform and fixed some small bugs. Also i decided to continue with the subnamespace style - one nested level is fine. Next big thing will be audio output using directsound or xaudio and of course implementing the linux platform. But as for now it is working great, i use it in all my C++ stuff know.
Maybe support wasapi for audio as DirectSound and XAudio are built on top of it ?

As for the namespace things, have you considered that C doesn't support them ?

Edited by Simon Anciaux on Reason: Typo
mrmixer
Maybe support wasapi for audio as DirectSound and XAudio are built on top of it ?
As for the namespace things, have you considered that C doesn't support them ?


For audio and win32 i will definitly use directsound + optionally xaudio2.

And for regarding C-Support:

This library is C++/11 only. In the past it was C, but as i was fighting against C all the time i changed it to C++/11 - without introducing any oop concepts or using the std library.

Why:

- Automatic typedef for struct and enums
- constexpr for seeing the expanded constants automatically in the IDE (Very useful)
- Enum class for having proper type checking (Normal C/C++ enums are the worst in < C++/11).

I could live without:

- Method/Operator overloading (Op overload is required for enum class)
- Namespaces (Prevent name conflicts, Categorizing)

Edited by Finalspace on
I am thinking about doing something like this, but i am not sure:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//! Optional C++/11 and namespace support
#if defined(FPL_CPP11_SUPPORTED)
#	define fpl_constant constexpr
#	define FPL_ENUM_DECL_TYPE(name, type) \
	enum class name : type
#	define FPL_ENUM_DECL_VALUE(type, name, value) \
	name = value
#	define FPL_ENUM_VALUE(type, name) \
	name
#else
#	define fpl_constant const static
#	define FPL_ENUM_DECL_TYPE(name, t) \
	enum name
#	define FPL_ENUM_DECL_VALUE(t, name, value) \
	t##_##name = value
#	define FPL_ENUM_VALUE(t, name) \
	t##_##name
#endif
#if !defined(FPL_NO_NAMESPACE)
#	define FPL_NS_BEGIN(name) \
	namespace name {
#	define FPL_NS_END(name) \
	}
#else
#	define FPL_NS_BEGIN(name)
#	define FPL_NS_END(name)
#endif

	//! Initialization flags (Window, Video, All, etc.)
	FPL_ENUM_DECL_TYPE(InitFlags, uint32_t) {
		//! No init flags
		FPL_ENUM_DECL_VALUE(InitFlags, None, 0),
			//! Create a single window
			FPL_ENUM_DECL_VALUE(InitFlags, Window, 1 << 0),
			//! Create a video context
			FPL_ENUM_DECL_VALUE(InitFlags, Video, 1 << 1),
			//! Default init flags for a window + video
			FPL_ENUM_DECL_VALUE(InitFlags, All, FPL_ENUM_VALUE(InitFlags, Window) | FPL_ENUM_VALUE(InitFlags, Video)),
	};


Instead of:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
	enum class InitFlags : uint32_t {
		//! No init flags
		None = 0,
		//! Create a single window
		Window = 1 << 0,
		//! Create a video context
		Video = 1 << 1,
		//! Default init flags for a window + video
		All = Window | Video,
	};


What do you think? This would make C++/11 optional and even not require any namespace. But the system code would be pretty ugly when using enums and namespace. This will also break the identitations entirely. :-(

In addition this code (in the enum declaration):

1
FPL_ENUM_DECL_VALUE(InitFlags, All, FPL_ENUM_VALUE(InitFlags, Window) | FPL_ENUM_VALUE(InitFlags, Video)),


wont compile at all :-(

Edited by Finalspace on
* Crap was written here

Edited by Finalspace on
Update v0.4.2 alpha:

  • Added: [Linux] Started linux implementation
  • Added: [Linux] Memory allocations
  • Added: [Linux] Atomic operations
  • Added: Check for C++/11 compiler and fail if not supported
  • Added: Nasty vstudio 2015+ workaround to detect C++/11
  • Added: &= operator overloading for enums
  • Changed: AtomicCompareAndExchange argument "comparand" and "exchange" flipped.
  • Changed: constexpr is now fpl_constant to make clear what is a constant
  • Removed: [Win32] CreateDIBSection is not needed for a software backbuffer
  • Fixed: [Win32] Software rendering was not working properly.
  • Fixed: Some AtomicCompareAndExchange signatures was still AtomicAndCompareExchange

State/Planned:

  • [Linux] Implement all non-window based functions
  • [Linux] Implement XWindow + GLX
  • [Linux] Implement Wayland + GLX
  • [Win32] Use dynamic library for all the things, so you simple just link to kernel32.lib and thats it
  • [Win32] DirectSound/XAudio2 audio output + api

Still unsure/Questions:

  • I dont like callers requiring C++/11 just for constexpr and enum class. Should i go back to C++ 199711?
  • I still dont like the nested namespaces, its just a couple of functions and types right now, so one namespace maybe totally enough and this i could make optional if i want to. But i dont know...
  • Would this project be suitable for handmade network?

Edited by Finalspace on
I don't think it would be a good idea to change your library to be able to enable/disable those functionalities. It's your choice to use whatever feature and language version you like.

Instead of namespaces I prefer to just put "ModuleName_" in front of a function or type name.

I don't like enum class either for the reason you love them. The type checking often is in the way when you want to convert to/from int, which happens somewhat often in my case (also don't like the :: syntax).

I don't know about constexpr (never used it).

Once again, if those feature work for you, use them !
mrmixer
I don't think it would be a good idea to change your library to be able to enable/disable those functionalities. It's your choice to use whatever feature and language version you like.

Instead of namespaces I prefer to just put "ModuleName_" in front of a function or type name.

I don't like enum class either for the reason you love them. The type checking often is in the way when you want to convert to/from int, which happens somewhat often in my case (also don't like the :: syntax).

I don't know about constexpr (never used it).

Once again, if those feature work for you, use them !


Yeah i tried the normal enum style, but i really dont like them. Just the fact that enum values are always in global scope when not putting in into classes is just wrong. "enum class" is that feature, c++ should had from the very beginning even though its not perfect. It still cannot handle flags without manually overloading the operators - but you can specify the actual type which will be used which is nice as well. enum class: uint8_t is really a 8-bit enum and reserves no more memory than 1 byte. And you get proper type checking. Typecasting to integer works as well.

constexpr is really nice, because formulas in them gets evaluated at compile time and can be immediatly seen in the IDE. But right now, the library does not use constexpr at all, because there are no const-expressions but rather constants. So a const static would totally be sufficient, good IDEs show the value for this as well when you mouse over it.

Edited by Finalspace on
Update v0.4.3 alpha (Big one):

  • New: Introduced IsAtomicCompareAndExchange
  • Added: [Linux] Implemented IsAtomicCompareAndExchange for all 32 and 64 bit integer types
  • Added: [Win32] Implemented IsAtomicCompareAndExchange for all 32 and 64 bit integer types
  • Added: [Win32] Loading gdi32.dll dynamically for ChoosePixelFormat, etc.
  • Added: [Win32] Loading opengl32.dll dynamically for wglGetProcAddress, wglMakeCurrent, etc.
  • Fixed: [Win32] Adding memory fence for AtomicReadWriteFence on non-x64 architectures
  • Fixed: [Win32] Adding memory fence for AtomicReadFence on non-x64 architectures
  • Fixed: [Win32] Adding memory fence for AtomicWriteFence on non-x64 architectures
  • Fixed: Solidified descriptions for all Atomic*Fence
  • Changed: Enabled FPL_FORCE_ASSERTIONS will ensure that C asserts are never used, because it may be compiled out.
  • Changed: Removed all FPL_WIN32_ kernel32 macros and replaced it with normal calls.
  • Changed: [Win32] Changed a lof ot the internals

State/Planned:

  • [Linux] Implement all non-window based functions
  • [Win32] Use dynamic library for all the things, so you simple just link to kernel32.lib and thats it (Partially done)
  • [Win32] DirectSound/XAudio2 audio output + api
Update v0.4.4 alpha:

  • New: [Win32] Implemented argument parsing for WinMain and wWinMain
  • Fixed: Corrected small things for doxygen
  • Changed: Renamed CopyAFile to FileCopy
  • Changed: Renamed DeleteAFile to FileDelete

Note:

Next update while take a while, because i want to get the linux implementation to the same level as the win32 implementation.
Finalspace
Implemented argument parsing for WinMain and wWinMain


Windows has API that will do this for you: CommandLineToArgvW . Your code can be smaller, and less prone to bugs. This function will parse arguments in standard way how windows users expect it (escaping, quotes, whitespaces, ...).

Edited by Mārtiņš Možeiko on
mmozeiko
Finalspace
Implemented argument parsing for WinMain and wWinMain


Windows has API that will do this for you: CommandLineToArgvW . Your code can be smaller, and less prone to bugs. This function will parse arguments in standard way how windows users expect it (escaping, quotes, whitespaces, ...).


Well i have seen that function before, but there was no CommandLineToArgvA equivalent so i decided to write it myself.
But when i think about it, i should rather convert the ansi source argument to a wide string and then call this function to safe me writing a full command line parser - which btw. is not that hard but time consuming as always. The only thing i always need to do is to convert the arguments back to UTF8.

Anyway, thanks for that hint. In the next version i will use CommandLineToArgvW.

Edited by Finalspace on
*Edit: I tried "CommandLineToArgvW" and it is just shit, why?

It returns the executable path splitted by spaces - which is just nonsense:

1
2
3
0x00000000004a3680 L"C:\\Users\\Finalspace\\Documents\\Visual"
0x00000000004a36c4 L"Studio"
0x00000000004a36d2 L"2015\\Projects\\Whatever\\fgt\\demos\\bin\\FPL_OpenGL\\x64-Debug\\FPL_OpenGL.exe"


How should i know which are the actual arguments and which are the executable path?

With the following arguments:

1
"argument with space" escaped\"string_in_quotes\" -someoption 42 -o ..\relative path\to something\


I get 8 arguments:

1
2
3
4
5
6
7
8
0x0000000000392148 L"argument with space"
0x0000000000392170 L"escaped\"string_in_quotes\""
0x00000000003921a4 L"-someoption"
0x00000000003921bc L"42"
0x00000000003921c2 L"-o"
0x00000000003921c8 L"..\\relative"
0x00000000003921e0 L"path\\to"
0x00000000003921f0 L"something\\"


At least forced quotes and escaping seem to work -.-

*Workaround: Call CommandLineToArgvW twice and the first time you concat the arguments to form the executable and the second time you start with the actual index the actual first argument starts... *sigh*

*Edit: Stupid tree vs forst... I take anything back, the first call gets the executable path only and the second call with arguments returns the actual arguments only.

Edited by Finalspace on