Skip to content
Waxed Display Server
← Back to Docs

Background Service

Waxed Background Service

Overview

The BackgroundService is a centralized service for managing desktop background images in the Waxed compositor. It provides async image loading, Vulkan descriptor management, and DMA-BUF export capabilities for zero-copy background rendering.

Key Responsibilities

  • Async Image Loading: Loads background images from disk without blocking the render thread
  • Descriptor Management: Provides Vulkan descriptor sets for background rendering
  • Mode Switching: Supports multiple display modes (contain, cover, tile, stretch)
  • DMA-BUF Export: Exports backgrounds as DMA-BUF for zero-copy import by plugins
  • Provider Handoff: Handles Vulkan provider plugin changes gracefully

Thread Safety

The service uses atomic flags and reader-writer synchronization to allow concurrent descriptor access from multiple plugins. All state-changing operations are synchronized, while read operations (like get_background_descriptor()) are lock-free.


BackgroundMode Enum

Defines how background images are scaled and positioned relative to the display.

enum class BackgroundMode {
    Contain,   // Scale image to fit entirely within display (preserve aspect ratio)
    Cover,     // Scale image to cover entire display (preserve aspect ratio, may crop)
    Tile,      // Display image at native resolution, tiling if smaller
    Stretch,   // Stretch image to fill display (may distort)
};

Mode Behaviors

ModeAspect RatioCroppingDistortionUse Case
ContainPreservedNeverNoneSeeing entire image
CoverPreservedMay cropNoneFilling display completely
TilePreservedNeverNonePatterns, textures
StretchNot preservedNeverYesExact fill regardless of ratio

BackgroundType Enum

Identifies the type of background content.

enum class BackgroundType {
    None,         // No background set
    StaticImage,  // Static image background
    Video,        // Video background (future, not yet implemented)
};

BackgroundDescriptor Structure

Contains all information needed by the compositor to render the background.

struct BackgroundDescriptor {
    VkDescriptorSet descriptor_set{};   // Vulkan descriptor set for sampling
    VkImageLayout image_layout{};       // Current image layout
    BackgroundMode mode;                // Display mode
    BackgroundType type;                // Content type
    uint32_t width;                     // Image width in pixels
    uint32_t height;                    // Image height in pixels
    uint64_t timeline_value;            // Timeline semaphore value (unused)
};

Usage Pattern

Plugins retrieve the descriptor via get_background_descriptor() during render:

auto descriptor_opt = background_service->get_background_descriptor();
if (descriptor_opt.has_value()) {
    const auto& desc = descriptor_opt.value();
    if (desc.descriptor_set != VK_NULL_HANDLE) {
        // Bind descriptor set and render
        vkCmdBindDescriptorSets(cmd, ..., &desc.descriptor_set, ...);
        // Draw fullscreen quad with desc.mode push constants
    }
}

BackgroundErrorCode Handling

Error codes returned via std::expected<T, BackgroundErrorCode>.

enum class BackgroundErrorCode {
    Success = 0,
    InvalidPath = 1,       // Empty or malformed path
    FileNotFound = 2,      // File doesn't exist on disk
    UnsupportedFormat = 3, // File extension not supported
    LoadFailed = 4,        // Image loading failed
    NoBackgroundSet = 5,   // Tried to get descriptor when none set
    InvalidMode = 6,       // Invalid BackgroundMode value
    VulkanError = 7,       // Vulkan operation failed
};

Supported Formats

  • .jpg, .jpeg - JPEG images
  • .png - PNG images
  • .bmp - Bitmap images
  • .tga - Targa images
  • .psd - Photoshop documents
  • .gif - GIF images (first frame only)
  • .hdr - HDR images
  • .pic, .pnm - Portable image formats

Initialization

Constructor

BackgroundService::Config config{};
config.max_width = 3840;
config.max_height = 2160;
config.ring_buffer_size = 2;

BackgroundService service(config);

init_vulkan()

Initializes Vulkan resources. Must be called after a Vulkan provider plugin registers.

auto result = service.init_vulkan(
    instance,           // vk::raii::Instance& (borrowed)
    physical_device,    // vk::raii::PhysicalDevice& (borrowed)
    device,             // vk::raii::Device& (borrowed)
    graphics_queue,     // vk::raii::Queue& (borrowed)
    queue_family_index  // uint32_t
);

Internal Operations:

  1. Stores device handle (non-owning reference)
  2. Creates TextureStreamer with 32MB staging buffer
  3. Configures triple buffering (ring buffer size = 3)
  4. Sets is_ready_ atomic flag

Re-initialization: If called when already initialized, the service calls invalidate_vulkan() first to handle provider handoff.


set_background_image()

Sets a background image from a file path. Returns immediately; loading happens async.

auto result = service.set_background_image(
    "/path/to/image.jpg",  // std::string
    BackgroundMode::Cover  // Display mode
);

Flow:

Plugin Call

Validate Path

Check File Exists

Check Format

Return Error if Invalid

Return Error if Unsupported

Update State current_mode and current_type

Store Path last_image_path

