Docs · C

Embedding the View in your own UI

An application with its own GPU interface -- an iced, egui, Slint or Qt Quick app, a game engine -- shows the View as a texture of its own: the View draws into a GPU texture, the application samples it wherever its toolkit draws an image, and forwards the mouse and keyboard with cadaclysm_view_input as it would for a window. No child window, no copy through the CPU. Two ways, both in cadaclysm_view.h 1.12, in the wgpu view library (cadaclysm_view):

Step 1

On your device

Name your device in a CadaclysmViewDevice. A Vulkan device made by wgpu (iced, egui, Slint and Bevy make theirs that way) sets CADACLYSM_VIEW_DEVICE_WGPU; any other passes the VkPhysicalDeviceFeatures2 chain it was created with, which must enable the features wgpu relies on that your GPU has -- the refusal names any that are missing. Your device, queue and instance stay yours: keep them alive until cadaclysm_view_destroy.

CadaclysmViewDevice d = {};
d.size = sizeof d;
d.api = CADACLYSM_VIEW_API_VULKAN;
d.flags = CADACLYSM_VIEW_DEVICE_WGPU;
d.device = vk_device;              /* VkDevice */
d.queue = vk_queue;                /* VkQueue: the one you submit on */
d.instance = vk_instance;
d.physical_device = vk_physical_device;
d.queue_family = queue_family;
d.queue_index = queue_index;
d.api_version = instance_api_version;
d.instance_extensions = instance_extensions;
d.instance_extension_count = instance_extension_count;
d.extensions = device_extensions;
d.extension_count = device_extension_count;
d.width = 1280;
d.height = 800;
CadaclysmView *view = cadaclysm_view_create_on_device(&d, NULL);

On Metal, api is CADACLYSM_VIEW_API_METAL and device and queue are your id<MTLDevice> and id<MTLCommandQueue>; nothing else is needed. Every call on the view may submit on your queue or wait on it -- cadaclysm_view_destroy waits for it to go idle -- so make them where you submit your own work.

Step 2

Each frame, on your device

Draw, then ask for the texture. A new texture -- the first one, and the one after each cadaclysm_view_resize -- comes back with drawn 0 and a new generation: wrap it into your renderer and make your first use of it (to sampled use) before the next draw. After a draw it is left in sampled use (Vulkan SHADER_READ_ONLY_OPTIMAL); if you use it for anything else, put it back in that state afterwards. Submit your work that samples it after the draw, on the same queue.

if (cadaclysm_view_needs_draw(view)) {
    cadaclysm_view_draw(view);
}
CadaclysmViewTexture t = {};
t.size = sizeof t;
if (cadaclysm_view_texture(view, &t) && t.generation != my_generation) {
    /* t.texture is the VkImage (Metal: the id<MTLTexture>), t.width x t.height,
       R8G8B8A8 holding sRGB-encoded colour: wrap it, transition it to sampled use. */
    my_generation = t.generation;
}

A wgpu application wraps the VkImage with texture_from_raw on its wgpu_hal::vulkan::Device and create_texture_from_hal, then samples it like any texture: in iced through a shader widget's primitive, in egui through egui_wgpu::Renderer::register_native_texture, in Slint through slint::Image::try_from(wgpu::Texture). Qt Quick takes the native texture with QNativeInterface::QSGVulkanTexture::fromNative (or QSGMetalTexture).

Step 3

Shared

Name your GPU by its id, never by name -- Windows lists one GPU twice: the adapter's LUID for Direct3D 12 or 11, deviceUUID and driverUUID for Vulkan (exported memory imports only into the same driver), registryID for Metal. Then adopt every texture of the new set: while nothing is drawn yet, cadaclysm_view_texture answers the slot you ask for in t.slot.

CadaclysmViewShared s = {};
s.size = sizeof s;
s.api = CADACLYSM_VIEW_API_D3D12;            /* or D3D11, VULKAN, METAL */
memcpy(s.adapter_luid, &adapter_luid, 8);
s.width = 1280;
s.height = 800;
CadaclysmView *view = cadaclysm_view_create_shared(&s, NULL);
for (uint32_t i = 0; i < 3; i++) {
    CadaclysmViewTexture t = {};
    t.size = sizeof t;
    t.slot = i;
    cadaclysm_view_texture(view, &t);
    /* t.texture: the slot's NT handle -- OpenSharedHandle (D3D12) or OpenSharedResource1 (D3D11);
       t.ready_fence and t.release_fence: the fences' handles -- OpenSharedHandle / OpenSharedFence. */
}

Each frame, draw and read the texture of the frame drawn: make your queue wait for the ready fence to reach t.ready_value, sample, and signal the release fence to t.release_value once your sampling work is submitted. The View never waits for you: with all three textures held, cadaclysm_view_draw returns false ("no released texture: the host has not released a frame") and the frame stays owed until you release one.

if (cadaclysm_view_needs_draw(view) && cadaclysm_view_draw(view)) {
    CadaclysmViewTexture t = {};
    t.size = sizeof t;
    cadaclysm_view_texture(view, &t);
    queue->Wait(ready_fence, t.ready_value);
    /* ... draw your frame, sampling slots[t.slot] ... */
    queue->Signal(release_fence, t.release_value);
}

Metal works the same with MTLSharedTextureHandle and MTLSharedEventHandle (newSharedTextureWithHandle, newSharedEventWithHandle, encodeWaitForEvent, encodeSignalEvent). On Vulkan the textures come as memory to import (an NT handle on Windows, an fd elsewhere: import a dup()), handed over in SHADER_READ_ONLY_OPTIMAL and released to VK_QUEUE_FAMILY_EXTERNAL, so you acquire each from it before use and release it back after. Set CADACLYSM_VIEW_SHARED_HOST_SEMAPHORES if your device has the external-semaphore extensions: the fences are then timeline semaphores. Without it there are none: cadaclysm_view_draw returns with its frame done, and you release with cadaclysm_view_release(view, t.release_value). A wgpu application on Vulkan is better on its own device (Step 1): wgpu has no queue-family transfers.

Next

Where next

Both ways take the same input, picking, levels of detail and timeline as any view; a view in texture mode may move between threads, one at a time. The view's section of the C API is the reference: CadaclysmViewDevice, CadaclysmViewShared, CadaclysmViewTexture, every refusal.

All docs