Try Live
Add Docs
Rankings
Pricing
Docs
Install
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
Low Level Graphics Library (LLGL)
https://github.com/lukasbanana/llgl
Admin
LLGL is a thin abstraction layer for a wide variety of modern and legacy rendering APIs across
...
Tokens:
78,807
Snippets:
403
Trust Score:
9
Update:
3 months ago
Context
Skills
Chat
Benchmark
92.3
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# LLGL - Low Level Graphics Library ## Introduction LLGL (Low Level Graphics Library) is a thin abstraction layer providing unified access to multiple modern and legacy rendering APIs across desktop and mobile platforms. The library targets Direct3D 11/12, Vulkan, OpenGL, Metal, and WebGL/WebAssembly, enabling developers to write graphics applications once and deploy across Windows, Linux, macOS, iOS, Android, and web platforms. Written primarily in C++11, LLGL includes comprehensive wrappers for C99, C# 6.0, and Go, making it accessible across diverse development environments. LLGL provides close coupling with underlying graphics APIs while simplifying architectural complexities common in modern rendering systems. The library's design philosophy emphasizes explicit state management, modular backend loading, and minimal overhead abstraction. Key features include pipeline state objects (PSOs), resource heaps, command buffer recording, multi-threaded rendering support, shader compilation from HLSL/GLSL/SPIR-V/Metal sources, and comprehensive buffer/texture management. The modular architecture loads rendering backends dynamically at runtime, allowing applications to gracefully handle missing APIs without linking dependencies. ## Core APIs and Functions ### Loading a Render System Dynamic rendering backend initialization with runtime module selection. ```cpp #include <LLGL/LLGL.h> // Simple loading with automatic error handling LLGL::RenderSystemPtr renderer = LLGL::RenderSystem::Load("Vulkan"); // Robust multi-backend fallback with error reporting LLGL::RenderSystemPtr myRenderer; for (const char* module : { "Direct3D12", "Direct3D11", "Vulkan", "OpenGL" }) { LLGL::Report report; myRenderer = LLGL::RenderSystem::Load(module, &report); if (myRenderer == nullptr) { LLGL::Log::Errorf("Failed to load %s: %s", module, report.GetText()); } else { LLGL::Log::Printf("Successfully loaded: %s", module); break; } } // Advanced loading with debugging enabled LLGL::RenderingDebugger debugger; LLGL::RenderSystemDescriptor rendererDesc; rendererDesc.moduleName = "Direct3D11"; rendererDesc.debugger = &debugger; rendererDesc.flags = LLGL::RenderSystemFlags::DebugDevice; LLGL::Report report; LLGL::RenderSystemPtr renderer = LLGL::RenderSystem::Load(rendererDesc, &report); // Query available backends on current platform std::vector<std::string> availableModules = LLGL::RenderSystem::FindModules(); for (const auto& module : availableModules) { LLGL::Log::Printf("Available: %s", module.c_str()); } ``` ### Creating Swap Chains and Windows SwapChain manages framebuffer presentation and surface creation. ```cpp #include <LLGL/LLGL.h> // Create swap chain with custom resolution and multisampling LLGL::SwapChainDescriptor swapChainDesc; swapChainDesc.resolution = { 1920, 1080 }; swapChainDesc.colorBits = 32; swapChainDesc.depthBits = 24; swapChainDesc.stencilBits = 8; swapChainDesc.samples = 8; // 8x MSAA swapChainDesc.fullscreen = false; swapChainDesc.swapBuffers = 2; LLGL::SwapChain* swapChain = renderer->CreateSwapChain(swapChainDesc); // Access and configure the window surface LLGL::Window& window = LLGL::CastTo<LLGL::Window>(swapChain->GetSurface()); window.SetTitle(L"LLGL Graphics Application"); window.Show(); // Query swap chain properties LLGL::Extent2D resolution = swapChain->GetResolution(); LLGL::Format colorFormat = swapChain->GetColorFormat(); LLGL::Format depthFormat = swapChain->GetDepthStencilFormat(); uint32_t samples = swapChain->GetSamples(); LLGL::Log::Printf("Resolution: %u x %u, Samples: %u", resolution.width, resolution.height, samples); // Enable V-Sync swapChain->SetVsyncInterval(1); // 1 = 60Hz, 2 = 30Hz, 0 = disabled // Main render loop while (window.ProcessEvents()) { // Rendering code here... swapChain->Present(); // Swap front/back buffers } // Cleanup renderer->Release(*swapChain); ``` ### Buffer Creation and Management Creating vertex/index/uniform buffers with CPU/GPU data transfer. ```cpp #include <LLGL/LLGL.h> // Define vertex structure struct Vertex { float position[3]; float normal[3]; float texCoord[2]; uint8_t color[4]; }; // Prepare vertex data Vertex vertices[] = { { { -1.0f, -1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f }, { 0.0f, 0.0f }, { 255, 0, 0, 255 } }, { { 1.0f, -1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f }, { 1.0f, 0.0f }, { 0, 255, 0, 255 } }, { { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f }, { 0.5f, 1.0f }, { 0, 0, 255, 255 } }, }; // Define vertex format LLGL::VertexFormat vertexFormat; vertexFormat.AppendAttribute({ "position", LLGL::Format::RGB32Float }); vertexFormat.AppendAttribute({ "normal", LLGL::Format::RGB32Float }); vertexFormat.AppendAttribute({ "texCoord", LLGL::Format::RG32Float }); vertexFormat.AppendAttribute({ "color", LLGL::Format::RGBA8UNorm }); vertexFormat.SetStride(sizeof(Vertex)); // Create vertex buffer with initial data LLGL::BufferDescriptor vertexBufferDesc; vertexBufferDesc.size = sizeof(vertices); vertexBufferDesc.bindFlags = LLGL::BindFlags::VertexBuffer; vertexBufferDesc.vertexAttribs = vertexFormat.attributes; LLGL::Buffer* vertexBuffer = renderer->CreateBuffer(vertexBufferDesc, vertices); // Create index buffer uint32_t indices[] = { 0, 1, 2 }; LLGL::BufferDescriptor indexBufferDesc; indexBufferDesc.size = sizeof(indices); indexBufferDesc.bindFlags = LLGL::BindFlags::IndexBuffer; indexBufferDesc.format = LLGL::Format::R32UInt; LLGL::Buffer* indexBuffer = renderer->CreateBuffer(indexBufferDesc, indices); // Create uniform/constant buffer with CPU write access struct UniformData { float modelViewProj[16]; float lightDir[4]; }; LLGL::BufferDescriptor uniformBufferDesc; uniformBufferDesc.size = sizeof(UniformData); uniformBufferDesc.bindFlags = LLGL::BindFlags::ConstantBuffer; uniformBufferDesc.cpuAccessFlags = LLGL::CPUAccessFlags::Write; LLGL::Buffer* uniformBuffer = renderer->CreateBuffer(uniformBufferDesc); // Update buffer data UniformData uniformData = { /* ... */ }; renderer->WriteBuffer(*uniformBuffer, 0, &uniformData, sizeof(uniformData)); // Map buffer for direct CPU access if (void* mapped = renderer->MapBuffer(*uniformBuffer, LLGL::CPUAccess::Write)) { memcpy(mapped, &uniformData, sizeof(uniformData)); renderer->UnmapBuffer(*uniformBuffer); } // Create dynamic staging buffer LLGL::BufferDescriptor stagingBufferDesc; stagingBufferDesc.size = 1024 * 1024; // 1 MB stagingBufferDesc.bindFlags = LLGL::BindFlags::CopySrc; stagingBufferDesc.cpuAccessFlags = LLGL::CPUAccessFlags::ReadWrite; LLGL::Buffer* stagingBuffer = renderer->CreateBuffer(stagingBufferDesc); // Cleanup renderer->Release(*vertexBuffer); renderer->Release(*indexBuffer); renderer->Release(*uniformBuffer); ``` ### Shader Compilation and Management Cross-platform shader loading supporting HLSL, GLSL, SPIR-V, and Metal. ```cpp #include <LLGL/LLGL.h> // Check supported shading languages const auto& languages = renderer->GetRenderingCaps().shadingLanguages; bool supportsHLSL = std::find(languages.begin(), languages.end(), LLGL::ShadingLanguage::HLSL) != languages.end(); // Load GLSL shaders from file LLGL::ShaderDescriptor vertShaderDesc; vertShaderDesc.type = LLGL::ShaderType::Vertex; vertShaderDesc.source = "shader.vert"; vertShaderDesc.sourceType = LLGL::ShaderSourceType::CodeFile; vertShaderDesc.vertex.inputAttribs = vertexFormat.attributes; LLGL::ShaderDescriptor fragShaderDesc; fragShaderDesc.type = LLGL::ShaderType::Fragment; fragShaderDesc.source = "shader.frag"; fragShaderDesc.sourceType = LLGL::ShaderSourceType::CodeFile; LLGL::Shader* vertShader = renderer->CreateShader(vertShaderDesc); LLGL::Shader* fragShader = renderer->CreateShader(fragShaderDesc); // Check for shader compilation errors for (LLGL::Shader* shader : { vertShader, fragShader }) { if (const LLGL::Report* report = shader->GetReport()) { if (report->HasErrors()) { LLGL::Log::Errorf("Shader error: %s", report->GetText()); } } } // Load HLSL shader with entry point and profile LLGL::ShaderDescriptor hlslVertDesc; hlslVertDesc.type = LLGL::ShaderType::Vertex; hlslVertDesc.source = "shader.hlsl"; hlslVertDesc.sourceType = LLGL::ShaderSourceType::CodeFile; hlslVertDesc.entryPoint = "VSMain"; hlslVertDesc.profile = "vs_5_0"; hlslVertDesc.vertex.inputAttribs = vertexFormat.attributes; // Load precompiled SPIR-V binary LLGL::ShaderDescriptor spirvDesc = LLGL::ShaderDescFromFile( LLGL::ShaderType::Vertex, "shader.spv" ); // Inline shader source LLGL::ShaderDescriptor inlineShaderDesc; inlineShaderDesc.type = LLGL::ShaderType::Fragment; inlineShaderDesc.sourceType = LLGL::ShaderSourceType::CodeString; inlineShaderDesc.source = "#version 330 core\n" "out vec4 fragColor;\n" "void main() {\n" " fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n" "}\n"; LLGL::Shader* inlineShader = renderer->CreateShader(inlineShaderDesc); // Cross-platform shader loading LLGL::Shader* crossPlatformVS = nullptr; if (std::find(languages.begin(), languages.end(), LLGL::ShadingLanguage::SPIRV) != languages.end()) { LLGL::ShaderDescriptor desc = LLGL::ShaderDescFromFile(LLGL::ShaderType::Vertex, "shader.spv"); crossPlatformVS = renderer->CreateShader(desc); } else if (std::find(languages.begin(), languages.end(), LLGL::ShadingLanguage::HLSL) != languages.end()) { LLGL::ShaderDescriptor desc = { LLGL::ShaderType::Vertex, "shader.hlsl", "main", "vs_5_0" }; crossPlatformVS = renderer->CreateShader(desc); } else if (std::find(languages.begin(), languages.end(), LLGL::ShadingLanguage::GLSL) != languages.end()) { LLGL::ShaderDescriptor desc = { LLGL::ShaderType::Vertex, "shader.vert" }; crossPlatformVS = renderer->CreateShader(desc); } // Cleanup renderer->Release(*vertShader); renderer->Release(*fragShader); ``` ### Graphics Pipeline State Objects Creating PSOs with render states, shaders, and pipeline configuration. ```cpp #include <LLGL/LLGL.h> // Basic graphics pipeline LLGL::GraphicsPipelineDescriptor pipelineDesc; pipelineDesc.vertexShader = vertShader; pipelineDesc.fragmentShader = fragShader; pipelineDesc.renderPass = swapChain->GetRenderPass(); pipelineDesc.primitiveTopology = LLGL::PrimitiveTopology::TriangleList; LLGL::PipelineState* pipeline = renderer->CreatePipelineState(pipelineDesc); // Advanced pipeline with custom rasterizer state LLGL::GraphicsPipelineDescriptor advancedPipelineDesc; advancedPipelineDesc.vertexShader = vertShader; advancedPipelineDesc.fragmentShader = fragShader; advancedPipelineDesc.geometryShader = geomShader; // Optional advancedPipelineDesc.renderPass = swapChain->GetRenderPass(); // Rasterizer configuration advancedPipelineDesc.rasterizer.polygonMode = LLGL::PolygonMode::Fill; advancedPipelineDesc.rasterizer.cullMode = LLGL::CullMode::Back; advancedPipelineDesc.rasterizer.frontCCW = true; advancedPipelineDesc.rasterizer.multiSampleEnabled = true; advancedPipelineDesc.rasterizer.antiAliasedLineEnabled = true; advancedPipelineDesc.rasterizer.depthClampEnabled = false; // Depth/stencil configuration advancedPipelineDesc.depth.testEnabled = true; advancedPipelineDesc.depth.writeEnabled = true; advancedPipelineDesc.depth.compareOp = LLGL::CompareOp::Less; advancedPipelineDesc.stencil.testEnabled = true; advancedPipelineDesc.stencil.front.stencilFailOp = LLGL::StencilOp::Keep; advancedPipelineDesc.stencil.front.depthFailOp = LLGL::StencilOp::Keep; advancedPipelineDesc.stencil.front.depthPassOp = LLGL::StencilOp::Replace; advancedPipelineDesc.stencil.front.compareOp = LLGL::CompareOp::Always; // Blend state configuration advancedPipelineDesc.blend.targets[0].blendEnabled = true; advancedPipelineDesc.blend.targets[0].srcColor = LLGL::BlendOp::SrcAlpha; advancedPipelineDesc.blend.targets[0].dstColor = LLGL::BlendOp::InvSrcAlpha; advancedPipelineDesc.blend.targets[0].colorArithmetic = LLGL::BlendArithmetic::Add; advancedPipelineDesc.blend.targets[0].srcAlpha = LLGL::BlendOp::One; advancedPipelineDesc.blend.targets[0].dstAlpha = LLGL::BlendOp::Zero; advancedPipelineDesc.blend.targets[0].alphaArithmetic = LLGL::BlendArithmetic::Add; LLGL::PipelineState* advancedPipeline = renderer->CreatePipelineState(advancedPipelineDesc); // Pipeline caching for faster loading LLGL::Blob cachedBlob = LLGL::Blob::CreateFromFile("pipeline.cache"); LLGL::PipelineCache* pipelineCache = renderer->CreatePipelineCache(cachedBlob); LLGL::PipelineState* cachedPipeline = renderer->CreatePipelineState(pipelineDesc, pipelineCache); // Save pipeline cache if (LLGL::Blob newCache = pipelineCache->GetBlob()) { std::ofstream file("pipeline.cache", std::ios::binary); file.write(reinterpret_cast<const char*>(newCache.GetData()), newCache.GetSize()); } // Compute pipeline LLGL::ComputePipelineDescriptor computePipelineDesc; computePipelineDesc.computeShader = computeShader; LLGL::PipelineState* computePipeline = renderer->CreatePipelineState(computePipelineDesc); // Cleanup renderer->Release(*pipeline); renderer->Release(*pipelineCache); ``` ### Texture Creation and Management Comprehensive texture operations including creation, updates, and sampling. ```cpp #include <LLGL/LLGL.h> // Create 2D texture with initial data LLGL::TextureDescriptor texture2DDesc; texture2DDesc.type = LLGL::TextureType::Texture2D; texture2DDesc.bindFlags = LLGL::BindFlags::Sampled; texture2DDesc.format = LLGL::Format::RGBA8UNorm; texture2DDesc.extent = { 1024, 1024, 1 }; texture2DDesc.mipLevels = 1; std::vector<uint8_t> imageData(1024 * 1024 * 4, 255); LLGL::ImageView imageView; imageView.format = LLGL::ImageFormat::RGBA; imageView.dataType = LLGL::DataType::UInt8; imageView.data = imageData.data(); imageView.dataSize = imageData.size(); LLGL::Texture* texture2D = renderer->CreateTexture(texture2DDesc, &imageView); // Create render target texture LLGL::TextureDescriptor renderTargetDesc; renderTargetDesc.type = LLGL::TextureType::Texture2D; renderTargetDesc.bindFlags = LLGL::BindFlags::Sampled | LLGL::BindFlags::ColorAttachment; renderTargetDesc.format = LLGL::Format::RGBA8UNorm; renderTargetDesc.extent = { 2048, 2048, 1 }; renderTargetDesc.mipLevels = 1; LLGL::Texture* renderTexture = renderer->CreateTexture(renderTargetDesc); // Create depth texture LLGL::TextureDescriptor depthTextureDesc; depthTextureDesc.type = LLGL::TextureType::Texture2D; depthTextureDesc.bindFlags = LLGL::BindFlags::DepthStencilAttachment; depthTextureDesc.format = LLGL::Format::D32Float; depthTextureDesc.extent = { 2048, 2048, 1 }; LLGL::Texture* depthTexture = renderer->CreateTexture(depthTextureDesc); // Create 3D texture LLGL::TextureDescriptor texture3DDesc; texture3DDesc.type = LLGL::TextureType::Texture3D; texture3DDesc.bindFlags = LLGL::BindFlags::Sampled; texture3DDesc.format = LLGL::Format::R8UNorm; texture3DDesc.extent = { 256, 256, 256 }; LLGL::Texture* texture3D = renderer->CreateTexture(texture3DDesc); // Create cube map LLGL::TextureDescriptor cubeMapDesc; cubeMapDesc.type = LLGL::TextureType::TextureCube; cubeMapDesc.bindFlags = LLGL::BindFlags::Sampled; cubeMapDesc.format = LLGL::Format::RGBA16Float; cubeMapDesc.extent = { 512, 512, 1 }; cubeMapDesc.mipLevels = 0; // Generate all mip levels LLGL::Texture* cubeMap = renderer->CreateTexture(cubeMapDesc); // Update texture region LLGL::TextureRegion region; region.subresource.baseArrayLayer = 0; region.subresource.numArrayLayers = 1; region.subresource.baseMipLevel = 0; region.subresource.numMipLevels = 1; region.offset = { 100, 100, 0 }; region.extent = { 512, 512, 1 }; std::vector<uint8_t> updateData(512 * 512 * 4); LLGL::ImageView updateView; updateView.format = LLGL::ImageFormat::RGBA; updateView.dataType = LLGL::DataType::UInt8; updateView.data = updateData.data(); updateView.dataSize = updateData.size(); renderer->WriteTexture(*texture2D, region, updateView); // Read texture data back auto texExtent = texture2D->GetMipExtent(0); std::vector<uint8_t> readbackData(texExtent.width * texExtent.height * 4); LLGL::MutableImageView readbackView; readbackView.format = LLGL::ImageFormat::RGBA; readbackView.dataType = LLGL::DataType::UInt8; readbackView.data = readbackData.data(); readbackView.dataSize = readbackData.size(); LLGL::TextureRegion readRegion; readRegion.subresource.baseArrayLayer = 0; readRegion.subresource.baseMipLevel = 0; readRegion.extent = texExtent; renderer->ReadTexture(*texture2D, readRegion, readbackView); // Sampler configuration LLGL::SamplerDescriptor samplerDesc; samplerDesc.addressModeU = LLGL::SamplerAddressMode::Repeat; samplerDesc.addressModeV = LLGL::SamplerAddressMode::Repeat; samplerDesc.addressModeW = LLGL::SamplerAddressMode::ClampToEdge; samplerDesc.minFilter = LLGL::SamplerFilter::Linear; samplerDesc.magFilter = LLGL::SamplerFilter::Linear; samplerDesc.mipMapFilter = LLGL::SamplerFilter::Linear; samplerDesc.maxAnisotropy = 16; samplerDesc.compareEnabled = false; LLGL::Sampler* sampler = renderer->CreateSampler(samplerDesc); // Cleanup renderer->Release(*texture2D); renderer->Release(*sampler); ``` ### Command Buffer Recording and Execution Recording and submitting GPU commands with immediate or deferred execution. ```cpp #include <LLGL/LLGL.h> // Create immediate command buffer (auto-submit) LLGL::CommandBuffer* immediateCmdBuffer = renderer->CreateCommandBuffer(LLGL::CommandBufferFlags::ImmediateSubmit); // Create deferred command buffer LLGL::CommandBufferDescriptor deferredDesc; deferredDesc.flags = 0; deferredDesc.numNativeBuffers = 2; // Double buffering LLGL::CommandBuffer* deferredCmdBuffer = renderer->CreateCommandBuffer(deferredDesc); // Get command queue for manual submission LLGL::CommandQueue* cmdQueue = renderer->GetCommandQueue(); // Basic rendering commands immediateCmdBuffer->Begin(); { // Set viewport and scissor immediateCmdBuffer->SetViewport(swapChain->GetResolution()); // Bind vertex and index buffers immediateCmdBuffer->SetVertexBuffer(*vertexBuffer); immediateCmdBuffer->SetIndexBuffer(*indexBuffer); // Begin render pass immediateCmdBuffer->BeginRenderPass(*swapChain); { // Clear render target const float clearColor[4] = { 0.1f, 0.1f, 0.2f, 1.0f }; immediateCmdBuffer->Clear(LLGL::ClearFlags::ColorDepth, clearColor); // Set pipeline and draw immediateCmdBuffer->SetPipelineState(*pipeline); immediateCmdBuffer->DrawIndexed(3, 0); } immediateCmdBuffer->EndRenderPass(); } immediateCmdBuffer->End(); // Auto-submitted due to ImmediateSubmit flag // Advanced rendering with resource binding deferredCmdBuffer->Begin(); { // Update buffer during recording (max 65536 bytes) UniformData uniforms = { /* ... */ }; deferredCmdBuffer->UpdateBuffer(*uniformBuffer, 0, &uniforms, sizeof(uniforms)); // Set viewport with custom depth range LLGL::Viewport viewport; viewport.x = 0.0f; viewport.y = 0.0f; viewport.width = 1920.0f; viewport.height = 1080.0f; viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; deferredCmdBuffer->SetViewport(viewport); // Set scissor rectangle LLGL::Scissor scissor = { 100, 100, 1720, 880 }; deferredCmdBuffer->SetScissor(scissor); // Bind resources via resource heap deferredCmdBuffer->SetResourceHeap(*resourceHeap); // Begin render pass with custom attachments immediateCmdBuffer->BeginRenderPass(*renderTarget); { // Clear with custom clear values LLGL::ClearAttachmentsDescriptor clearDesc; clearDesc.flags = LLGL::ClearFlags::ColorDepth; clearDesc.clearColor[0] = { 0.2f, 0.3f, 0.4f, 1.0f }; clearDesc.depthValue = 1.0f; deferredCmdBuffer->ClearAttachments(1, &clearDesc); // Bind pipeline and resources deferredCmdBuffer->SetPipelineState(*pipeline); deferredCmdBuffer->SetVertexBuffer(*vertexBuffer); deferredCmdBuffer->SetIndexBuffer(*indexBuffer); // Draw calls deferredCmdBuffer->DrawIndexed(36, 0); // Draw cube deferredCmdBuffer->Draw(3, 0); // Draw triangle // Instanced rendering deferredCmdBuffer->DrawIndexedInstanced(36, 100, 0, 0); // 100 cubes } deferredCmdBuffer->EndRenderPass(); // Copy operations LLGL::TextureRegion srcRegion = { /* ... */ }; LLGL::TextureRegion dstRegion = { /* ... */ }; deferredCmdBuffer->CopyTexture(*srcTexture, srcRegion, *dstTexture, dstRegion); // Generate mipmaps deferredCmdBuffer->GenerateMips(*texture2D); } deferredCmdBuffer->End(); // Manual submission to command queue cmdQueue->Submit(*deferredCmdBuffer); // Fence synchronization LLGL::Fence* fence = renderer->CreateFence(); cmdQueue->Submit(*deferredCmdBuffer, fence); cmdQueue->WaitFence(*fence, UINT64_MAX); // Wait indefinitely // Present swap chain swapChain->Present(); // Cleanup renderer->Release(*immediateCmdBuffer); renderer->Release(*deferredCmdBuffer); renderer->Release(*fence); ``` ### Resource Heaps and Pipeline Layouts Efficient resource binding with descriptor sets for modern APIs. ```cpp #include <LLGL/LLGL.h> // Define pipeline layout with binding points LLGL::PipelineLayoutDescriptor layoutDesc; // Heap binding for uniform buffer LLGL::BindingDescriptor uniformBinding; uniformBinding.type = LLGL::ResourceType::Buffer; uniformBinding.bindFlags = LLGL::BindFlags::ConstantBuffer; uniformBinding.stageFlags = LLGL::StageFlags::VertexStage | LLGL::StageFlags::FragmentStage; uniformBinding.slot.index = 0; // Heap binding for texture LLGL::BindingDescriptor textureBinding; textureBinding.type = LLGL::ResourceType::Texture; textureBinding.bindFlags = LLGL::BindFlags::Sampled; textureBinding.stageFlags = LLGL::StageFlags::FragmentStage; textureBinding.slot.index = 1; // Heap binding for sampler LLGL::BindingDescriptor samplerBinding; samplerBinding.type = LLGL::ResourceType::Sampler; samplerBinding.bindFlags = 0; samplerBinding.stageFlags = LLGL::StageFlags::FragmentStage; samplerBinding.slot.index = 2; layoutDesc.heapBindings = { uniformBinding, textureBinding, samplerBinding }; LLGL::PipelineLayout* pipelineLayout = renderer->CreatePipelineLayout(layoutDesc); // Create resource heap with initial resources LLGL::ResourceHeapDescriptor heapDesc; heapDesc.pipelineLayout = pipelineLayout; heapDesc.numResourceViews = 3; LLGL::ResourceViewDescriptor resourceViews[3]; resourceViews[0] = uniformBuffer; resourceViews[1] = texture2D; resourceViews[2] = sampler; LLGL::ResourceHeap* resourceHeap = renderer->CreateResourceHeap( heapDesc, LLGL::ArrayView<LLGL::ResourceViewDescriptor>(resourceViews, 3) ); // Update resource heap dynamically LLGL::ResourceViewDescriptor updatedViews[2]; updatedViews[0] = newUniformBuffer; updatedViews[1] = newTexture; renderer->WriteResourceHeap(*resourceHeap, 0, LLGL::ArrayView<LLGL::ResourceViewDescriptor>(updatedViews, 2)); // Use resource heap in command buffer cmdBuffer->Begin(); cmdBuffer->SetPipelineState(*pipeline); cmdBuffer->SetResourceHeap(*resourceHeap); cmdBuffer->DrawIndexed(36, 0); cmdBuffer->End(); // Multiple descriptor sets LLGL::ResourceHeapDescriptor multiSetDesc; multiSetDesc.pipelineLayout = pipelineLayout; multiSetDesc.numResourceViews = 6; // 2 sets of 3 resources LLGL::ResourceViewDescriptor multiSetViews[6]; // First set multiSetViews[0] = uniformBuffer1; multiSetViews[1] = texture1; multiSetViews[2] = sampler1; // Second set multiSetViews[3] = uniformBuffer2; multiSetViews[4] = texture2; multiSetViews[5] = sampler2; LLGL::ResourceHeap* multiSetHeap = renderer->CreateResourceHeap( multiSetDesc, LLGL::ArrayView<LLGL::ResourceViewDescriptor>(multiSetViews, 6) ); // Bind specific descriptor set cmdBuffer->SetResourceHeap(*multiSetHeap, 0); // First set cmdBuffer->DrawIndexed(36, 0); cmdBuffer->SetResourceHeap(*multiSetHeap, 1); // Second set cmdBuffer->DrawIndexed(36, 0); // Cleanup renderer->Release(*pipelineLayout); renderer->Release(*resourceHeap); ``` ### C99 API Usage Complete C99 wrapper example for cross-language compatibility. ```c #include <LLGL-C/LLGL.h> #include <stdio.h> #include <string.h> int main(int argc, char* argv[]) { // Load render system module if (llglLoadRenderSystem("OpenGL") == 0) { fprintf(stderr, "Failed to load OpenGL\n"); return 1; } // Create swap chain LLGLSwapChainDescriptor swapChainDesc = { .resolution = { 800, 600 }, .colorBits = 32, .depthBits = 24, .stencilBits = 8, .samples = 8 }; LLGLSwapChain swapChain = llglCreateSwapChain(&swapChainDesc); // Get window surface LLGLSurface surface = llglGetSurface(swapChain); LLGLWindow window = LLGL_GET_AS(LLGLWindow, surface); llglSetWindowTitle(window, L"C99 LLGL Example"); // Define vertex structure typedef struct Vertex { float position[2]; uint8_t color[4]; } Vertex; Vertex vertices[] = { { { 0.0f, 0.5f }, { 255, 0, 0, 255 } }, { { 0.5f, -0.5f }, { 0, 255, 0, 255 } }, { { -0.5f, -0.5f }, { 0, 0, 255, 255 } } }; // Vertex attributes LLGLVertexAttribute vertexAttribs[2] = { { .name = "position", .format = LLGLFormatRG32Float, .location = 0, .offset = offsetof(Vertex, position), .stride = sizeof(Vertex) }, { .name = "color", .format = LLGLFormatRGBA8UNorm, .location = 1, .offset = offsetof(Vertex, color), .stride = sizeof(Vertex) } }; // Create vertex buffer LLGLBufferDescriptor bufferDesc = { .size = sizeof(vertices), .bindFlags = LLGLBindVertexBuffer, .numVertexAttribs = 2, .vertexAttribs = vertexAttribs }; LLGLBuffer vertexBuffer = llglCreateBuffer(&bufferDesc, vertices); // Create shaders LLGLShaderDescriptor vertShaderDesc = { .type = LLGLShaderTypeVertex, .source = "shader.vert", .sourceType = LLGLShaderSourceTypeCodeFile, .vertex = { .numInputAttribs = 2, .inputAttribs = vertexAttribs } }; LLGLShaderDescriptor fragShaderDesc = { .type = LLGLShaderTypeFragment, .source = "shader.frag", .sourceType = LLGLShaderSourceTypeCodeFile }; LLGLShader vertShader = llglCreateShader(&vertShaderDesc); LLGLShader fragShader = llglCreateShader(&fragShaderDesc); // Check shader errors LLGLReport vertReport = llglGetShaderReport(vertShader); if (llglHasReportErrors(vertReport)) { fprintf(stderr, "Vertex shader error: %s\n", llglGetReportText(vertReport)); return 1; } // Create graphics pipeline LLGLGraphicsPipelineDescriptor pipelineDesc = { .vertexShader = vertShader, .fragmentShader = fragShader, .renderPass = llglGetRenderTargetRenderPass(LLGL_GET_AS(LLGLRenderTarget, swapChain)), .primitiveTopology = LLGLPrimitiveTopologyTriangleList, .rasterizer = { .multiSampleEnabled = 1 }, .blend.targets[0].colorMask = LLGLColorMaskAll }; LLGLPipelineState pipeline = llglCreateGraphicsPipelineState(&pipelineDesc); // Create command buffer LLGLCommandBufferDescriptor cmdBufDesc = { .flags = LLGLCommandBufferImmediateSubmit }; LLGLCommandBuffer cmdBuffer = llglCreateCommandBuffer(&cmdBufDesc); // Render loop LLGLExtent2D resolution; llglGetSurfaceContentSize(surface, &resolution); while (llglProcessSurfaceEvents()) { // Begin recording llglBegin(cmdBuffer); // Set viewport LLGLViewport viewport = { .x = 0, .y = 0, .width = (float)resolution.width, .height = (float)resolution.height, .minDepth = 0.0f, .maxDepth = 1.0f }; llglSetViewport(cmdBuffer, &viewport); // Set vertex buffer llglSetVertexBuffer(cmdBuffer, vertexBuffer); // Begin render pass llglBeginRenderPass(cmdBuffer, LLGL_GET_AS(LLGLRenderTarget, swapChain)); // Clear and draw float clearColor[4] = { 0.1f, 0.1f, 0.2f, 1.0f }; llglClear(cmdBuffer, LLGLClearColor, clearColor); llglSetPipelineState(cmdBuffer, pipeline); llglDraw(cmdBuffer, 3, 0); // End render pass llglEndRenderPass(cmdBuffer); llglEnd(cmdBuffer); // Present llglPresent(swapChain); } // Cleanup llglUnloadRenderSystem(); return 0; } ``` ## Use Cases and Integration LLGL serves as the foundation for cross-platform graphics applications requiring explicit control over rendering pipelines while maintaining API compatibility across backends. Primary use cases include game engines, CAD applications, scientific visualization tools, and graphics research frameworks where developers need unified access to Direct3D, Vulkan, OpenGL, and Metal without middleware overhead. The library excels in scenarios requiring multi-platform deployment from a single codebase, supporting Windows (D3D11/D3D12/Vulkan/OpenGL), Linux (Vulkan/OpenGL), macOS (Metal/Vulkan/OpenGL), iOS (Metal/OpenGL ES), Android (OpenGL ES/Vulkan), and WebAssembly (WebGL). Integration patterns follow standard C++ RAII principles with explicit resource management through RenderSystem interfaces. The modular backend system enables graceful degradation—applications attempt loading preferred APIs (Vulkan, D3D12) before falling back to legacy backends (OpenGL, D3D11). Multi-threaded rendering workflows leverage deferred command buffers recorded in parallel and submitted sequentially through command queues. Resource heaps optimize binding overhead for modern APIs while maintaining compatibility with legacy binding models through extended command buffer interfaces. The library integrates seamlessly with window management frameworks (GLFW, SDL, Qt) via custom Surface implementations or uses built-in Window/Canvas abstractions. Performance-critical applications benefit from pipeline state caching, reducing PSO creation overhead across application restarts. LLGL's C99, C#, and Go wrappers extend integration to mixed-language projects, enabling graphics backends in non-C++ ecosystems without FFI complexity.