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
@ProfJski Thank you very much for reading and commenting! No worries, no plans to prune functionality for the moment. The analysis was intended to understand usage of the library.
About VR support, I understand that feature is quite advance and most user do not require it, I implemented it because I worked with Oculus Rift for some time and it was a personal project just to understand the technology behind VR, so, I finally reviewed it and kept it inside the library. Note that is an VR simulator (basically the stereo rendering), integrating it with a real VR device would require that device SDK. In any case, it could be useful for prototyping or even custom VR devices.
About
DrawTriangle3D()
, that's the expected behaviour. In 3D world triangles usually require a normal vector pointing in some direction to determine the visible surface, the automatic way to define that vector is defining its vertex in a specific order, that order depends on the graphics API configuration. By default OpenGL uses counter-clock-wise order to define the normal for a triangle. There is some option to make the triangle surface visible from both sides but it's not the common approach, actually, it's more common to define two triangles using the same vertex if you want two faces.About
DrawMode2D()
, by default raylib is configured for 2d mode but sometimes more advance transformations are required over the "2d camera mode", that mode allows applying those transformations using a comprehensiveCamera2D
object (movements, offset, zoom, rotation). Another option to do the same (but a bit more complex) could be using aRenderTexture
.About
CheckCollisionSphere()
, no plans to remove it. :DActually, I did not include the .cpp files in the analysis! I just included them and repeated the process right now, full results: