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
TheContext 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;
};
struct ContextConfiguration {
bool useOpenGL = true;
int majorVersion = 4;
int minorVersion = 1;
OpenGLProfile profile = OpenGLProfile::Core;
std::string applicationName;
std::string applicationVersion;
bool createValidationLayers = true;
};
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:153class 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;
};
struct DeviceInfo {
std::string deviceName;
std::string vendorName;
std::string driverVersion;
std::string renderingVersion;
std::string opalVersion;
};
// 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:831class 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;
};
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)
};
enum class MemoryUsageType {
GPUOnly, // Device-local memory (fastest)
CPUToGPU, // Staging memory for uploads
GPUToCPU // Readback memory for downloads
};
// 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:582class 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);
};
enum class PrimitiveStyle {
Points,
Lines,
LineStrip,
Triangles,
TriangleStrip,
TriangleFan,
Patches // For tessellation shaders
};
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:285class 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;
};
enum class TextureType {
Texture2D,
TextureCubeMap,
Texture3D,
Texture2DArray,
Texture2DMultisample
};
enum class TextureFormat {
Rgba8,
sRgba8, // sRGB color space
Rgb8,
sRgb8,
Rgba16F, // HDR floating-point
Rgb16F,
Depth24Stencil8,
DepthComponent24,
Depth32F,
Red8,
Red16F
};
// 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:1075class 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();
};
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:913class 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;
};
// 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::GPUOnlyfor 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
Related APIs
- Aurora Terrain API - Terrain generation using Opal
- Hydra Atmosphere API - Weather and fluid rendering