Enqueue to TextureStreamer

Return Success Immediately

Error Handling:

  • Returns std::unexpected(InvalidPath) if path is empty
  • Returns std::unexpected(FileNotFound) if file doesn’t exist
  • Returns std::unexpected(UnsupportedFormat) if extension not recognized
  • Returns std::unexpected(LoadFailed) if Vulkan not initialized

get_background_descriptor()

Retrieves the current background descriptor for rendering. Thread-safe.

auto descriptor_opt = service.get_background_descriptor();
if (descriptor_opt.has_value()) {
    const auto& desc = descriptor_opt.value();
    // Use desc.descriptor_set, desc.mode, etc.
}

Behavior:

  • Returns std::nullopt if no background is set or not ready
  • Returns std::nullopt if Vulkan is not initialized
  • Returns freshest ready slot from TextureStreamer
  • Double-dereferences vk::raii::DescriptorSet optional to get raw handle

Thread Safety: Multiple threads can call this concurrently without locks.


clear_background()

Resets the background state to None.

service.clear_background();

Operations:

  1. Sets current_mode_ to BackgroundMode::Contain
  2. Sets current_type_ to BackgroundType::None
  3. Clears current_descriptor_
  4. Does NOT invalidate Vulkan resources

Idempotent: Safe to call even when no background is set.


Push Constant Calculation

calculate_push_constants()

Static method for calculating shader push constants based on mode and dimensions.

auto push = BackgroundService::calculate_push_constants(
    BackgroundMode::Cover,    // Render mode
    1920,                     // Source image width
    1080,                     // Source image height
    2560,                     // Target display width
    1440                      // Target display height
);

PushConstants Structure

struct PushConstants {
    float scale_x;    // UV/Position scale X
    float scale_y;    // UV/Position scale Y
    float offset_x;   // UV/Position offset X
    float offset_y;   // UV/Position offset Y
    int32_t mode;     // 0=contain, 1=cover, 2=tile, 3=stretch
};

Mode Calculations

Push Constant Calculations

STRETCH Mode - Fill Entirely, May Distort

scale_x = scale_y = 1.0

offset_x = offset_y = 0.0

Ignores aspect ratio completely

Variables

img_aspect = img_width / img_height

target_aspect = target_width / target_height

TILE Mode - Native Resolution, Repeat if Smaller

scale_x = target_width / img_width

offset_x = offset_y = 0.0

scale_y = target_height / img_height

Shader implements tiling via fract(UV * scale)

COVER Mode - Fill Entirely, May Crop, Preserve Ratio

scale_x = scale_y = 1.0

offset_x = offset_y = 0.0

Shader handles cropping via full UV coverage

CONTAIN Mode - Fit Entirely, Preserve Ratio

if img_aspect > target_aspect

scale_x = 1.0

scale_y = target_aspect / img_aspect

else

scale_x = img_aspect / target_aspect

scale_y = 1.0

offset_x = offset_y = 0.0


DMA-BUF Export

BackgroundDmaBuf Structure

DMA-BUF handle for zero-copy background transfer. Ownership is transferred to caller.

struct BackgroundDmaBuf {
    core::utils::UniqueFd dma_buf_fd;  // DMA-BUF file descriptor (RAII)
    uint32_t width;              // Image width in pixels
    uint32_t height;             // Image height in pixels
    uint32_t stride;             // Row stride in bytes
    uint32_t format;             // DRM fourcc format (DRM_FORMAT_ABGR8888)
    uint64_t modifier;           // DRM format modifier
    VkDeviceSize allocation_size; // Total allocation size
    uint32_t memory_type_index;  // Vulkan memory type index

    // Pre-calculated push constants
    float scale_x;
    float scale_y;
    float offset_x;
    float offset_y;
    int32_t mode;
};

set_background_dma_buf()

Async callback-based API for DMA-BUF export.

auto result = service.set_background_dma_buf(
    "/path/to/image.jpg",   // Image path
    BackgroundMode::Contain, // Display mode
    1920,                    // Target display width
    1080,                    // Target display height
    [](const BackgroundDmaBuf& dma_buf, void* user_data) {
        // Callback invoked from worker thread when ready
        // Import dma_buf.dma_buf_fd into your Vulkan texture
    },
    nullptr                  // User data pointer
);

Flow:

Yes

No

Plugin Call set_background_dma_buf

Validate Path File Format

Store callback and user_data

Enqueue load to TextureStreamer with target dimensions

Start callback polling thread

Return Immediately

Worker Thread Polls

Render Thread Continues

Check texture_streamer is_ready

Plugin renders black

When ready?

export_slot_to_dma_buf

Calculate push constants

dup DMA-BUF fd for callback

Invoke callback with BackgroundDmaBuf

Clear callback stop polling thread

Wait for callback

Callback invoked

Plugin imports DMA-BUF

Plugin renders background

Callback Thread Behavior

  • Polls every 10ms for texture readiness
  • Stops when callback is invoked (callback cleared)
  • Stops when stop token is requested
  • Runs check_texture_and_notify() in loop

check_texture_and_notify()

