Skip to main content

Opal Rendering Backend

Opal provides a unified rendering abstraction layer that works across OpenGL, Vulkan, and Metal backends. It manages context creation, device acquisition, command buffers, and GPU resources.
This is an alpha API and may change in future releases.

Backend Selection

Opal automatically selects the appropriate rendering backend based on your platform and build configuration:
  • OpenGL: Cross-platform default (version 4.1+ Core profile)
  • Vulkan: High-performance option with explicit control
  • Metal: Native macOS/iOS support
// Configure OpenGL backend
ContextConfiguration config;
config.useOpenGL = true;
config.majorVersion = 4;
config.minorVersion = 1;
config.profile = OpenGLProfile::Core;

auto context = Context::create(config);

Core Classes

Context

The Context class wraps platform initialization and window management. include/opal/opal.h:60
class Context {
public:
    static std::shared_ptr<Context> create(ContextConfiguration config = {});
    
    void setFlag(int flag, bool enabled);
    void setFlag(int flag, int value);
    
    GLFWwindow* makeWindow(int width, int height, const char* title,
                          GLFWmonitor* monitor = nullptr,
                          GLFWwindow* share = nullptr);
    GLFWwindow* getWindow() const;
    void makeCurrent();
    
    GLFWwindow* window;
    ContextConfiguration config;
};
Configuration Options:
struct ContextConfiguration {
    bool useOpenGL = true;
    int majorVersion = 4;
    int minorVersion = 1;
    OpenGLProfile profile = OpenGLProfile::Core;
    std::string applicationName;
    std::string applicationVersion;
    bool createValidationLayers = true;
};
Example:
ContextConfiguration config;
config.useOpenGL = true;
config.majorVersion = 4;
config.minorVersion = 1;
config.applicationName = "My Atlas App";
config.createValidationLayers = true;

auto context = Context::create(config);
context->makeWindow(1920, 1080, "Atlas Engine");

Device

Represents the rendering device abstraction. Manages command buffer acquisition, framebuffer access, and device queries. include/opal/opal.h:153
class Device {
public:
    static std::shared_ptr<Device> acquire(const std::shared_ptr<Context>& context);
    
    std::shared_ptr<CommandBuffer> acquireCommandBuffer();
    void submitCommandBuffer(const std::shared_ptr<CommandBuffer>& commandBuffer);
    
    std::shared_ptr<Framebuffer> getDefaultFramebuffer();
    DeviceInfo getDeviceInfo();
    
    long frameCount = 0;
};
Device Information:
struct DeviceInfo {
    std::string deviceName;
    std::string vendorName;
    std::string driverVersion;
    std::string renderingVersion;
    std::string opalVersion;
};
Example:
// Create context and acquire device
auto context = Context::create();
auto device = Device::acquire(context);

// Get device information
DeviceInfo info = device->getDeviceInfo();
std::cout << "GPU: " << info.deviceName << std::endl;
std::cout << "Vendor: " << info.vendorName << std::endl;

// Acquire command buffer for rendering
auto commandBuffer = device->acquireCommandBuffer();
commandBuffer->start();
// ... record rendering commands ...
commandBuffer->commit();
device->submitCommandBuffer(commandBuffer);

Buffer

Manages GPU memory buffers for vertices, indices, uniforms, and general-purpose data. include/opal/opal.h:831
class Buffer {
public:
    static std::shared_ptr<Buffer> create(
        BufferUsage usage,
        size_t size,
        const void* data = nullptr,
        MemoryUsageType memoryUsage = MemoryUsageType::GPUOnly,
        int callerId = -1
    );
    
    void updateData(size_t offset, size_t size, const void* data);
    void bind(int callerId = -1) const;
    void unbind(int callerId = -1) const;
    
    uint bufferID;
    BufferUsage usage;
    MemoryUsageType memoryUsage;
};
Buffer Usage Types:
enum class BufferUsage {
    VertexBuffer,      // Vertex attribute data
    IndexArray,        // Index/element data
    GeneralPurpose,    // Generic buffer usage
    UniformBuffer,     // Uniform block data
    ShaderRead,        // Shader storage (read-only)
    ShaderReadWrite    // Shader storage (read-write)
};
Memory Types:
enum class MemoryUsageType {
    GPUOnly,    // Device-local memory (fastest)
    CPUToGPU,   // Staging memory for uploads
    GPUToCPU    // Readback memory for downloads
};
Example:
// Create vertex buffer
std::vector<float> vertices = { /* vertex data */ };
auto vertexBuffer = Buffer::create(
    BufferUsage::VertexBuffer,
    vertices.size() * sizeof(float),
    vertices.data(),
    MemoryUsageType::GPUOnly
);

