Compressed Textures
Load block-compressed DDS images into pages to reduce GPU memory and improve load times.
On this page
You can load block-compressed DDS images into pages to reduce video memory and load assets faster than PNG files.
Because the GPU samples compressed blocks directly, textures stay compressed in video memory— using four to eight times less memory than uncompressed surfaces.
AppCore's stock drivers support compressed textures out of the box (custom drivers can add support by handling compressed bitmaps).
📘 Pro Edition Required
Compressed textures require the Pro edition or higher. You can check for support at compile time with
UL_HAS(COMPRESSED_TEXTURES)(see Editions and Feature Macros). Below the Pro edition,.ddsimages don't load.
Using DDS Images on the Page
Reference .dds files anywhere an image URL is accepted, such as an <img> element or a CSS background-image property:
<img src="hud.dds">
The library recognizes DDS images by the .dds file extension or by the image/vnd.ms-dds and image/x-dds MIME types.
Authoring DDS Files
Converting Images with texconv
Convert source images to DDS using Microsoft's texconv utility:
texconv -f BC7_UNORM -dx10 -m 1 -pmalpha -o assets hud.png
Pass BC1_UNORM, BC2_UNORM, BC3_UNORM, or BC7_UNORM to -f (the library also accepts their _SRGB counterparts). BC7 requires -dx10 to write a DirectX 10 header— formats from BC1 through BC3 also work with older DXT1, DXT3, or DXT5 headers.
Ultralight uses only the top mip level, so pass -m 1 to keep the file small. The renderer supports 2D textures only (cubemaps and texture arrays are not supported).
🚧 Premultiply Alpha When Converting
The renderer treats all compressed texels as premultiplied alpha. Straight alpha causes translucent edges to blend incorrectly, so pass
-pmalphawhen converting assets with transparency.
Choosing a Format
Choose a compression format based on the image's alpha requirements and quality needs:
| Format | Also Known As | Block Size | Use |
|---|---|---|---|
BitmapFormat::BC1_UNORM |
DXT1 | 8 bytes | Opaque images (optional 1-bit alpha)— smallest file size |
BitmapFormat::BC2_UNORM |
DXT3 | 16 bytes | Explicit 4-bit alpha |
BitmapFormat::BC3_UNORM |
DXT5 | 16 bytes | Smooth alpha gradients and fast CPU decode |
BitmapFormat::BC7_UNORM |
None | 16 bytes | Highest quality color and alpha (slowest CPU decode) |
Fallback Without GPU Support
If the GPU driver doesn't advertise support for a format family, or if a View runs on the CPU renderer, the library decompresses the blocks to BGRA in software.
The image renders identically on screen, but without GPU memory savings. Decoding BC1 and BC3 on the CPU is faster than decoding PNG files, while BC7 decodes at roughly the same speed as PNG.
Loading Compressed Blocks from Memory
If you already hold compressed blocks in memory, wrap them in a Bitmap and register an ImageSource (see Displaying Custom Textures):
#include <Ultralight/Ultralight.h>
using namespace ultralight;
void RegisterHUD(const void* blocks, uint32_t width, uint32_t height) {
///
/// Copy BC7 blocks we hold in memory (free them afterward if you like).
/// Rows are measured in blocks, so size them with the format's block
/// geometry rather than width * bpp.
///
BitmapFormatInfo info = GetBitmapFormatInfo(BitmapFormat::BC7_UNORM);
uint32_t row_pitch = ((width + info.block_width - 1) / info.block_width) *
info.block_bytes;
uint32_t block_rows = (height + info.block_height - 1) / info.block_height;
RefPtr<Bitmap> bitmap = Bitmap::Create(width, height, BitmapFormat::BC7_UNORM,
row_pitch, blocks,
row_pitch * block_rows);
///
/// Register it under the name a .imgsrc file on the page refers to.
///
ImageSourceProvider::instance().AddImageSource("hud",
ImageSource::CreateFromBitmap(bitmap));
}
Block-compressed formats store 4x4 pixel blocks, so row pitch and buffer sizes are calculated from block dimensions rather than individual pixels (bpp() returns 0 on compressed bitmaps). Call GetBitmapFormatInfo() to inspect block width, height, and byte size.
Compressed bitmaps don't support per-pixel operations. Calls to Bitmap::Erase(), Bitmap::DrawBitmap(), Bitmap::Resample(), and format conversions fail or do nothing on compressed surfaces (see Working with Bitmaps). You can test whether an instance is compressed using Bitmap::is_compressed().
Supporting Compressed Textures in a Custom Driver
Advertising Format Support
Advertise supported format families by setting bits on GPUDeviceCaps::compressed_formats in GPUDriver::GetDeviceCaps() (see GPU Device Capabilities). Custom drivers advertise no formats by default— the library falls back to decompressing blocks on the CPU.
Creating the Native Texture
In GPUDriver::CreateTexture(), check Bitmap::is_compressed() to branch on compressed bitmaps:
#include <Ultralight/Ultralight.h>
using namespace ultralight;
class MyGPUDriver : public GPUDriver {
public:
void CreateTexture(uint32_t texture_id, RefPtr<Bitmap> bitmap,
uint32_t flags) override {
if (bitmap->is_compressed()) {
///
/// Lock it read-only and hand the blocks to the GPU as-is. bpp() is 0
/// here, so size the upload from size() and row_bytes(), never from
/// width * bpp.
///
auto pixels = bitmap->LockPixelsSafe();
UploadCompressedBlocks(texture_id, bitmap->format(), bitmap->width(),
bitmap->height(), pixels.data(), pixels.size(),
bitmap->row_bytes());
return;
}
// Uncompressed formats and render targets: see Implementing a GPUDriver.
}
private:
// Pseudo-code, create a native texture in the block-compressed format
// matching format and upload num_bytes from blocks with row_pitch bytes per
// row of blocks. The texture is immutable, so static storage is fine.
void UploadCompressedBlocks(uint32_t texture_id, BitmapFormat format,
uint32_t width, uint32_t height,
const void* blocks, size_t num_bytes,
uint32_t row_pitch) {}
};
Compressed textures are uploaded once and never updated— flags includes kGPUTextureFlag_Immutable, so you can safely allocate them in static GPU memory.
Selecting Native Formats
The stock reference drivers support compressed formats across several graphics APIs:
| Graphics API | Reference Driver Support |
|---|---|
| Direct3D 11, Direct3D 12 | All BC formats |
| Metal | When the device supports BC formats |
| OpenGL | BC1 through BC3 with the S3TC extension, BC7 with BPTC |
🚧 Use Plain UNORM Formats
Create native textures with plain UNORM formats (eg,
DXGI_FORMAT_BC7_UNORMorGL_COMPRESSED_RGBA_BPTC_UNORM), never sRGB formats. The library blends stored sRGB values directly— an sRGB format causes the GPU to decode on sampling, leaving colors too dark.
Inspect the reference driver source in the SDK's platform/ folder for API-specific upload calls.