mirror of
https://github.com/nicbarker/clay.git
synced 2025-12-23 09:41:04 +00:00
- **Initialize Window**: - Successfully created a GLFW window with dimensions 1280x720. - Set up window hints for OpenGL version and core profile, enabling multisampling, and enabling depth testing. - **Setup Renderer**: - Initialized the Clay rendering context with a memory arena and dimensions. - Set up the measure text and render text functions using stb_image.h and stb_truetype.h. - Initialized the GLES3 renderer with 4096 texture units. - Loaded a Roboto-Regular font atlas and set it as the default font for rendering. - **Main Loop**: - Called `Clay_UpdateScrollContainers` to handle scroll events. - Set the layout dimensions and cleared the color buffer and depth buffer. - Render the Clay video demo layout. - Swapped the window buffers to display the rendered video. - **Cleanup**: - Cleaned up the GLFW window and renderer resources when the application is closed. This setup provides a basic framework for rendering videos in GLES3 with GLFW, leveraging stb_image.h for asset loading and Clay for the rendering engine. - Configure GLFW and SDL2 in the main files - Fix the video bugs in the main file 🪝 Stb dependency to be managed with cmake in examples 💀 Allow clients to configure headers, also expose Gles3_Renderer through header-only mode 🧹 Quality of life: automatically set screen dimensions to renderer Before users had to set them manually 📚 **🎨 Renderers/GLES3:** Improve round-rectangle clipping with uniform border thickness Implemented improvements to the renderer for GLES3, ensuring better handling of rounded rectangles with borders, making the layout more visually appealing. - Added two new functions `RenderHeaderButton1`, `RenderHeaderButton2`, and `RenderHeaderButton3` for creating header buttons with different styles. - Updated the `CreateLayout` function to include these new buttons in the right panel. - Added a TODO note for handling the outer radius calculation, as it seems to be incorrect in the current implementation. - Replace `bl_i + B` and `br_i + B` with `bl` and `br` respectively to simplify the code. - Simplify the logic for checking pixel inside the inner rounded rect by directly using `innerLocal`. 📥 Change borders to be inset - Fixed incorrect border calculation in the shader. - Added support for inset borders by adjusting the boundary calculations based on `CLAY_BORDERS_ARE_INSET`. This change also gives the renderer more choice in handling different border styles. 🏗️ CMake builds for GLES3 renderer examples
368 lines
10 KiB
C
368 lines
10 KiB
C
#pragma once
|
|
|
|
#include <stdbool.h>
|
|
#include <clay.h>
|
|
|
|
#include <stb_image.h>
|
|
#include <stb_truetype.h>
|
|
|
|
#include "clay_renderer_gles3.h"
|
|
|
|
typedef struct LoadedImage
|
|
{
|
|
unsigned char *data;
|
|
int width;
|
|
int height;
|
|
int channels;
|
|
} LoadedImage;
|
|
|
|
typedef struct LoadedImageInternal
|
|
{
|
|
LoadedImage pub;
|
|
} LoadedImageInternal;
|
|
|
|
static LoadedImageInternal g_imageSlot;
|
|
|
|
const LoadedImage *loadImage(const char *path, bool flip)
|
|
{
|
|
if (!path)
|
|
return NULL;
|
|
|
|
stbi_set_flip_vertically_on_load(flip ? 1 : 0);
|
|
|
|
int w = 0;
|
|
int h = 0;
|
|
int c = 0;
|
|
|
|
unsigned char *data = stbi_load(path, &w, &h, &c, 0);
|
|
if (!data)
|
|
{
|
|
// Failed
|
|
g_imageSlot.pub.data = NULL;
|
|
g_imageSlot.pub.width = 0;
|
|
g_imageSlot.pub.height = 0;
|
|
g_imageSlot.pub.channels = 0;
|
|
return NULL;
|
|
}
|
|
|
|
g_imageSlot.pub.data = data;
|
|
g_imageSlot.pub.width = w;
|
|
g_imageSlot.pub.height = h;
|
|
g_imageSlot.pub.channels = c;
|
|
|
|
return &g_imageSlot.pub;
|
|
}
|
|
|
|
void freeImage(const LoadedImage *img)
|
|
{
|
|
if (!img || !img->data)
|
|
return;
|
|
|
|
// cast back to internal container
|
|
stbi_image_free((void *)img->data);
|
|
|
|
// reset slot
|
|
g_imageSlot.pub.data = NULL;
|
|
g_imageSlot.pub.width = 0;
|
|
g_imageSlot.pub.height = 0;
|
|
g_imageSlot.pub.channels = 0;
|
|
}
|
|
|
|
typedef struct Stb_FontData
|
|
{
|
|
float bakePxH; // font baking height (e.g. 48.0f)
|
|
float ascentPx; // in baked pixels (at bake_px size)
|
|
float descentPx; // usually negative (at bake_px size)
|
|
int firstChar; // e.g. 32
|
|
int charCount; // e.g. 96
|
|
stbtt_bakedchar *cdata;
|
|
int atlasW;
|
|
int atlasH;
|
|
} Stb_FontData;
|
|
|
|
bool Stb_LoadFont(
|
|
GLuint *textureOut,
|
|
Stb_FontData *fontOut,
|
|
const char *ttfPath,
|
|
float bakePxH, // Height of a char in pixels
|
|
int atlasW, // Width of atlas in pixels
|
|
int atlasH // Height of atlas in pixels
|
|
)
|
|
{
|
|
fontOut->firstChar = 32; // ASCII space
|
|
fontOut->charCount = 96; // 32..127
|
|
fontOut->bakePxH = bakePxH;
|
|
fontOut->atlasW = atlasW;
|
|
fontOut->atlasH = atlasH;
|
|
|
|
// allocate baked-char array
|
|
fontOut->cdata = (stbtt_bakedchar *)malloc(
|
|
sizeof(stbtt_bakedchar) // Store baked info
|
|
* fontOut->charCount // For each char
|
|
);
|
|
if (!fontOut->cdata)
|
|
{
|
|
fprintf(stderr, "Cannot allocate cdata\n");
|
|
return false;
|
|
}
|
|
|
|
FILE *f = fopen(ttfPath, "rb");
|
|
if (!f)
|
|
{
|
|
fprintf(stderr, "Could not open font: %s\n", ttfPath);
|
|
return false;
|
|
}
|
|
|
|
fseek(f, 0, SEEK_END);
|
|
long sz = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
|
|
unsigned char *ttf_buf = (unsigned char *)malloc(sz);
|
|
fread(ttf_buf, 1, sz, f);
|
|
fclose(f);
|
|
|
|
// temporary atlas memory
|
|
unsigned char *atlas = (unsigned char *)malloc(atlasW * atlasH);
|
|
memset(atlas, 0, atlasW * atlasH);
|
|
|
|
// bake
|
|
int res = stbtt_BakeFontBitmap(
|
|
ttf_buf, // raw TTF file
|
|
0, // font index inside TTF (0 = first font)
|
|
bakePxH, // pixel height of glyphs to generate
|
|
atlas, // OUT: bitmap buffer (unsigned char*)
|
|
atlasW, atlasH, // size of bitmap buffer
|
|
fontOut->firstChar, // first character to bake (e.g., 32 = space)
|
|
fontOut->charCount, // how many sequential chars to bake
|
|
fontOut->cdata // OUT: array of stbtt_bakedchar
|
|
);
|
|
|
|
stbtt_fontinfo fi;
|
|
if (!stbtt_InitFont(&fi, ttf_buf, stbtt_GetFontOffsetForIndex(ttf_buf, 0)))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int ascent, descent, lineGap;
|
|
stbtt_GetFontVMetrics(&fi, &ascent, &descent, &lineGap);
|
|
|
|
// Convert the font's "font units" to pixels proportional to bakePxH size:
|
|
float scaleForBake = stbtt_ScaleForPixelHeight(&fi, bakePxH);
|
|
|
|
fontOut->ascentPx = ascent * scaleForBake;
|
|
fontOut->descentPx = descent * scaleForBake; // this is typically negative
|
|
|
|
free(ttf_buf);
|
|
|
|
if (res <= 0)
|
|
{
|
|
fprintf(stderr, "Font baking failed\n");
|
|
free(atlas);
|
|
free(fontOut->cdata);
|
|
fontOut->cdata = NULL;
|
|
return false;
|
|
}
|
|
|
|
// Creating glyphVtxArray atlas texture
|
|
glGenTextures(1, textureOut);
|
|
glBindTexture(GL_TEXTURE_2D, *textureOut);
|
|
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_R8,
|
|
atlasW, atlasH,
|
|
0, GL_RED, GL_UNSIGNED_BYTE, atlas);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
|
|
free(atlas);
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline Clay_Dimensions Stb_MeasureText(
|
|
Clay_StringSlice glyphVtxArray,
|
|
Clay_TextElementConfig *config,
|
|
void *userData)
|
|
{
|
|
Stb_FontData *fontData = (Stb_FontData *)userData;
|
|
|
|
if (!fontData->cdata)
|
|
{
|
|
fprintf(
|
|
stderr,
|
|
"MeasureText cannot do anything when cdata is not baked: '%.*s' → %d x %d px\n",
|
|
(int)glyphVtxArray.length, glyphVtxArray.chars, 0, 0);
|
|
return (Clay_Dimensions){.width = 0, .height = 0};
|
|
}
|
|
|
|
float x = 0.0f;
|
|
float y = 0.0f;
|
|
|
|
const char *str = glyphVtxArray.chars;
|
|
int len = glyphVtxArray.length;
|
|
|
|
float scale = config->fontSize / fontData->bakePxH;
|
|
|
|
float letterSpacing = (float)config->letterSpacing;
|
|
float lineHeight = (config->lineHeight > 0)
|
|
? (float)config->lineHeight
|
|
: fontData->bakePxH;
|
|
|
|
for (int i = 0; i < len; i++)
|
|
{
|
|
unsigned char c = str[i];
|
|
|
|
if (c < fontData->firstChar // before range
|
|
|| c >= fontData->firstChar + fontData->charCount // after range
|
|
)
|
|
{
|
|
// Unsupported char: treat as space
|
|
fprintf(stderr, "illegal char %d\n", (int)c);
|
|
x += fontData->bakePxH * 0.25f;
|
|
continue;
|
|
}
|
|
|
|
stbtt_bakedchar *b = &fontData->cdata[c - fontData->firstChar];
|
|
|
|
// horizontal advance while moving along word characters
|
|
x += b->xadvance * scale + letterSpacing;
|
|
}
|
|
|
|
float ascent = fontData->ascentPx * scale;
|
|
float descent = fontData->descentPx * scale; // negative
|
|
float lineH = (ascent - descent); // total line height in pixels (at requested fontSize)
|
|
|
|
return (Clay_Dimensions){
|
|
.width = x,
|
|
.height = y + lineH,
|
|
};
|
|
}
|
|
|
|
static inline void Stb_RenderText(
|
|
Clay_RenderCommand *cmd,
|
|
Gles3_GlyphVtxArray *glyphVtxArray,
|
|
void *userData)
|
|
{
|
|
const Clay_TextRenderData *tr = &cmd->renderData.text;
|
|
|
|
float cr = tr->textColor.r / 255.0f;
|
|
float cg = tr->textColor.g / 255.0f;
|
|
float cb = tr->textColor.b / 255.0f;
|
|
float ca = tr->textColor.a / 255.0f;
|
|
float fontToUse = (float)tr->fontId;
|
|
|
|
Stb_FontData *fontArray = (Stb_FontData *)userData;
|
|
Stb_FontData *stbFontData = &fontArray[tr->fontId];
|
|
if (!stbFontData->cdata)
|
|
return;
|
|
|
|
Clay_StringSlice ss = tr->stringContents;
|
|
const char *txt = ss.chars;
|
|
int len = (int)ss.length;
|
|
|
|
float scale = tr->fontSize / stbFontData->bakePxH;
|
|
float ascent = stbFontData->ascentPx * scale; // pixels above baseline
|
|
float x = cmd->boundingBox.x;
|
|
float y = cmd->boundingBox.y + ascent; // baseline (note: no descent)
|
|
|
|
for (int i = 0; i < len; i++)
|
|
{
|
|
char ch = txt[i];
|
|
|
|
int idx = ch - stbFontData->firstChar;
|
|
if (idx < 0 || idx >= stbFontData->charCount)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
stbtt_bakedchar *bc = &stbFontData->cdata[idx];
|
|
|
|
float gw = (float)(bc->x1 - bc->x0); // glyph width in atlas pixels
|
|
float gh = (float)(bc->y1 - bc->y0); // glyph height
|
|
|
|
float sw = gw * scale; // scaled width on screen
|
|
float sh = gh * scale; // scaled height
|
|
|
|
float ox = bc->xoff * scale; // baseline offset
|
|
float oy = bc->yoff * scale;
|
|
|
|
// top-left corner on screen (pixel coords)
|
|
float x0 = x + ox;
|
|
float y0 = y + oy;
|
|
float x1 = x0 + sw;
|
|
float y1 = y0 + sh;
|
|
|
|
// atlas size (you can make it configurable later)
|
|
float atlasW = stbFontData->atlasW;
|
|
float atlasH = stbFontData->atlasH;
|
|
|
|
float u0 = bc->x0 / atlasW;
|
|
float v0 = bc->y0 / atlasH;
|
|
float u1 = bc->x1 / atlasW;
|
|
float v1 = bc->y1 / atlasH;
|
|
|
|
// append 6 vertices (two triangles) to your buffer
|
|
GlyphVtx *v = &glyphVtxArray->instData[glyphVtxArray->count * 6];
|
|
|
|
v[0] = (GlyphVtx){x0, y0, u0, v0, cr, cg, cb, ca, fontToUse};
|
|
v[1] = (GlyphVtx){x1, y0, u1, v0, cr, cg, cb, ca, fontToUse};
|
|
v[2] = (GlyphVtx){x0, y1, u0, v1, cr, cg, cb, ca, fontToUse};
|
|
|
|
v[3] = (GlyphVtx){x0, y1, u0, v1, cr, cg, cb, ca, fontToUse};
|
|
v[4] = (GlyphVtx){x1, y0, u1, v0, cr, cg, cb, ca, fontToUse};
|
|
v[5] = (GlyphVtx){x1, y1, u1, v1, cr, cg, cb, ca, fontToUse};
|
|
|
|
// advance pen by baked xadvance + letter spacing
|
|
x += (bc->xadvance * scale) + tr->letterSpacing;
|
|
|
|
// prevent buffer overrun
|
|
if (glyphVtxArray->count >= glyphVtxArray->capacity)
|
|
{
|
|
break;
|
|
}
|
|
glyphVtxArray->count++;
|
|
}
|
|
}
|
|
|
|
bool Stb_LoadImage(GLuint *textureOut, const char *path)
|
|
{
|
|
const LoadedImage *li = loadImage(path, false);
|
|
if (!li || !li->data)
|
|
{
|
|
fprintf(stderr, "Failed to load texture at: %s\n", path);
|
|
return false;
|
|
}
|
|
|
|
glGenTextures(1, textureOut);
|
|
glBindTexture(GL_TEXTURE_2D, *textureOut);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
|
|
GLenum format = (li->channels == 4) ? GL_RGBA : GL_RGB;
|
|
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
|
|
glTexImage2D(
|
|
GL_TEXTURE_2D, // target
|
|
0, // level
|
|
format, // internal format int
|
|
li->width,
|
|
li->height,
|
|
0, // border
|
|
format, // format, GLEnum
|
|
GL_UNSIGNED_BYTE, // Type
|
|
li->data // pixels
|
|
);
|
|
|
|
glGenerateMipmap(GL_TEXTURE_2D);
|
|
|
|
freeImage(li);
|
|
return true;
|
|
} |