// Update buffer data
vertexBuffer->updateData(0, sizeof(float) * 3, newData);

Pipeline

Configures the complete graphics pipeline including shaders, vertex layout, rasterization, depth testing, and blending. include/opal/opal.h:582
class Pipeline {
public:
    static std::shared_ptr<Pipeline> create();
    
    // Shader configuration
    void setShaderProgram(std::shared_ptr<ShaderProgram> program);
    
    // Vertex input
    void setVertexAttributes(const std::vector<VertexAttribute>& attributes,
                           const VertexBinding& binding);
    
    // Primitive assembly
    void setPrimitiveStyle(PrimitiveStyle style);
    void setPatchVertices(int count);  // For tessellation
    
    // Viewport
    void setViewport(int x, int y, int width, int height);
    
    // Rasterization
    void setRasterizerMode(RasterizerMode mode);
    void setCullMode(CullMode mode);
    void setFrontFace(FrontFace face);
    void setLineWidth(float width);
    
    // Depth testing
    void enableDepthTest(bool enabled);
    void setDepthCompareOp(CompareOp op);
    void enableDepthWrite(bool enabled);
    
    // Blending
    void enableBlending(bool enabled);
    void setBlendFunc(BlendFunc srcFactor, BlendFunc dstFactor);
    void setBlendEquation(BlendEquation equation);
    
    // Multisampling
    void enableMultisampling(bool enabled);
    
    // Polygon offset (shadow mapping)
    void enablePolygonOffset(bool enabled);
    void setPolygonOffset(float factor, float units);
    
    void build();
    void bind();
    
    // Uniform binding
    void setUniform1f(const std::string& name, float v0);
    void setUniform2f(const std::string& name, float v0, float v1);
    void setUniform3f(const std::string& name, float v0, float v1, float v2);
    void setUniform4f(const std::string& name, float v0, float v1, float v2, float v3);
    void setUniform1i(const std::string& name, int v0);
    void setUniformBool(const std::string& name, bool value);
    void setUniformMat4f(const std::string& name, const glm::mat4& matrix);
    
    // Texture binding
    void bindTexture(const std::string& name,
                    const std::shared_ptr<Texture>& texture,
                    int unit,
                    int callerId = -1);
    
    // Buffer binding
    template<typename T>
    void bindBuffer(const std::string& name, const std::vector<T>& data);
    void bindBufferData(const std::string& name, const void* data, size_t size);
};
Primitive Styles:
enum class PrimitiveStyle {
    Points,
    Lines,
    LineStrip,
    Triangles,
    TriangleStrip,
    TriangleFan,
    Patches  // For tessellation shaders
};
Example:
auto pipeline = Pipeline::create();

// Set shader program
pipeline->setShaderProgram(shaderProgram);

// Configure vertex attributes
std::vector<VertexAttribute> attributes = {
    {"position", VertexAttributeType::Float, 0, 0, false, 3, sizeof(Vertex)},
    {"normal", VertexAttributeType::Float, 12, 1, false, 3, sizeof(Vertex)},
    {"texCoord", VertexAttributeType::Float, 24, 2, false, 2, sizeof(Vertex)}
};
VertexBinding binding{sizeof(Vertex), VertexBindingInputRate::Vertex};
pipeline->setVertexAttributes(attributes, binding);

// Set primitive style
pipeline->setPrimitiveStyle(PrimitiveStyle::Triangles);

// Configure viewport
pipeline->setViewport(0, 0, 1920, 1080);

// Enable depth testing
pipeline->enableDepthTest(true);
pipeline->setDepthCompareOp(CompareOp::Less);

// Enable backface culling
pipeline->setCullMode(CullMode::Back);
pipeline->setFrontFace(FrontFace::CounterClockwise);

// Build and bind
pipeline->build();
pipeline->bind();

// Set uniforms
pipeline->setUniformMat4f("model", modelMatrix);
pipeline->setUniformMat4f("view", viewMatrix);
pipeline->setUniformMat4f("projection", projectionMatrix);

Texture

Manages 2D, 3D, cubemap, and multisampled textures. include/opal/opal.h:285
class Texture {
public:
    static std::shared_ptr<Texture> create(
        TextureType type,
        TextureFormat format,
        int width,
        int height,
        TextureDataFormat dataFormat = TextureDataFormat::Rgba,
        const void* data = nullptr,
        uint mipLevels = 1
    );
    
