mirror of
https://github.com/nicbarker/clay.git
synced 2025-12-23 17:41:06 +00:00
📦 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:
parent
389a044cd2
commit
24b42b7b1c
2
examples/GLES3-GLFW-video-demo/.gitignore
vendored
Normal file
2
examples/GLES3-GLFW-video-demo/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/build/
|
||||
/website-demo-macos-glfw*
|
||||
98
examples/GLES3-GLFW-video-demo/CMakeLists.txt
Normal file
98
examples/GLES3-GLFW-video-demo/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
cmake_minimum_required(VERSION 3.27)
|
||||
project(GLES3_GLFW_video_demo C)
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
||||
# -------------------------------------------------
|
||||
# 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)
|
||||
|
||||
# -------------------------------------------------
|
||||
# GLFW
|
||||
# -------------------------------------------------
|
||||
FetchContent_Declare(
|
||||
glfw
|
||||
GIT_REPOSITORY https://github.com/glfw/glfw.git
|
||||
GIT_TAG 3.4
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
FetchContent_MakeAvailable(glfw)
|
||||
|
||||
# Disable examples/tests/docs (important)
|
||||
set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||
set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE)
|
||||
|
||||
# -------------------------------------------------
|
||||
# Executable
|
||||
# -------------------------------------------------
|
||||
add_executable(GLES3_GLFW_video_demo
|
||||
main.c
|
||||
)
|
||||
|
||||
target_include_directories(GLES3_GLFW_video_demo
|
||||
PUBLIC
|
||||
.
|
||||
../..
|
||||
${stb_SOURCE_DIR}
|
||||
)
|
||||
|
||||
# -------------------------------------------------
|
||||
# Link libraries
|
||||
# -------------------------------------------------
|
||||
target_link_libraries(GLES3_GLFW_video_demo
|
||||
PRIVATE
|
||||
glfw
|
||||
)
|
||||
|
||||
# -------------------------------------------------
|
||||
# Platform-specific OpenGL / GLES
|
||||
# -------------------------------------------------
|
||||
if(APPLE)
|
||||
find_library(OPENGL_FRAMEWORK OpenGL)
|
||||
target_link_libraries(GLES3_GLFW_video_demo PRIVATE ${OPENGL_FRAMEWORK})
|
||||
|
||||
# Needed for GLFW on macOS
|
||||
target_link_libraries(GLES3_GLFW_video_demo
|
||||
PRIVATE
|
||||
"-framework Cocoa"
|
||||
"-framework IOKit"
|
||||
"-framework CoreVideo"
|
||||
)
|
||||
elseif(WIN32)
|
||||
target_link_libraries(GLES3_GLFW_video_demo PRIVATE opengl32)
|
||||
elseif(UNIX)
|
||||
target_link_libraries(GLES3_GLFW_video_demo PRIVATE GL)
|
||||
endif()
|
||||
|
||||
# -------------------------------------------------
|
||||
# Build flags (kept minimal)
|
||||
# -------------------------------------------------
|
||||
if(MSVC)
|
||||
target_compile_options(GLES3_GLFW_video_demo PRIVATE /W3)
|
||||
else()
|
||||
target_compile_options(GLES3_GLFW_video_demo PRIVATE -Wall -Wextra)
|
||||
endif()
|
||||
|
||||
# -------------------------------------------------
|
||||
# Copy resources
|
||||
# -------------------------------------------------
|
||||
add_custom_command(
|
||||
TARGET GLES3_GLFW_video_demo POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/resources
|
||||
${CMAKE_CURRENT_BINARY_DIR}/resources
|
||||
)
|
||||
33
examples/GLES3-GLFW-video-demo/Makefile.emscripten
Normal file
33
examples/GLES3-GLFW-video-demo/Makefile.emscripten
Normal 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_GLFW=3
|
||||
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
|
||||
29
examples/GLES3-GLFW-video-demo/Makefile.macos
Normal file
29
examples/GLES3-GLFW-video-demo/Makefile.macos
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# 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
|
||||
|
||||
# GLFW (Homebrew)
|
||||
CXXFLAGS += -I$(shell brew --prefix glfw)/include
|
||||
LDLIBS += -L$(shell brew --prefix glfw)/lib -lglfw
|
||||
|
||||
|
||||
# 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-glfw
|
||||
55
examples/GLES3-GLFW-video-demo/README.md
Normal file
55
examples/GLES3-GLFW-video-demo/README.md
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
GLES3 Renderer Video Demo (Using GLFW)
|
||||
======================================
|
||||
|
||||
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 GLFW, and the renderer is framework agnostic
|
||||
|
||||
How to build it with CMake?
|
||||
---------------------------
|
||||
|
||||
Cmake build is the easiest way to build it:
|
||||
|
||||
mkdir build
|
||||
cmake -S . -B ./build
|
||||
|
||||
How to build and run on Emscripten:
|
||||
----------------------------------
|
||||
|
||||
For Emscripten the build is a bit custom,
|
||||
but it depends on CMakeBuild to install header-stb dependency.
|
||||
So you still need to build it with CMake first.
|
||||
|
||||
And then you have to source the Emscripten SDK:
|
||||
|
||||
source /path/to/emscripten/emsdk/emsdk_env.sh
|
||||
|
||||
Then build it with hand-crafted Makefile.emscripten:
|
||||
|
||||
make -f Makefile.emscripten test
|
||||
|
||||
and then navigate to http://localhost:8080
|
||||
|
||||
207
examples/GLES3-GLFW-video-demo/main.c
Normal file
207
examples/GLES3-GLFW-video-demo/main.c
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
#include <stdio.h>
|
||||
|
||||
#include <GLFW/glfw3.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;
|
||||
GLFWwindow *glfwWindow;
|
||||
int screenWidth, screenHeight;
|
||||
} VideoCtx;
|
||||
|
||||
VideoCtx g_ctx;
|
||||
|
||||
static int initVideo(VideoCtx *ctx, const int initialWidth, const int initialHeight)
|
||||
{
|
||||
if (!glfwInit())
|
||||
{
|
||||
fprintf(stderr, "Failed to init GLFW\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
glfwDefaultWindowHints();
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
||||
glfwWindowHint(GLFW_SAMPLES, 4); // enable multisampling
|
||||
|
||||
// NO solution for high DPI yet
|
||||
glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE);
|
||||
|
||||
g_ctx.glfwWindow = glfwCreateWindow(initialWidth, initialHeight, "GLES3 GLFW Video Demo", NULL, NULL);
|
||||
|
||||
if (g_ctx.glfwWindow == NULL)
|
||||
{
|
||||
fprintf(stderr, "Failed to create GLFW window\n");
|
||||
glfwTerminate();
|
||||
return 0;
|
||||
}
|
||||
glfwMakeContextCurrent(g_ctx.glfwWindow);
|
||||
|
||||
glfwGetWindowSize(g_ctx.glfwWindow, &g_ctx.screenWidth, &g_ctx.screenHeight);
|
||||
glViewport(0, 0, g_ctx.screenWidth, g_ctx.screenHeight);
|
||||
printf("Frame buffer size %dx%d\n", 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
|
||||
|
||||
static Clay_Vector2 g_scrollDelta = {0.0f, 0.0f};
|
||||
static double g_lastTime = 0.0;
|
||||
static double g_deltaTime = 0.0;
|
||||
static void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
|
||||
{
|
||||
g_scrollDelta.x += (float)xoffset;
|
||||
g_scrollDelta.y += (float)yoffset;
|
||||
}
|
||||
|
||||
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);
|
||||
// This example uses stb loader, but you can inject your custom loader
|
||||
// to load Images and Fonts if you don't want to use STB library
|
||||
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);
|
||||
glfwSetScrollCallback(g_ctx.glfwWindow, scroll_callback);
|
||||
}
|
||||
|
||||
|
||||
void loop()
|
||||
{
|
||||
Clay_Vector2 scrollDelta = {0.0f, 0.0f};
|
||||
|
||||
glfwPollEvents();
|
||||
|
||||
/* Quit handling */
|
||||
if (glfwWindowShouldClose(g_ctx.glfwWindow))
|
||||
{
|
||||
g_ctx.shouldContinue = false;
|
||||
}
|
||||
|
||||
/* Consume scroll delta (accumulated via callback) */
|
||||
scrollDelta = g_scrollDelta;
|
||||
g_scrollDelta.x = 0.0f;
|
||||
g_scrollDelta.y = 0.0f;
|
||||
|
||||
/* Delta time (milliseconds, like your SDL version) */
|
||||
double now = glfwGetTime(); // seconds
|
||||
g_deltaTime = (now - g_lastTime) * 1000.0;
|
||||
g_lastTime = now;
|
||||
|
||||
double mouseX = 0.0;
|
||||
double mouseY = 0.0;
|
||||
glfwGetCursorPos(g_ctx.glfwWindow, &mouseX, &mouseY);
|
||||
|
||||
Clay_Vector2 mousePosition = {
|
||||
(float)mouseX,
|
||||
(float)mouseY
|
||||
};
|
||||
|
||||
int mousePressed =
|
||||
glfwGetMouseButton(g_ctx.glfwWindow, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS;
|
||||
|
||||
Clay_SetPointerState(mousePosition, mousePressed);
|
||||
|
||||
Clay_UpdateScrollContainers(
|
||||
true,
|
||||
(Clay_Vector2){scrollDelta.x, scrollDelta.y},
|
||||
g_deltaTime);
|
||||
|
||||
glfwGetWindowSize(g_ctx.glfwWindow, &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
|
||||
glClearColor(0.1f, 0.2f, 0.1f, 1.0f);
|
||||
|
||||
ClayVideoDemo_Data data = ClayVideoDemo_Initialize();
|
||||
Clay_RenderCommandArray cmds = ClayVideoDemo_CreateLayout(&data);
|
||||
|
||||
Gles3_Render(&g_gles3, cmds, g_stbFonts);
|
||||
|
||||
glfwSwapBuffers(g_ctx.glfwWindow);
|
||||
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
BIN
examples/GLES3-GLFW-video-demo/resources/Roboto-Regular.ttf
Normal file
BIN
examples/GLES3-GLFW-video-demo/resources/Roboto-Regular.ttf
Normal file
Binary file not shown.
3
examples/GLES3-SDL2-sidebar-scrolling-container/.gitignore
vendored
Normal file
3
examples/GLES3-SDL2-sidebar-scrolling-container/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
/build/
|
||||
/website-demo-macos-sdl2*
|
||||
/macos-sidebar-scrolling-container*
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
cmake_minimum_required(VERSION 3.27)
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
project(GLES3_SDL2_sidebar_scrolling_container 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_sidebar_scrolling_container main.c)
|
||||
target_compile_options(GLES3_SDL2_sidebar_scrolling_container PUBLIC)
|
||||
target_include_directories(GLES3_SDL2_sidebar_scrolling_container
|
||||
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_sidebar_scrolling_container PUBLIC
|
||||
SDL2::SDL2main
|
||||
SDL2::SDL2-static
|
||||
)
|
||||
|
||||
# -------------------------------------------------
|
||||
# Platform-specific OpenGL / GLES
|
||||
# -------------------------------------------------
|
||||
find_package(SDL2 REQUIRED)
|
||||
find_library(OPENGL_FRAMEWORK OpenGL)
|
||||
target_link_libraries(GLES3_SDL2_sidebar_scrolling_container
|
||||
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_sidebar_scrolling_container POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/resources
|
||||
${CMAKE_CURRENT_BINARY_DIR}/resources
|
||||
)
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
# 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/profile-picture.png@resources/profile-picture.png
|
||||
LDLIBS += --preload-file $(PWD)/resources/millbank.jpeg@resources/millbank.jpeg
|
||||
LDLIBS += --preload-file $(PWD)/resources/Roboto-Regular.ttf@resources/Roboto-Regular.ttf
|
||||
LDLIBS += --preload-file $(PWD)/resources/RobotoMono-Medium.ttf@resources/RobotoMono-Medium.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
|
||||
|
|
@ -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/macos-sidebar-scrolling-container-sdl2
|
||||
57
examples/GLES3-SDL2-sidebar-scrolling-container/README.md
Normal file
57
examples/GLES3-SDL2-sidebar-scrolling-container/README.md
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
GLES3 Renderer Scrolling Container (Using SDL2)
|
||||
===============================================
|
||||
|
||||
This directory contains a complete example thatn can me used to test all different draw commands
|
||||
using work-in-progress GLES3 renderer.
|
||||
While it still needs refinement, the renderer is already functional and demonstrates the core rendering pipeline.
|
||||
|
||||
The images used as resources in this example, namely:
|
||||
|
||||
- millbank.jpeg a window with a panoramic city view;
|
||||
- and profile-picture.png showing objects aligned in a circle;
|
||||
|
||||
are taken with my phone camera and are dedicated to the Public Domain.
|
||||
|
||||
How to build it with CMake?
|
||||
---------------------------
|
||||
|
||||
Cmake build is the easiest way to build it:
|
||||
|
||||
mkdir build
|
||||
cmake -S . -B ./build
|
||||
|
||||
How to build and run on Emscripten:
|
||||
----------------------------------
|
||||
|
||||
For Emscripten the build is a bit custom,
|
||||
but it depends on CMakeBuild to install header-stb dependency.
|
||||
So you still need to build it with CMake first.
|
||||
|
||||
And then you have to source the Emscripten SDK:
|
||||
|
||||
source /path/to/emscripten/emsdk/emsdk_env.sh
|
||||
|
||||
Then build it with hand-crafted Makefile.emscripten:
|
||||
|
||||
make -f Makefile.emscripten test
|
||||
|
||||
and then navigate to http://localhost:8080
|
||||
|
||||
How to build and run on Mac:
|
||||
----------------------------
|
||||
|
||||
Requires SDL2:
|
||||
|
||||
brew install sdl2
|
||||
|
||||
Requires STB:
|
||||
|
||||
cmake -B build
|
||||
|
||||
Build it with:
|
||||
|
||||
make -f Makefile.macos
|
||||
|
||||
And run with:
|
||||
|
||||
./macos-sidebar-scrolling-container-sdl2
|
||||
565
examples/GLES3-SDL2-sidebar-scrolling-container/main.c
Normal file
565
examples/GLES3-SDL2-sidebar-scrolling-container/main.c
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 264 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 181 KiB |
2
examples/GLES3-SDL2-video-demo/.gitignore
vendored
Normal file
2
examples/GLES3-SDL2-video-demo/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/build/
|
||||
/website-demo-macos-sdl2*
|
||||
83
examples/GLES3-SDL2-video-demo/CMakeLists.txt
Normal file
83
examples/GLES3-SDL2-video-demo/CMakeLists.txt
Normal 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
|
||||
)
|
||||
33
examples/GLES3-SDL2-video-demo/Makefile.emscripten
Normal file
33
examples/GLES3-SDL2-video-demo/Makefile.emscripten
Normal 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
|
||||
28
examples/GLES3-SDL2-video-demo/Makefile.macos
Normal file
28
examples/GLES3-SDL2-video-demo/Makefile.macos
Normal 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
|
||||
38
examples/GLES3-SDL2-video-demo/README.md
Normal file
38
examples/GLES3-SDL2-video-demo/README.md
Normal 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.
|
||||
|
||||
198
examples/GLES3-SDL2-video-demo/main.c
Normal file
198
examples/GLES3-SDL2-video-demo/main.c
Normal 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
|
||||
}
|
||||
BIN
examples/GLES3-SDL2-video-demo/resources/Roboto-Regular.ttf
Normal file
BIN
examples/GLES3-SDL2-video-demo/resources/Roboto-Regular.ttf
Normal file
Binary file not shown.
|
|
@ -219,7 +219,7 @@ Clay_RenderCommandArray ClayVideoDemo_CreateLayout(ClayVideoDemo_Data *data) {
|
|||
SidebarClickData *clickData = (SidebarClickData *)(data->frameArena.memory + data->frameArena.offset);
|
||||
*clickData = (SidebarClickData) { .requestedDocumentIndex = i, .selectedDocumentIndex = &data->selectedDocumentIndex };
|
||||
data->frameArena.offset += sizeof(SidebarClickData);
|
||||
CLAY_AUTO_ID({ .layout = sidebarButtonLayout, .backgroundColor = (Clay_Color) { 120, 120, 120, Clay_Hovered() ? 120 : 0 }, .cornerRadius = CLAY_CORNER_RADIUS(8) }) {
|
||||
CLAY_AUTO_ID({ .layout = sidebarButtonLayout, .backgroundColor = (Clay_Color) { 120, 120, 120, (float)(Clay_Hovered() ? 120 : 0) }, .cornerRadius = CLAY_CORNER_RADIUS(8) }) {
|
||||
Clay_OnHover(HandleSidebarInteraction, clickData);
|
||||
CLAY_TEXT(document.title, CLAY_TEXT_CONFIG({
|
||||
.fontId = FONT_ID_BODY_16,
|
||||
|
|
|
|||
822
renderers/GLES3/clay_renderer_gles3.h
Normal file
822
renderers/GLES3/clay_renderer_gles3.h
Normal file
|
|
@ -0,0 +1,822 @@
|
|||
#ifndef CLAY_RENDERER_GLES3_H
|
||||
#define CLAY_RENDERER_GLES3_H
|
||||
|
||||
// There may be custom header customizations, very client specific
|
||||
// let client indicate that they manage headers by setting GLSL_VERSION
|
||||
#ifndef GLSL_VERSION
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/html5.h>
|
||||
#include <GLES3/gl3.h>
|
||||
#define GLSL_VERSION "#version 300 es"
|
||||
#else
|
||||
// Only apple computers now sorry
|
||||
// That means it is not really GLES3 but desktop OpenGL 3
|
||||
// Luckily, it is compatible with GLES3
|
||||
#include <OpenGL/gl3.h>
|
||||
#define GLSL_VERSION "#version 330 core"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define MAX_IMAGES 4
|
||||
#define MAX_FONTS 4
|
||||
|
||||
/*
|
||||
* Instanced rendering for Rects/Images/Borders
|
||||
* will use this data
|
||||
* Note, it needs to be padded to 4 floats
|
||||
* Draws:
|
||||
* - One rectangular with possibly rounded corner
|
||||
* - And possibly with a hole inside (with rounded edges too, if corners are rounded)
|
||||
* - It could also draw a picture with alsoe rounded corner
|
||||
*/
|
||||
typedef struct RectInstance
|
||||
{
|
||||
float x, y, w, h; // 4 Draw where on screen
|
||||
float u0, v0, u1, v1; // 4 Atlas region
|
||||
float r, g, b, a; // 4 Color
|
||||
float radiusTL, radiusTR; // 2 Corner rounding
|
||||
float radiusBL, radiusBR; // 2
|
||||
float borderL, borderR; // 2 Border widths
|
||||
float borderT, borderB; // 2
|
||||
float texToUse; // 1 Texture atlas to take an image from (1-4)
|
||||
float pad[3]; // 3
|
||||
} RectInstance;
|
||||
|
||||
/*
|
||||
* Struct for glyph instanced rendering
|
||||
* Each glyph consists of 6 vertexes (to make 2 triangle of a quad)
|
||||
*/
|
||||
typedef struct GlyphVtx
|
||||
{
|
||||
float x, y; // To draw Where
|
||||
float u, v; // To draw What
|
||||
float r, g, b, a; // Text color
|
||||
float atlasTexUnit; // Shader will have all samples loaded but this will point which to use
|
||||
float pad[3]; // 3
|
||||
} GlyphVtx;
|
||||
|
||||
typedef struct Gles3_GlyphVtxArray
|
||||
{
|
||||
GlyphVtx *instData;
|
||||
int capacity;
|
||||
int count;
|
||||
} Gles3_GlyphVtxArray;
|
||||
|
||||
typedef struct Gles3_QuadInstanceArray
|
||||
{
|
||||
RectInstance *instData; // packed per-instance floats
|
||||
int capacity; // how many instances it can hold
|
||||
int count; // how many instances does it actually hold
|
||||
} Gles3_QuadInstanceArray;
|
||||
|
||||
typedef struct Gles3_ImageConfig
|
||||
{
|
||||
int textureToUse;
|
||||
float u0, v0;
|
||||
float u1, v1;
|
||||
} Gles3_ImageConfig;
|
||||
|
||||
#ifndef CLAY_RENDERER_GLES3_IMPLEMENTATION
|
||||
typedef struct Gles3_Renderer Gles3_Renderer;
|
||||
#endif
|
||||
|
||||
#ifdef CLAY_RENDERER_GLES3_IMPLEMENTATION
|
||||
|
||||
#include <math.h>
|
||||
#include <clay.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "clay_renderer_gles3.h"
|
||||
|
||||
enum
|
||||
{
|
||||
ATTR_QUAD_POS = 0,
|
||||
ATTR_QUAD_RECT = 1,
|
||||
ATTR_QUAD_COLOR = 2,
|
||||
ATTR_QUAD_UV = 3,
|
||||
ATTR_QUAD_RAD = 4,
|
||||
ATTR_QUAD_BORDER = 5,
|
||||
ATTR_QUAD_TEX = 6,
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
ATTR_GLYPH_POS = 0,
|
||||
ATTR_GLYPH_UV = 1,
|
||||
ATTR_GLYPH_COLOR = 2,
|
||||
ATTR_GLYPH_TEX = 3,
|
||||
};
|
||||
|
||||
/*
|
||||
* rendering
|
||||
*/
|
||||
|
||||
const char *GLES3_QUAD_VERTEX_SHADER =
|
||||
GLSL_VERSION
|
||||
"\n"
|
||||
"precision mediump float;\n"
|
||||
"layout(location = 0) in vec2 aPos; // unit quad (0..1)\n"
|
||||
"layout(location = 1) in vec4 aRect; // x,y,w,h (pixels)\n"
|
||||
"layout(location = 3) in vec4 aUV; // u0,v0,u1,v1\n"
|
||||
"layout(location = 2) in vec4 aColor; // rgba\n"
|
||||
"layout(location = 4) in vec4 aCornerRadii;\n"
|
||||
"layout(location = 5) in vec4 aBorderWidths;\n"
|
||||
"layout(location = 6) in float aTexSlot;\n"
|
||||
"uniform vec2 uScreen; // screen size in pixels\n"
|
||||
"out vec2 vPos;\n"
|
||||
"out vec4 vRect;\n"
|
||||
"out vec4 vColor;\n"
|
||||
"out vec2 vUV;\n"
|
||||
"out vec4 vCornerRadii;\n"
|
||||
"out vec4 vBorderWidths;\n"
|
||||
"out float vTexSlot;\n"
|
||||
"void main() {\n"
|
||||
" vec2 pos = vec2(aPos.x * aRect.z + aRect.x, aPos.y * aRect.w + aRect.y);\n"
|
||||
" vec2 ndc = pos / uScreen * 2.0 - 1.0; // ndc.y increases up; pos y increases down (we will inve\n"
|
||||
" ndc.y = -ndc.y;\n"
|
||||
" gl_Position = vec4(ndc, 0.0, 1.0);\n"
|
||||
" vPos = aPos;\n"
|
||||
" vRect = aRect;\n"
|
||||
" vColor = aColor;\n"
|
||||
" vUV = mix(aUV.xy, aUV.zw, aPos);\n"
|
||||
" vCornerRadii = aCornerRadii;\n"
|
||||
" vBorderWidths = aBorderWidths;\n"
|
||||
" vTexSlot = aTexSlot;\n"
|
||||
"}\n";
|
||||
|
||||
const char *GLES3_QUAD_FRAGMENT_SHADER =
|
||||
GLSL_VERSION
|
||||
"\n"
|
||||
"precision mediump float;\n"
|
||||
"in vec2 vPos;\n"
|
||||
"in vec4 vRect;\n"
|
||||
"in vec4 vColor;\n"
|
||||
"in vec2 vUV;\n"
|
||||
"in vec4 vCornerRadii;\n"
|
||||
"in vec4 vBorderWidths;\n"
|
||||
"in float vTexSlot;\n"
|
||||
"uniform sampler2D uTex0;\n"
|
||||
"uniform sampler2D uTex1;\n"
|
||||
"uniform sampler2D uTex2;\n"
|
||||
"uniform sampler2D uTex3;\n"
|
||||
"out vec4 frag;\n"
|
||||
"void main() {\n"
|
||||
" // Pixel coordinates in pixel space\n"
|
||||
" vec2 pix = vRect.xy + vPos * vRect.zw;\n"
|
||||
" float x0 = vRect.x;\n"
|
||||
" float y0 = vRect.y;\n"
|
||||
" float w = vRect.z;\n"
|
||||
" float h = vRect.w;\n"
|
||||
" // Local position inside the rectangle (0..w, 0..h)\n"
|
||||
" vec2 local = pix - vec2(x0, y0);\n"
|
||||
" // Original corner radii\n"
|
||||
" float tl = vCornerRadii.x;\n"
|
||||
" float tr = vCornerRadii.y;\n"
|
||||
" float bl = vCornerRadii.z;\n"
|
||||
" float br = vCornerRadii.w;\n"
|
||||
" // Border thicknesses\n"
|
||||
" float L = vBorderWidths.x;\n"
|
||||
" float R = vBorderWidths.y;\n"
|
||||
" float T = vBorderWidths.z;\n"
|
||||
" float B = vBorderWidths.w;\n"
|
||||
" bool CLAY_BORDERS_ARE_INSET = true; // it is true\n"
|
||||
" bool isBorder = (L > 0.0 || R > 0.0 || T > 0.0 || B > 0.0);\n"
|
||||
" float outerAlpha = 1.0;\n"
|
||||
" // If it is not a border but rect or image, then it only has outer border what is provided\n"
|
||||
" // Otherwise it increases the outter border, but the provided borde is the ineer border\n"
|
||||
" // I think is better not increase rounding radius when that radius is smaller than border thickness\n"
|
||||
" float outter_tl;\n"
|
||||
" float outter_tr;\n"
|
||||
" float outter_bl;\n"
|
||||
" float outter_br;\n"
|
||||
" if (CLAY_BORDERS_ARE_INSET) {\n"
|
||||
" // Actural behaviour\n"
|
||||
" outter_tl = tl;\n"
|
||||
" outter_tr = tr;\n"
|
||||
" outter_bl = bl;\n"
|
||||
" outter_br = br;\n"
|
||||
" tl = (tl > min(T, L)) ? tl - min(T, L) : tl;\n"
|
||||
" tr = (tr > min(T, R)) ? tr - min(T, R) : tr;\n"
|
||||
" bl = (bl > min(B, L)) ? bl - min(B, L) : bl;\n"
|
||||
" br = (br > min(B, R)) ? br - min(B, R) : br;\n"
|
||||
" } else {\n"
|
||||
" // Hypothetical behaviour\n"
|
||||
" outter_tl = (tl > min(T, L)) ? tl + min(T, L) : tl;\n"
|
||||
" outter_tr = (tr > min(T, R)) ? tr + min(T, R) : tr;\n"
|
||||
" outter_bl = (bl > min(B, L)) ? bl + min(B, L) : bl;\n"
|
||||
" outter_br = (br > min(B, R)) ? br + min(B, R) : br;\n"
|
||||
" }\n"
|
||||
" if (outter_tl > 0.0 && local.x < outter_tl && local.y < outter_tl)\n"
|
||||
" outerAlpha = step(length(local - vec2(outter_tl, outter_tl)), outter_tl);\n"
|
||||
" if (outter_tr > 0.0 && local.x > w - outter_tr && local.y < outter_tr)\n"
|
||||
" outerAlpha *= step(length(local - vec2(w - outter_tr, outter_tr)), outter_tr);\n"
|
||||
" if (outter_bl > 0.0 && local.x < outter_bl && local.y > h - outter_bl)\n"
|
||||
" outerAlpha *= step(length(local - vec2(outter_bl, h - outter_bl)), outter_bl);\n"
|
||||
" if (outter_br > 0.0 && local.x > w - outter_br && local.y > h - outter_br)\n"
|
||||
" outerAlpha *= step(length(local - vec2(w - outter_br, h - outter_br)), outter_br);\n"
|
||||
" if (outerAlpha < 0.5)\n"
|
||||
" discard;\n"
|
||||
" // -------- Border logic --------\n"
|
||||
" if (isBorder) {\n"
|
||||
" float iw = w - L - R;\n"
|
||||
" float ih = h - T - B;\n"
|
||||
" vec2 innerLocal = local - vec2(L, T);\n"
|
||||
" // Check if pixel is inside inner rounded rect\n"
|
||||
" bool insideInner = true;\n"
|
||||
" if (tl > 0.0 && innerLocal.x < tl && innerLocal.y < tl)\n"
|
||||
" insideInner = (length(innerLocal - vec2(tl, tl)) <= tl);\n"
|
||||
" if (tr > 0.0 && innerLocal.x > iw - tr && innerLocal.y < tr)\n"
|
||||
" insideInner = insideInner && (length(innerLocal - vec2(iw - tr, tr)) <= tr);\n"
|
||||
" // Bottom-left\n"
|
||||
" if (bl> 0.0 && innerLocal.x < bl && innerLocal.y > ih - bl) \n"
|
||||
" insideInner = insideInner && (length(innerLocal - vec2(bl, ih - bl)) <= bl);\n"
|
||||
" // Bottom-right\n"
|
||||
" if (br > 0.0 && innerLocal.x > iw - br && innerLocal.y > ih - br)\n"
|
||||
" insideInner = insideInner && (length(innerLocal - vec2(iw - br, ih - br)) <= br);\n"
|
||||
" // Discard pixels inside inner rounded rect\n"
|
||||
" if (insideInner && innerLocal.x >= 0.0 && innerLocal.x <= iw && innerLocal.y >= 0.0 && innerLocal.y <= ih)\n"
|
||||
" discard;\n"
|
||||
" frag = vColor;\n"
|
||||
" return;\n"
|
||||
" }\n"
|
||||
" // -------- Non-border rectangle or image --------\n"
|
||||
" if (vTexSlot < 0.0) {\n"
|
||||
" frag = vColor;\n"
|
||||
" } else {\n"
|
||||
" int slot = int(vTexSlot + 0.5);\n"
|
||||
" if (slot == 0) frag = texture(uTex0, vUV);\n"
|
||||
" if (slot == 1) frag = texture(uTex1, vUV);\n"
|
||||
" if (slot == 2) frag = texture(uTex2, vUV);\n"
|
||||
" if (slot == 3) frag = texture(uTex3, vUV);\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
|
||||
const char *GLES3_TEXT_VERTEX_SHADER =
|
||||
GLSL_VERSION
|
||||
"\n"
|
||||
"precision mediump float;\n"
|
||||
"layout(location = 0) in vec2 aPos;\n"
|
||||
"layout(location = 1) in vec2 aUV;\n"
|
||||
"layout(location = 2) in vec4 aColor;\n"
|
||||
"layout(location = 3) in float aTexSlot;\n"
|
||||
"uniform vec2 uScreen;\n"
|
||||
"out vec2 vUV;\n"
|
||||
"out vec4 vColor;\n"
|
||||
"out float vTexSlot;\n"
|
||||
"void main() {\n"
|
||||
" vec2 ndc = (aPos / uScreen) * 2.0 - 1.0;\n"
|
||||
" gl_Position = vec4(ndc * vec2(1.0, -1.0), 0.0, 1.0);\n"
|
||||
" vUV = aUV;\n"
|
||||
" vColor = aColor;\n"
|
||||
" vTexSlot = aTexSlot;\n"
|
||||
"}\n";
|
||||
|
||||
const char *GLES3_TEXT_FRAGMENT_SHADER =
|
||||
GLSL_VERSION
|
||||
"\n"
|
||||
"precision mediump float;\n"
|
||||
"in vec2 vUV;\n"
|
||||
"in vec4 vColor;\n"
|
||||
"in float vTexSlot;\n"
|
||||
"uniform sampler2D uTex0;\n"
|
||||
"uniform sampler2D uTex1;\n"
|
||||
"uniform sampler2D uTex2;\n"
|
||||
"uniform sampler2D uTex3;\n"
|
||||
"out vec4 fragColor;\n"
|
||||
"void main() {\n"
|
||||
" int slot = int(vTexSlot + 0.5);\n"
|
||||
" float coverage;\n"
|
||||
" if (slot == 0) coverage = texture(uTex0, vUV).r;\n"
|
||||
" if (slot == 1) coverage = texture(uTex1, vUV).r;\n"
|
||||
" if (slot == 2) coverage = texture(uTex2, vUV).r;\n"
|
||||
" if (slot == 3) coverage = texture(uTex3, vUV).r;\n"
|
||||
" fragColor = vec4(vColor.rgb, vColor.a * coverage);\n"
|
||||
"} \n";
|
||||
|
||||
/**
|
||||
* This renderer accumulates all quads and glyphs of every draw coommand
|
||||
* in their array, and flushes them in just 2 instanced draw calls to OpenGL
|
||||
*/
|
||||
typedef struct Gles3_Renderer
|
||||
{
|
||||
Clay_Arena clayMemory;
|
||||
|
||||
// It is super important keep track on the performance of this renderer:
|
||||
uint64_t totalDrawCallsToOpenGl;
|
||||
|
||||
float screenWidth;
|
||||
float screenHeight;
|
||||
|
||||
/* Quads rendering */
|
||||
GLuint quadVAO;
|
||||
GLuint quadVBO;
|
||||
GLuint quadInstanceVBO;
|
||||
GLuint quadShaderId;
|
||||
GLuint imageTextures[MAX_IMAGES];
|
||||
Gles3_QuadInstanceArray quadInstanceArray; // Each instance is one quad
|
||||
|
||||
/* Fonts rendering */
|
||||
GLuint textVAO;
|
||||
GLuint textVBO;
|
||||
GLuint textShader;
|
||||
GLuint fontTextures[MAX_FONTS];
|
||||
Gles3_GlyphVtxArray glyphVtxArray; // Instance data: every vertex is an element,
|
||||
// 6 elements per each instance
|
||||
|
||||
// Text renderer is delegated to external function, which is supposed
|
||||
// to add glyph data based on passed render text command
|
||||
void (*renderTextFunction)(
|
||||
Clay_RenderCommand *cmd, // Will be always of CLAY_RENDER_COMMAND_TYPE_TEXT
|
||||
Gles3_GlyphVtxArray *accum, // 6 vertices need to be added to this array
|
||||
void *userData // Fonts pallete
|
||||
);
|
||||
} Gles3_Renderer;
|
||||
|
||||
static GLuint Gles3__CompileShader(GLenum type, const char *source)
|
||||
{
|
||||
GLuint shader = glCreateShader(type);
|
||||
glShaderSource(shader, 1, &source, NULL);
|
||||
glCompileShader(shader);
|
||||
|
||||
GLint success;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
|
||||
if (!success)
|
||||
{
|
||||
char infoLog[512];
|
||||
glGetShaderInfoLog(shader, 512, NULL, infoLog);
|
||||
|
||||
printf("ERROR::SHADER::COMPILATION_FAILED\n");
|
||||
printf("SHADER SOURCE:\n%s\n", source);
|
||||
printf("SHADER TYPE: ");
|
||||
if (type == GL_VERTEX_SHADER)
|
||||
printf("Vertex Shader");
|
||||
else if (type == GL_FRAGMENT_SHADER)
|
||||
printf("Fragment Shader");
|
||||
else
|
||||
printf("Unknown");
|
||||
printf("\nSHADER COMPILATION ERROR:\n%s\n", infoLog);
|
||||
abort();
|
||||
}
|
||||
return shader;
|
||||
}
|
||||
|
||||
GLuint Gles3__CreateShaderProgram(
|
||||
const char *vertexShaderSource,
|
||||
const char *fragmentShaderSource)
|
||||
{
|
||||
GLuint vertexShader =
|
||||
Gles3__CompileShader(GL_VERTEX_SHADER, vertexShaderSource);
|
||||
GLuint fragmentShader =
|
||||
Gles3__CompileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);
|
||||
|
||||
GLuint shaderProgram = glCreateProgram();
|
||||
glAttachShader(shaderProgram, vertexShader);
|
||||
glAttachShader(shaderProgram, fragmentShader);
|
||||
glLinkProgram(shaderProgram);
|
||||
|
||||
glDeleteShader(vertexShader);
|
||||
glDeleteShader(fragmentShader);
|
||||
|
||||
return shaderProgram;
|
||||
}
|
||||
|
||||
void Gles3_Initialize(Gles3_Renderer *renderer, int maxInstances)
|
||||
{
|
||||
renderer->totalDrawCallsToOpenGl = 0;
|
||||
// compile shader
|
||||
renderer->quadShaderId = Gles3__CreateShaderProgram(
|
||||
GLES3_QUAD_VERTEX_SHADER, GLES3_QUAD_FRAGMENT_SHADER);
|
||||
|
||||
glUseProgram(renderer->quadShaderId);
|
||||
glUniform1i(glGetUniformLocation(renderer->quadShaderId, "uTex0"), 0);
|
||||
glUniform1i(glGetUniformLocation(renderer->quadShaderId, "uTex1"), 1);
|
||||
glUniform1i(glGetUniformLocation(renderer->quadShaderId, "uTex2"), 2);
|
||||
glUniform1i(glGetUniformLocation(renderer->quadShaderId, "uTex3"), 3);
|
||||
|
||||
// create unit quad VBO (0..1)
|
||||
const float quadVerts[8] = {0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f};
|
||||
glGenVertexArrays(1, &renderer->quadVAO);
|
||||
glBindVertexArray(renderer->quadVAO);
|
||||
|
||||
glGenBuffers(1, &renderer->quadVBO);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, renderer->quadVBO);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVerts), quadVerts, GL_STATIC_DRAW);
|
||||
|
||||
// attribute 0: aPos (vec2), per-vertex
|
||||
glEnableVertexAttribArray(ATTR_QUAD_POS);
|
||||
glVertexAttribPointer(ATTR_QUAD_POS, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)0);
|
||||
glVertexAttribDivisor(ATTR_QUAD_POS, 0);
|
||||
|
||||
// create instance buffer big enough
|
||||
Gles3_QuadInstanceArray *quads = &renderer->quadInstanceArray;
|
||||
quads->capacity = maxInstances;
|
||||
quads->instData =
|
||||
(RectInstance *)malloc(sizeof(RectInstance) * quads->capacity);
|
||||
quads->count = 0;
|
||||
|
||||
glGenBuffers(1, &renderer->quadInstanceVBO);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, renderer->quadInstanceVBO);
|
||||
glBufferData(GL_ARRAY_BUFFER,
|
||||
sizeof(RectInstance) * quads->capacity,
|
||||
NULL,
|
||||
GL_DYNAMIC_DRAW);
|
||||
|
||||
// set up instance attributes
|
||||
GLsizei stride = sizeof(RectInstance);
|
||||
|
||||
glEnableVertexAttribArray(ATTR_QUAD_RECT);
|
||||
glVertexAttribPointer(ATTR_QUAD_RECT, 4, GL_FLOAT, GL_FALSE,
|
||||
stride, (void *)offsetof(RectInstance, x));
|
||||
glVertexAttribDivisor(ATTR_QUAD_RECT, 1);
|
||||
|
||||
glEnableVertexAttribArray(ATTR_QUAD_COLOR);
|
||||
glVertexAttribPointer(ATTR_QUAD_COLOR, 4, GL_FLOAT, GL_FALSE,
|
||||
stride, (void *)offsetof(RectInstance, r));
|
||||
glVertexAttribDivisor(ATTR_QUAD_COLOR, 1);
|
||||
|
||||
glEnableVertexAttribArray(ATTR_QUAD_UV);
|
||||
glVertexAttribPointer(ATTR_QUAD_UV, 4, GL_FLOAT, GL_FALSE,
|
||||
stride, (void *)offsetof(RectInstance, u0));
|
||||
glVertexAttribDivisor(ATTR_QUAD_UV, 1);
|
||||
|
||||
glEnableVertexAttribArray(ATTR_QUAD_RAD);
|
||||
glVertexAttribPointer(ATTR_QUAD_RAD, 4, GL_FLOAT, GL_FALSE,
|
||||
stride, (void *)offsetof(RectInstance, radiusTL));
|
||||
glVertexAttribDivisor(ATTR_QUAD_RAD, 1);
|
||||
|
||||
glEnableVertexAttribArray(ATTR_QUAD_BORDER);
|
||||
glVertexAttribPointer(ATTR_QUAD_BORDER, 4, GL_FLOAT, GL_FALSE,
|
||||
stride, (void *)offsetof(RectInstance, borderL));
|
||||
glVertexAttribDivisor(ATTR_QUAD_BORDER, 1);
|
||||
|
||||
glEnableVertexAttribArray(ATTR_QUAD_TEX);
|
||||
glVertexAttribPointer(ATTR_QUAD_TEX, 1, GL_FLOAT, GL_FALSE,
|
||||
stride, (void *)offsetof(RectInstance, texToUse));
|
||||
glVertexAttribDivisor(ATTR_QUAD_TEX, 1);
|
||||
|
||||
glBindVertexArray(1);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
// Ok now we will initialize text!
|
||||
Gles3_GlyphVtxArray *gVerts = &renderer->glyphVtxArray;
|
||||
|
||||
// configure capacity
|
||||
gVerts->capacity = maxInstances;
|
||||
gVerts->count = 0;
|
||||
|
||||
// allocate CPU-side vertex buffer: 6 vertices per glyph
|
||||
gVerts->instData = (GlyphVtx *)malloc(sizeof(GlyphVtx) * 6 * gVerts->capacity);
|
||||
if (!gVerts->instData)
|
||||
{
|
||||
fprintf(stderr, "Failed to allocate glyph_vertices\n");
|
||||
gVerts->capacity = 0;
|
||||
}
|
||||
|
||||
// create VAO/VBO for text rendering
|
||||
glGenVertexArrays(1, &renderer->textVAO);
|
||||
glBindVertexArray(renderer->textVAO);
|
||||
|
||||
glGenBuffers(1, &renderer->textVBO);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, renderer->textVBO);
|
||||
glBufferData(GL_ARRAY_BUFFER,
|
||||
sizeof(GlyphVtx) * 6 * gVerts->capacity,
|
||||
NULL,
|
||||
GL_DYNAMIC_DRAW);
|
||||
|
||||
GLsizei gv_stride = sizeof(GlyphVtx);
|
||||
|
||||
glEnableVertexAttribArray(ATTR_GLYPH_POS);
|
||||
glVertexAttribPointer(ATTR_GLYPH_POS, 2, GL_FLOAT, GL_FALSE, gv_stride, (void *)(offsetof(GlyphVtx, x)));
|
||||
|
||||
glEnableVertexAttribArray(ATTR_GLYPH_UV);
|
||||
glVertexAttribPointer(ATTR_GLYPH_UV, 2, GL_FLOAT, GL_FALSE, gv_stride, (void *)(offsetof(GlyphVtx, u)));
|
||||
|
||||
glEnableVertexAttribArray(ATTR_GLYPH_COLOR);
|
||||
glVertexAttribPointer(ATTR_GLYPH_COLOR, 4, GL_FLOAT, GL_FALSE, gv_stride, (void *)(offsetof(GlyphVtx, r)));
|
||||
|
||||
glEnableVertexAttribArray(ATTR_GLYPH_TEX);
|
||||
glVertexAttribPointer(ATTR_GLYPH_TEX, 1, GL_FLOAT, GL_FALSE, gv_stride, (void *)(offsetof(GlyphVtx, atlasTexUnit)));
|
||||
|
||||
glBindVertexArray(0);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
renderer->textShader = Gles3__CreateShaderProgram(
|
||||
GLES3_TEXT_VERTEX_SHADER, GLES3_TEXT_FRAGMENT_SHADER);
|
||||
glUseProgram(renderer->textShader);
|
||||
|
||||
// Link sampler uniforms in the text shader to the correct texture units.
|
||||
// Each uniform tells the shader which unit to read from.
|
||||
glUniform1i(glGetUniformLocation(renderer->textShader, "uTex0"), 0);
|
||||
glUniform1i(glGetUniformLocation(renderer->textShader, "uTex1"), 1);
|
||||
glUniform1i(glGetUniformLocation(renderer->textShader, "uTex2"), 2);
|
||||
glUniform1i(glGetUniformLocation(renderer->textShader, "uTex3"), 3);
|
||||
}
|
||||
|
||||
void Gles3_SetRenderTextFunction(
|
||||
Gles3_Renderer *renderer,
|
||||
void (*renderTextFunction)(
|
||||
Clay_RenderCommand *cmd, Gles3_GlyphVtxArray *accum, void *userData),
|
||||
void *userData)
|
||||
{
|
||||
renderer->renderTextFunction = renderTextFunction;
|
||||
}
|
||||
|
||||
void Gles3_Render(
|
||||
Gles3_Renderer *renderer,
|
||||
Clay_RenderCommandArray cmds,
|
||||
void *userData // eg. fonts
|
||||
)
|
||||
{
|
||||
Clay_Dimensions layoutDimensions = Clay_GetCurrentContext()->layoutDimensions;
|
||||
renderer->screenWidth = layoutDimensions.width;
|
||||
renderer->screenHeight = layoutDimensions.height;
|
||||
|
||||
Gles3_QuadInstanceArray *quads = &renderer->quadInstanceArray;
|
||||
Gles3_GlyphVtxArray *gVerts = &renderer->glyphVtxArray;
|
||||
|
||||
gVerts->count = 0;
|
||||
|
||||
for (int i = 0; i < cmds.length; i++)
|
||||
{
|
||||
Clay_RenderCommand *cmd = Clay_RenderCommandArray_Get(&cmds, i);
|
||||
Clay_BoundingBox boundingBox = (Clay_BoundingBox){
|
||||
.x = roundf(cmd->boundingBox.x),
|
||||
.y = roundf(cmd->boundingBox.y),
|
||||
.width = roundf(cmd->boundingBox.width),
|
||||
.height = roundf(cmd->boundingBox.height),
|
||||
};
|
||||
|
||||
bool scissorChanged = false;
|
||||
switch (cmd->commandType)
|
||||
{
|
||||
case CLAY_RENDER_COMMAND_TYPE_TEXT:
|
||||
{
|
||||
renderer->renderTextFunction(
|
||||
cmd,
|
||||
&renderer->glyphVtxArray,
|
||||
userData);
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE:
|
||||
case CLAY_RENDER_COMMAND_TYPE_IMAGE:
|
||||
{
|
||||
Clay_RectangleRenderData *config = &cmd->renderData.rectangle;
|
||||
Clay_Color c = config->backgroundColor;
|
||||
|
||||
// Convert to float 0..1
|
||||
float rf = c.r / 255.0f;
|
||||
float gf = c.g / 255.0f;
|
||||
float bf = c.b / 255.0f;
|
||||
float af = c.a / 255.0f;
|
||||
|
||||
bool isImage = cmd->commandType == CLAY_RENDER_COMMAND_TYPE_IMAGE;
|
||||
|
||||
// Ensure we don't overflow the capacity
|
||||
if (quads->count >= quads->capacity)
|
||||
{
|
||||
printf("Clay renderer: instance overflow!\n");
|
||||
break;
|
||||
}
|
||||
|
||||
int idx = quads->count;
|
||||
RectInstance *dst = &quads->instData[idx];
|
||||
dst->x = boundingBox.x;
|
||||
dst->y = boundingBox.y;
|
||||
dst->w = boundingBox.width;
|
||||
dst->h = boundingBox.height;
|
||||
|
||||
if (isImage)
|
||||
{
|
||||
Gles3_ImageConfig *imgConf = (Gles3_ImageConfig *)cmd->renderData.image.imageData;
|
||||
dst->u0 = imgConf->u0;
|
||||
dst->v0 = imgConf->v0;
|
||||
dst->u1 = imgConf->u1;
|
||||
dst->v1 = imgConf->v1;
|
||||
dst->texToUse = (float)imgConf->textureToUse;
|
||||
}
|
||||
else
|
||||
{
|
||||
dst->u0 = dst->v0 = 0.0f;
|
||||
dst->u1 = dst->v1 = 1.0f;
|
||||
dst->texToUse = -1.0f; // This means no image, use albedo color
|
||||
}
|
||||
|
||||
// colour
|
||||
dst->r = rf;
|
||||
dst->g = gf;
|
||||
dst->b = bf;
|
||||
dst->a = af;
|
||||
|
||||
// corner radii
|
||||
Clay_CornerRadius r = config->cornerRadius;
|
||||
dst->radiusTL = r.topLeft;
|
||||
dst->radiusTR = r.topRight;
|
||||
dst->radiusBL = r.bottomLeft;
|
||||
dst->radiusBR = r.bottomRight;
|
||||
|
||||
dst->borderT = 0.0f;
|
||||
dst->borderR = 0.0f;
|
||||
dst->borderB = 0.0f;
|
||||
dst->borderL = 0.0f;
|
||||
|
||||
quads->count++;
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START:
|
||||
{
|
||||
scissorChanged = true;
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END:
|
||||
{
|
||||
scissorChanged = true;
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_BORDER:
|
||||
{
|
||||
Clay_BorderRenderData *br = &cmd->renderData.border;
|
||||
|
||||
float rf = br->color.r / 255.0f;
|
||||
float gf = br->color.g / 255.0f;
|
||||
float bf = br->color.b / 255.0f;
|
||||
float af = br->color.a / 255.0f;
|
||||
|
||||
float x = boundingBox.x;
|
||||
float y = boundingBox.y;
|
||||
float w = boundingBox.width;
|
||||
float h = boundingBox.height;
|
||||
|
||||
float top = br->width.top;
|
||||
float bottom = br->width.bottom;
|
||||
float left = br->width.left;
|
||||
float right = br->width.right;
|
||||
|
||||
int idx = quads->count;
|
||||
RectInstance *dst = &quads->instData[idx];
|
||||
|
||||
dst->x = x - left;
|
||||
dst->y = y - top;
|
||||
dst->w = w + right;
|
||||
dst->h = h + bottom;
|
||||
|
||||
dst->borderB = bottom;
|
||||
dst->borderL = left;
|
||||
dst->borderT = top;
|
||||
dst->borderR = right;
|
||||
|
||||
// Clay borders are inset, but adding support to outset borders
|
||||
// Is as easy as this + some minor changes in shader too
|
||||
bool CLAY_BORDERS_ARE_INSET = true;
|
||||
if (CLAY_BORDERS_ARE_INSET)
|
||||
{
|
||||
// Normal behaviour
|
||||
dst->x = x;
|
||||
dst->y = y;
|
||||
dst->w = w;
|
||||
dst->h = h;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hypotethical behaviour, if the borders were outside
|
||||
dst->x = x - left;
|
||||
dst->y = y - top;
|
||||
dst->w = w + left + right;
|
||||
dst->h = h + top + bottom;
|
||||
}
|
||||
|
||||
dst->u0 = 0.0f;
|
||||
dst->v0 = 0.0f;
|
||||
dst->u1 = 1.0f;
|
||||
dst->v1 = 1.0f;
|
||||
|
||||
dst->r = rf;
|
||||
dst->g = gf;
|
||||
dst->b = bf;
|
||||
dst->a = af;
|
||||
|
||||
dst->radiusTL = br->cornerRadius.topLeft;
|
||||
dst->radiusTR = br->cornerRadius.topRight;
|
||||
dst->radiusBR = br->cornerRadius.bottomRight;
|
||||
dst->radiusBL = br->cornerRadius.bottomLeft;
|
||||
|
||||
dst->texToUse = -1.0f;
|
||||
|
||||
quads->count++;
|
||||
break;
|
||||
}
|
||||
|
||||
case CLAY_RENDER_COMMAND_TYPE_CUSTOM:
|
||||
{
|
||||
// printf("Unhandled clay cmd: custom\n");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
printf("Error: unhandled render command\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Flush draw calls if scissors about to change in this iteration
|
||||
if (i == cmds.length - 1 || scissorChanged)
|
||||
{
|
||||
scissorChanged = false;
|
||||
// Render Recatangles and Images
|
||||
if (quads->count > 0)
|
||||
{
|
||||
glUseProgram(renderer->quadShaderId);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->imageTextures[0]);
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->imageTextures[1]);
|
||||
glActiveTexture(GL_TEXTURE2);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->imageTextures[2]);
|
||||
glActiveTexture(GL_TEXTURE3);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->imageTextures[3]);
|
||||
|
||||
// set uniforms
|
||||
GLint locScreen = glGetUniformLocation(renderer->quadShaderId, "uScreen");
|
||||
glUniform2f(locScreen,
|
||||
(float)renderer->screenWidth,
|
||||
(float)renderer->screenHeight);
|
||||
|
||||
glBindVertexArray(renderer->quadVAO);
|
||||
|
||||
// upload all instances at once
|
||||
glBindBuffer(GL_ARRAY_BUFFER, renderer->quadInstanceVBO);
|
||||
|
||||
// rectangles are solid colour — disable atlas use
|
||||
glBufferSubData(GL_ARRAY_BUFFER,
|
||||
0,
|
||||
quads->count * sizeof(RectInstance),
|
||||
quads->instData);
|
||||
|
||||
// draw unit quad (4 verts) instanced
|
||||
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, quads->count);
|
||||
renderer->totalDrawCallsToOpenGl += 1;
|
||||
|
||||
glBindVertexArray(0);
|
||||
glUseProgram(0);
|
||||
}
|
||||
// Clrear instance arrays, as they were flushed to their render calls
|
||||
quads->count = 0;
|
||||
|
||||
// Text rendering
|
||||
if (renderer->glyphVtxArray.count > 0)
|
||||
{
|
||||
glUseProgram(renderer->textShader);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->fontTextures[0]);
|
||||
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->fontTextures[1]);
|
||||
|
||||
glActiveTexture(GL_TEXTURE2);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->fontTextures[2]);
|
||||
|
||||
glActiveTexture(GL_TEXTURE3);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->fontTextures[3]);
|
||||
|
||||
GLint uScreenLoc = glGetUniformLocation(renderer->textShader, "uScreen");
|
||||
glUniform2f(uScreenLoc, renderer->screenWidth, renderer->screenHeight);
|
||||
|
||||
glBindVertexArray(renderer->textVAO);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, renderer->textVBO);
|
||||
|
||||
glBufferSubData(
|
||||
GL_ARRAY_BUFFER,
|
||||
0,
|
||||
sizeof(struct GlyphVtx) * 6 * gVerts->count,
|
||||
renderer->glyphVtxArray.instData);
|
||||
|
||||
glDrawArrays(GL_TRIANGLES, 0, renderer->glyphVtxArray.count * 6);
|
||||
renderer->totalDrawCallsToOpenGl += 1;
|
||||
|
||||
glBindVertexArray(0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
renderer->glyphVtxArray.count = 0;
|
||||
|
||||
if (cmd->commandType == CLAY_RENDER_COMMAND_TYPE_SCISSOR_START)
|
||||
{
|
||||
Clay_BoundingBox bb = cmd->boundingBox;
|
||||
GLint x = (GLint)bb.x;
|
||||
GLint y = (GLint)(renderer->screenHeight - (bb.y + bb.height));
|
||||
GLsizei w = (GLsizei)bb.width;
|
||||
GLsizei h = (GLsizei)bb.height;
|
||||
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
glScissor(x, y, w, h);
|
||||
}
|
||||
else
|
||||
{
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
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…
Reference in a new issue