This commit is contained in:
Krzosa Karol
2025-11-22 15:37:59 +01:00
parent cd8d166c0c
commit 2e087824a3
4 changed files with 728 additions and 716 deletions

View File

@@ -2,7 +2,6 @@
for %%a in (%*) do set "%%~a=1"
set common=-I ../src/ -g -fdiagnostics-absolute-paths -Wno-unsequenced -Wno-single-bit-bitfield-constant-conversion -Wall -Wno-missing-braces -Wextra -Wno-missing-field-initializers
set wasm_flags=--target=wasm32 -nostdlib -mbulk-memory -msimd128 -Wl,-export-dynamic,--allow-undefined,--import-memory,--no-entry,--initial-memory=131072000,--max-memory=4294967296

View File

@@ -1,5 +1,6 @@
#include "core_platform.h"
#include "core_ctx.h"
#include "stb_sprintf.h"
#if PLATFORM_WASM && PLATFORM_EMSCRIPTEN == 0
#define gb_wasm_export __attribute__((visibility("default")))
@@ -69,7 +70,7 @@ fn void os_core_init(void) {
perm.commit = perm.reserve = memory_size;
os_core_small_init(&perm);
tcx->perm = perm;
ma_push_arena_ex(&tcx->perm, &tcx->temp, mib(16));
ma_push_arena_ex(&tcx->perm, &tcx->temp, mib(64));
ma_push_arena_ex(&tcx->perm, &tcx->scratch[0], mib(2));
ma_push_arena_ex(&tcx->perm, &tcx->scratch[1], kib(256));
ma_push_arena_ex(&tcx->perm, &tcx->scratch[2], kib(64));
@@ -134,7 +135,7 @@ static print_buf buf_init(void) {
static void buf_printf(print_buf* b, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
const int len = vsnprintf(NULL, 0, fmt, args) + 1;
const int len = stbsp_vsnprintf(NULL, 0, fmt, args) + 1;
va_end(args);
const int new_end = b->pos + len;
@@ -145,7 +146,7 @@ static void buf_printf(print_buf* b, const char* fmt, ...) {
}
va_start(args, fmt);
b->pos += vsnprintf(b->buf + b->pos, len, fmt, args);
b->pos += stbsp_vsnprintf(b->buf + b->pos, len, fmt, args);
va_end(args);
}
@@ -157,7 +158,6 @@ char* b_stacktrace_get_string(void) {
}
#define B_STACKTRACE_MAX_DEPTH 1024
#define B_STACKTRACE_ERROR_FLAG ((DWORD64)1 << 63)
typedef struct b_stacktrace_entry {

View File

@@ -4,57 +4,138 @@
<meta charset="UTF-8">
<title>Canvas</title>
<style>
html,body{margin:0;height:100%;overflow:hidden;padding:0;font-size: 100px;font-family: FiraCode;}
canvas{display:block;width:100%;height:250px;border-bottom: dotted;}
@font-face {
font-family: 'FiraCode';
src: url('./FiraCode-Regular.ttf');
font-weight: normal;
font-style: normal;
font-display: swap;
}
div{
position:relative;
display: block;
width: 100%;
text-align: center;
border-bottom: dotted;
}
html,body{margin:0;height:100%;overflow:hidden;padding:0;}
canvas{display:block;width:100%;height:100%;}
</style>
</head>
<body>
<canvas id="glcanvas"></canvas>
<div>
<p>Hello world (html)</p>
</div>
<canvas id="2dcanvas"></canvas>
<script>
function createShader(gl, type, src) {
const sh = gl.createShader(type);
gl.shaderSource(sh, src);
gl.compileShader(sh);
if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
console.error('Shader compile error:', gl.getShaderInfoLog(sh));
gl.deleteShader(sh);
return null;
}
return sh;
}
function createProgram(gl, vs, fs) {
const prg = gl.createProgram();
gl.attachShader(prg, vs);
gl.attachShader(prg, fs);
gl.linkProgram(prg);
if (!gl.getProgramParameter(prg, gl.LINK_STATUS)) {
console.error('Program link error:', gl.getProgramInfoLog(prg));
gl.deleteProgram(prg);
return null;
CANVAS = null;
GL = null;
class wglRender {
constructor() {
const VERTEX_SRC = `#version 300 es
precision mediump float;
in vec2 a_position;
out vec2 v_texCoord;
void main() {
v_texCoord = (a_position + 1.0) * 0.5; // [0,1]
v_texCoord.y = (1.0 - v_texCoord.y); // Flip the Y to get the standard memory behavior
gl_Position = vec4(a_position, 0.0, 1.0);
}`;
const FRAGMENT_SRC = `#version 300 es
precision mediump float;
in vec2 v_texCoord;
uniform sampler2D u_texture;
out vec4 outColor;
void main() {
outColor = texture(u_texture, v_texCoord);
}`;
CANVAS = document.getElementById('glcanvas');
GL = CANVAS.getContext('webgl2');
if (!GL) { alert('WebGL 2 not supported'); return; }
const vs = this.createShader(GL.VERTEX_SHADER, VERTEX_SRC);
const fs = this.createShader(GL.FRAGMENT_SHADER, FRAGMENT_SRC);
this.program = this.createProgram(vs, fs);
this.posLoc = GL.getAttribLocation(this.program, 'a_position');
this.texLoc = GL.getUniformLocation(this.program, 'u_texture');
// Fullscreen quad
const quad = new Float32Array([
-1, -1, // bottom left
1, -1, // bottom right
-1, 1, // top left
-1, 1, // top left
1, -1, // bottom right
1, 1 // top right
]);
this.vao = GL.createVertexArray();
GL.bindVertexArray(this.vao);
this.vbo = GL.createBuffer();
GL.bindBuffer(GL.ARRAY_BUFFER, this.vbo);
GL.bufferData(GL.ARRAY_BUFFER, quad, GL.STATIC_DRAW);
GL.enableVertexAttribArray(this.posLoc);
GL.vertexAttribPointer(this.posLoc, 2, GL.FLOAT, false, 0, 0);
}
render() {
GL.useProgram(this.program);
GL.bindVertexArray(this.vao);
GL.activeTexture(GL.TEXTURE0);
GL.bindTexture(GL.TEXTURE_2D, this.texture);
GL.uniform1i(this.texLoc, 0);
GL.drawArrays(GL.TRIANGLES, 0, 6);
}
updateTextureSize(wasm) {
const dpr = window.devicePixelRatio || 1;
const w = Math.floor(CANVAS.clientWidth * dpr);
const h = Math.floor(CANVAS.clientHeight * dpr);
if (w !== this.w || h !== this.h) {
CANVAS.width = w;
CANVAS.height = h;
GL.viewport(0, 0, w, h);
this.w = w;
this.h = h;
if (this.texture) {
GL.deleteTexture(this.texture);
}
const rawPtr = wasm.exports.update(w, h, window.devicePixelRatio || 1.0);
const pixels = wasm.getU8View(rawPtr, w*h*4);
{
this.texture = GL.createTexture();
GL.bindTexture(GL.TEXTURE_2D, this.texture);
// Allocate an empty texture of the canvas size
GL.texImage2D(
GL.TEXTURE_2D, 0, GL.RGBA8,
w, h, 0,
GL.RGBA, GL.UNSIGNED_BYTE, pixels
);
// Set parameters no filtering needed for empty data
GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.NEAREST);
GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.NEAREST);
GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE);
GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE);
}
// GL.activeTexture(GL.TEXTURE0);
// GL.bindTexture(GL.TEXTURE_2D, this.texture);
// GL.texSubImage2D(GL.TEXTURE_2D, 0, 0, 0, w, h, GL.RGBA, GL.UNSIGNED_BYTE,pixels);
}
}
createShader(type, src) {
const sh = GL.createShader(type);
GL.shaderSource(sh, src);
GL.compileShader(sh);
if (!GL.getShaderParameter(sh, GL.COMPILE_STATUS)) {
console.error('Shader compile error:', GL.getShaderInfoLog(sh));
GL.deleteShader(sh);
return null;
}
return sh;
}
createProgram(vs, fs) {
const prg = GL.createProgram();
GL.attachShader(prg, vs);
GL.attachShader(prg, fs);
GL.linkProgram(prg);
if (!GL.getProgramParameter(prg, GL.LINK_STATUS)) {
console.error('Program link error:', GL.getProgramInfoLog(prg));
GL.deleteProgram(prg);
return null;
}
return prg;
}
return prg;
}
class WASMMemory {
@@ -67,18 +148,23 @@ class WASMMemory {
this.exports = null;
}
readString(str, len) {
decodeString(str, len) {
const arr = this.u8.subarray(str, str+len);
const text = this.utf8decoder.decode(arr);
return text;
}
readUint8(p, len) {
getU8View(p, len) {
const arr = this.u8.subarray(p, p+len);
return arr;
}
writeString(ptr, ptr_len, string) {
getView(p, len) {
const res = new DataView(this.mem.buffer, p, p+len);
return res;
}
encodeString(ptr, ptr_len, string) {
const bytes = this.utf8encoder.encode(string);
let i = 0;
for (; i < bytes.length && i < (ptr_len-1); i += 1) {
@@ -94,10 +180,10 @@ async function WASMInit(name) {
const binary = await request.arrayBuffer();
const import_table = {
memory: mem.mem,
wasm_parse_float: (str, len) => { return parseFloat(mem.readString(str, len)); },
wasm_alert: (str, len) => { alert(mem.readString(str,len)); },
wasm_parse_float: (str, len) => { return parseFloat(mem.decodeString(str, len)); },
wasm_alert: (str, len) => { alert(mem.decodeString(str,len)); },
wasm_trap: () => { throw new Error(); },
wasm_write_to_console: (str, len) => { console.log(mem.readString(str, len)); },
wasm_write_to_console: (str, len) => { console.log(mem.decodeString(str, len)); },
};
const program = await WebAssembly['instantiate'](binary, { "env": import_table });
const instance = program['instance'];
@@ -107,146 +193,17 @@ async function WASMInit(name) {
}
(async () => {
const render = new wglRender();
const wasm = await WASMInit("main.wasm");
wasm.exports.init();
const VERTEX_SRC = `#version 300 es
precision mediump float;
in vec2 a_position;
out vec2 v_texCoord;
void main() {
v_texCoord = (a_position + 1.0) * 0.5; // [0,1]
v_texCoord.y = (1.0 - v_texCoord.y); // Flip the Y to get the standard memory behavior
gl_Position = vec4(a_position, 0.0, 1.0);
}`;
const FRAGMENT_SRC = `#version 300 es
precision mediump float;
in vec2 v_texCoord;
uniform sampler2D u_texture;
out vec4 outColor;
void main() {
outColor = texture(u_texture, v_texCoord);
}`;
/* ---------- WebGL setup ---------- */
const canvas = document.getElementById('glcanvas');
const gl = canvas.getContext('webgl2');
if (!gl) { alert('WebGL 2 not supported'); return; }
const vs = createShader(gl, gl.VERTEX_SHADER, VERTEX_SRC);
const fs = createShader(gl, gl.FRAGMENT_SHADER, FRAGMENT_SRC);
const program = createProgram(gl, vs, fs);
const posLoc = gl.getAttribLocation(program, 'a_position');
const texLoc = gl.getUniformLocation(program, 'u_texture');
// Fullscreen quad
const quad = new Float32Array([
-1, -1, // bottom left
1, -1, // bottom right
-1, 1, // top left
-1, 1, // top left
1, -1, // bottom right
1, 1 // top right
]);
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
const vbo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bufferData(gl.ARRAY_BUFFER, quad, gl.STATIC_DRAW);
gl.enableVertexAttribArray(posLoc);
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
/* ---------- Dynamic texture handling ---------- */
let texture = null;
let texWidth = 0, texHeight = 0;
let pixels = null;
function createTexture(w, h) {
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
// Allocate an empty texture of the canvas size
gl.texImage2D(
gl.TEXTURE_2D, 0, gl.RGBA8,
w, h, 0,
gl.RGBA, gl.UNSIGNED_BYTE, null
);
// Set parameters no filtering needed for empty data
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
return tex;
function frameLoop() {
render.updateTextureSize(wasm);
render.render();
requestAnimationFrame(frameLoop);
}
function updateTextureSize() {
const dpr = window.devicePixelRatio || 1;
const w = Math.floor(canvas.clientWidth * dpr);
const h = Math.floor(canvas.clientHeight * dpr);
if (w !== texWidth || h !== texHeight) {
texWidth = w; texHeight = h;
if (texture) gl.deleteTexture(texture);
texture = createTexture(w, h);
let rawPtr = wasm.exports.update(texWidth, texHeight, window.devicePixelRatio || 1.0);
pixels = wasm.readUint8(rawPtr, texWidth*texHeight*4);
{
const canvas = document.getElementById('2dcanvas');
const dpr = window.devicePixelRatio || 1;
const w = Math.floor(canvas.clientWidth * dpr);
const h = Math.floor(canvas.clientHeight * dpr);
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
let size = 100*dpr;
ctx.font = `${size}px FiraCode`;
const text = 'Hello world (canvas)';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
ctx.fillStyle = '#000';
ctx.fillText(text, centerX, centerY);
}
}
}
/* ---------- Canvas resize routine ---------- */
function resizeCanvasToDisplaySize() {
const dpr = window.devicePixelRatio || 1;
const w = Math.floor(canvas.clientWidth * dpr);
const h = Math.floor(canvas.clientHeight * dpr);
if (canvas.width !== w || canvas.height !== h) {
canvas.width = w;
canvas.height = h;
gl.viewport(0, 0, w, h);
}
}
/* ---------- Render loop ---------- */
function render() {
resizeCanvasToDisplaySize();
updateTextureSize();
gl.useProgram(program);
gl.bindVertexArray(vao);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, texWidth, texHeight, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
gl.uniform1i(texLoc, 0);
gl.drawArrays(gl.TRIANGLES, 0, 6);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
requestAnimationFrame(frameLoop);
})();

View File

@@ -2,8 +2,8 @@
#include "core/core.c"
#define STB_TRUETYPE_IMPLEMENTATION
#define STBTT_ifloor(x) ((int)f64_floor(x))
#define STBTT_iceil(x) ((int)f64_ceil(x))
#define STBTT_ifloor(x) ((i32)f64_floor(x))
#define STBTT_iceil(x) ((i32)f64_ceil(x))
#define STBTT_sqrt(x) (f64_sqrt(x))
#define STBTT_fmod(x,y) (f64_mod(x,y))
#define STBTT_pow(x,y) (f64_pow(x,y))
@@ -20,32 +20,123 @@
#include "vendor/stb/stb_truetype.h"
#include "fira_code.c"
typedef struct glyph_t glyph_t;
struct glyph_t {
u8 *data;
i32 width, height;
i32 xoff, yoff;
i32 left_side_bearing, xadvance;
};
typedef struct font_t font_t;
struct font_t {
glyph_t glyphs[96];
i32 ascent, descent, line_gap, size;
f32 scale;
};
typedef struct bitmap_t bitmap_t;
struct bitmap_t {
u32 *data;
i32 x, y;
};
typedef struct glyph_t glyph_t;
struct glyph_t {
bitmap_t bitmap;
i32 xoff, yoff;
i32 left_side_bearing, xadvance;
};
typedef struct font_t font_t;
struct font_t {
glyph_t glyphs[256];
bitmap_t atlas;
i32 ascent, descent, line_gap, size;
f32 scale;
i32 height;
};
fn font_t create_font(i32 size) {
font_t font = {0};
font.size = size;
stbtt_fontinfo stb_font;
i32 rc = stbtt_InitFont(&stb_font, main_font_data, 0);
assert_expr(rc != 0);
font.scale = stbtt_ScaleForPixelHeight(&stb_font, (f32)font.size);
stbtt_GetFontVMetrics(&stb_font, &font.ascent, &font.descent, &font.line_gap);
font.height = f32_round((font.ascent - font.descent) + font.line_gap) * font.scale;
for (i32 c = 0; c < 256; c += 1) {
glyph_t *glyph = font.glyphs + (c - ' ');
u8 *temp_data = (u8 *)stbtt_GetCodepointBitmap(&stb_font, 0, font.scale, c, &glyph->bitmap.x, &glyph->bitmap.y, &glyph->xoff, &glyph->yoff);
glyph->bitmap.data = ma_push_array(&tcx->temp, u32, glyph->bitmap.x * glyph->bitmap.y);
for (i32 y = 0; y < glyph->bitmap.y; y += 1) {
for (i32 x = 0; x < glyph->bitmap.x; x += 1) {
glyph->bitmap.data[x + y * glyph->bitmap.x] = temp_data[x + y * glyph->bitmap.x] << 24;
}
}
stbtt_GetCodepointHMetrics(&stb_font, c, &glyph->xadvance, &glyph->left_side_bearing);
}
return font;
}
fn font_t create_font_atlas(i32 size) {
stbtt_fontinfo stb_font;
i32 rc = stbtt_InitFont(&stb_font, main_font_data, 0);
assert_expr(rc != 0);
font_t font = { 0 };
font.size = size;
font.atlas.x = 512;
font.atlas.y = 512;
font.atlas.data = ma_push_array(&tcx->temp, u32, font.atlas.x * font.atlas.y);
font.scale = stbtt_ScaleForPixelHeight(&stb_font, (f32)font.size);
stbtt_GetFontVMetrics(&stb_font, &font.ascent, &font.descent, &font.line_gap);
i32 xiter = 2;
i32 yiter = 2;
i32 max_yiter_for_row = 0;
for (i32 c = ' '; c < '~'; c += 1) {
glyph_t *glyph = font.glyphs + (c - ' ');
u8 *temp_data = (u8 *)stbtt_GetCodepointBitmap(&stb_font, 0, font.scale, c, &glyph->bitmap.x, &glyph->bitmap.y, &glyph->xoff, &glyph->yoff);
if (xiter + glyph->bitmap.x >= font.atlas.x) {
xiter = 0;
yiter += max_yiter_for_row + 2;
}
max_yiter_for_row = MAX(max_yiter_for_row, glyph->bitmap.y);
for (i32 y = 0; y < glyph->bitmap.y; y += 1) {
for (i32 x = 0; x < glyph->bitmap.x; x += 1) {
font.atlas.data[(xiter + x) + (yiter + y)*font.atlas.x] = temp_data[x + y * glyph->bitmap.x] << 24;
}
}
xiter += glyph->bitmap.x + 2;
}
return font;
}
fn_wasm_export void init(void) {
os_core_init();
}
fn v2i32_t draw_string(bitmap_t *canvas, font_t *font, int ix, int iy, b32 draw, char *string) {
fn void draw_rect(bitmap_t *canvas, bitmap_t *bitmap, i32 x, i32 y) {
i32 x0 = x;
i32 y0 = y;
i32 x1 = x + bitmap->x;
i32 y1 = y + bitmap->y;
i32 cx0 = CLAMP(x0, 0, canvas->x);
i32 cy0 = CLAMP(y0, 0, canvas->y);
i32 cx1 = CLAMP(x1, 0, canvas->x);
i32 cy1 = CLAMP(y1, 0, canvas->y);
if (cx0 >= cx1 || cy0 >= cy1) {
return;
}
i32 sx_off = cx0 - x0;
i32 sy_off = cy0 - y0;
i32 sy = sy_off;
for (i32 y = cy0; y < cy1; y += 1) {
i32 sx = sx_off;
for (i32 x = cx0; x < cx1; x += 1) {
canvas->data[x + y * canvas->x] = bitmap->data[sx + sy * bitmap->x];
sx += 1;
}
sy += 1;
}
}
fn v2i32_t draw_string(bitmap_t *canvas, font_t *font, i32 ix, i32 iy, b32 draw, char *string) {
i32 xiter = 0;
v2i32_t R = {0};
for (char *it = string; *it; it += 1) {
@@ -53,60 +144,25 @@ fn v2i32_t draw_string(bitmap_t *canvas, font_t *font, int ix, int iy, b32 draw,
i32 curr_xiter = xiter;
xiter += g->xadvance * font->scale;
i32 x0 = curr_xiter + g->xoff + ix;
i32 y0 = g->yoff + font->ascent*font->scale + iy;
i32 x1 = g->width + x0;
i32 y1 = g->height + y0;
i32 cx0 = CLAMP(x0, 0, canvas->x);
i32 cy0 = CLAMP(y0, 0, canvas->y);
i32 cx1 = CLAMP(x1, 0, canvas->x);
i32 cy1 = CLAMP(y1, 0, canvas->y);
R.x = xiter;
R.y = MAX(R.y, g->height);
R.y = MAX(R.y, g->bitmap.y);
if (!draw || (cx0 >= cx1 || cy0 >= cy1)) {
if (!draw) {
continue;
}
i32 sx_off = cx0 - x0;
i32 sy_off = cy0 - y0;
i32 sy = sy_off;
for (i32 y = cy0; y < cy1; y += 1) {
i32 sx = sx_off;
for (i32 x = cx0; x < cx1; x += 1) {
canvas->data[x + y * canvas->x] = g->data[sx + sy * g->width] << 24;
sx += 1;
}
sy += 1;
}
draw_rect(canvas, &g->bitmap, curr_xiter + g->xoff + ix, g->yoff + font->ascent*font->scale + iy);
}
return R;
}
fn_wasm_export u32 *update(i32 width, i32 height, f32 dpr) {
gb font_t font = {0};
font.size = (i32)(130.f * dpr);
stbtt_fontinfo stb_font;
int rc = stbtt_InitFont(&stb_font, main_font_data, stbtt_GetFontOffsetForIndex(main_font_data,0));
assert_expr(rc != 0);
font.scale = stbtt_ScaleForPixelHeight(&stb_font, (f32)font.size);
stbtt_GetFontVMetrics(&stb_font, &font.ascent, &font.descent, &font.line_gap);
for (int c = ' '; c < '~'; c += 1) {
glyph_t *glyph = font.glyphs + (c - ' ');
u8 *temp_data = (u8 *)stbtt_GetCodepointBitmap(&stb_font, 0, font.scale, c, &glyph->width, &glyph->height, &glyph->xoff, &glyph->yoff);
glyph->data = ma_push_array_copy(&tcx->perm, temp_data, glyph->width*glyph->height);
stbtt_GetCodepointHMetrics(&stb_font, c, &glyph->xadvance, &glyph->left_side_bearing);
}
ma_set0(&tcx->temp);
u32 *pixels = ma_push_array(&tcx->temp, u32, width * height);
bitmap_t canvas = {pixels, width, height};
v2i32_t size = draw_string(&canvas, &font, 200, 500, false, "Hello world (stb)");
draw_string(&canvas, &font, (width-size.x)/2, height - size.y*2, true, "Hello world (stb)");
font_t font = create_font_atlas(60);
draw_rect(&canvas, &font.atlas, 0, 0);
//v2i32_t size = draw_string(&canvas, &font, 200, 500, false, "Hello world (stb)");
//draw_string(&canvas, &font, (width-size.x)/2, height - size.y*2, true, "Hello world (stb)");
return pixels;
}