    static std::shared_ptr<Texture> createMultisampled(
        TextureFormat format,
        int width,
        int height,
        int samples = 4
    );
    
    static std::shared_ptr<Texture> createDepthCubemap(
        TextureFormat format,
        int resolution
    );
    
    static std::shared_ptr<Texture> create3D(
        TextureFormat format,
        int width,
        int height,
        int depth,
        TextureDataFormat dataFormat = TextureDataFormat::Rgba,
        const void* data = nullptr
    );
    
    void updateData(const void* data, int width, int height,
                   TextureDataFormat dataFormat = TextureDataFormat::Rgba);
    void updateFace(int faceIndex, const void* data, int width, int height,
                   TextureDataFormat dataFormat = TextureDataFormat::Rgba);
    void readData(void* buffer,
                 TextureDataFormat dataFormat = TextureDataFormat::Rgba);
    
    void generateMipmaps(uint levels);
    void setWrapMode(TextureAxis axis, TextureWrapMode mode);
    void setFilterMode(TextureFilterMode minFilter, TextureFilterMode magFilter);
    
    uint textureID;
    TextureType type;
    TextureFormat format;
    int width;
    int height;
    int samples;
};
Texture Types:
enum class TextureType {
    Texture2D,
    TextureCubeMap,
    Texture3D,
    Texture2DArray,
    Texture2DMultisample
};
Texture Formats:
enum class TextureFormat {
    Rgba8,
    sRgba8,              // sRGB color space
    Rgb8,
    sRgb8,
    Rgba16F,             // HDR floating-point
    Rgb16F,
    Depth24Stencil8,
    DepthComponent24,
    Depth32F,
    Red8,
    Red16F
};
Example:
// Create a standard 2D texture
auto texture = Texture::create(
    TextureType::Texture2D,
    TextureFormat::Rgba8,
    1024, 1024,
    TextureDataFormat::Rgba,
    imageData
);

// Configure filtering and wrapping
texture->setFilterMode(TextureFilterMode::LinearMipmapLinear,
                       TextureFilterMode::Linear);
texture->setWrapMode(TextureAxis::S, TextureWrapMode::Repeat);
texture->setWrapMode(TextureAxis::T, TextureWrapMode::Repeat);

// Generate mipmaps
texture->generateMipmaps(4);

CommandBuffer

Records and submits rendering commands. include/opal/opal.h:1075
class CommandBuffer {
public:
    void start();
    void beginPass(std::shared_ptr<RenderPass> renderPass);
    void endPass();
    void commit();
    
    // Binding
    void bindPipeline(const std::shared_ptr<Pipeline>& pipeline);
    void bindDrawingState(std::shared_ptr<DrawingState> drawingState);
    
    // Drawing
    void draw(uint vertexCount, uint instanceCount = 1,
             uint firstVertex = 0, uint firstInstance = 0, int objectId = -1);
    void drawIndexed(uint indexCount, uint instanceCount = 1,
                    uint firstIndex = 0, int vertexOffset = 0,
                    uint firstInstance = 0, int objectId = -1);
    void drawPatches(uint vertexCount, uint firstVertex = 0, int objectId = -1);
    
    // Clearing
    void clearColor(float r, float g, float b, float a);
    void clearDepth(float depth);
    void clear(float r, float g, float b, float a, float depth);
    
    // Resolve (MSAA)
    void performResolve(const std::shared_ptr<ResolveAction>& resolveAction);
    
    int getAndResetDrawCallCount();
};
Example:
auto commandBuffer = device->acquireCommandBuffer();

commandBuffer->start();
commandBuffer->beginPass(renderPass);

// Clear framebuffer
commandBuffer->clear(0.1f, 0.1f, 0.1f, 1.0f, 1.0f);

// Bind pipeline and resources
commandBuffer->bindPipeline(pipeline);
commandBuffer->bindDrawingState(drawingState);

// Draw geometry
commandBuffer->drawIndexed(indexCount);

commandBuffer->endPass();
commandBuffer->commit();

device->submitCommandBuffer(commandBuffer);

Framebuffer

Manages render targets with color and depth attachments. include/opal/opal.h:913
class Framebuffer {
public:
    static std::shared_ptr<Framebuffer> create(int width, int height);
    
    void addAttachment(const Attachment& attachment);
    void attachTexture(const std::shared_ptr<Texture>& texture, int attachmentIndex);
    void attachCubemap(const std::shared_ptr<Texture>& texture,
                      Attachment::Type attachmentType);
    void attachCubemapFace(const std::shared_ptr<Texture>& texture,
                          int face,
                          Attachment::Type attachmentType);
    
