mirror of
https://github.com/nicbarker/clay.git
synced 2025-12-23 17:41:06 +00:00
Merge 0e9da36bda into 389a044cd2
This commit is contained in:
commit
18e19898e8
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -4,4 +4,5 @@ cmake-build-release/
|
||||||
.idea/
|
.idea/
|
||||||
node_modules/
|
node_modules/
|
||||||
*.dSYM
|
*.dSYM
|
||||||
.vs/
|
.vs/
|
||||||
|
out/
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
cmake_minimum_required(VERSION 3.27)
|
cmake_minimum_required(VERSION 3.25)
|
||||||
project(clay)
|
project(clay)
|
||||||
|
|
||||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
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_DEMOS "Build video demo and website" OFF)
|
||||||
option(CLAY_INCLUDE_CPP_EXAMPLE "Build C++ example" OFF)
|
option(CLAY_INCLUDE_CPP_EXAMPLE "Build C++ example" OFF)
|
||||||
option(CLAY_INCLUDE_RAYLIB_EXAMPLES "Build raylib examples" 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_WIN32_GDI_EXAMPLES "Build Win32 GDI examples" OFF)
|
||||||
option(CLAY_INCLUDE_SOKOL_EXAMPLES "Build Sokol examples" OFF)
|
option(CLAY_INCLUDE_SOKOL_EXAMPLES "Build Sokol examples" OFF)
|
||||||
option(CLAY_INCLUDE_PLAYDATE_EXAMPLES "Build Playdate 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}")
|
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-video-demo")
|
||||||
add_subdirectory("examples/sokol-corner-radius")
|
add_subdirectory("examples/sokol-corner-radius")
|
||||||
endif()
|
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
|
# 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)
|
if(CLAY_INCLUDE_PLAYDATE_EXAMPLES)
|
||||||
|
|
|
||||||
80
README.md
80
README.md
|
|
@ -126,6 +126,86 @@ The above example, rendered correctly will look something like the following:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### 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:
|
In summary, the general order of steps is:
|
||||||
|
|
||||||
1. [Clay_SetLayoutDimensions(dimensions)](#clay_setlayoutdimensions)
|
1. [Clay_SetLayoutDimensions(dimensions)](#clay_setlayoutdimensions)
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,73 @@
|
||||||
#include <iostream>
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <windows.h>
|
||||||
|
#include <wingdi.h>
|
||||||
|
|
||||||
#define CLAY_IMPLEMENTATION
|
#define CLAY_IMPLEMENTATION
|
||||||
#include "../../clay.h"
|
#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);
|
printf("%s", errorData.errorText.chars);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(void)
|
||||||
uint64_t totalMemorySize = Clay_MinMemorySize();
|
{
|
||||||
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, (char *)malloc(totalMemorySize));
|
uint64_t l_TotalMemorySize = Clay_MinMemorySize();
|
||||||
Clay_Initialize(clayMemory, Clay_Dimensions {1024,768}, Clay_ErrorHandler { HandleClayErrors });
|
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_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_TEXT(CLAY_STRING(""), CLAY_TEXT_CONFIG({ .fontId = 0 }));
|
||||||
}
|
}
|
||||||
Clay_EndLayout();
|
Clay_EndLayout();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
56
examples/vulkan-demo/CMakeLists.txt
Normal file
56
examples/vulkan-demo/CMakeLists.txt
Normal 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)
|
||||||
28
examples/vulkan-demo/README.md
Normal file
28
examples/vulkan-demo/README.md
Normal 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.
|
||||||
1141
examples/vulkan-demo/main.cpp
Normal file
1141
examples/vulkan-demo/main.cpp
Normal file
File diff suppressed because it is too large
Load diff
294
renderers/vulkan/clay_renderer_vulkan.c
Normal file
294
renderers/vulkan/clay_renderer_vulkan.c
Normal 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.
|
||||||
Loading…
Reference in a new issue