Skip to content

Commit 4d9042b

Browse files
authored
Merge pull request #1495 from floooh/sokol-framebuffer
implement sokol_framebuffer.h
2 parents 5cc3e91 + 068edb4 commit 4d9042b

11 files changed

Lines changed: 5866 additions & 94 deletions

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
## Updates
22

3+
### 11-May-2026
4+
5+
- A new header `sokol_framebuffer.h` has been added which provides a 'CPU framebuffer'
6+
in 32-bits-per-pixel direct-color mode or 8-bits-per-pixel color-palette mode
7+
(Mode13H-style).
8+
9+
Implementation PR: https://github.com/floooh/sokol/pull/1495
10+
11+
New samples:
12+
- [framebuffer-sapp.c](https://floooh.github.io/sokol-html5/framebuffer-sapp.html): a 'simplest possible' example
13+
- [ilbm-sapp.c](https://floooh.github.io/sokol-html5/ilbm-sapp.html): load and display Amiga IFF ILBM images
14+
15+
The following side-projects have also been moved to sokol_framebuffer.h (see the
16+
sokol_framebuffer.h implementation PR for links to the side-project PRs):
17+
- [Doom on Sokol](https://floooh.github.io/doom-sokol/)
18+
- [Tiny Emulators](https://floooh.github.io/tiny8bit/)
19+
- [Pacman.c](https://floooh.github.io/pacman.c/pacman.html)
20+
321
### 04-May-2026
422

523
- sokol_app.h android: The sokol-app Android backend now uses the Choreographer

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
# Sokol
88

9-
[**See what's new**](https://github.com/floooh/sokol/blob/master/CHANGELOG.md) (**21-Apr-2026**: new header: sokol_letterbox.h)
9+
[**See what's new**](https://github.com/floooh/sokol/blob/master/CHANGELOG.md) (**11-May-2026**: new header: sokol_framebuffer.h)
1010

1111
[![Build](/../../actions/workflows/main.yml/badge.svg)](/../../actions/workflows/main.yml) [![Bindings](/../../actions/workflows/gen_bindings.yml/badge.svg)](/../../actions/workflows/gen_bindings.yml) [![build](https://github.com/floooh/sokol-zig/actions/workflows/main.yml/badge.svg)](https://github.com/floooh/sokol-zig/actions/workflows/main.yml) [![build](https://github.com/floooh/sokol-nim/actions/workflows/main.yml/badge.svg)](https://github.com/floooh/sokol-nim/actions/workflows/main.yml) [![Odin](https://github.com/floooh/sokol-odin/actions/workflows/main.yml/badge.svg)](https://github.com/floooh/sokol-odin/actions/workflows/main.yml)[![Rust](https://github.com/floooh/sokol-rust/actions/workflows/main.yml/badge.svg)](https://github.com/floooh/sokol-rust/actions/workflows/main.yml)[![Dlang](https://github.com/floooh/sokol-d/actions/workflows/build.yml/badge.svg)](https://github.com/floooh/sokol-d/actions/workflows/build.yml)[![C3](https://github.com/floooh/sokol-c3/actions/workflows/build.yml/badge.svg)](https://github.com/floooh/sokol-c3/actions/workflows/build.yml)
1212

@@ -85,6 +85,7 @@ useful details for integrating the Sokol headers into your own project with your
8585
- [**sokol\_color.h**](https://github.com/floooh/sokol/blob/master/util/sokol_color.h): X11 style color constants and functions for creating sg_color objects
8686
- [**sokol\_spine.h**](https://github.com/floooh/sokol/blob/master/util/sokol_spine.h): a sokol-style wrapper around the Spine C runtime (http://en.esotericsoftware.com/spine-in-depth)
8787
- [**sokol\_letterbox.h**](https://github.com/floooh/sokol/blob/master/util/sokol_letterbox.h): compute viewport params for rendering fixed-aspect-ratio content in a variable-aspect-ratio canvas
88+
- [**sokol\_framebuffer.h**](https://github.com/floooh/sokol/blob/master/util/sokol_framebuffer.h): provides CPU-framebuffers rendered via sokol_gfx.h
8889

8990
## 'Official' Language Bindings
9091

shdgen/shdgen.ts

Lines changed: 69 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,34 @@
11
import { parseArgs } from 'jsr:@std/cli@^1/parse-args';
22

3-
type Shader = { filename: string, prefix: string, prog: string };
4-
type Header = { path: string, shaders: Shader[] };
5-
6-
const headers: Header[] = [
7-
// sokol_gl.h
8-
{
9-
path: '../util/sokol_gl.h',
10-
shaders: [ { filename: 'sokol_gl.glsl', prefix: '_sgl', prog: 'shd' } ],
11-
},
12-
// sokol_debugtext.h
13-
{
14-
path: '../util/sokol_debugtext.h',
15-
shaders: [ { filename: 'sokol_debugtext.glsl', prefix: '_sdtx', prog: 'shd' } ],
16-
},
17-
// sokol_fontstash.h
18-
{
19-
path: '../util/sokol_fontstash.h',
20-
shaders: [ { filename: 'sokol_fontstash.glsl', prefix: '_sfons', prog: 'shd' } ],
21-
},
22-
// sokol_imgui.h
23-
{
24-
path: '../util/sokol_imgui.h',
25-
shaders: [ { filename: 'sokol_imgui.glsl', prefix: '_simgui', prog: 'shd' } ],
26-
},
27-
// sokol_nuklear.h
28-
{
29-
path: '../util/sokol_nuklear.h',
30-
shaders: [ { filename: 'sokol_nuklear.glsl', prefix: '_snk', prog: 'shd' } ],
31-
},
32-
// sokol_spine.h
33-
{
34-
path: '../util/sokol_spine.h',
35-
shaders: [ { filename: 'sokol_spine.glsl', prefix: '_sspine', prog: 'shd' } ],
36-
},
37-
];
38-
393
const denoArgs = parseArgs(Deno.args, {
40-
boolean: ['hlsl'],
41-
string: ['shdcroot', 'branch'],
4+
boolean: ['hlsl', 'local'],
5+
string: ['shdcroot', 'branch', 'only'],
426
default: {
437
hlsl: false,
8+
local: false,
449
shdcroot: '../../sokol-tools-bin',
4510
branch: 'master',
11+
only: null,
4612
},
4713
});
4814

15+
type Item = { header: string, shader: string, prefix: string, progs: string[] };
16+
17+
const items: Item[] = [
18+
{ header: '../util/sokol_gl.h', shader: 'sokol_gl.glsl', prefix: '_sgl', progs: ['shd'] },
19+
{ header: '../util/sokol_debugtext.h', shader: 'sokol_debugtext.glsl', prefix: '_sdtx', progs: ['shd'] },
20+
{ header: '../util/sokol_fontstash.h', shader: 'sokol_fontstash.glsl', prefix: '_sfons', progs: ['shd'] },
21+
{ header: '../util/sokol_imgui.h', shader: 'sokol_imgui.glsl', prefix: '_simgui', progs: ['shd'] },
22+
{ header: '../util/sokol_nuklear.h', shader: 'sokol_nuklear.glsl', prefix: '_snk', progs: ['shd'] },
23+
{ header: '../util/sokol_spine.h', shader: 'sokol_spine.glsl', prefix: '_sspine', progs: ['shd'] },
24+
{
25+
header: '../util/sokol_framebuffer.h',
26+
shader: 'sokol_framebuffer.glsl',
27+
prefix: '_sfb',
28+
progs: ['rgba8', 'palette8', 'render'],
29+
}
30+
];
31+
4932
function dirExists(path: string): boolean {
5033
try {
5134
return Deno.statSync(path).isDirectory;
@@ -62,34 +45,47 @@ async function main(): Promise<void> {
6245

6346
if (denoArgs.hlsl) {
6447
// only compile hlsl shaders into binaries (runs on CI)
65-
for (const hdr of headers) {
66-
for (const shd of hdr.shaders) {
67-
await compile(shd, true);
68-
}
48+
for (const item of items) {
49+
await compile(item);
6950
}
7051
} else {
7152
// compile non-HLSL shader locally, and HLSL by triggering a remote Github CI run
72-
await compileHlslRemote();
73-
for (const hdr of headers) {
53+
if (!denoArgs.local) {
54+
await compileHlslRemote();
55+
}
56+
for (const item of items) {
57+
await compile(item);
7458
const outp: string[] = []
75-
for (const shd of hdr.shaders) {
76-
await compile(shd, false);
77-
outp.push(...gatherShader(shd));
59+
for (const prog of item.progs) {
60+
outp.push(...gatherShader(item, prog));
7861
}
79-
inject(hdr.path, outp);
62+
if (denoArgs.only && !item.header.includes(denoArgs.only)) {
63+
continue;
64+
}
65+
inject(item.header, outp);
8066
}
8167
}
8268
}
8369

84-
async function compile(shd: Shader, hlsl: boolean): Promise<void> {
85-
await shdc([
86-
'-i', shd.filename,
87-
'-o', `out/${shd.filename}`,
70+
async function compile(item: Item): Promise<void> {
71+
let slangs: string;
72+
if (denoArgs.hlsl) {
73+
slangs = 'hlsl4';
74+
} else {
75+
slangs = 'glsl410:glsl300es:metal_macos:metal_ios:metal_sim:wgsl:spirv_vk';
76+
if (denoArgs.local) {
77+
slangs += ':hlsl4';
78+
}
79+
}
80+
const args = [
81+
'-i', item.shader,
8882
'-t', 'out/tmpdir',
89-
'-l', hlsl ? 'hlsl4' : 'glsl410:glsl300es:metal_macos:metal_ios:metal_sim:wgsl:spirv_vk',
90-
'-f', 'bare_yaml',
83+
'-l', slangs,
9184
'-b',
92-
]);
85+
];
86+
// compile once as regular C header and once as bare_yaml
87+
await shdc([...args, '-o', `out/${item.shader}.h`]);
88+
await shdc([...args, '-o', `out/${item.shader}`, '-f', 'bare_yaml']);
9389
}
9490

9591
function bytesToCArray(name: string, bytes: Uint8Array): string[] {
@@ -104,41 +100,45 @@ function bytesToCArray(name: string, bytes: Uint8Array): string[] {
104100
return lines;
105101
}
106102

107-
function gatherSlang(shd: Shader, slang: string, ext: string, isBinary: boolean): string[] {
103+
function gatherSlang(item: Item, prog: string, slang: string, ext: string, isBinary: boolean): string[] {
108104
const res: string[] = [];
109105
for (const stage of ['vertex', 'fragment']) {
110-
const path = `out/${shd.filename}_${shd.prog}_${slang}_${stage}${ext}`;
106+
const path = `out/${item.shader}_${prog}_${slang}_${stage}${ext}`;
111107
let bytes: Uint8Array = Deno.readFileSync(path);
112108
if (!isBinary) {
113109
// for source code, append a terminating zero
114110
bytes = new Uint8Array([...bytes, 0]);
115111
}
116-
const cArrayName = `${shd.prefix}_${shd.prog}_${stage==='vertex'?'vs':'fs'}_${isBinary?'bytecode':'source'}_${slang}`;
112+
const cArrayName = `${item.prefix}_${prog}_${stage==='vertex'?'vs':'fs'}_${isBinary?'bytecode':'source'}_${slang}`;
117113
const cArray = bytesToCArray(cArrayName, bytes);
118114
res.push(...cArray);
119115
}
120116
return res;
121117
}
122118

123-
function gatherShader(shd: Shader): string[] {
119+
function gatherShader(item: Item, prog: string): string[] {
124120
const res: string[] = [];
125121
res.push('#if defined(SOKOL_GLCORE)');
126-
res.push(...gatherSlang(shd, 'glsl410', '.glsl', false));
122+
res.push(...gatherSlang(item, prog, 'glsl410', '.glsl', false));
127123
res.push('#elif defined(SOKOL_GLES3)');
128-
res.push(...gatherSlang(shd, 'glsl300es', '.glsl', false));
124+
res.push(...gatherSlang(item, prog, 'glsl300es', '.glsl', false));
129125
res.push('#elif defined(SOKOL_METAL)');
130-
res.push(...gatherSlang(shd, 'metal_macos', '.metallib', true));
131-
res.push(...gatherSlang(shd, 'metal_ios', '.metallib', true));
132-
res.push(...gatherSlang(shd, 'metal_sim', '.metal', false));
126+
res.push(...gatherSlang(item, prog, 'metal_macos', '.metallib', true));
127+
res.push(...gatherSlang(item, prog, 'metal_ios', '.metallib', true));
128+
res.push(...gatherSlang(item, prog, 'metal_sim', '.metal', false));
133129
res.push('#elif defined(SOKOL_D3D11)');
134-
res.push(...gatherSlang(shd, 'hlsl4', '.fxc', true));
130+
if (denoArgs.local) {
131+
res.push(...gatherSlang(item, prog, 'hlsl4', '.hlsl', false));
132+
} else {
133+
res.push(...gatherSlang(item, prog, 'hlsl4', '.fxc', true));
134+
}
135135
res.push('#elif defined(SOKOL_WGPU)');
136-
res.push(...gatherSlang(shd, 'wgsl', '.wgsl', false));
136+
res.push(...gatherSlang(item, prog, 'wgsl', '.wgsl', false));
137137
res.push('#elif defined(SOKOL_VULKAN)');
138-
res.push(...gatherSlang(shd, 'spirv_vk', '', true));
138+
res.push(...gatherSlang(item, prog, 'spirv_vk', '', true));
139139
res.push('#elif defined(SOKOL_DUMMY_BACKEND)');
140-
res.push(`static const char* ${shd.prefix}_vs_source_dummy = "";`);
141-
res.push(`static const char* ${shd.prefix}_fs_source_dummy = "";`);
140+
res.push(`static const char* ${item.prefix}_${prog}_vs_source_dummy = "";`);
141+
res.push(`static const char* ${item.prefix}_${prog}_fs_source_dummy = "";`);
142142
res.push('#else');
143143
res.push('#error "Please define one of SOKOL_GLCORE, SOKOL_GLES3, SOKOL_D3D11, SOKOL_METAL, SOKOL_WGPU, SOKOL_VULKAN or SOKOL_DUMMY_BACKEND!"');
144144
res.push('#endif');

shdgen/sokol_framebuffer.glsl

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
@vs offscreen_vs
2+
@glsl_options flip_vert_y
3+
layout(binding=0) uniform offscreen_vs_params {
4+
vec2 uv_offset;
5+
vec2 uv_scale;
6+
};
7+
out vec2 uv;
8+
9+
void main() {
10+
float x = (gl_VertexIndex & 1) != 0 ? 2.0 : 0.0;
11+
float y = (gl_VertexIndex & 2) != 0 ? 2.0 : 0.0;
12+
gl_Position = vec4(vec2(x, y) * 2.0 - 1.0, 0.5, 1.0);
13+
uv = (vec2(x, 1.0 - y) * uv_scale) + uv_offset;
14+
}
15+
@end
16+
@fs rgba8_fs
17+
layout(binding=0) uniform texture2D fb_tex;
18+
layout(binding=0) uniform sampler smp;
19+
in vec2 uv;
20+
out vec4 frag_color;
21+
void main() {
22+
frag_color = texture(sampler2D(fb_tex, smp), uv);
23+
}
24+
@end
25+
@program rgba8 offscreen_vs rgba8_fs
26+
27+
// offscreen shader with color palette decoding
28+
@fs palette8_fs
29+
layout(binding=0) uniform texture2D fb_tex;
30+
layout(binding=1) uniform texture2D pal_tex;
31+
layout(binding=0) uniform sampler smp;
32+
in vec2 uv;
33+
out vec4 frag_color;
34+
void main() {
35+
float pix = texture(sampler2D(fb_tex, smp), uv).x;
36+
frag_color = vec4(texture(sampler2D(pal_tex, smp), vec2(pix,0)).xyz, 1.0);
37+
}
38+
@end
39+
@program palette8 offscreen_vs palette8_fs
40+
41+
@vs render_vs
42+
layout(binding=0) uniform render_vs_params {
43+
int rotate;
44+
};
45+
out vec2 uv;
46+
47+
void main() {
48+
vec2 in_pos, in_uv;
49+
in_pos.x = (gl_VertexIndex & 1) != 0 ? 2.0 : 0.0;
50+
in_pos.y = (gl_VertexIndex & 2) != 0 ? 2.0 : 0.0;
51+
if (rotate == 0) {
52+
in_uv.x = in_pos.x;
53+
in_uv.y = 1.0 - in_pos.y;
54+
} else {
55+
in_uv.x = 1.0 - in_pos.y;
56+
in_uv.y = 1.0 - in_pos.x;
57+
}
58+
gl_Position = vec4(in_pos*2.0-1.0, 0.5, 1.0);
59+
uv = in_uv;
60+
}
61+
@end
62+
@fs render_fs
63+
layout(binding=0) uniform texture2D tex;
64+
layout(binding=0) uniform sampler smp;
65+
in vec2 uv;
66+
out vec4 frag_color;
67+
68+
void main() {
69+
frag_color = vec4(texture(sampler2D(tex, smp), uv).xyz, 1.0);
70+
}
71+
@end
72+
@program render render_vs render_fs

util/sokol_debugtext.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3609,8 +3609,8 @@ static const uint8_t _sdtx_shd_fs_bytecode_spirv_vk[816] = {
36093609
0x09,0x00,0x00,0x00,0x1d,0x00,0x00,0x00,0xfd,0x00,0x01,0x00,0x38,0x00,0x01,0x00,
36103610
};
36113611
#elif defined(SOKOL_DUMMY_BACKEND)
3612-
static const char* _sdtx_vs_source_dummy = "";
3613-
static const char* _sdtx_fs_source_dummy = "";
3612+
static const char* _sdtx_shd_vs_source_dummy = "";
3613+
static const char* _sdtx_shd_fs_source_dummy = "";
36143614
#else
36153615
#error "Please define one of SOKOL_GLCORE, SOKOL_GLES3, SOKOL_D3D11, SOKOL_METAL, SOKOL_WGPU, SOKOL_VULKAN or SOKOL_DUMMY_BACKEND!"
36163616
#endif
@@ -4153,8 +4153,8 @@ static void _sdtx_setup_common(void) {
41534153
shd_desc.fragment_func.bytecode = SG_RANGE(_sdtx_shd_fs_bytecode_spirv_vk);
41544154
shd_desc.fragment_func.entry = "main";
41554155
#else
4156-
shd_desc.vertex_func.source = _sdtx_vs_source_dummy;
4157-
shd_desc.fragment_func.source = _sdtx_fs_source_dummy;
4156+
shd_desc.vertex_func.source = _sdtx_shd_vs_source_dummy;
4157+
shd_desc.fragment_func.source = _sdtx_shd_fs_source_dummy;
41584158
#endif
41594159
_sdtx.shader = sg_make_shader(&shd_desc);
41604160
SOKOL_ASSERT(SG_INVALID_ID != _sdtx.shader.id);

util/sokol_fontstash.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1705,8 +1705,8 @@ static const uint8_t _sfons_shd_fs_bytecode_spirv_vk[888] = {
17051705
0xfd,0x00,0x01,0x00,0x38,0x00,0x01,0x00,
17061706
};
17071707
#elif defined(SOKOL_DUMMY_BACKEND)
1708-
static const char* _sfons_vs_source_dummy = "";
1709-
static const char* _sfons_fs_source_dummy = "";
1708+
static const char* _sfons_shd_vs_source_dummy = "";
1709+
static const char* _sfons_shd_fs_source_dummy = "";
17101710
#else
17111711
#error "Please define one of SOKOL_GLCORE, SOKOL_GLES3, SOKOL_D3D11, SOKOL_METAL, SOKOL_WGPU, SOKOL_VULKAN or SOKOL_DUMMY_BACKEND!"
17121712
#endif
@@ -1841,8 +1841,8 @@ static int _sfons_render_create(void* user_ptr, int width, int height) {
18411841
shd_desc.fragment_func.bytecode = SG_RANGE(_sfons_shd_fs_bytecode_spirv_vk);
18421842
shd_desc.fragment_func.entry = "main";
18431843
#else
1844-
shd_desc.vertex_func.source = _sfons_vs_source_dummy;
1845-
shd_desc.fragment_func.source = _sfons_fs_source_dummy;
1844+
shd_desc.vertex_func.source = _sfons_shd_vs_source_dummy;
1845+
shd_desc.fragment_func.source = _sfons_shd_fs_source_dummy;
18461846
#endif
18471847
shd_desc.label = "sfons-shader";
18481848
sfons->shd = sg_make_shader(&shd_desc);

0 commit comments

Comments
 (0)