    void disableColorBuffer();  // For depth-only rendering
    void setDrawBuffers(int attachmentCount);
    
    void bind();
    void unbind();
    
    bool getStatus() const;
    
    uint framebufferID;
    int width;
    int height;
    std::vector<Attachment> attachments;
};
Example:
// Create framebuffer with color and depth attachments
auto framebuffer = Framebuffer::create(1920, 1080);

auto colorTexture = Texture::create(
    TextureType::Texture2D,
    TextureFormat::Rgba8,
    1920, 1080
);

auto depthTexture = Texture::create(
    TextureType::Texture2D,
    TextureFormat::Depth24Stencil8,
    1920, 1080
);

framebuffer->attachTexture(colorTexture, 0);
Attachment depthAttachment{Attachment::Type::DepthStencil, depthTexture};
framebuffer->addAttachment(depthAttachment);

framebuffer->bind();

Complete Example

Here’s a complete example showing how to use Opal to render a triangle:
#include <opal/opal.h>

using namespace opal;

int main() {
    // Create context and device
    ContextConfiguration config;
    config.useOpenGL = true;
    auto context = Context::create(config);
    context->makeWindow(1280, 720, "Opal Triangle");
    
    auto device = Device::acquire(context);
    
    // Create vertex buffer
    float vertices[] = {
        -0.5f, -0.5f, 0.0f,  // Bottom-left
         0.5f, -0.5f, 0.0f,  // Bottom-right
         0.0f,  0.5f, 0.0f   // Top
    };
    auto vertexBuffer = Buffer::create(
        BufferUsage::VertexBuffer,
        sizeof(vertices),
        vertices,
        MemoryUsageType::GPUOnly
    );
    
    // Create drawing state
    auto drawingState = DrawingState::create(vertexBuffer);
    
    // Create shaders
    const char* vertexShaderSrc = R"(
        #version 410 core
        layout(location = 0) in vec3 position;
        void main() {
            gl_Position = vec4(position, 1.0);
        }
    )";
    
    const char* fragmentShaderSrc = R"(
        #version 410 core
        out vec4 FragColor;
        void main() {
            FragColor = vec4(1.0, 0.5, 0.2, 1.0);
        }
    )";
    
    auto vertexShader = Shader::createFromSource(vertexShaderSrc, ShaderType::Vertex);
    auto fragmentShader = Shader::createFromSource(fragmentShaderSrc, ShaderType::Fragment);
    
    auto shaderProgram = ShaderProgram::create();
    shaderProgram->attachShader(vertexShader);
    shaderProgram->attachShader(fragmentShader);
    shaderProgram->link();
    
    // Create pipeline
    auto pipeline = Pipeline::create();
    pipeline->setShaderProgram(shaderProgram);
    
    std::vector<VertexAttribute> attributes = {
        {"position", VertexAttributeType::Float, 0, 0, false, 3, 12}
    };
    VertexBinding binding{12, VertexBindingInputRate::Vertex};
    pipeline->setVertexAttributes(attributes, binding);
    
    pipeline->setPrimitiveStyle(PrimitiveStyle::Triangles);
    pipeline->setViewport(0, 0, 1280, 720);
    pipeline->build();
    
    // Render loop
    while (!glfwWindowShouldClose(context->getWindow())) {
        glfwPollEvents();
        
        auto commandBuffer = device->acquireCommandBuffer();
        commandBuffer->start();
        
        auto renderPass = RenderPass::create();
        renderPass->setFramebuffer(device->getDefaultFramebuffer());
        
        commandBuffer->beginPass(renderPass);
        commandBuffer->clear(0.1f, 0.1f, 0.1f, 1.0f, 1.0f);
        
        commandBuffer->bindPipeline(pipeline);
        commandBuffer->bindDrawingState(drawingState);
        commandBuffer->draw(3);
        
        commandBuffer->endPass();
        commandBuffer->commit();
        
        device->submitCommandBuffer(commandBuffer);
        glfwSwapBuffers(context->getWindow());
    }
    
    return 0;
}

Best Practices

Performance Tips:
  • Reuse pipelines and buffers when possible
  • Use MemoryUsageType::GPUOnly for static geometry
  • Batch draw calls to minimize state changes
  • Enable multisampling for better visual quality
Common Pitfalls:
  • Always call pipeline->build() before binding
  • Don’t forget to call commandBuffer->start() before recording
  • Match vertex attribute offsets with your vertex structure layout
  • Check framebuffer status with getStatus() before rendering

Build docs developers (and LLMs) love