How is raylib API used out there? What are the most popular function calls? And the less used ones? What new API calls made an impact? Which ones get completely ignored by the users?
raylib has grown in the last years from 80 API functions to 475 API functions. Did the users adapted to that amount of improvements? Did the new features fit their needs?
I was looking for some answer so I decided to do a quick market analysis of raylib API usage, actually it was a nice weekend project. I decided to analyse some public GitHub projects using raylib.
- fetch, a free tool to automatize GitHub repos cloning
- Notepad++, my main code editor, with some Regex used for search and replace
- Agent Ransack, for quick searches and data export
raylib_usage_analyzer.exe
, a custom tool I wrote for the job
1. Get a list of repos to analyze and clone them
To get the a list of repos to analyze I decided to extract it from awesome-raylib/README.md, that list has already been curated by a raylib user and it's organized by the type of data (Gist, Bindings, Software, Examples, Tutorials...).
Another option was searching on GitHub for C repos including raylib keyword (unfortunately is not possible to search for #include "raylib.h"
) but it required some more work to process the data.
I decided to pick Softwares
and Examples
categories from awesome-raylib, copied the links into a separate text file and using some Notepad++ Regex I filtered it to get only the repos address.
2. Clone the repos locally for quick searching
The repos list was not very long (about 180 repos), considering most of them were probably small C projects, I decided to clone all them locally for a faster analysis.
To do that I looked for some command-line tool that allows me to sync data from GitHub repos, after I quick search I found gget
tool. Unfortunately, after a quick test I realized that tool did not allow me to download the full repo from a branch, it seems it was only intended for release artifacts. Fortunately, the same tool README
listed several alternatives, one of them was fetch
tool, it allows to clone a repo fom an specified branch:
fetch --repo="https://github.com/raysan5/rfxgen" --branch=master ./raysan5/rfxgen
So, I prepared a small fetch_raylib_repos.bat
with multiple command-line fetch calls for every repo in the previous list. Again, I used some search and replace and some Regex to get the list quickly.
-
ISSUE 1: In the
fetch
call it's mandatory to specify the required branch (despite the software --help lists it as Optional) and I didn't have the main branch for all those repos, I decided to try with the two most common branch names:main
andmaster
. I decided to run the script twice, once looking formain
and a second time looking formaster
. In case one of those was not found the fetch process just fails and next repo is tried. -
ISSUE 2: Doing so many
fetch
accesses to GitHub from the same IP is not allowed so, after cloning ~40 repos, GitHub blocked my IP. Fortunately, I got a static IP so reseting the modem was a quick-and-dirty solution to relaunch the process several times.
Some repos were lost in the process (maybe by the branch name, maybe by the IP blocking...) but at the end it got ~120 repos cloned. I manually added some of my local repos with all the projects I've developed with raylib in the last years, it was ~20 additional projects for a total of ~140 projects to analyze.
3. Get a list of files to analyze
Now that I had the repos cloned I needed the list of files to scan. I decided to limit them only the .c files contained in those repos, those should be the files containing raylib API calls.
To get the list I just used Agent Ransack tool to do a search for *.c
files on the required directories, I exported the list with the filename, directory, size, date, etc. as a .csv
file and then I did some Regex magic on Notepad++ to get the list of files. One file per line with the complete access path. I got a list with +2000 files.
-
ISSUE 1: Some repos contained the original raylib repo so the list contained many dupplicate files. As far as there were not that many lines I just filtered them manually, removing dupplicate raylib repo references or other C library references that I knew did not include raylib API calls. After filtering I got ~720 c files to analyze.
-
ISSUE 2: I needed to re-learn some Regex, I can't remember last time I used it so it took me a while to refresh it for my needs.
4. Analyze the files -> Custom tool
The analysis process was simple, just open every file and look for the 475 possible raylib API functions usage on that file. Probably a right tool for that job would have been Python but I decided to code my own custom tool in C. Two reasons: it's been a long time since I touched some Python while C is righ now my lingua mater and second reason was speed, I had the sense that C would be faster than Python analyzing the +330K lines of code involved. Another benefit is that I could customize the output data as I want to be copy pasted in this article.
So, I just wrote a small C tool for the job, it was ~240 lines of code with comments, here the main
function (not including auxiliar functions):
/**********************************************************************************************
raylib API usage analyzer
LICENSE: zlib/libpng
This code is licensed under an unmodified zlib/libpng license, which is an OSI-certified,
BSD-like license that allows static linking with closed source software:
Copyright (c) 2021 Ramon Santamaria (@raysan5)
**********************************************************************************************/
#include <stdlib.h> // Required for: malloc(), calloc(), realloc(), free()
#include <stdio.h> // Required for: printf(), fopen(), fseek(), ftell(), fread(), fclose()
#include <string.h> // Required for: strstr() [TextFindIndex(), TextFindCount()]
#include <time.h> // Required for: clock_t, clock(), CLOCKS_PER_SEC
#define MAX_LINE_LENGTH 256 // Max line length for GetTextLines()
//----------------------------------------------------------------------------------
// Module Functions Declaration
//----------------------------------------------------------------------------------
static char *LoadFileText(const char *fileName, int *length); // Load text file into buffer (Unix line-ending returned, 1 byte, '\n')
static char **GetTextLines(const char *buffer, int length, int *linesCount); // Get separated text lines, memory must be freed manually
static int GetTextLinesCount(const char *buffer, int length); // Get the number of lines in the text (expecting lines ending with '\n')
static int TextFindIndex(const char *text, const char *find); // Find text index position for a provided string
static int TextFindCount(const char *text, const char *find); // Find and count text occurrence within a string
//------------------------------------------------------------------------------------
// Program main entry point
//------------------------------------------------------------------------------------
int main(void)
{
int length = 0;
char *buffer = LoadFileText("files_to_analize.txt", &length);
// Get all the lines separated
int filesCount = 0;
char **fileNameList = GetTextLines(buffer, length, &filesCount);
free(buffer);
buffer = NULL;
length = 0;
buffer = LoadFileText("raylib_function_names.txt", &length);
// Get all function names separated
int funcNamesCount = 0;
char **raylibFuncNames = GetTextLines(buffer, length, &funcNamesCount);
free(buffer);
buffer = NULL;
length = 0;
int *raylibFuncCounts = calloc(funcNamesCount, sizeof(int));
// Let's measure time to scan files
clock_t timeStart = clock();
int totalCodeLinesToScan = 0;
// Scan all files looking for raylib functions usage
for (int i = 0; i < filesCount; i++)
{
//printf("Next file to scan: %s\n", fileNameList[i]);
buffer = LoadFileText(fileNameList[i], &length);
if ((buffer != NULL) && (length > 0))
{
totalCodeLinesToScan += GetTextLinesCount(buffer, length);
// Count raylib functions usage
for (int n = 0; n < funcNamesCount; n++)
{
int funcCount = TextFindCount(buffer, raylibFuncNames[n]);
if (funcCount > 0)
{
//printf(" [%i] Function: %s()\n", funcCount, raylibFuncNames[n]);
// Store ocurrences count of raylibFuncNames[n]
raylibFuncCounts[n] += funcCount;
}
}
free(buffer);
buffer = NULL;
length = 0;
}
}
for (int i = 0; i < filesCount; i++) free(fileNameList[i]);
free(fileNameList);
clock_t timeEnd = clock();
double timeElapsed = (double)(timeEnd - timeStart)/CLOCKS_PER_SEC;
// Show surprising results!
printf("FINAL RESULTS:\n\n");
printf(" TOTAL FILES ANALIZED: %i\n", filesCount);
printf(" TOTAL raylib FUNCTIONS SEARCHED: %i\n", funcNamesCount);
printf(" TOTAL CODE LINES ANALIZED: %i\n", totalCodeLinesToScan);
printf(" TOTAL ELAPSED TIME: %f seconds\n\n", timeElapsed);
for (int n = 0; n < funcNamesCount; n++)
{
//printf(" [%i] Function: %s()\n", raylibFuncCounts[n], raylibFuncNames[n]);
printf("| %s() | %i |\n", raylibFuncNames[n], raylibFuncCounts[n]);
}
for (int i = 0; i < funcNamesCount; i++) free(raylibFuncNames[i]);
free(raylibFuncNames);
free(raylibFuncCounts);
return 0;
}
Here it comes the interesting piece of data. I divided the results by raylib modules and I just listed the raylib API functions calls really relevant. First some general data:
TOTAL FILES ANALIZED: 719
TOTAL raylib FUNCTIONS SEARCHED: 475
TOTAL CODE LINES ANALIZED: 331071
TOTAL ELAPSED TIME: 4.390000 seconds
Interesting to note that analyzing 719 code files with +330K code lines with 475 searches per file only took 4.39 seconds on a Intel(R) Core(TM) i7-3537U CPU @ 2.00GHz
. It seems quite fast despite not having any kind of code optimization.
As expected the most used calls are the raylib mandatory functions:
API function | usage count |
---|---|
InitWindow() | 491 |
WindowShouldClose() | 478 |
CloseWindow() | 492 |
ClearBackground() | 554 |
BeginDrawing() | 464 |
EndDrawing() | 464 |
Some other calls used a lot are the ones included in examples and that probably most users used as a reference, from that list I was surprised by the big amount of GetRandomValue()
calls. Also interesting to note that many users use GetFrameTime()
to get delta time variation in a frame, this call is hardly ever used in examples.
API function | usage count |
---|---|
SetTargetFPS() | 472 |
GetScreenWidth() | 813 |
GetScreenHeight() | 725 |
GetFrameTime() | 43 |
GetTime() | 107 |
GetRandomValue() | 1097 |
SetConfigFlags() | 57 |
TraceLog() | 416 |
SetTraceLogLevel() | 24 |
Some windowing functions calls that catch my attention for being used more than I expected...
API function | usage count |
---|---|
IsWindowState() | 26 |
ToggleFullscreen() | 20 |
SetWindowTitle() | 49 |
SetWindowPosition() | 11 |
SetWindowMinSize() | 14 |
SetWindowSize() | 27 |
...and some functions that catch my attention for being used way less than expected:
API function | usage count |
---|---|
SetWindowMonitor() | 0 |
GetWindowHandle() | 0 |
GetMonitorCount() | 1 |
GetCurrentMonitor() | 0 |
GetMonitorPosition() | 0 |
GetMonitorPhysicalWidth() | 0 |
GetMonitorPhysicalHeight() | 0 |
GetMonitorRefreshRate() | 0 |
GetWindowPosition() | 1 |
GetWindowScaleDPI() | 0 |
GetMonitorName() | 0 |
Drawing modes is an important part of raylib because they define framebuffer usage and drawing transformations. It surprises me that Mode2D
is not used that much, raylib by default uses a 2D mode but BeginMode2D()/EndMode2D()
allows custom camera transformations, essential for any professional 2d game. TextureMode
allows changing target framebuffer to draw, it's nice to see it is used a lot, it proves it's a really useful mode. BlendMode
and ScissorMode
are also used but it seems VrStereoMode
is not popular at all, probably because it was added quite late and not many users use raylib for VR development.
API function | usage count |
---|---|
BeginMode2D() | 10 |
EndMode2D() | 10 |
BeginMode3D() | 66 |
EndMode3D() | 66 |
BeginTextureMode() | 165 |
EndTextureMode() | 164 |
BeginShaderMode() | 31 |
EndShaderMode() | 32 |
BeginBlendMode() | 7 |
EndBlendMode() | 7 |
BeginScissorMode() | 10 |
EndScissorMode() | 10 |
BeginVrStereoMode() | 1 |
EndVrStereoMode() | 1 |
Shaders is an important raylib feature and it seems it's being used a lot, despite most users just stuck to the basic calls: load the shader, get a location, set a value for the location and unload the shader. As seen in previous table, ShaderMode
is also useful.
API function | usage count |
---|---|
LoadShader() | 77 |
LoadShaderFromMemory() | 1 |
GetShaderLocation() | 128 |
GetShaderLocationAttrib() | 1 |
SetShaderValue() | 118 |
SetShaderValueV() | 1 |
SetShaderValueMatrix() | 1 |
SetShaderValueTexture() | 1 |
UnloadShader() | 38 |
Some functions were added for more advance matrix transformation usage but most of them do not seem to be that popular:
API function | usage count |
---|---|
GetCameraMatrix() | 0 |
GetCameraMatrix2D() | 0 |
GetWorldToScreen() | 5 |
GetWorldToScreenEx() | 0 |
GetWorldToScreen2D() | 2 |
GetScreenToWorld2D() | 8 |
File system functionality is quite new (callbacks system, memory data loading) and doesn't seem to be used much yet:
API function | usage count |
---|---|
SetTraceLogCallback() | 1 |
SetLoadFileDataCallback() | 0 |
SetSaveFileDataCallback() | 0 |
SetLoadFileTextCallback() | 0 |
SetSaveFileTextCallback() | 0 |
LoadFileData() | 7 |
UnloadFileData() | 5 |
SaveFileData() | 2 |
LoadFileText() | 7 |
UnloadFileText() | 1 |
SaveFileText() | 4 |
but some file utilities prove to be quite useful:
API function | usage count |
---|---|
IsFileExtension() | 274 |
GetFileExtension() | 38 |
GetFileName() | 128 |
GetFileNameWithoutExt() | 34 |
GetDirectoryPath() | 29 |
IsFileDropped() | 32 |
GetDroppedFiles() | 33 |
ClearDroppedFiles() | 35 |
In terms of Inputs, the standard keyboard/mouse calls are the most used ones:
API function | usage count |
---|---|
IsKeyPressed() | 846 |
IsKeyDown() | 708 |
IsKeyReleased() | 34 |
IsMouseButtonPressed() | 217 |
IsMouseButtonDown() | 98 |
IsMouseButtonReleased() | 44 |
GetMouseX() | 56 |
GetMouseY() | 53 |
GetMousePosition() | 233 |
GetMouseWheelMove() | 28 |
It seems gamepad/touch/gestures inputs are not used that much:
API function | usage count |
---|---|
IsGamepadButtonPressed() | 7 |
IsGamepadButtonDown() | 54 |
IsGamepadButtonReleased() | 0 |
IsGamepadButtonUp() | 0 |
GetGamepadButtonPressed() | 6 |
GetGamepadAxisCount() | 4 |
GetGamepadAxisMovement() | 38 |
GetGamepadMappings() | 0 |
GetTouchX() | 0 |
GetTouchY() | 0 |
GetTouchPosition() | 11 |
IsGestureDetected() | 26 |
About the Camera functions, only the calls used in examples:
API function | usage count |
---|---|
SetCameraMode() | 59 |
UpdateCamera() | 77 |
SetCameraPanControl() | 0 |
SetCameraAltControl() | 0 |
SetCameraSmoothZoomControl() | 0 |
SetCameraMoveControls() | 1 |
The most used shapes drawing functions (+20 calls) are the following ones, interesting to note that most of the shapes functions are used several times.
API function | usage count |
---|---|
DrawRectangle() | 1607 |
DrawCircle() | 266 |
DrawRectangleRec() | 196 |
DrawRectangleLines() | 160 |
DrawRectangleLinesEx() | 49 |
DrawLine() | 193 |
DrawCircleV() | 76 |
DrawPixel() | 46 |
DrawTriangle() | 49 |
Shapes collision checking functions:
API function | usage count |
---|---|
CheckCollisionRecs() | 125 |
CheckCollisionCircles() | 24 |
CheckCollisionCircleRec() | 28 |
CheckCollisionPointRec() | 208 |
CheckCollisionPointCircle() | 19 |
CheckCollisionPointTriangle() | 2 |
CheckCollisionLines() | 0 |
GetCollisionRec() | 4 |
As expected, the most used functions for the textures
module are the ones to load image/texture/render-texture and the drawing ones:
API function | usage count |
---|---|
LoadImage() | 222 |
UnloadImage() | 209 |
LoadTexture() | 503 |
LoadTextureFromImage() | 169 |
LoadRenderTexture() | 99 |
UnloadTexture() | 347 |
UnloadRenderTexture() | 88 |
DrawTexture() | 879 |
DrawTextureRec() | 132 |
DrawTexturePro() | 306 |
There are also some other functions that are used more than I expected, usually for image management:
API function | usage count |
---|---|
ExportImage() | 50 |
ImageFormat() | 76 |
SetTextureFilter() | 62 |
GenImageChecked() | 19 |
ImageCopy() | 18 |
ImageResize() | 38 |
ImageColorReplace() | 31 |
ImageDrawRectangle() | 22 |
ImageDraw() | 48 |
Fade() | 600 |
GetColor() | 198 |
GetPixelDataSize() | 15 |
All the other functions are usually under 10 calls but most of them have been used several times.
Personally, I consider the text
module one of the most important ones in raylib, it offer a lot of functionality to deal with text and fonts. Actually, it has the most used raylib function: DrawText()
. Here the most used ones:
API function | usage count |
---|---|
DrawText() | 2527 |
DrawTextEx() | 253 |
MeasureText() | 316 |
MeasureTextEx() | 43 |
TextFormat() | 655 |
DrawFPS() | 72 |
LoadFont() | 76 |
LoadFontEx() | 19 |
UnloadFont() | 53 |
GetFontDefault() | 67 |
Text management functions usage, some of them seem to be quite useful:
API function | usage count |
---|---|
TetGlyphIndex() | 0 |
TextCopy() | 18 |
TextIsEqual() | 13 |
TextLength() | 23 |
TextSubtext() | 17 |
TextReplace() | 3 |
TextInsert() | 2 |
TextJoin() | 11 |
TextSplit() | 21 |
TextAppend() | 1 |
TextFindIndex() | 2 |
TextToUpper() | 10 |
TextToLower() | 4 |
TextToPascal() | 4 |
TextToInteger() | 56 |
TextToUtf8() | 1 |
This module offers a set of functions to draw 3d models (immediate-mode) and also generate/load meshes to be drawn as models.
Here there are the most used immediate-mode 3d drawing functions, interesting to see that the other 3d shapes are called less than 10 times and some of them are never used: DrawPoint3D()
, DrawTriangle3D()
, DrawTriangleStrip3D()
. Does it worth to keep (and maintain) those functions?
API function | usage count |
---|---|
DrawCube() | 89 |
DrawCubeWires() | 34 |
DrawSphere() | 24 |
DrawGrid() | 39 |
In terms of mesh generation functions, we have:
API function | usage count |
---|---|
GenMeshPoly() | 3 |
GenMeshPlane() | 3 |
GenMeshCube() | 27 |
GenMeshSphere() | 6 |
GenMeshHemiSphere() | 2 |
GenMeshCylinder() | 6 |
GenMeshTorus() | 7 |
GenMeshKnot() | 2 |
GenMeshHeightmap() | 1 |
GenMeshCubicmap() | 13 |
General Model/Mesh loading and drawing functions, the most used ones (> 5 times):
API function | usage count |
---|---|
LoadModel() | 114 |
LoadModelFromMesh() | 63 |
UnloadModel() | 86 |
UploadMesh() | 20 |
SetMaterialTexture() | 17 |
DrawModel() | 108 |
DrawModelEx() | 14 |
Intersting to note that most of the functions provided by the models
module API are hardly ever used...
Collisions checking functions neither seem to be used a lot...
API function | usage count |
---|---|
CheckCollisionSpheres() | 0 |
CheckCollisionBoxes() | 8 |
CheckCollisionBoxSphere() | 2 |
GetRayCollisionSphere() | 1 |
GetRayCollisionBox() | 4 |
GetRayCollisionModel() | 2 |
GetRayCollisionMesh() | 0 |
GetRayCollisionTriangle() | 1 |
GetRayCollisionQuad() | 1 |
The most used functions are the expected ones: device management and load/play sounds/music:
API function | usage count |
---|---|
InitAudioDevice() | 50 |
CloseAudioDevice() | 42 |
LoadSound() | 95 |
UnloadSound() | 82 |
PlaySound() | 154 |
LoadMusicStream() | 41 |
UnloadMusicStream() | 32 |
PlayMusicStream() | 48 |
UpdateMusicStream() | 34 |
StopMusicStream() | 24 |
SetMusicVolume() | 14 |
Note that IsAudioDeviceReady()
and IsMusicStreamPlaying()
functions that have never been used and raw AudioStream
functionality hardly ever used.
-
It's interesting to see how much functionality of raylib is actually used. In general, most users just use a small fraction of the functionality provided by the library, mostly focused on 2d development for small test games and demos.
-
raylib 1.0 provided a total of 80 API functions, after 8 years of development that number has grown up to 475 API functions but it seems the most used ones still belong to that first 80-functions set that had hardly changed (in terms of signature) in 8 years.
-
Some of the new functionality that proved to be useful was the file management functionality, some image processing functions and the text management functionality.
-
A lot of work has been put in the 3d models API but it does not seem to be used a lot yet, 3d is a complex topic and dealing with it in a low-level library like raylib requires a big amount of work in comparison to a professional engine.
-
There are many functions that only a small numbers of users require and functions that bobody is using, some of those functions are user-contributed because those users required that functionality. The functions with 0 use-case worth a separate analysis to decide if they should really be in the library.
In general, I was not surprised by the results obtained. I knew the most used functions would be 2d related ones, considering that most users are probably students learning about videogames programming and using the basic features provided in the examples. Personally the most interesting part of this analysis was seeing the functions that nobody is using, those cases worth a further analysis, maybe those functions are actually not that useful for the raylib target user or they were simply added recently and not many user had time to try them.
Feel free to comment! :D
Interesting analysis. I'd recommend not pruning functionality too early. It might take some time for the code base to mature and use certain functions. I've been using RayLib on-and-off for many months now and there are still parts of its functionality I'd like to explore. The usage data might also indicate where documentation and/or examples might be beneficial to highlight functionality.
A rich set of functions also makes RayLib more attractive to adopt when someone is looking for a library. I have not yet needed to check for Ray-Sphere collisions, but I'm glad the function is there. I don't look at the API names and say to myself "That's random! Why is that there?" Your feature development seems very logical so 475 is definitely greater than 80, or even 400 in that regard.
Some examples, using the the functions you mention above as not being implemented much:
Might need a more mature code base: VR support
I've long thought that the VR support is very cool. I don't have a VR headset currently, but if I did, I'd love to see some of my 3D code in VR. Since I don't have one, however, I haven't coded for it. Even if I did have one, I would probably work on my 3D rendering on a monitor before adding in VR display. Once things looked good on a monitor, only then would I build in the VR -- once things are 90% done. If someone is making a game, etc., adoption of functions like VR might come later in the process, when the focus is on polish and porting to other platforms. Perhaps likewise Gamepad functions?
Might need some documentation: DrawTriangle3D()
I tried it once, but some triangles wouldn't render. Then I looked at the code in models.c and saw that the vertexes must be specified in counter-clockwise order. I had no idea how to sort them in this fashion, so I gave up and did something else instead. Also, counter-clockwise with respect to what? If one views the same triangle from one side, going from A to B to C, the order might be clockwise, and from the other side, it will counter-clockwise. Likewise, I find meshes and models daunting.
Might need an example: BeginMode2D
This was surprising to me, too. When I first started with RayLib I thought that all 2D drawing had to be done within
BeginMode2D
. Then I noticed it didn't. Sometimes old habits die hard, or old code dies hard, so I'd do some basic viewport translation and zoom without using the camera functions inBeginMode2D
, e.g., for Mandelbrot or other displays. The other thing I find useful is that RayGUI can be sent to screen coordinates before drawing inBeginMode2D
, which basically makes it easy for the GUI to be an "overlay" independent of what's on screen, just like it is withBeginMode3D
. That makes life easy.Might make life easy for future users.: CheckCollisionSphere()
I presume as RayLib becomes more popular, coders with less experience may constitute a larger percentage of users. For earlier adopters of RayLib, checking for sphere collision may be so easy they don't think to look for a function. "Is the distance between the centers greater than radius1+radius 2?" can be coded in one line. But if someone doesn't know that it's nice to have. Moreover, your versions of such functions are often more efficient.
You would have gotten a usage stat of 1 for
DrawPoint3D
from me, but the code you analyzed (3dSierpinski-IFS.cpp) still had it as a home-cooked function since I wrote it before the pull request. Maybe I should update it? :-) That said, I imagine that fewer people will use RayLib for a scatter plot than for game development, but you never know the ways it might go.