Aperture UI Render Path System
Aperture UI Render Path System
Overview
The Aperture UI Render Path System provides a comprehensive abstraction layer for integrating advanced graphics APIs, similar to Coherent Labs Gameface's Renoir library. This system allows end users to render the vertices, textures, and other resources that APHTML outputs using their preferred graphics API while maintaining high performance and flexibility.
Architecture
The render path system consists of several key components:
1. APHIRenderContext
The main interface that provides access to the underlying graphics API. This is the primary interface that users will implement for their specific graphics API.
2. APHIRenderContextFactory
A factory interface for creating render contexts for different graphics APIs without having to know the specific implementation details.
3. Resource Interfaces
APHIBuffer- For vertex, index, and constant buffersAPHITexture- For textures and render targetsAPHIShaderProgram- For shader programsAPHISampler- For sampler statesAPHIRenderTarget- For render targetsAPHIRenderPass- For recording rendering commands
4. Legacy Interface
The APHIRenderPath interface maintains backward compatibility with existing code while providing access to the new render context system.
Getting Started
1. Creating a Render Context
#include <APHarrlow/RenderPath/Interfaces/APHIRenderPath.h>
#include <APHarrlow/RenderPath/Interfaces/APHIRenderContextFactory.h>
// Get the global factory
auto& factory = aperture::harrlow::renderpath::GetRenderContextFactory();
// Create a render context for your graphics API
auto renderContext = factory.CreateRenderContext("Vulkan", myVulkanDevice);
// Initialize the context
if (!renderContext->Initialize(myVulkanDevice, "Vulkan")) {
// Handle initialization error
}
2. Creating Resources
// Create a vertex buffer
auto vertexBuffer = renderContext->CreateVertexBuffer(
vertexData, vertexDataSize, sizeof(Vertex),
{{"usage", "Static"}}
);
// Create a texture
auto texture = renderContext->CreateTexture(
1024, 1024, "RGBA8",
textureData, 1,
{{"usage", "ShaderResource"}}
);
// Create a shader program
auto shaderProgram = renderContext->CreateShaderProgram(
vertexShaderSource, fragmentShaderSource,
{{"position", "Float3"}, {"texcoord", "Float2"}},
{{"USE_NORMAL_MAPPING", "1"}}
);
3. Recording Render Commands
// Begin a render pass
auto renderPass = renderContext->BeginRenderPass(
renderTarget, nsColor::Black, true, false
);
// Set render state
renderPass->SetViewport(nsRectFloat(0, 0, 1920, 1080));
renderPass->SetBlendState(true, "SrcAlpha", "OneMinusSrcAlpha");
// Bind resources
renderPass->SetVertexBuffer(0, vertexBuffer);
renderPass->SetIndexBuffer(indexBuffer);
renderPass->SetShaderProgram(shaderProgram);
renderPass->SetTexture(0, texture, sampler);
// Draw
renderPass->DrawIndexed(indexCount);
// End the render pass
renderContext->EndRenderPass();
// Submit commands
renderContext->SubmitCommands();
Implementing Your Own Graphics API
To integrate your own graphics API, you need to implement the APHIRenderContext interface. Here's an example for a custom graphics API:
1. Create Your Render Context Class
class MyCustomRenderContext : public aperture::harrlow::renderpath::APHIRenderContext
{
public:
MyCustomRenderContext();
virtual ~MyCustomRenderContext();
// Implement all virtual methods from APHIRenderContext
bool Initialize(void* graphicsContext, const nsString& contextType) override;
void Shutdown() override;
nsString GetGraphicsAPIType() const override;
// ... implement all other methods
};
2. Implement Resource Classes
class MyCustomBuffer : public aperture::harrlow::renderpath::APHIBuffer
{
public:
MyCustomBuffer(MyCustomDevice* device, MyCustomBufferHandle handle, size_t size);
virtual ~MyCustomBuffer();
size_t GetSize() const override;
nsVariantDictionary GetUsage() const override;
void* GetNativeHandle() const override;
void* Map(size_t offset = 0, size_t size = 0) override;
void Unmap() override;
private:
MyCustomDevice* m_device;
MyCustomBufferHandle m_handle;
size_t m_size;
// ... other members
};
3. Register Your Implementation
// Create a factory function
nsObjectPtr<aperture::harrlow::renderpath::APHIRenderContext> CreateMyCustomRenderContext(void* graphicsContext)
{
return nsMakeObjectPtr<MyCustomRenderContext>();
}
// Register with the global factory
auto& factory = aperture::harrlow::renderpath::GetRenderContextFactory();
factory.RegisterAPIImplementation("MyCustomAPI", CreateMyCustomRenderContext);
Supported Graphics APIs
The render path system is designed to support any graphics API. Example implementations are provided for:
- Vulkan - See
APHIVulkanRenderContext.hfor a complete example - DirectX 11/12 - Can be implemented similarly to the Vulkan example
- OpenGL - Can be implemented using OpenGL commands
- Metal - Can be implemented using Metal API
- WebGPU - Can be implemented using WebGPU API
Performance Considerations
1. Resource Management
- Reuse resources when possible to avoid frequent creation/destruction
- Use appropriate buffer usage flags for your graphics API
- Consider using resource pools for frequently allocated resources
2. Command Recording
- Batch similar draw calls together
- Minimize state changes between draw calls
- Use instanced rendering when appropriate
3. Synchronization
- Use fences for GPU synchronization when needed
- Avoid unnecessary CPU-GPU synchronization
- Consider using multiple command buffers for parallel recording
Integration with APHTML
The render path system is designed to work seamlessly with APHTML's output:
1. Geometry Rendering
APHTML outputs vertices and indices that can be directly used with the render context:
// APHTML provides geometry data
auto geometryData = aphtml->GetGeometryData();
// Create buffers from APHTML data
auto vertexBuffer = renderContext->CreateVertexBuffer(
geometryData.vertices.data(),
geometryData.vertices.size() * sizeof(Vertex),
sizeof(Vertex)
);
auto indexBuffer = renderContext->CreateIndexBuffer(
geometryData.indices.data(),
geometryData.indices.size() * sizeof(uint32_t),
"UInt32"
);
2. Texture Rendering
APHTML outputs textures that can be uploaded to the GPU:
// APHTML provides texture data
auto textureData = aphtml->GetTextureData();
// Create texture from APHTML data
auto texture = renderContext->CreateTexture(
textureData.width, textureData.height, textureData.format,
textureData.pixels.data()
);
3. Shader Integration
APHTML can provide shader sources that can be compiled by the render context:
// APHTML provides shader sources
auto shaderSources = aphtml->GetShaderSources();
// Create shader program from APHTML sources
auto shaderProgram = renderContext->CreateShaderProgram(
shaderSources.vertex, shaderSources.fragment,
shaderSources.vertexLayout
);
Best Practices
1. Error Handling
Always check return values and handle errors appropriately:
auto buffer = renderContext->CreateVertexBuffer(data, size, stride);
if (!buffer) {
// Handle creation failure
return false;
}
2. Resource Cleanup
Ensure proper cleanup of resources:
// Resources are automatically cleaned up when they go out of scope
// due to the use of nsObjectPtr, but you can also explicitly release them
buffer = nullptr;
texture = nullptr;
3. Thread Safety
The render context is not thread-safe by default. If you need multi-threading:
// Use separate render contexts for different threads
// or implement proper synchronization in your render context
std::mutex renderMutex;
std::lock_guard<std::mutex> lock(renderMutex);
renderContext->SubmitCommands();
4. Debugging
Use the provided utility functions for debugging:
// Check API support
if (!renderContext->IsTextureFormatSupported("RGBA8")) {
// Handle unsupported format
}
// Get device capabilities
auto maxTextureSize = renderContext->GetMaxTextureSize();
auto supportedFormats = renderContext->GetSupportedTextureFormats();
Migration from Legacy Interface
If you're using the legacy APHIRenderPath interface, you can migrate gradually:
1. Keep Using Legacy Interface
The legacy interface is still available and functional:
// Legacy code continues to work
auto renderPath = CreateLegacyRenderPath();
renderPath->CreateGeometryBuffer(verts, indices);
renderPath->RenderGeometryBuffer(buffer);
2. Access New Features
You can access the new render context from the legacy interface:
auto renderContext = renderPath->GetRenderContext();
if (renderContext) {
// Use new features
auto texture = renderContext->CreateTexture(1024, 1024, "RGBA8");
}
3. Gradual Migration
Migrate one component at a time:
// Start with resource creation
auto renderContext = renderPath->GetRenderContext();
auto newBuffer = renderContext->CreateVertexBuffer(data, size, stride);
// Then migrate rendering
auto renderPass = renderContext->BeginRenderPass(renderTarget);
renderPass->SetVertexBuffer(0, newBuffer);
renderPass->Draw(vertexCount);
renderContext->EndRenderPass();
Conclusion
The Aperture UI Render Path System provides a powerful and flexible foundation for integrating advanced graphics APIs. By following the patterns and examples provided, you can create high-performance rendering solutions that work seamlessly with APHTML's output while maintaining full control over your graphics pipeline.
For more examples and advanced usage patterns, see the example implementations in the Examples/ directory.

