Aperture UI
Engineer Notes

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 buffers
  • APHITexture - For textures and render targets
  • APHIShaderProgram - For shader programs
  • APHISampler - For sampler states
  • APHIRenderTarget - For render targets
  • APHIRenderPass - 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.h for 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.

Copyright © 2026