This commit is contained in:
ThatTanishqTak 2025-12-04 21:26:46 -06:00 committed by GitHub
commit 18e19898e8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 1668 additions and 12 deletions

3
.gitignore vendored
View file

@ -4,4 +4,5 @@ cmake-build-release/
.idea/
node_modules/
*.dSYM
.vs/
.vs/
out/

View file

@ -1,9 +1,9 @@
cmake_minimum_required(VERSION 3.27)
cmake_minimum_required(VERSION 3.25)
project(clay)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
option(CLAY_INCLUDE_ALL_EXAMPLES "Build all examples" ON)
option(CLAY_INCLUDE_ALL_EXAMPLES "Build all examples" OFF)
option(CLAY_INCLUDE_DEMOS "Build video demo and website" OFF)
option(CLAY_INCLUDE_CPP_EXAMPLE "Build C++ example" OFF)
option(CLAY_INCLUDE_RAYLIB_EXAMPLES "Build raylib examples" OFF)
@ -12,6 +12,7 @@ option(CLAY_INCLUDE_SDL3_EXAMPLES "Build SDL 3 examples" OFF)
option(CLAY_INCLUDE_WIN32_GDI_EXAMPLES "Build Win32 GDI examples" OFF)
option(CLAY_INCLUDE_SOKOL_EXAMPLES "Build Sokol examples" OFF)
option(CLAY_INCLUDE_PLAYDATE_EXAMPLES "Build Playdate examples" OFF)
option(CLAY_INCLUDE_VULAKN_EXAMPLES "Build Vulkan examples" ON)
message(STATUS "CLAY_INCLUDE_DEMOS: ${CLAY_INCLUDE_DEMOS}")
@ -44,6 +45,9 @@ if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_SOKOL_EXAMPLES)
add_subdirectory("examples/sokol-video-demo")
add_subdirectory("examples/sokol-corner-radius")
endif()
if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_VULAKN_EXAMPLES)
add_subdirectory("examples/vulkan-demo")
endif()
# Playdate example not included in ALL because users need to install the playdate SDK first which requires a license agreement
if(CLAY_INCLUDE_PLAYDATE_EXAMPLES)

View file

