aboutsummaryrefslogtreecommitdiff
path: root/content/notes
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2025-04-11 21:17:07 +0200
committerMitja Felicijan <mitja.felicijan@gmail.com>2025-04-11 21:17:07 +0200
commita6bc7a7697ac4745d9f724811cecfafb98b9f209 (patch)
tree7feef2a3b4a537591a6690965ef702ff3e9cc82f /content/notes
parent62f954cf1afc8bfc17ef3622154f3ec025a1b69e (diff)
downloadmitjafelicijan.com-a6bc7a7697ac4745d9f724811cecfafb98b9f209.tar.gz
Update
Diffstat (limited to 'content/notes')
-rw-r--r--content/notes/2025-04-08-embedding-game-assets-within-your-binary.md246
1 files changed, 0 insertions, 246 deletions
diff --git a/content/notes/2025-04-08-embedding-game-assets-within-your-binary.md b/content/notes/2025-04-08-embedding-game-assets-within-your-binary.md
deleted file mode 100644
index 70f5d0f..0000000
--- a/content/notes/2025-04-08-embedding-game-assets-within-your-binary.md
+++ /dev/null
@@ -1,246 +0,0 @@
1---
2title: Embedding game assets within your executable binary
3url: embedding-game-assets-within-your-binary.html
4date: 2025-04-08T16:13:13+02:00
5type: note
6draft: false
7tags: [gamedev]
8---
9
10## Why?
11
12Normally, assets live outside the binary, but there is a valid reason to embed
13them. Specially if you do not want to deal with reading and parsing external
14files. This makes development and distribution much easier.
15
16In this example, I'm using [raylib](https://github.com/raysan5/raylib) and C,
17but this can also be done with [SDL](https://github.com/libsdl-org/SDL) or
18other libraries.
19
20Code for these notes is available as an
21[embedding-binary-data.tar.xz](/assets/notes/embedding-binary-data.tar.xz)
22tarball, but beware that this only includes the Linux build of raylib so please
23change to appropriate operating system.
24
25You can also check code on GitHub
26[@mitjafelicijan/probe/c-embedding-data](https://github.com/mitjafelicijan/probe/tree/master/c-embedding-data).
27
28## Project structure
29
30We are going to keep it clean and simple here. I am using pre-build version of
31raylib just to simplify compilation. All the code is located in the `main.c`
32file, so refer to that.
33
34```
35├── data Contains assets
36│   ├── armor.png Image example
37│   └── dejavusans-mono.ttf Font example
38├── libs
39│   └── raylib-5.5_linux_amd64 Includes prebuild raylib
40├── main.c Main file
41└── Makefile Build instruction
42```
43
44## Main game loop
45
46We first need to setup main game loop and get the game running.
47
48```c
49#include <stdio.h>
50#include "raylib.h"
51
52int main(void) {
53 SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_VSYNC_HINT | FLAG_WINDOW_HIGHDPI);
54 InitWindow(900, 400, "Embedding assets");
55 SetTargetFPS(60);
56
57 while (!WindowShouldClose()) {
58 BeginDrawing();
59 ClearBackground(BLACK);
60 EndDrawing();
61 }
62
63 CloseWindow();
64 return 0;
65}
66```
67
68And then prepare basic `Makefile` to compile this.
69
70```make
71RAYLIB_VER := 5.5_linux_amd64
72CC ?= cc
73CFLAGS := -Wall -Wextra -Wunused -Wno-unused-parameter -Wswitch-enum -Wpedantic -Wno-bitwise-instead-of-logical
74LDFLAGS := -I./libs -I./libs/raylib-$(RAYLIB_VER)/include ./libs/raylib-$(RAYLIB_VER)/lib/libraylib.a -lm
75
76game:
77 $(CC) $(CFLAGS) -o game main.c $(LDFLAGS)
78```
79
80Now we can compile with `make game` and get the following.
81
82![Basic window](/assets/notes/embedding-window.png)
83
84## Converting assets to C header files
85
86Here we use [`xxd`](https://linux.die.net/man/1/xxd) which is used to make a
87hexdump or do the reverse. This tool contains an interesting option `-i` which
88allows us to output the file in C include file style. And this is exactly what
89we need.
90
91Modified `Makefile` now looks like.
92
93```make
94RAYLIB_VER := 5.5_linux_amd64
95CC ?= cc
96CFLAGS := -Wall -Wextra -Wunused -Wno-unused-parameter -Wswitch-enum -Wpedantic -Wno-bitwise-instead-of-logical
97LDFLAGS := -I./libs -I./libs/raylib-$(RAYLIB_VER)/include ./libs/raylib-$(RAYLIB_VER)/lib/libraylib.a -lm
98
99game: embed
100 $(CC) $(CFLAGS) -o game main.c $(LDFLAGS)
101
102embed:
103 xxd -i data/armor.png > data/armor.h
104 xxd -i data/dejavusans-mono.ttf > data/dejavusans-mono.h
105```
106
107This converted binary data files into C header style files which contain the
108array of bytes and the size of the array of bytes. This will be useful later
109with raylib code.
110
111If we execute `make embed` we will create C header files. But running `make
112game` will also call embed as well, so no need to call it separately.
113
114An example of such a file (in our case armor.png) looks like this.
115
116```c
117// data/armor.h
118unsigned char data_armor_png[] = {
119 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
120 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x01, 0x40,
121 0x08, 0x06, 0x00, 0x00, 0x00, 0xcd, 0x90, 0xa5, 0xaa, 0x00, 0x00, 0x00,
122 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00,
123 0x00, 0x06, 0x62, 0x4b, 0x47, 0x44, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
124 0xa0, 0xbd, 0xa7, 0x93, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73,
125 0x00, 0x00, 0x17, 0x12, 0x00, 0x00, 0x17, 0x12, 0x01, 0x67, 0x9f, 0xd2,
126 0x52, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 0x45, 0x07, 0xdc, 0x0c,
127 0x1b, 0x07, 0x30, 0x17, 0xb2, 0x1a, 0xee, 0xda, 0x00, 0x00, 0x20, 0x00,
128 ...
129 0x81, 0x98, 0xa6, 0xb9, 0x2d, 0x37, 0xac, 0x6d, 0x57, 0xb0, 0xed, 0xea,
130 0x86, 0xac, 0xa1, 0xa6, 0x6b, 0xf8, 0x54, 0x1f, 0x8e, 0xe3, 0x90, 0xcb,
131 0xe6, 0x36, 0x7d, 0x4e, 0x6b, 0xab, 0xcb, 0x78, 0xbd, 0x5e, 0x6c, 0xc7,
132 0x01, 0x8f, 0xa7, 0x5e, 0x46, 0x22, 0x37, 0x17, 0x76, 0x13, 0x4d, 0x6c,
133 0xab, 0x0c, 0xb0, 0x89, 0x26, 0x9a, 0x68, 0xe2, 0x45, 0xa3, 0x99, 0x2b,
134 0x34, 0xd1, 0x44, 0x13, 0xff, 0xb7, 0xf8, 0x27, 0x9b, 0x96, 0x5c, 0x0d,
135 0x8b, 0xbb, 0x21, 0xa6, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44,
136 0xae, 0x42, 0x60, 0x82
137};
138unsigned int data_armor_png_len = 118324;
139```
140
141## Embedding and compiling
142
143Now it's time to include this files in our `main.c` code and use them. raylib's
144API fits perfectly with this style of converting binary files.
145
146```c
147#include <stdio.h>
148#include "raylib.h"
149
150#include "data/armor.h"
151#include "data/dejavusans-mono.h"
152
153#define FONT_SIZE 24
154
155int main(void) {
156 SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_VSYNC_HINT | FLAG_WINDOW_HIGHDPI);
157 InitWindow(900, 400, "Embedding assets");
158 SetTargetFPS(60);
159
160 // Load font from memory.
161 Font font = LoadFontFromMemory(".ttf", data_dejavusans_mono_ttf, data_dejavusans_mono_ttf_len, FONT_SIZE, NULL, 0);
162 SetTextureFilter(font.texture, TEXTURE_FILTER_TRILINEAR); // Font antialising.
163
164 // Load image from memory and create texture from it.
165 Image armor = LoadImageFromMemory(".png", data_armor_png, data_armor_png_len);
166 Texture2D armor_texture = LoadTextureFromImage(armor);
167 UnloadImage(armor);
168
169 while (!WindowShouldClose()) {
170 BeginDrawing();
171 ClearBackground(BLACK);
172
173 // Draw the armor texture.
174 DrawTexture(armor_texture, 20, 20, WHITE);
175
176 // Draw some text on the screen.
177 DrawTextEx(font, "Hello embedded assets.", (Vector2){ 400, 20 }, FONT_SIZE, 0, WHITE);
178 DrawTextEx(font, "This is example how we can use embedded fonts.", (Vector2){ 400, 50 }, FONT_SIZE - 4, 0, WHITE);
179
180 EndDrawing();
181 }
182
183 UnloadTexture(armor_texture);
184 CloseWindow();
185 return 0;
186}
187```
188
189This makes embedding assets quite straightforward. All we need to do is include
190header files and then provide byte arrays to the appropriate functions. Like I
191said raylib has a bunch of `FromMemory` functions that make this quite easy.
192
193This produces a single binary `game` that includes both assets, so there is no
194need to also copy over these assets. Mind you, these binaries can potentially
195get quite big and if that is the problem, you could always compile this into a
196`so` library and include that. This way you could create data packs for audio,
197graphics, etc. and ship that alongside your game binary.
198
199Run `make -B game` and run the game.
200
201![Basic window](/assets/notes/embedding-assets.png)
202
203## Honorable mention: C23-embed-directive
204
205`#embed` is a preprocessor directive to include (binary) resources in the
206build, where a resource is defined as a source of data accessible from the
207translation environment. This has been introduces with the C23 standard so it's
208quite new.
209
210Speaking plainly, `#embed` allows inclusion of binary data in a program
211executable image, as arrays of unsigned char or other types, without the need
212for an external script run from a Makefile.
213
214- I do like this approach, but some compilers might not support this feature,
215 and this is why I will be sticking with manual approach for now.
216- One additional drawback is that every time you compile your game, all the
217 assets need to be re-read and converted. I have not tested this heavily, but
218 this could potentially significantly increase build times when you have a lot
219 of assets.
220- Not using Make could be detrimental to incremental builds. But this would
221 also need to be tested. I do not claim that this is a real problem. Test this
222 yourself.
223
224Read more about [Binary resource
225inclusion](https://en.cppreference.com/w/c/preprocessor/embed).
226
227Below is a quick example how to use this new directive.
228
229```c
230#include <stdint.h>
231#include <stdio.h>
232
233const uint8_t image_data[] = {
234 #embed "image.png"
235};
236
237int main(void) {
238 printf("Image size: %d bytes\n", sizeof(image_data));
239 return 0;
240}
241```
242
243## Credits
244
245- https://opengameart.org/content/armor-icons-by-equipment-slot-with-transparency
246- https://dejavu-fonts.github.io/