Saturday, January 10, 2026
Visualize C code
CFLOW
GNU cflow analyzes a collection of C source files and prints a graph, charting control flow within the program.
GRAPHVIZ
Graphviz is open source graph visualization software. Graph visualization is a way of representing structural information as diagrams of abstract graphs and networks. It has important applications in networking, bioinformatics, software engineering, database and web design, machine learning, and in visual interfaces for other technical domains.
Ubuntu/debian install
sudo apt-get install cflow graphviz
Generate "callgraph.dot" (text file that image renderer will use)
#run this in project dir
cflow $(find . -name "*.c" -o -name "*.h") --format=dot > callgraph.dot
Render to PNG
# fdp (force-directed placement)
fdp -Tpng callgraph.dot -o callgraph.png
# circo (circular layout)
circo -Tpng callgraph.dot -o callgraph.png
# neato
neato -Tpng callgraph.dot -o callgraph.png
Result
To get SVG output use:
fdp -Tsvg callgraph.dot -o callgraph.svg
circo -Tsvg callgraph.dot -o callgraph.svg
neato -Tsvg callgraph.dot -o callgraph.svg
Friday, January 9, 2026
Compile C project to single text file for AI analysis.
Simple script that collects all your .h/.c files and adds project path before collected text - and ads them to single text file that you can then give to AI for analysis. Add script to you project root.
#!/usr/bin/env bash
# combine_sources.sh
# Creates a single text file (combined.txt) that contains the contents of every
# .c and .h file in the project, prefixed by a comment showing its path.
# Exit on any error
set -e
# Output file placed in the directory where this script lives (project root)
output="$(dirname "$0")/combined.txt"
> "$output" # truncate/create the file
# Find all .c and .h files under the project root (excluding the script itself)
find "$(dirname "$0")" -type f \( -name "*.c" -o -name "*.h" \) ! -name "$(basename "$0")" |
while IFS= read -r file; do
# Write a header comment with the relative path
echo "# $file" >> "$output"
# Append the file's contents
cat "$file" >> "$output"
# Add a blank line between files for readability
echo "" >> "$output"
done
echo "All source files have been combined into $output"
Saturday, January 3, 2026
stdint cheatsheet
| Category |
Type Name |
Description |
| Exact-width | int8_t | 8-bit signed integer |
uint8_t | 8-bit unsigned integer |
int16_t | 16-bit signed integer |
uint16_t | 16-bit unsigned integer |
int32_t | 32-bit signed integer |
uint32_t | 32-bit unsigned integer |
int64_t | 64-bit signed integer |
uint64_t | 64-bit unsigned integer |
| Minimum-width | int_least8_t | Signed int with at least 8 bits |
uint_least8_t | Unsigned int with at least 8 bits |
int_least16_t | Signed int with at least 16 bits |
uint_least16_t | Unsigned int with at least 16 bits |
int_least32_t | Signed int with at least 32 bits |
uint_least32_t | Unsigned int with at least 32 bits |
int_least64_t | Signed int with at least 64 bits |
uint_least64_t | Unsigned int with at least 64 bits |
| Fastest minimum-width | int_fast8_t | Fastest signed int with at least 8 bits |
uint_fast8_t | Fastest unsigned int with at least 8 bits |
int_fast16_t | Fastest signed int with at least 16 bits |
uint_fast16_t | Fastest unsigned int with at least 16 bits |
int_fast32_t | Fastest signed int with at least 32 bits |
uint_fast32_t | Fastest unsigned int with at least 32 bits |
int_fast64_t | Fastest signed int with at least 64 bits |
uint_fast64_t | Fastest unsigned int with at least 64 bits |
| Pointer-holding | intptr_t | Signed integer capable of holding a void pointer |
uintptr_t | Unsigned integer capable of holding a void pointer |
| Greatest-width | intmax_t | Maximum-width signed integer type |
| uintmax_t | Maximum-width unsigned integer type |
Friday, January 2, 2026
What are those "ARG1 | ARG2" wierd arguments in C?
In C, a bit-mask argument refers to passing an integer value to a function where individual bits (or groups of bits) in that integer represent boolean flags or options. This technique allows you to combine multiple settings into a single argument using bitwise operations.
How It Works
Each option is typically defined as a power of two (i.e., a number with only one bit set), so that each option corresponds to a unique bit position.
#define OPTION_A (1 << 0) // binary: 0001 (1)
#define OPTION_B (1 << 1) // binary: 0010 (2)
#define OPTION_C (1 << 2) // binary: 0100 (4)
#define OPTION_D (1 << 3) // binary: 1000 (8)
Combining Options
You combine options using the bitwise OR (|) operator:
int flags = OPTION_A | OPTION_C; // binary: 0101 (5)
This means both OPTION_A and OPTION_C are enabled.
Checking Options
Inside the function, you test whether a particular option is set using the bitwise AND (&) operator:
#include <stdio.h>
#define VERBOSE (1 << 0) // 1
#define UPPERCASE (1 << 1) // 2
#define DRY_RUN (1 << 2) // 4
void process_text(const char* text, int flags) {
if (flags & DRY_RUN) {
printf("[DRY RUN] Would process: %s\n", text);
return;
}
char buffer[100];
if (flags & UPPERCASE) {
// Simple uppercase conversion (for demo)
for (int i = 0; text[i]; i++) {
buffer[i] = (text[i] >= 'a' && text[i] <= 'z') ? text[i] - 32 : text[i];
}
buffer[strlen(text)] = '\0';
text = buffer;
}
if (flags & VERBOSE) {
printf("Processing text: %s\n", text);
} else {
printf("%s\n", text);
}
}
int main() {
process_text("hello", VERBOSE | UPPERCASE); // Enables both options
process_text("world", DRY_RUN);
return 0;
}
How many flags can you stack?
It depends on the size (in bits) of the integer type you use:
| Type |
Bits |
Max Distinct Flags |
| unsigned char / uint8_t |
8 |
8 |
| unsigned short / uint16_t |
16 |
16 |
| unsigned int / uint32_t |
32 |
32 |
| unsigned long long / uint64_t |
64 |
64 |
Another 32bit example
#include <stdint.h>
#include <stdio.h>
// Mouse buttons
#define LMOUSEBUTTON (1U << 0) // Left mouse button
#define RMOUSEBUTTON (1U << 1) // Right mouse button
#define MMOUSEBUTTON (1U << 2) // Middle mouse button
// Keyboard modifier keys
#define SHIFTKEY (1U << 3)
#define CTRLKEY (1U << 4)
#define ALTKEY (1U << 5)
#define SUPERKEY (1U << 6) // Windows / Command key
// Movement keys (WASD + arrows)
#define W_KEY (1U << 7)
#define A_KEY (1U << 8)
#define S_KEY (1U << 9)
#define D_KEY (1U << 10)
#define UP_ARROW (1U << 11)
#define DOWN_ARROW (1U << 12)
#define LEFT_ARROW (1U << 13)
#define RIGHT_ARROW (1U << 14)
// Action keys
#define SPACE_KEY (1U << 15)
#define ENTER_KEY (1U << 16)
#define ESC_KEY (1U << 17)
#define TAB_KEY (1U << 18)
// Common game keys
#define E_KEY (1U << 19)
#define Q_KEY (1U << 20)
#define R_KEY (1U << 21)
#define F_KEY (1U << 22)
#define MOUSE_WHEEL_UP (1U << 23)
#define MOUSE_WHEEL_DOWN (1U << 24)
// Extra (reserved for future use or custom actions)
#define CUSTOM_1 (1U << 25)
#define CUSTOM_2 (1U << 26)
#define CUSTOM_3 (1U << 27)
#define CUSTOM_4 (1U << 28)
// You can go up to (1U << 31) safely on 32-bit unsigned
// Optional: Combined masks
#define MODIFIERS_MASK (SHIFTKEY | CTRLKEY | ALTKEY | SUPERKEY)
#define MOVEMENT_MASK (W_KEY | A_KEY | S_KEY | D_KEY | \
UP_ARROW | DOWN_ARROW | LEFT_ARROW | RIGHT_ARROW)
// Helper function to check if a specific key/flag is pressed
static inline int is_key_pressed(uint32_t input_state, uint32_t key_flag)
{
return (input_state & key_flag) != 0;
}
// Example: Check if running (W + SHIFT)
static inline int is_running(uint32_t input_state)
{
return is_key_pressed(input_state, W_KEY) && is_key_pressed(input_state, SHIFTKEY);
}
int main(void)
{
// Simulate an input state: Left mouse + Shift + W pressed
uint32_t state = LMOUSEBUTTON | SHIFTKEY | W_KEY;
if (is_key_pressed(state, LMOUSEBUTTON))
{
printf("Left mouse is pressed\n");
}
if (is_running(state))
{
printf("Player is running forward!\n");
}
// Additional example checks
if (is_key_pressed(state, SHIFTKEY))
{
printf("Shift key is held\n");
}
return 0;
}
Notes:
* All values use 1U (unsigned) to avoid undefined behavior with shifts ≥16 on some systems.
* Stays safely within 32 bits (bits 0–28 used above; bits 29–31 are free).
* You can add more (e.g., 1U << 29, 1U << 30, 1U << 31) if needed.
* Avoid 1 << 31 without the U suffix — that’s a signed overflow!
Wednesday, December 31, 2025
Color in programming
24‑bit color uses three 8‑bit channels—red, green, and blue. Allowing 256 intensity levels per channel. This yields 16,777,216 possible colors, often called “true color.”
/* 24‑bit RGB (8 bits per channel, no alpha) */
typedef struct {
unsigned char r; // red 0‑255
unsigned char g; // green 0‑255
unsigned char b; // blue 0‑255
} rgb24_t;
rgb24_t sky = {135, 206, 235}; // light sky blue
32‑bit color adds an extra 8‑bit channel to the 24‑bit RGB format. The fourth channel is typically an alpha (transparency) channel, giving each pixel 256 levels of opacity.
/* 32‑bit RGBA (8 bits per channel, includes alpha) */
typedef struct {
unsigned char r; // red
unsigned char g; // green
unsigned char b; // blue
unsigned char a; // alpha (0 = transparent, 255 = opaque)
} rgba32_t;
rgba32_t pixel = {255, 0, 0, 128}; // semi‑transparent red
OpenGL supports color components with more than 8 bits (256 levels) per channel. Modern OpenGL provides several higher‑precision formats:
| Bits per component |
Typical internal format |
Typical use case |
| 10 bits |
GL_RGB10_A2 |
HDR rendering, packed 10‑bit RGB + 2‑bit alpha |
| 11 bits |
GL_R11F_G11F_B10F |
HDR with floating‑point storage |
| 16 bits (fixed) |
GL_RGB16, GL_RGBA16 |
High‑precision color buffers, scientific visualization |
| 16 bits (float) |
GL_R16F, GL_RG16F, GL_RGB16F, GL_RGBA16F |
HDR rendering, tone‑mapping pipelines |
| 32 bits (float) |
GL_R32F, GL_RG32F, GL_RGB32F, GL_RGBA32F |
Full‑precision HDR, linear color pipelines |
| 64 bits (float) |
GL_R64F, GL_RG64F, GL_RGB64F, GL_RGBA64F |
Very high dynamic range, scientific computing |
Photoshop (most popular graphical editor) supports:
| OpenGL format (bits per component) |
Photoshop equivalent |
10 bits – GL_RGB10_A2 |
Not available; Photoshop does not expose a 10‑bit packed RGB + 2‑bit alpha mode. |
11 bits – GL_R11F_G11F_B10F |
Not available; Photoshop has no 11‑bit floating‑point channel layout. |
16 bits (fixed) – GL_RGB16, GL_RGBA16 |
16‑bit/channel (per‑channel integer) – Photoshop’s “16 Bits/Channel” mode matches these fixed‑point formats. |
16 bits (float) – GL_R16F, GL_RG16F, GL_RGB16F, GL_RGBA16F |
16‑bit/channel (per‑channel floating‑point) – Photoshop’s “16 Bits/Channel (Float)” mode (available in the 16‑bit workflow) corresponds to these floating‑point formats. |
32 bits (float) – GL_R32F, GL_RG32F, GL_RGB32F, GL_RGBA32F |
32‑bit/channel (per‑channel floating‑point) – Photoshop’s “32 Bits/Channel (Float)” mode (used for HDR/EXR files) matches these formats. |
64 bits (float) – GL_R64F, GL_RG64F, GL_RGB64F, GL_RGBA64F |
Not available; Photoshop does not provide a 64‑bit per‑channel mode. |
Modern AMD Ryzen systems (including those with integrated Radeon Graphics, e.g., Ryzen 5000/7000/8000/9000 series APUs, and discrete Radeon GPUs like RX 6000/7000/8000 series) fully support 32-bit floating-point formats such as GL_R32F, GL_RG32F, GL_RGB32F, GL_RGBA32F for textures, renderbuffers, and framebuffers in OpenGL.
These formats are part of the core OpenGL specification since version 3.0 (via ARB_color_buffer_float and related extensions), and AMD's drivers have supported them reliably for many years across Radeon hardware. They are widely used for HDR rendering, deferred shading, compute shaders, and other high-precision pipelines without issues on modern AMD GPUs.
However, 64-bit floating-point formats (double-precision) such as GL_R64F, GL_RG64F, GL_RGB64F, GL_RGBA64F are not supported on modern AMD Ryzen integrated graphics or consumer-grade Radeon GPUs (RDNA/RDNA2/RDNA3/RDNA4 architectures).
These formats require the ARB_gpu_shader_fp64 extension (for double-precision computations) and hardware-level support for double-precision textures/renderbuffers. AMD's consumer GPUs, including Ryzen APUs, lack native double-precision floating-point units at the required performance/level for OpenGL exposure. While some older professional FirePro cards or specific extensions provided limited/emulated support in the past, current Ryzen systems do not expose these 64-bit float formats. Attempting to use them will result in errors (e.g., framebuffer incomplete or unsupported internal format).
In summary:
| 32-bit float formats |
Fully supported and recommended for high-dynamic-range workflows. |
| 64-bit float formats |
Not available; use 32-bit floats instead, or switch to Vulkan/DirectX for potential alternatives on AMD hardware. |
Display Limitations
Physical panel capabilities: Virtually all consumer monitors (including HDR-capable ones) use panels with 8-bit or 10-bit per channel native depth (some premium professional displays reach 12-bit, but rarely higher). No consumer display panel natively supports 32-bit or 64-bit floating-point per channel. The panel physically modulates light in fixed integer steps (typically 256–1024 levels per channel for SDR, up to ~1024 for HDR10).
Output signal limitations: Modern connections (HDMI 2.1, DisplayPort 1.4+) and GPUs support at most 12-bit integer per channel output for HDR (e.g., via HDR10 or Dolby Vision signaling). OpenGL's default framebuffer or swapchain typically outputs in 8-bit or 10-bit integer formats. Even when using floating-point pixel formats for the window surface (via extensions like WGL_ARB_pixel_format_float on Windows), the actual signal sent to the monitor converts to integer bits—usually 10-bit for HDR.
Tone mapping requirement: 32-bit float formats allow unbounded HDR values (>1.0, even thousands for bright lights) with high precision. However, monitors cannot display values outside their brightness range (e.g., typical HDR monitors peak at 400–2000 nits). You must apply tone mapping (and often gamma/sRGB conversion) in a shader before presenting to the default framebuffer. Without this, bright areas clamp or appear blown out, and subtle gradients may band due to final integer output.
HDR-specific constraints: True HDR monitors require 10-bit signaling (often with scRGB or HDR10 metadata). Floating-point rendering excels internally for HDR pipelines (avoiding clamping during lighting calculations), but the final output to an HDR display still reduces to 10-bit integer per channel after tone mapping.
File support
| File Format |
Channels supported |
32‑bit float per channel? |
Typical use case |
Notes / Limitations |
| OpenEXR (.exr) |
R, RG, RGB, RGBA, multi‑channel |
Yes (full support) |
HDR rendering, VFX, film production, photography |
Industry standard for HDR. Supports 16‑bit half‑float & 32‑bit float. Very flexible (layers, metadata). |
| Radiance HDR (.hdr) |
RGB only |
Yes |
HDR photography, environment maps |
Older format, simpler than EXR. Only RGB (no alpha), no metadata layers. |
| TIFF (.tif/.tiff) |
R, RG, RGB, RGBA, multi‑channel |
Yes (with Float32) |
Professional photography, scientific imaging |
Must specify 32‑bit float samples. Not all software reads float TIFF correctly. |
| PNG |
RGB, RGBA |
No (only 8‑bit or 16‑bit integer) |
General web & graphics |
PNG does not support floating‑point. |
| JPEG / JPEG 2000 |
RGB, grayscale |
No |
Photos, web |
Lossy or integer‑only. No float support. |
| DDS (.dds) |
R, RG, RGB, RGBA |
Yes (with R32F, RG32F, RGB32F, RGBA32F) |
Real‑time graphics, games |
DirectDraw Surface. Used by DirectX/OpenGL apps. Good for textures. |
| KTX / KTX2 (.ktx/.ktx2) |
R, RG, RGB, RGBA |
Yes |
Textures for OpenGL/Vulkan |
Modern container for GPU textures. Supports float formats natively. |
| PFM (.pfm) |
RGB, grayscale |
Yes |
Simple HDR images, academic/research |
Very basic format, no compression, no alpha, no metadata. |
| ILM OpenEXR (deep) |
Multi‑layer / deep RGBA |
Yes |
Advanced VFX (deep compositing) |
Extension of OpenEXR for deep images (multiple samples per pixel). |
Neither WebP nor BMP supports 32-bit floating-point per channel (i.e., true GL_R32F, GL_RG32F, GL_RGB32F, or GL_RGBA32F formats with IEEE 754 single-precision floats)
GIMP (current stable versions, e.g., 2.10/3.0 series as of late 2025) and Krita (current versions, e.g., 5.2+) fully support 32-bit floating-point per channel workflows. This corresponds exactly to OpenGL's GL_R32F, GL_RG32F, GL_RGB32F, and GL_RGBA32F formats: single-precision IEEE 754 floating-point data per channel, allowing unbounded HDR values (above 1.0 or even negative) with high dynamic range and precision.
| Software |
Key 32‑bit Float Features |
| GIMP |
- Convert images to 32‑bit floating point via Image → Precision → 32‑bit floating point (Linear or Perceptual gamma).
- Internal processing always 32‑bit float → true HDR editing (exposure, curves, etc. without clipping).
- Import/Export 32‑bit float through OpenEXR (.exr) and TIFF (Float32).
- Ideal for HDR photography, scientific imaging, or high‑precision intermediates (higher RAM usage).
|
| Krita |
- Native 32‑bit float per channel (set when creating or converting documents).
- ~23‑24 bits effective precision in 0‑1 range with extended dynamic range for HDR/scene‑referenced work.
- Optimized for digital painting in HDR (OCIO color management, unbounded blending, values > 1.0).
- Robust OpenEXR (.exr) import/export, including multilayer support – great for VFX/matte‑painting pipelines.
- Also supports 16‑bit half‑float; 32‑bit float offers maximum precision at higher RAM/performance cost.
|
C examples
Here are C language structs that directly match the memory layout and size of OpenGL's 32-bit floating-point texture/internal formats (GL_R32F, GL_RG32F, GL_RGB32F, GL_RGBA32F), along with simple usage examples.
#include <stdint.h>
#include <stdio.h>
// 1 channel: GL_R32F → one 32-bit float
typedef struct {
float r;
} Float1;
// 2 channels: GL_RG32F → two 32-bit floats
typedef struct {
float r;
float g;
} Float2;
// 3 channels: GL_RGB32F → three 32-bit floats
typedef struct {
float r;
float g;
float b;
} Float3;
// 4 channels: GL_RGBA32F → four 32-bit floats
typedef struct {
float r;
float g;
float b;
float a;
} Float4;
Size and Alignment Check
printf("Size of Float1 (GL_R32F): %zu bytes\n", sizeof(Float1)); // 4
printf("Size of Float2 (GL_RG32F): %zu bytes\n", sizeof(Float2)); // 8
printf("Size of Float3 (GL_RGB32F): %zu bytes\n", sizeof(Float3)); // 12
printf("Size of Float4 (GL_RGBA32F): %zu bytes\n", sizeof(Float4)); // 16
Usage Example
// Example 1: Single red channel value (e.g., heightmap or distance field)
Float1 height = { .r = 1234.567f };
printf("Height value: %.3f\n", height.r);
// Example 2: 2D position or normal (x, y)
Float2 position = { .r = -5.5f, .g = 10.0f };
printf("Position: (%.2f, %.2f)\n", position.r, position.g);
// Example 3: HDR color without alpha
Float3 hdr_color = { .r = 10.5f, .g = 2.3f, .b = 0.8f }; // bright sunlight-like
printf("HDR Color: r=%.2f g=%.2f b=%.2f\n",
hdr_color.r, hdr_color.g, hdr_color.b);
// Example 4: Full HDR color with alpha
Float4 hdr_rgba = { .r = 15.0f, .g = 8.0f, .b = 3.0f, .a = 1.0f };
printf("HDR RGBA: r=%.2f g=%.2f b=%.2f a=%.2f\n",
hdr_rgba.r, hdr_rgba.g, hdr_rgba.b, hdr_rgba.a);
// Example 5: Array of pixels (e.g., for uploading to OpenGL texture)
Float4 pixels[4] = {
{ 1.0f, 0.0f, 0.0f, 1.0f }, // red
{ 0.0f, 1.0f, 0.0f, 1.0f }, // green
{ 0.0f, 0.0f, 1.0f, 1.0f }, // blue
{ 10.0f, 10.0f, 10.0f, 1.0f } // very bright white (HDR)
};
// In real OpenGL code, you would upload like this:
// glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 2, 2, 0, GL_RGBA, GL_FLOAT, pixels);
SDL2 Example
// gcc -o float_texture main.c -lSDL2 -lGLEW -lGL
#include <SDL2/SDL.h>
#include <GL/glew.h> // or glad.h if you prefer GLAD
#include <stdio.h>
#include <stdlib.h>
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
#define TEX_WIDTH 4
#define TEX_HEIGHT 4
// Simple vertex shader
const char* vertexShaderSource = R"(
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 TexCoord;
void main() {
gl_Position = vec4(aPos, 0.0, 1.0);
TexCoord = aTexCoord;
}
)";
// Fragment shader with basic Reinhard tone mapping
const char* fragmentShaderSource = R"(
#version 330 core
in vec2 TexCoord;
out vec4 FragColor;
uniform sampler2D hdrTexture;
void main() {
vec4 color = texture(hdrTexture, TexCoord);
// Simple Reinhard tone mapping: color / (color + 1.0)
vec3 toned = color.rgb / (color.rgb + vec3(1.0));
// Gamma correction
toned = pow(toned, vec3(1.0/2.2));
FragColor = vec4(toned, color.a);
}
)";
GLuint compileShader(GLenum type, const char* source) {
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &source, NULL);
glCompileShader(shader);
GLint success;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
char infoLog[512];
glGetShaderInfoLog(shader, 512, NULL, infoLog);
printf("Shader compilation failed:\n%s\n", infoLog);
exit(1);
}
return shader;
}
int main(int argc, char* argv[]) {
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
printf("SDL_Init Error: %s\n", SDL_GetError());
return 1;
}
// Request OpenGL 3.3 core profile
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
// Important: Request floating-point framebuffer support (optional but helpful)
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
SDL_Window* window = SDL_CreateWindow("SDL2 + OpenGL 32-bit Float Texture",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
WINDOW_WIDTH, WINDOW_HEIGHT,
SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);
if (!window) {
printf("Window creation failed: %s\n", SDL_GetError());
SDL_Quit();
return 1;
}
SDL_GLContext context = SDL_GL_CreateContext(window);
if (!context) {
printf("OpenGL context creation failed: %s\n", SDL_GetError());
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
// Initialize GLEW (or GLAD)
glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK) {
printf("GLEW init failed\n");
return 1;
}
// Compile shaders
GLuint vertexShader = compileShader(GL_VERTEX_SHADER, vertexShaderSource);
GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);
GLuint shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
GLint success;
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
char infoLog[512];
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
printf("Program linking failed:\n%s\n", infoLog);
return 1;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
// Fullscreen quad vertices (position + texcoord)
float quadVertices[] = {
// positions // tex coords
-1.0f, 1.0f, 0.0f, 1.0f,
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 1.0f,
1.0f, -1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 1.0f, 1.0f
};
GLuint VAO, VBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
// Create GL_RGBA32F texture (4x4 for demo)
GLuint hdrTexture;
glGenTextures(1, &hdrTexture);
glBindTexture(GL_TEXTURE_2D, hdrTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, TEX_WIDTH, TEX_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Fill with HDR data (values > 1.0!)
float pixels[TEX_WIDTH * TEX_HEIGHT * 4] = {
5.0f, 0.1f, 0.1f, 1.0f, // bright red
0.1f, 4.0f, 0.1f, 1.0f, // bright green
0.1f, 0.1f, 8.0f, 1.0f, // bright blue
10.0f, 10.0f, 10.0f, 1.0f, // very bright white
0.5f, 0.5f, 0.5f, 1.0f, // gray
2.0f, 1.0f, 0.5f, 0.8f, // orange-ish with alpha
0.0f, 0.0f, 0.0f, 0.0f, // transparent black
1.0f, 1.0f, 1.0f, 1.0f, // normal white
// ... repeat or extend as needed
3.0f, 2.0f, 1.0f, 1.0f,
1.0f, 2.0f, 3.0f, 1.0f,
2.0f, 3.0f, 1.0f, 1.0f,
15.0f, 8.0f, 4.0f, 1.0f, // extremely bright
0.2f, 0.3f, 0.8f, 1.0f,
0.8f, 0.2f, 0.4f, 1.0f,
0.4f, 0.6f, 0.9f, 1.0f,
20.0f, 20.0f, 20.0f, 1.0f // blinding white
};
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, TEX_WIDTH, TEX_HEIGHT, GL_RGBA, GL_FLOAT, pixels);
// You can also create other formats:
// GLuint r32f_tex; glTexImage2D(..., GL_R32F, ...);
// GLuint rg32f_tex; glTexImage2D(..., GL_RG32F, ...);
// GLuint rgb32f_tex; glTexImage2D(..., GL_RGB32F, ...);
glUseProgram(shaderProgram);
glUniform1i(glGetUniformLocation(shaderProgram, "hdrTexture"), 0);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
int running = 1;
SDL_Event event;
while (running) {
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) running = 0;
}
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, hdrTexture);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
SDL_GL_SwapWindow(window);
}
// Cleanup
glDeleteTextures(1, &hdrTexture);
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteProgram(shaderProgram);
SDL_GL_DeleteContext(context);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
Monday, December 29, 2025
Bresenham's line algorithm
Bresenham's line algorithm is a highly efficient computer graphics method for drawing straight lines on pixel-based displays (rasters) by selecting the best pixels to approximate the true line, using only fast integer arithmetic (addition, subtraction, bit shifts) instead of slower floating-point math. It works by calculating an "error" or "decision" parameter at each step to decide whether the next pixel should be at the current Y-level or moved up (or down) by one pixel, efficiently creating the illusion of a continuous line by minimizing error.
// Compile with:
// gcc -std=c99 -O2 -Wall main.c -o main `sdl2-config --cflags --libs`
// gcc -std=c99 -O2 -Wall main.c -o main $(pkg-config --cflags --libs sdl2)
#include <SDL.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#define WIDTH 640
#define HEIGHT 480
// Bresenham's line algorithm to draw a line from (x0,y0) to (x1,y1)
void drawLine(Uint32* pixels, int x0, int y0, int x1, int y1, Uint32 color)
{
// Clamp coordinates to window bounds
if (x0 < 0) x0 = 0;
if (x0 >= WIDTH) x0 = WIDTH - 1;
if (y0 < 0) y0 = 0;
if (y0 >= HEIGHT) y0 = HEIGHT - 1;
if (x1 < 0) x1 = 0;
if (x1 >= WIDTH) x1 = WIDTH - 1;
if (y1 < 0) y1 = 0;
if (y1 >= HEIGHT) y1 = HEIGHT - 1;
int dx = abs(x1 - x0);
int dy = abs(y1 - y0);
int sx = (x0 < x1) ? 1 : -1;
int sy = (y0 < y1) ? 1 : -1;
int err = dx - dy;
while (1)
{
if (x0 >= 0 && x0 < WIDTH && y0 >= 0 && y0 < HEIGHT)
pixels[y0 * WIDTH + x0] = color;
if (x0 == x1 && y0 == y1) break;
int e2 = 2 * err;
if (e2 > -dy)
{
err -= dy;
x0 += sx;
}
if (e2 < dx)
{
err += dx;
y0 += sy;
}
}
}
int main(int argc, char* argv[])
{
if (SDL_Init(SDL_INIT_VIDEO) != 0)
{
fprintf(stderr, "SDL_Init Error: %s\n", SDL_GetError());
return 1;
}
SDL_Window* window = SDL_CreateWindow("Simple Paint - Connected Lines",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WIDTH, HEIGHT, SDL_WINDOW_SHOWN);
if (!window)
{
fprintf(stderr, "SDL_CreateWindow Error: %s\n", SDL_GetError());
SDL_Quit();
return 1;
}
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
SDL_Texture* texture = SDL_CreateTexture(renderer,
SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);
Uint32* pixels = malloc(WIDTH * HEIGHT * sizeof(Uint32));
if (!pixels)
{
fprintf(stderr, "Failed to allocate pixel buffer\n");
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
// White background (0xFFFFFFFF = ARGB white)
memset(pixels, 0xFF, WIDTH * HEIGHT * sizeof(Uint32));
int quit = 0;
int drawing = 0;
int prevX = 0, prevY = 0;
Uint32 drawColor = 0xFF000000; // Black (ARGB)
while (!quit)
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_QUIT:
quit = 1;
break;
case SDL_MOUSEBUTTONDOWN:
if (event.button.button == SDL_BUTTON_LEFT)
{
drawing = 1;
prevX = event.button.x;
prevY = event.button.y;
// Draw single pixel on click
if (prevX >= 0 && prevX < WIDTH && prevY >= 0 && prevY < HEIGHT)
pixels[prevY * WIDTH + prevX] = drawColor;
}
break;
case SDL_MOUSEBUTTONUP:
if (event.button.button == SDL_BUTTON_LEFT)
drawing = 0;
break;
case SDL_MOUSEMOTION:
if (drawing)
{
int currX = event.motion.x;
int currY = event.motion.y;
// Draw line from previous position to current
drawLine(pixels, prevX, prevY, currX, currY, drawColor);
prevX = currX;
prevY = currY;
}
break;
case SDL_KEYDOWN:
if (event.key.keysym.sym == SDLK_c) // Press 'C' to clear
{
memset(pixels, 0xFF, WIDTH * HEIGHT * sizeof(Uint32));
}
break;
}
}
// Update texture and render
SDL_UpdateTexture(texture, NULL, pixels, WIDTH * sizeof(Uint32));
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);
SDL_Delay(16); // ~60 FPS cap
}
free(pixels);
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
Sunday, December 28, 2025
Create C programming library
Static library
A static library (.a) is an archive of object (.o) files; at link time the linker copies needed object code from the .a into the final executable, making the executable self-contained.
You can link static .a lib at program build/compile time what you can't do is load/unload it while programm is running.
Install compiler tools and editor (see how easy it is on Ubuntu Linux :D)
🚀 sudo apt update
🚀 sudo apt install build-essential geany geany-plugins
Create file "lib.c" that will be our lib
#include <stdio.h>
#define STR_1 "MY STRING ONE"
#define STR_2 "MY STRING TWO"
#define STR_3 "MY STRING THREE"
void write_one(){
puts(STR_1);
}
void write_two(){
puts(STR_2);
}
void write_three(){
puts(STR_3);
}
Generate object (.o) file
🚀 gcc -O2 -Wall -c lib.c -o lib.o
Archive utility creates static lib.a library
🚀 ar rcs lib.a lib.o
List members of lib.a
🚀 ar t lib.a
lib.o
List symbols
🚀 nm -g lib.a
lib.o:
U puts
0000000000000000 T write_one
0000000000000020 T write_three
0000000000000010 T write_two
Now, let's pretrend we have new project where we will import our .a lib
#include <stdio.h>
int main(void) {
printf("Hello, world!\n");
write_one(); //let's try to call function from our lib
return 0;
}
Compile main.c with lib.a
🚀 gcc -o main main.c -L. -l:lib.a
main.c: In function ‘main’:
main.c:6:5: warning: implicit declaration of function ‘write_one’ [-Wimplicit-function-declaration]
6 | write_one();
| ^~~~~~~~~
The warning means the compiler didn't see a declaration for write_one() before you called it. So we either add declarations in our main.c file or use header file. This is why we use header .h files with libs.
main.c with declarations (lib function names)
#include <stdio.h>
void write_one(void);
void write_two(void);
void write_three(void);
int main(void) {
printf("Hello, world!\n");
write_one();
return 0;
}
Or other option, lib header
#ifndef LIB_H
#define LIB_H
void write_one(void);
void write_two(void);
void write_three(void);
#endif
#include <stdio.h>
#include "lib.h" //imported header
int main(void) {
printf("Hello, world!\n");
write_one();
return 0;
}
Dynamic library
Compile position-independent object
🚀 gcc -O2 -fPIC -Wall -c lib.c -o lib.o
Create shared library
🚀 gcc -shared -o lib.so lib.o
Link with explicit filename
🚀 gcc -O2 -Wall -o main main.c ./lib.so -I.
Show shared dependencies
🚀 ldd ./main
linux-vdso.so.1 (0x0000777e16876000)
./lib.so (0x0000777e16866000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x0000777e16600000)
/lib64/ld-linux-x86-64.so.2 (0x0000777e16878000)
Show exported symbols
🚀 nm -D lib.so
w __cxa_finalize@GLIBC_2.2.5
w __gmon_start__
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
U puts@GLIBC_2.2.5
0000000000001120 T write_one
0000000000001140 T write_three
0000000000001130 T write_two
Install lib into system
🚀 sudo cp libmylib.so /usr/local/lib/
🚀 sudo cp lib.h /usr/local/include/
🚀 sudo ldconfig
ldconfig is a system utility on Linux that maintains the shared‑library cache used by the dynamic linker (ld.so).
Scans directories listed in /etc/ld.so.conf and any directories added with the -L option in the command line.
Finds shared libraries (*.so, *.so.*) in those directories.
Creates/updates the cache file /etc/ld.so.cache, which stores the full paths of the libraries.
Creates symbolic links (e.g., libfoo.so → libfoo.so.1.2.3) for versioned libraries when the -v or -n options are used.
After that you can use:
🚀 gcc -O2 -Wall -o main main.c -L. -lmylibname -I.
Plugin system
dlopen is a POSIX function that loads a shared library (dynamic‑link library) into a running process at runtime. It is part of the dynamic loading API defined in <dlfcn.h>.
Create new "main-load.c" file
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
/* prototypes for function-pointer types matching the library symbols */
typedef void (*write_fn_t)(void);
int main(void){
void *handle;
write_fn_t write_one;
char *err;
handle = dlopen("./lib.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "dlopen: %s\n", dlerror());
return 1;
}
dlerror();
*(void **)(&write_one) = dlsym(handle, "write_one");
if ((err = dlerror()) != NULL) {
fprintf(stderr, "dlsym: %s\n", err);
dlclose(handle);
return 1;
}
write_one();
dlclose(handle);
return 0;
}
Compile main-load.c
gcc -O2 -Wall -o main main-load.c -ldl
dlopen is a POSIX function that loads a shared library (dynamic‑link library) into a running process at runtime. It is part of the dynamic loading API defined in <dlfcn.h>.
| Function |
Purpose |
dlsym(void *handle, const char *symbol) |
Retrieve the address of a symbol (function or variable) from the loaded library. |
dlclose(void *handle) |
Unload the library and release resources. |
dlerror(void) |
Return a human‑readable string describing the most recent error from dlopen, dlsym, or dlclose. |
We can provide symbol that are not known like this:
Plugin code:
#include <stdio.h>
#define STR_1 "MY STRING ONE"
#define STR_2 "MY STRING TWO"
#define STR_3 "MY STRING THREE"
void write_one() { puts(STR_1); }
void write_two() { puts(STR_2); }
void write_three() { puts(STR_3); }
/* ------------------ Plugin interface ------------------ */
// Function pointer type for the writes
typedef void (*write_fn_t)(void);
// Structure to describe one function
typedef struct {
const char *name;
write_fn_t func;
} PluginFunction;
// This is the well-known entry point the host will call
// It returns the number of functions and fills the array pointer
void get_plugin_functions(PluginFunction **funcs, int *count)
{
static PluginFunction functions[] = {
{ "write_one", write_one },
{ "write_two", write_two },
{ "write_three", write_three }
};
*funcs = functions;
*count = sizeof(functions) / sizeof(functions[0]);
}
Plugin loader code:
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
// Type for the write functions
typedef void (*write_fn_t)(void);
// Type for the plugin functions description
typedef struct {
const char *name;
write_fn_t func;
} PluginFunction;
// Prototype for the registration function that every plugin must export
typedef void (*get_plugin_functions_t)(PluginFunction **funcs, int *count);
int main(void)
{
void *handle = dlopen("./my-plugin.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "dlopen: %s\n", dlerror());
return 1;
}
// Clear any old errors
dlerror();
// Look up the registration function (this name is known in advance)
get_plugin_functions_t get_funcs = (get_plugin_functions_t)dlsym(handle, "get_plugin_functions");
char *err = dlerror();
if (err != NULL) {
fprintf(stderr, "dlsym (get_plugin_functions): %s\n", err);
dlclose(handle);
return 1;
}
// Ask the plugin to give us its functions
PluginFunction *functions = NULL;
int count = 0;
get_funcs(&functions, &count);
printf("Loaded %d functions from the plugin:\n", count);
// Call every discovered function
for (int i = 0; i < count; i++) {
printf(" Calling %s...\n", functions[i].name);
functions[i].func(); // Actually call it
}
dlclose(handle);
return 0;
}