@ -126,6 +126,86 @@ The above example, rendered correctly will look something like the following:
![Clay Example](https://github.com/user-attachments/assets/1928c6d4-ada9-4a4c-a3d1-44fe9b23b3bd)
### Visual Studio 2022 / MSVC build configuration
The current README did not spell out how to set up Clay with Visual Studio 2022, so the following steps target MSVC users building a single `CLAY_IMPLEMENTATION` translation unit alongside a Vulkan renderer. These steps also leave room for future improvements such as better swapchain-resize handling and validation tightening.
- **Language standard & warning level:**
- Set the **C Language Standard** to **ISO C17** (`/std:c17`) in *Configuration Properties → C/C++ → Language*. Clay is plain C, but C++20 projects can include the generated object because the C translation unit is compiled separately.
- Use **Warning Level** `/W4` (or `/Wall` when validating new integrations) and treat warnings as errors for the Clay translation unit to catch integration issues early.
- Keep `CLAY_IMPLEMENTATION` defined in exactly one `.c` file to avoid multiple-definition linker errors; all other translation units should include `clay.h` without the define.
- **Vulkan SDK include/lib setup (LunarG):**
- Install the [LunarG Vulkan SDK](https://vulkan.lunarg.com/doc/sdk) and ensure the `VULKAN_SDK` environment variable is set by the installer.
- Add `$(VULKAN_SDK)\Include` to *Configuration Properties → C/C++ → General → Additional Include Directories*.
- Add `$(VULKAN_SDK)\Lib` to *Configuration Properties → Linker → General → Additional Library Directories* and link against `vulkan-1.lib` (plus any allocator / shader utility libs you use).
- When mixing with ImGui backends, keep the [ImGui wiki guidelines](https://github.com/ocornut/imgui/wiki) handy for sampler, descriptor, and swapchain best practices.
**Minimal MSVC-friendly Vulkan loop (C17) showing Clay command mapping and scissor stack for scrolling**
```C
// This translation unit should be compiled with /std:c17 and /W4 using MSVC in Visual Studio 2022.
// The snippet iterates Clay_RenderCommandArray, maps commands to Vulkan pipelines, and maintains a scissor stack for scroll regions.
static void RenderClayCommands(VkCommandBuffer l_CommandBuffer, Clay_RenderCommandArray l_RenderCommands) {
VkPipeline l_RectPipeline = /* created elsewhere */ VK_NULL_HANDLE;
VkPipeline l_TextPipeline = /* created elsewhere */ VK_NULL_HANDLE;
VkRect2D l_ScissorStack[8] = {0};
int32_t l_ScissorDepth = 0;
for (int32_t l_CommandIndex = 0; l_CommandIndex < l_RenderCommands.length; ++l_CommandIndex) {
Clay_RenderCommand *l_Command = &l_RenderCommands.internalArray[l_CommandIndex];
switch (l_Command->commandType) {
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: {
// Push the scrollable clip rect to keep nested scroll areas isolated.
VkRect2D l_NewScissor = {
.offset = { (int32_t)l_Command->boundingBox.x, (int32_t)l_Command->boundingBox.y },
.extent = { (uint32_t)l_Command->boundingBox.width, (uint32_t)l_Command->boundingBox.height }
};
if (l_ScissorDepth < (int32_t)(sizeof l_ScissorStack / sizeof l_ScissorStack[0])) {
l_ScissorStack[l_ScissorDepth++] = l_NewScissor;
vkCmdSetScissor(l_CommandBuffer, 0, 1, &l_NewScissor);
}
break;
}
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: {
// Pop the scissor so parents resume drawing without child clips.
if (l_ScissorDepth > 0) {
--l_ScissorDepth;
if (l_ScissorDepth > 0) {
vkCmdSetScissor(l_CommandBuffer, 0, 1, &l_ScissorStack[l_ScissorDepth - 1]);
}
}
break;
}
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
// Map to a color-pass pipeline and draw the quad for this UI rect.
vkCmdBindPipeline(l_CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, l_RectPipeline);
// ...bind vertex buffers and push constants for l_Command->renderData.rectangle.backgroundColor
// ...issue vkCmdDraw to render the rectangle
break;
}
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
// Select the text pipeline; glyph atlas binding follows ImGui-style descriptor usage.
vkCmdBindPipeline(l_CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, l_TextPipeline);
// ...bind font atlas descriptors, set per-draw uniforms, and vkCmdDrawIndexed for glyphs
break;
}
default: {
// Additional Clay command types (images, borders, custom) can be routed here.
break;
}
}
}
}
```
**Troubleshooting and future enhancements**
- If scissor clipping appears inverted, verify that the swapchain surface transform is accounted for before setting `VkRect2D`.
- MSVC link errors referencing `CLAY_IMPLEMENTATION` usually mean more than one translation unit defined the macro; keep it in a single `.c` file.
- Vulkan validation warnings about descriptor lifetimes often stem from text pipelines; cross-check against the [ImGui wiki recommendations](https://github.com/ocornut/imgui/wiki).
- Consider adding a resize strategy that rebuilds pipelines only when swapchain formats change (future enhancement), and batch multiple Clay rectangles into shared vertex buffers to reduce draw calls.
In summary, the general order of steps is:
1. [Clay_SetLayoutDimensions(dimensions)](#clay_setlayoutdimensions)

View file

@ -1,21 +1,73 @@
#include <iostream>
#include <cstdio>
#include <cstdlib>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <wingdi.h>
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
Clay_LayoutConfig layoutElement = Clay_LayoutConfig { .padding = {5} };
// Simple layout config with prefixed naming to match the MSVC-friendly style guide.
Clay_LayoutConfig s_LayoutElement = Clay_LayoutConfig{ .padding = {5} };
void HandleClayErrors(Clay_ErrorData errorData) {
// Win32-compatible font handle that can be reused by the text measuring callback.
static HFONT s_TextFont = static_cast<HFONT>(GetStockObject(DEFAULT_GUI_FONT));
// Bridge Clay's text measuring hook to GDI so layouts get accurate extents on Windows.
Clay_Dimensions HandleMeasureText(Clay_StringSlice text, Clay_TextElementConfig* config, void* userData)
{
(void)config; // Config is available for future font switching or styling.
HFONT* l_FontHandle = static_cast<HFONT*>(userData);
HFONT l_ResolvedFont = l_FontHandle != nullptr && *l_FontHandle != nullptr ? *l_FontHandle : s_TextFont;
HDC l_DeviceContext = GetDC(nullptr);
if (l_DeviceContext == nullptr)
{
return Clay_Dimensions{ 0, 0 };
}
HGDIOBJ l_PreviousFont = nullptr;
if (l_ResolvedFont != nullptr)
{
l_PreviousFont = SelectObject(l_DeviceContext, l_ResolvedFont);
}
SIZE l_TextSize{ 0, 0 };
int l_TextLength = static_cast<int>(text.length);
GetTextExtentPoint32A(l_DeviceContext, text.chars, l_TextLength, &l_TextSize);
if (l_PreviousFont != nullptr)
{
SelectObject(l_DeviceContext, l_PreviousFont);
}
ReleaseDC(nullptr, l_DeviceContext);
// Future improvement: swap GDI for DirectWrite or cache glyph metrics to avoid repeated calls.
return Clay_Dimensions{ static_cast<float>(l_TextSize.cx), static_cast<float>(l_TextSize.cy) };
}
void HandleClayErrors(Clay_ErrorData errorData)
{
printf("%s", errorData.errorText.chars);
}
int main(void) {
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, (char *)malloc(totalMemorySize));
Clay_Initialize(clayMemory, Clay_Dimensions {1024,768}, Clay_ErrorHandler { HandleClayErrors });
int main(void)
{
uint64_t l_TotalMemorySize = Clay_MinMemorySize();
Clay_Arena l_ClayMemory = Clay_CreateArenaWithCapacityAndMemory(l_TotalMemorySize, static_cast<char*>(malloc(l_TotalMemorySize)));
// Initialize the Clay context and immediately provide a Windows-friendly text measure callback.
Clay_Initialize(l_ClayMemory, Clay_Dimensions{ 1024, 768 }, Clay_ErrorHandler{ HandleClayErrors });
Clay_SetMeasureTextFunction(HandleMeasureText, &s_TextFont);
// The measure function lets Clay compute text bounds before laying out widgets.
Clay_BeginLayout();
CLAY_AUTO_ID({ .layout = layoutElement, .backgroundColor = {255,255,255,0} }) {
CLAY_AUTO_ID({ .layout = s_LayoutElement, .backgroundColor = {255,255,255,0} })
{
CLAY_TEXT(CLAY_STRING(""), CLAY_TEXT_CONFIG({ .fontId = 0 }));
}
Clay_EndLayout();
return 0;
}
}

View file

@ -0,0 +1,56 @@
cmake_minimum_required(VERSION 3.27)
project(clay_examples_vulkan_demo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(FetchContent)
set(FETCHCONTENT_QUIET FALSE)
# GLFW is used only for window creation and Vulkan surface management in this demo.
# When other examples are enabled (e.g., raylib), GLFW may already be present via
# their dependencies. Guarding the population prevents duplicate target errors
# (CMP0002) while still reusing the existing GLFW target when available.
if(NOT TARGET glfw)
FetchContent_Declare(
glfw
GIT_REPOSITORY "https://github.com/glfw/glfw.git"
GIT_TAG "3.4"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(glfw)
else()
message(STATUS "Reusing existing GLFW target for Vulkan demo")
endif()
find_package(Vulkan REQUIRED)
add_executable(clay_examples_vulkan_demo
main.cpp
)
target_include_directories(clay_examples_vulkan_demo PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/..
${CMAKE_CURRENT_SOURCE_DIR}/../..
)
target_link_libraries(clay_examples_vulkan_demo PRIVATE
glfw
Vulkan::Vulkan
)
if(MSVC)
# Keep MSVC warnings visible while allowing the demo to compile cleanly.
target_compile_options(clay_examples_vulkan_demo PRIVATE /W4)
else()
target_compile_options(clay_examples_vulkan_demo PRIVATE -Wall -Wextra)
endif()
# Allow Visual Studio users to copy sample resources (fonts, textures) beside the executable later.
add_custom_target(clay_examples_vulkan_demo_copy_resources ALL
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/resources
COMMENT "Prepare resource directory for future font/texture assets"
)
add_dependencies(clay_examples_vulkan_demo clay_examples_vulkan_demo_copy_resources)

View file

@ -0,0 +1,28 @@
# Clay Vulkan Demo
This Visual Studio 2022 friendly sample shows how to pair Clay's layout engine with a Vulkan swapchain. It uses GLFW for the Win32 surface and stitches Clay render commands through the Vulkan renderer adapter.
## Build prerequisites
- [Vulkan SDK](https://vulkan.lunarg.com/doc/sdk) installed and the `VULKAN_SDK` environment variable set.
- Visual Studio 2022 with CMake and the MSVC toolset.
- Git submodules are not required because GLFW is fetched automatically through CMake's `FetchContent`.
## Configure and build (Visual Studio 2022)
1. Open a **Developer PowerShell for VS 2022** so the compiler environment variables are available.
2. Generate a build directory:
```pwsh
cmake -S examples/vulkan-demo -B build/vulkan-demo -G "Visual Studio 17 2022" -A x64
```
3. Build the executable:
```pwsh
cmake --build build/vulkan-demo --config Debug
```
4. Run the demo from the build tree (the app opens a GLFW window):
```pwsh
build/vulkan-demo/Debug/clay_examples_vulkan_demo.exe
```
## Notes and future work
- The code is intentionally verbose and commented with links to the Vulkan SDK reference and the ImGui wiki for font atlas/descriptor sharing advice.
- Swapchain, render pass, and command buffer setup are present, but pipeline configuration uses placeholders so you can wire your own shaders.
- TODOs remain for dynamic descriptor management and richer font handling; these are natural next steps after the basic window + swapchain bring-up.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,294 @@
// Clay Vulkan renderer sample (C17/C20 friendly)
// This file demonstrates how to traverse Clay_RenderCommandArray and submit Vulkan draw calls.
// The implementation favors clarity over micro-optimizations and documents future improvements.
// Vulkan reference: https://vulkan.lunarg.com/doc/sdk
// ImGui integration notes (for font atlas usage ideas): https://github.com/ocornut/imgui/wiki
#include <stdint.h>
#include <string.h>
#include <vulkan/vulkan.h>
#include "clay.h"
// Forward declarations -------------------------------------------------------
struct ClayVulkanRenderer;
static void ClayVulkan_PushScissor(struct ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer);
static void ClayVulkan_PopScissor(struct ClayVulkanRenderer* renderer, VkCommandBuffer commandBuffer);
static void ClayVulkan_DrawRectangle(struct ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer);
static void ClayVulkan_DrawBorder(struct ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer);
static void ClayVulkan_DrawImage(struct ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer);
static void ClayVulkan_DrawText(struct ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer);
static void ClayVulkan_DrawCustom(struct ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer);
// Resource cache keyed by Clay IDs ------------------------------------------
typedef struct ClayVulkanTexture
{
uint32_t m_Id; // Clay image id
VkImageView m_ImageView;
VkSampler m_Sampler;
VkDescriptorSet m_DescriptorSet;
} ClayVulkanTexture;
typedef struct ClayVulkanFont
{
uint16_t m_FontId;
uint16_t m_FontSize;
void* m_FontUserData; // Pointer to glyph atlas / font metrics owned by the application
} ClayVulkanFont;
typedef struct ClayVulkanResourceCache
{
ClayVulkanTexture m_Textures[64];
uint32_t m_TextureCount;
ClayVulkanFont m_Fonts[32];
uint32_t m_FontCount;
} ClayVulkanResourceCache;
// Renderer state ------------------------------------------------------------
typedef struct ClayVulkanRenderer
{
VkDevice m_Device;
VkRenderPass m_RenderPass; // Compatible with the framebuffer bound by the host application
VkPipeline m_RectPipeline;
VkPipeline m_TextPipeline;
VkPipeline m_ImagePipeline;
VkPipelineLayout m_PipelineLayout;
VkCommandBuffer m_ActiveCommandBuffer;
VkRect2D m_ScissorStack[16];
uint32_t m_ScissorCount;
ClayVulkanResourceCache m_ResourceCache;
// The sample keeps descriptor pools outside; the host passes ready-to-bind descriptor sets.
} ClayVulkanRenderer;
// Utility: fetch or insert cached texture -----------------------------------
static ClayVulkanTexture* ClayVulkan_GetTexture(ClayVulkanResourceCache* cache, uint32_t id)
{
for (uint32_t it_Index = 0; it_Index < cache->m_TextureCount; ++it_Index)
{
ClayVulkanTexture* l_Texture = &cache->m_Textures[it_Index];
if (l_Texture->m_Id == id)
{
return l_Texture;
}
}
if (cache->m_TextureCount >= (uint32_t)(sizeof(cache->m_Textures) / sizeof(cache->m_Textures[0])))
{
return NULL; // Out of cache entries; caller can log and skip drawing.
}
ClayVulkanTexture* l_Texture = &cache->m_Textures[cache->m_TextureCount++];
memset(l_Texture, 0, sizeof(*l_Texture));
l_Texture->m_Id = id;
return l_Texture;
}
// Utility: fetch or insert cached font --------------------------------------
static ClayVulkanFont* ClayVulkan_GetFont(ClayVulkanResourceCache* cache, uint16_t fontId, uint16_t fontSize)
{
for (uint32_t it_Index = 0; it_Index < cache->m_FontCount; ++it_Index)
{
ClayVulkanFont* l_Font = &cache->m_Fonts[it_Index];
if (l_Font->m_FontId == fontId && l_Font->m_FontSize == fontSize)
{
return l_Font;
}
}
if (cache->m_FontCount >= (uint32_t)(sizeof(cache->m_Fonts) / sizeof(cache->m_Fonts[0])))
{
return NULL;
}
ClayVulkanFont* l_Font = &cache->m_Fonts[cache->m_FontCount++];
memset(l_Font, 0, sizeof(*l_Font));
l_Font->m_FontId = fontId;
l_Font->m_FontSize = fontSize;
return l_Font;
}
// Text measurement hook ------------------------------------------------------
// Backs Clay_SetMeasureTextFunction and aligns with Clay_TextRenderData expectations.
static Clay_Dimensions ClayVulkan_MeasureText(Clay_StringSlice text, Clay_TextElementConfig* config, void* userData)
{
ClayVulkanRenderer* l_Renderer = (ClayVulkanRenderer*)userData;
ClayVulkanFont* l_Font = ClayVulkan_GetFont(&l_Renderer->m_ResourceCache, config->fontId, config->fontSize);
// The sample assumes the host supplies pixel metrics via m_FontUserData (e.g., stb_truetype bake results).
// For demonstration, approximate width using a monospaced assumption and letter spacing.
const float s_DefaultGlyphWidth = (float)config->fontSize * 0.6f;
float l_Width = (float)text.length * s_DefaultGlyphWidth + (float)config->letterSpacing * (float)text.length;
float l_Height = (float)config->lineHeight;
(void)l_Font; // In a real integration, sample glyph advances from l_Font->m_FontUserData.
// MSVC doesn't support C99 compound literals in C mode; build the return value explicitly for portability.
Clay_Dimensions l_Dimensions = { 0 };
l_Dimensions.width = l_Width;
l_Dimensions.height = l_Height;
return l_Dimensions;
}
// Scissor management ---------------------------------------------------------
static void ClayVulkan_PushScissor(ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer)
{
if (renderer->m_ScissorCount >= (uint32_t)(sizeof(renderer->m_ScissorStack) / sizeof(renderer->m_ScissorStack[0])))
{
return; // Avoid overflow; future improvement could resize dynamically.
}
VkRect2D l_Rect = { 0 };
l_Rect.offset.x = (int32_t)command->boundingBox.x;
l_Rect.offset.y = (int32_t)command->boundingBox.y;
l_Rect.extent.width = (uint32_t)command->boundingBox.width;
l_Rect.extent.height = (uint32_t)command->boundingBox.height;
renderer->m_ScissorStack[renderer->m_ScissorCount++] = l_Rect;
vkCmdSetScissor(commandBuffer, 0, 1, &l_Rect);
}
static void ClayVulkan_PopScissor(ClayVulkanRenderer* renderer, VkCommandBuffer commandBuffer)
{
if (renderer->m_ScissorCount == 0)
{
return;
}
renderer->m_ScissorCount--;
if (renderer->m_ScissorCount == 0)
{
// Fall back to a full viewport scissor. The host should have set a default scissor before calling render.
return;
}
VkRect2D l_Rect = renderer->m_ScissorStack[renderer->m_ScissorCount - 1];
vkCmdSetScissor(commandBuffer, 0, 1, &l_Rect);
}
// Rendering helpers ----------------------------------------------------------
static void ClayVulkan_DrawRectangle(ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer)
{
(void)renderer;
// The sample assumes a pipeline using push constants for color + corner radius and a unit quad vertex buffer.
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, renderer->m_RectPipeline);
// Future improvement: instance multiple rectangles in a single vkCmdDrawIndexed to amortize state changes.
vkCmdDraw(commandBuffer, 6, 1, 0, 0);
}
static void ClayVulkan_DrawBorder(ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer)
{
(void)command;
// Border rendering could expand to 4 rectangles; here we rely on a shader that interprets width + corner radius from push constants.
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, renderer->m_RectPipeline);
vkCmdDraw(commandBuffer, 6, 1, 0, 0);
}
static void ClayVulkan_DrawImage(ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer)
{
ClayVulkanTexture* l_Texture = ClayVulkan_GetTexture(&renderer->m_ResourceCache, command->id);
if (!l_Texture || !l_Texture->m_DescriptorSet || !l_Texture->m_ImageView || !l_Texture->m_Sampler)
{
// Ensure descriptor sets are prepared before binding; application is responsible for keeping resources live.
return;
}
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, renderer->m_ImagePipeline);
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, renderer->m_PipelineLayout, 0, 1, &l_Texture->m_DescriptorSet, 0, NULL);
vkCmdDraw(commandBuffer, 6, 1, 0, 0);
}
static void ClayVulkan_DrawText(ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer)
{
Clay_TextRenderData l_Text = command->renderData.text;
ClayVulkanFont* l_Font = ClayVulkan_GetFont(&renderer->m_ResourceCache, l_Text.fontId, l_Text.fontSize);
(void)l_Font;
// The host is expected to have built glyph vertices into a transient buffer before calling this renderer.
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, renderer->m_TextPipeline);
// Future improvement: bind per-font descriptor sets for font atlas textures instead of assuming a global one.
vkCmdDraw(commandBuffer, (uint32_t)l_Text.stringContents.length * 4, 1, 0, 0);
}
static void ClayVulkan_DrawCustom(ClayVulkanRenderer* renderer, const Clay_RenderCommand* command, VkCommandBuffer commandBuffer)
{
(void)renderer;
(void)command;
// Custom elements give the host full control. Applications can use command->renderData.custom.customData to pick pipelines.
// Future improvement: provide callback hooks so applications can supply their own vkCmd* sequence here.
(void)commandBuffer;
}
// Main entry point -----------------------------------------------------------
void Clay_Vulkan_Render(ClayVulkanRenderer* renderer, Clay_RenderCommandArray renderCommands, VkCommandBuffer commandBuffer)
{
renderer->m_ActiveCommandBuffer = commandBuffer;
// Command traversal: the array is already z-sorted; we iterate sequentially and rely on lightweight state changes.
for (int32_t it_Command = 0; it_Command < renderCommands.length; ++it_Command)
{
Clay_RenderCommand* l_Command = Clay_RenderCommandArray_Get(&renderCommands, it_Command);
switch (l_Command->commandType)
{
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE:
ClayVulkan_DrawRectangle(renderer, l_Command, commandBuffer);
break;
case CLAY_RENDER_COMMAND_TYPE_BORDER:
ClayVulkan_DrawBorder(renderer, l_Command, commandBuffer);
break;
case CLAY_RENDER_COMMAND_TYPE_TEXT:
ClayVulkan_DrawText(renderer, l_Command, commandBuffer);
break;
case CLAY_RENDER_COMMAND_TYPE_IMAGE:
ClayVulkan_DrawImage(renderer, l_Command, commandBuffer);
break;
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START:
ClayVulkan_PushScissor(renderer, l_Command, commandBuffer);
break;
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END:
ClayVulkan_PopScissor(renderer, commandBuffer);
break;
case CLAY_RENDER_COMMAND_TYPE_CUSTOM:
ClayVulkan_DrawCustom(renderer, l_Command, commandBuffer);
break;
default:
break;
}
}
}
// Initialization helpers ----------------------------------------------------
void Clay_VulkanRenderer_Init(ClayVulkanRenderer* renderer, VkDevice device, VkRenderPass renderPass, VkPipelineLayout pipelineLayout)
{
renderer->m_Device = device;
renderer->m_RenderPass = renderPass;
renderer->m_PipelineLayout = pipelineLayout;
renderer->m_RectPipeline = VK_NULL_HANDLE;
renderer->m_TextPipeline = VK_NULL_HANDLE;
renderer->m_ImagePipeline = VK_NULL_HANDLE;
renderer->m_ActiveCommandBuffer = VK_NULL_HANDLE;
renderer->m_ScissorCount = 0;
memset(&renderer->m_ResourceCache, 0, sizeof(renderer->m_ResourceCache));
}
void Clay_VulkanRenderer_SetPipelines(ClayVulkanRenderer* renderer, VkPipeline rectPipeline, VkPipeline textPipeline, VkPipeline imagePipeline)
{
renderer->m_RectPipeline = rectPipeline;
renderer->m_TextPipeline = textPipeline;
renderer->m_ImagePipeline = imagePipeline;
}
// Hook into Clay's text measuring API.
void Clay_VulkanRenderer_RegisterTextMeasure(ClayVulkanRenderer* renderer)
{
Clay_SetMeasureTextFunction(ClayVulkan_MeasureText, renderer);
}
// Example for populating the texture cache with externally created descriptors.
void Clay_VulkanRenderer_RegisterTexture(ClayVulkanRenderer* renderer, uint32_t clayId, VkImageView imageView, VkSampler sampler, VkDescriptorSet descriptorSet)
{
ClayVulkanTexture* l_Texture = ClayVulkan_GetTexture(&renderer->m_ResourceCache, clayId);
if (!l_Texture)
{
return;
}
l_Texture->m_ImageView = imageView;
l_Texture->m_Sampler = sampler;
l_Texture->m_DescriptorSet = descriptorSet;
}
// Future improvements -------------------------------------------------------
// - Descriptor recycling: shrink transient allocations by pooling VkDescriptorSet objects per frame.
// - Dynamic rendering: replace render pass dependency with vkCmdBeginRendering for more flexible swapchain formats.
// - Batching: group adjacent rectangles or text quads into large instance draws using secondary command buffers.