📦 GLES3 renderer and demo examples using it

- **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:
Luke 10X 2025-12-12 19:31:50 -05:00
parent 389a044cd2
commit 24b42b7b1c
27 changed files with 2768 additions and 1 deletions

View file

@ -0,0 +1,2 @@
/build/
/website-demo-macos-sdl2*

View file

@ -0,0 +1,83 @@
cmake_minimum_required(VERSION 3.27)
set(CMAKE_C_STANDARD 99)
project(GLES3_SDL2_video_demo C)
# -------------------------------------------------
# FetchContent
# -------------------------------------------------
include(FetchContent)
set(FETCHCONTENT_QUIET FALSE)
# -------------------------------------------------
# STB (header-only)
# -------------------------------------------------
FetchContent_Declare(
stb
GIT_REPOSITORY https://github.com/nothings/stb.git
GIT_TAG master
)
FetchContent_MakeAvailable(stb)
# -------------------------------------------------
# SDL2
# -------------------------------------------------
FetchContent_Declare(
SDL2
GIT_REPOSITORY "https://github.com/libsdl-org/SDL.git"
GIT_TAG "release-2.30.10"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(SDL2)
# -------------------------------------------------
# Executable
# -------------------------------------------------
add_executable(GLES3_SDL2_video_demo main.c)
target_compile_options(GLES3_SDL2_video_demo PUBLIC)
target_include_directories(GLES3_SDL2_video_demo
PUBLIC
. # This renderer
../.. # Clay
${stb_SOURCE_DIR} # STB header only depencency that does not have its own CMake build
)
# -------------------------------------------------
# Link libraries
# -------------------------------------------------
target_link_libraries(GLES3_SDL2_video_demo PUBLIC
SDL2::SDL2main
SDL2::SDL2-static
)
# -------------------------------------------------
# Platform-specific OpenGL / GLES
# -------------------------------------------------
find_package(SDL2 REQUIRED)
find_library(OPENGL_FRAMEWORK OpenGL)
target_link_libraries(GLES3_SDL2_video_demo
PRIVATE
${OPENGL_FRAMEWORK}
)
# -------------------------------------------------
# Build flags (kept minimal)
# -------------------------------------------------
if(MSVC)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
else()
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
endif()
# -------------------------------------------------
# Copy resources
# -------------------------------------------------
add_custom_command(
TARGET GLES3_SDL2_video_demo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/resources
${CMAKE_CURRENT_BINARY_DIR}/resources
)

View file

@ -0,0 +1,33 @@
# vim: set tabstop=4 shiftwidth=4 expandtab noexpandtab:
#
# PROGRAM COMPONENTS
#
CXX = emcc
CXXFLAGS = -std=c99
CXXFLAGS += -O0
CXXFLAGS += -I../..
CXXFLAGS += -I./build/_deps/stb-src
LDLIBS += -s USE_ZLIB=1
LDLIBS += -s USE_SDL=2
LDLIBS += -s FULL_ES2=1 -s USE_WEBGL2=1
LDLIBS += -s ALLOW_MEMORY_GROWTH=1 -s GL_UNSAFE_OPTS=0
LDLIBS += -s STACK_SIZE=2048kb
LDLIBS += -s EXPORTED_FUNCTIONS=['_main']
LDLIBS += -s ASSERTIONS=1 -s SAFE_HEAP=1
LDLIBS += --preload-file $(PWD)/resources/Roboto-Regular.ttf@resources/Roboto-Regular.ttf
main:
mkdir -p build/emscripten
time $(CXX) $(CXXFLAGS) \
$(PWD)/main.c \
$(LDLIBS) -o build/emscripten/index.html
test:
make -f Makefile.emscripten main \
&& (cd build/emscripten && python3 -mhttp.server)
.PHONY: main

View file

@ -0,0 +1,28 @@
# vim: set tabstop=4 shiftwidth=4 expandtab noexpandtab:
CXX = clang
CXXFLAGS = -std=c99
CXXFLAGS += -g -O0 -fno-omit-frame-pointer
CXXFLAGS += -ferror-limit=1
CXXFLAGS += -I../..
CXXFLAGS += -I./build/_deps/stb-src
CXXFLAGS += -DGL_SILENCE_DEPRECATION
# SDL2 (Homebrew)
CXXFLAGS += -I$(shell brew --prefix sdl2)/include/SDL2
LDLIBS += -L$(shell brew --prefix sdl2)/lib -lSDL2
# macOS system frameworks (OpenGL needs these)
LDLIBS += -framework OpenGL
LDLIBS += -framework Cocoa
LDLIBS += -framework IOKit
LDLIBS += -framework CoreVideo
main:
mkdir -p build
time $(CXX) $(CXXFLAGS) \
$(PWD)/main.c \
$(LDLIBS) \
-o build/website-demo-macos-sdl2

View file

@ -0,0 +1,38 @@
GLES3 Renderer Video Demo (Using SDL2)
======================================
This directory contains a standard Video-Demo example
using work-in-progress GLES3 renderer.
While it still needs refinement, the renderer is already functional and demonstrates the core rendering pipeline.
Current features
- Supports all draw commands except custom.
- In the best-case scenario (no clipping):
- All quad-based commands (Rectangle, Image, Border) are rendered in a single draw call.
- All glyphs belonging to the same font are rendered in one instanced draw call.
- When clipping (scissoring) is used:
- The renderer flushes draw calls before and after each scissor region.
- Supports up to 4 fonts and 4 image textures.
- Image textures may also be used as texture atlases.
- Custom UserData provides per-image UV coordinates, allowing multiple images to share a single OpenGL texture.
- Uses stb_image.h and stb_truetype.h as single-header dependencies for asset loading.
- The loading layer is modular and can be replaced with a different asset pipeline if needed.
Currently builds on:
- Emscripten
- clang++ / macOS
- CMake support is not available yet.
Windowing and platform support
This example uses SDL2, but the renderer is framework agnostic.
For sake of example you can also build it with
hand-crafted Makefile.macos
make -f Makefile.emscripten test
and then navigate to http://localhost:8080
On Emscripten it works well.

View file

@ -0,0 +1,198 @@
#include <SDL.h>
#define STB_IMAGE_IMPLEMENTATION
#define STB_TRUETYPE_IMPLEMENTATION
#define CLAY_IMPLEMENTATION
#define CLAY_RENDERER_GLES3_IMPLEMENTATION
#include <clay.h>
#include "../../renderers/GLES3/clay_renderer_gles3.h"
#include "../shared-layouts/clay-video-demo.c"
#include "../../renderers/GLES3/clay_renderer_gles3_loader_stb.c"
typedef struct VideoCtx
{
int shouldContinue;
SDL_Window *sdlWindow;
SDL_GLContext sdlContext;
int screenWidth, screenHeight;
} VideoCtx;
VideoCtx g_ctx;
static int initVideo(VideoCtx *ctx, const int initialWidth, const int initialHeight)
{
SDL_Init(SDL_INIT_VIDEO);
#if defined(__EMSCRIPTEN__)
// OpenGL ES 3 profile
SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, "1");
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#else
// Apple MacOs will use it own legacy desktop GL instead
// I know, I lied, I said this was an GLES3
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
#endif
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
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);
g_ctx.sdlWindow = SDL_CreateWindow(
"SDL2 GLES3",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
initialWidth,
initialHeight,
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_SHOWN
);
g_ctx.sdlContext = SDL_GL_CreateContext(g_ctx.sdlWindow);
SDL_ShowWindow(g_ctx.sdlWindow);
SDL_Delay(1);
SDL_GL_GetDrawableSize(g_ctx.sdlWindow, &g_ctx.screenWidth, &g_ctx.screenHeight);
glViewport(0, 0, g_ctx.screenWidth, g_ctx.screenHeight);
glEnable(GL_BLEND);
// Enables blending, which allows transparent textures to be rendered properly.
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Sets the blending function.
// - `GL_SRC_ALPHA`: Uses the alpha value of the source (texture or color).
// - `GL_ONE_MINUS_SRC_ALPHA`: Makes the destination color blend with the background based on alpha.
// This is commonly used for standard transparency effects.
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
// Enables depth testing, ensuring that objects closer to the camera are drawn in front of those farther away.
// This prevents objects from rendering incorrectly based on draw order.
return 1;
}
void My_ErrorHandler(Clay_ErrorData errorData)
{
printf("[ClaY ErroR] %s", errorData.errorText.chars);
}
Stb_FontData g_stbFonts[MAX_FONTS]; // Fonts userData
Gles3_Renderer g_gles3; // The renderer itself
Uint64 NOW = 0;
Uint64 LAST = 0;
double deltaTime = 0;
// is executed before everything
void init()
{
size_t clayRequiredMemory = Clay_MinMemorySize();
g_gles3.clayMemory = (Clay_Arena){
.capacity = clayRequiredMemory,
.memory = (char *)malloc(clayRequiredMemory),
};
Clay_Context *clayCtx = Clay_Initialize(
g_gles3.clayMemory,
(Clay_Dimensions){
.width = (float)g_ctx.screenWidth,
.height = (float)g_ctx.screenHeight,
},
(Clay_ErrorHandler){
.errorHandlerFunction = My_ErrorHandler,
});
// Note that MeasureText has to be set after the Context is set!
Clay_SetCurrentContext(clayCtx);
Clay_SetMeasureTextFunction(Stb_MeasureText, &g_stbFonts);
Gles3_SetRenderTextFunction(&g_gles3, Stb_RenderText, &g_stbFonts);
Gles3_Initialize(&g_gles3, 4096);
int atlasW = 1024;
int atlasH = 1024;
if (!Stb_LoadFont(
&g_gles3.fontTextures[0],
&g_stbFonts[0],
"resources/Roboto-Regular.ttf",
24.0f, // bake pixel height
atlasW,
atlasH))
abort();
Clay_SetDebugModeEnabled(true);
}
void loop()
{
glClearColor(0.1f, 0.2f, 0.1f, 1.0f);
Clay_Vector2 scrollDelta = {};
SDL_Event event;
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_QUIT:
{
g_ctx.shouldContinue = false;
}
case SDL_MOUSEWHEEL:
{
scrollDelta.x = event.wheel.x;
scrollDelta.y = event.wheel.y;
break;
}
}
}
LAST = NOW;
NOW = SDL_GetPerformanceCounter();
deltaTime = (double)((NOW - LAST) * 1000 / (double)SDL_GetPerformanceFrequency());
int mouseX = 0;
int mouseY = 0;
Uint32 mouseState = SDL_GetMouseState(&mouseX, &mouseY);
Clay_Vector2 mousePosition = (Clay_Vector2){(float)mouseX, (float)mouseY};
Clay_SetPointerState(mousePosition, mouseState & SDL_BUTTON(1));
Clay_UpdateScrollContainers(
true,
(Clay_Vector2){scrollDelta.x, scrollDelta.y},
deltaTime);
SDL_GL_GetDrawableSize(g_ctx.sdlWindow, &g_ctx.screenWidth, &g_ctx.screenHeight);
glViewport(0, 0, g_ctx.screenWidth, g_ctx.screenHeight);
Clay_SetLayoutDimensions((Clay_Dimensions){(float)g_ctx.screenWidth, (float)g_ctx.screenHeight});
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE); // Clay renderer is simple and never writes to depth buffer
ClayVideoDemo_Data data = ClayVideoDemo_Initialize();
Clay_RenderCommandArray cmds = ClayVideoDemo_CreateLayout(&data);
Gles3_Render(&g_gles3, cmds, g_stbFonts);
SDL_GL_SwapWindow(g_ctx.sdlWindow);
}
// Just initializes and spins the animation loop
int main()
{
initVideo(&g_ctx, 1280, 720);
init();
g_ctx.shouldContinue = true;
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop(loop, 0, 1);
#else
while (g_ctx.shouldContinue)
{
loop();
}
#endif
}