mirror of
https://github.com/nicbarker/clay.git
synced 2026-04-30 07:01:17 +00:00
[Renderers/GLES3] 📦 GLES3 renderer and demo examples using it (#565)
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
Odin Bindings Update / check_changes (push) Has been cancelled
Odin Bindings Update / build (macos-latest) (push) Has been cancelled
Odin Bindings Update / build (ubuntu-latest) (push) Has been cancelled
Odin Bindings Update / commit (push) Has been cancelled
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
Odin Bindings Update / check_changes (push) Has been cancelled
Odin Bindings Update / build (macos-latest) (push) Has been cancelled
Odin Bindings Update / build (ubuntu-latest) (push) Has been cancelled
Odin Bindings Update / commit (push) Has been cancelled
- **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
This commit is contained in:
parent
7d099ad870
commit
76ec3632d8
27 changed files with 2768 additions and 1 deletions
368
renderers/GLES3/clay_renderer_gles3_loader_stb.c
Normal file
368
renderers/GLES3/clay_renderer_gles3_loader_stb.c
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
#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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue