Using a Custom GPUDriver
Ultralight can emit raw GPU geometry / low-level draw calls to paint directly on the GPU without an intermediate CPU bitmap. We recommend this integration method for best performance.
Video Tutorial For DX11 Integration
Integrating Ultralight into a DX11 app? A member of our community graciously provided a complete video tutorial!
Virtual GPU Architecture
Ultralight was designed from the outset to be renderer-agnostic. All draw calls are emitted via the GPUDriver interface and expected to be translated into various platform-specific GPU technologies (D3D, Metal, OpenGL, etc.).
This approach allows Ultralight to be integrated directly with the native renderer of your game.

GPUDriver API
The first step to using a custom GPUDriver
is to subclass the GPUDriver interface.
You'll need to handle tasks like creating a texture, creating vertex/index buffers, and binding shaders.
All GPUDriver
calls are dispatched during Renderer::Render()
but drawing is not performed immediately-- Ultralight queues drawing commands via GPUDriver::UpdateCommandList()
and expects you to dispatch these yourself.
- For porting to Direct3D 11, see: AppCore/src/win/d3d11
- For porting to Direct3D 12, see: AppCore/src/win/d3d12
- For porting to OpenGL, see: AppCore/src/linux/gl
- For porting to Metal, see AppCore/src/mac/metal
Enabling the GPU Renderer
You'll need to tell Config to enable the GPU renderer and pass your custom GPUDriver instance to Platform::instance().set_gpu_driver()
.
#include <Ultralight/Ultralight.h>
using namespace ultralight;
void Init() {
Config config;
config.use_gpu_renderer = true;
config.device_scale = 1.0;
Platform::instance().set_gpu_driver(custom_gpu_driver_instance);
Ref<Renderer> renderer = Renderer::Create();
}
Shader Programs
Ultralight relies on vertex and pixel shaders for CSS transforms and to draw things like borders, rounded rectangles, shadows, and gradients.
Right now we have only two shader types: kShaderType_Fill
and kShaderType_FillPath
.
Both use the same uniforms but have different vertex types.
Here are the reference implementations for Direct3D (HLSL):
Vertex Shader (HLSL) | Pixel Shader (HLSL) | |
---|---|---|
kShaderType_Fill | v2f_c4f_t2f_t2f_d28f.hlsl | fill.hlsl |
kShaderType_FillPath | v2f_c4f_t2f.hlsl | fill_path.hlsl |
If your engine uses a custom shader language, you'll need to port these files over.
Blending Modes
Ultralight uses a custom blend mode-- you'll need to use the same blending functions in your engine to get color-accurate results when dispatching DrawGeometry commands with blending enabled.
For reference, here is the render target blend description for the D3D11 driver:
D3D11_RENDER_TARGET_BLEND_DESC rt_blend_desc;
ZeroMemory(&rt_blend_desc, sizeof(rt_blend_desc));
rt_blend_desc.BlendEnable = true;
rt_blend_desc.SrcBlend = D3D11_BLEND_ONE;
rt_blend_desc.DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
rt_blend_desc.BlendOp = D3D11_BLEND_OP_ADD;
rt_blend_desc.SrcBlendAlpha = D3D11_BLEND_INV_DEST_ALPHA;
rt_blend_desc.DestBlendAlpha = D3D11_BLEND_ONE;
rt_blend_desc.BlendOpAlpha = D3D11_BLEND_OP_ADD;
rt_blend_desc.RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
Render Loop Integration
Within your application's main run loop, you should:
- Call
Renderer::Update()
as often as possible - Call
Renderer::Render()
once per frame. - After calling
Renderer::Render()
, check ifGPUDriver
has any pending commands, and dispatch them by callingGPUDriver::DrawCommandList()
. - Get the texture handle for each
View
and display it on an on-screen quad.
void UpdateLogic() {
// Calling Update() allows the library to service resource callbacks,
// JavaScript events, and other timers.
renderer->Update();
}
void RenderFrame() {
renderer->Render();
if (gpu_driver->HasCommandsPending())
gpu_driver->DrawCommandList();
// Do rest of your drawing here.
}
Binding the View Texture
Ultralight doesn't actually draw anything to the screen-- all Views
are drawn to an offscreen render texture that you can display however you wish.
To get the texture for a View
, you'll need to call View::render_target()
:
RenderTarget rtt_info = view->render_target();
// Get the Ultralight texture ID, use this with GPUDriver::BindTexture()
uint32_t tex_id = rtt_info.texture_id;
// Textures may have extra padding-- to compensate for this you'll need
// to adjust your UV coordinates when mapping onto geometry.
Rect uv_coords = rtt_info.uv_coords;
Once you have this texture you can display it on-screen as a quad or projected onto some other geometry in-game.
Updated over 2 years ago