Internal method that polls texture readiness and invokes callback.

Operations:

  1. Check has_pending_load_ atomic flag
  2. Check dma_buf_callback_ is set
  3. Query texture_streamer_->is_ready(pending_sequence_)
  4. When ready: export to DMA-BUF
  5. Calculate push constants for target resolution
  6. Duplicate DMA-BUF file descriptor (via dup())
  7. Populate BackgroundDmaBuf structure
  8. Invoke callback with populated structure
  9. Clear callback and pending state

Vulkan Provider Handoff

invalidate_vulkan()

Called when the Vulkan provider plugin is unloaded. Shuts down TextureStreamer and nullifies handles.

service.invalidate_vulkan();

Operations:

  1. Calls texture_streamer_->shutdown()
  2. Resets texture_streamer_ unique_ptr
  3. Sets is_ready_ to false
  4. Clears device_ handle (non-owning, just nulls)

After Invalidation:

  • All operations fail until new provider registers
  • last_image_path_ and last_image_mode_ are preserved
  • Background can be restored after re-init

restore_background()

Re-enqueues the last background after Vulkan re-initialization.

service.restore_background();

Operations:

  1. Checks if last_image_path_ is not empty
  2. Checks if texture_streamer_ is available
  3. Calls texture_streamer_->request_load() with stored path and mode
  4. Logs restoration

Provider Handoff Sequence:

Provider Plugin A Unloading

Core calls invalidate_vulkan

TextureStreamer shutdown handles cleared

Background preserved in last_image_path

Provider Plugin B Loads

Display continues

Plugin B registers as Vulkan provider

Core calls init_vulkan

TextureStreamer recreated

No background black screen

Last image re-enqueued

Core calls restore_background

Background appears


Integration with TextureStreamer

TextureStreamer Role

The TextureStreamer handles:

  • Async image loading from disk (stbi/stbi_load)
  • Vulkan buffer/image allocation
  • Staging buffer uploads
  • DMA-BUF export via vkGetMemoryFdKHR

Ring Buffer

Triple buffering (ring buffer size = 3) ensures:

  • Smooth frame delivery
  • No stalls during upload
  • Always a ready frame for rendering

Sequence Numbers

Each load request returns a sequence number:

  • Used to track specific texture load
  • Queried via is_ready(sequence)
  • Retrieved via get_ready_texture(sequence)

Complete Loading Flow Diagram

TextureStreamerBackgroundServicePluginTextureStreamerBackgroundServicePluginBackground Loading FlowContinue renderingWorker thread loads image from diskRender with descriptorset_background_image()request_load(path)Return immediatelyget_background_descriptor()get_freshest_ready()Upload to GPUBackgroundDescriptor CallbackThreadBackgroundServicePluginCallbackThreadBackgroundServicePluginDMA-BUF Export FlowRender blackLoad imageSleep 10msloop[Poll is_ready]Upload GPUImport DMA-BUFRender backgroundset_background_dma_buf()request_load()Return immediatelyPoll is_ready()Not ready yetPoll is_ready()Ready!export_slot_to_dma_buf()calculate_push_constants()dup() DMA-BUF fdcallback(dma_buf)

Utility Functions

parse_background_mode()

Parses a mode string (case-insensitive) to BackgroundMode.

auto mode = BackgroundService::parse_background_mode("Cover");  // BackgroundMode::Cover
auto mode = BackgroundService::parse_background_mode("COVER");  // BackgroundMode::Cover
auto mode = BackgroundService::parse_background_mode("invalid"); // BackgroundMode::Contain (default)

background_mode_to_string()

Converts BackgroundMode to lowercase string.

auto str = BackgroundService::background_mode_to_string(BackgroundMode::Tile);  // "tile"

State Queries

bool ready = service.is_ready();           // Vulkan initialized?
bool valid = service.is_vulkan_valid();    // Same as is_ready()
auto mode = service.get_current_mode();    // Current BackgroundMode
auto type = service.get_current_type();    // Current BackgroundType

Usage Example: Desktop Plugin

// In plugin init
auto result = core->funcs->set_background(
    core,
    "/usr/share/backgrounds/wallpaper.jpg",
    "cover"
);

// In render loop
BackgroundDescriptor bg_desc;
if (core->funcs->get_background_descriptor(core, &bg_desc) == 0) {
    if (bg_desc.descriptor_set != VK_NULL_HANDLE) {
        // Background is ready
        vkCmdBindDescriptorSets(cmd, ..., &bg_desc.descriptor_set, ...);

        // Get push constants
        auto push = BackgroundService::calculate_push_constants(
            bg_desc.mode,
            bg_desc.width,
            bg_desc.height,
            display_width,
            display_height
        );

        vkCmdPushConstants(cmd, ..., &push);

        // Draw fullscreen quad
        vkCmdDraw(cmd, 4, 1, 0, 0);
    }
}

  • TextureStreamer: Handles async texture loading and DMA-BUF export
  • ShaderService: Provides shader hot-reload for background rendering
  • PluginManager: Manages Vulkan provider registration and handoff