Skip to main content

Kanvas Technical Internals

This section documents Kanvas as an engineering system rather than as a user-facing Gradle plugin.

It is organized around boundaries, invariants, failure modes, and data flow.

Source-grounded

Technical claims in this section are tied to the public Kanvas implementation. Source links point directly to the files that implement the behavior being described.

Private renderer ABI

Kanvas intentionally integrates with Skiko/Skia implementation details. Those boundaries can move between Compose/Skiko releases, so runtime bytecode validation and explicit transform failures are part of the architecture, not optional debugging extras.

System model​

┌──────────────────────────────────────────────┐
│ Kotlin / Compose application │
└──────────────────────┬───────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Gradle integration │
│ - target task resolution │
│ - runtime provisioning │
│ - javaagent injection │
│ - package integration │
└──────────────────────┬───────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ JVM instrumentation │
│ - Skiko frame boundary │
│ - Skia canvas interception │
│ - renderer audit │
└──────────────────────┬───────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Capture / takeover state │
│ - transform state │
│ - clip / save stack │
│ - paint / image / text lowering │
│ - texture resources │
└──────────────────────┬───────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ JNI command protocol │
│ - bounded frame batches │
│ - explicit texture lifecycle │
└──────────────────────┬───────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Native presentation │
│ Metal / Vulkan / OpenGL / Direct3D 9 │
└──────────────────────────────────────────────┘

Core invariants​

Kanvas is built around a few constraints that show up repeatedly in the source.

1. Do not arm takeover until capture is structurally ready​

Both the frame transformer and canvas transformer must be installed before live takeover is armed.

Otherwise Kanvas could suppress the original renderer before it has enough information to reproduce the frame.

See Capture Pipeline.

2. Capture the final visual boundary, not merely the earliest available callback​

Modern Skiko records Compose work before replaying the completed Picture.

Kanvas therefore prefers the final draw(Canvas) replay boundary over the earlier recording phase.

See Frame Boundary & Picture Replay.

3. Capture and presentation ownership are separate states​

Kanvas can observe renderer activity without necessarily suppressing the original presentation path.

That distinction is used for compatibility testing and failure isolation.

See Presentation Suppression.

4. Treat JVM/native crossing as a validated protocol​

The JNI bridge does not accept arbitrary object graphs. It receives bounded primitive command arrays and explicit texture resources.

See Draw Command Protocol.

5. A successful build is not renderer certification​

Build-system compatibility, Skiko bytecode compatibility, JNI ABI compatibility, backend creation, and real GPU behavior are separate layers.

See Compatibility Strategy.


Integration & Lifecycle​

Gradle Plugin Integration​

How the root-project plugin resolves the target application, maps stable Kanvas lifecycle tasks onto arbitrary Gradle layouts, and injects runtime launch state.

Runtime Provisioning​

How Kanvas resolves a local runtime, explicit runtime artifacts, or a cached source checkout, and why released plugin builds default to the matching vX.Y.Z source tag.

Packaging Integration​

How the runtime is embedded into Compose Desktop app images and why packaged execution cannot rely on development-tree paths.


Capture & Takeover​

JVM Instrumentation​

Java-agent startup, targeted bytecode transformation, runtime-source auditing, and strict renderer ownership.

Frame Boundary & Picture Replay​

Why modern Skiko's final picture replay is the preferred frame boundary and how older recording-mode layouts remain supported as a fallback.

Capture Pipeline​

Thread-local frame state, canvas identity, transform/clip mirroring, physical frame sizing, takeover arming, and strict capture semantics.

Presentation Suppression​

Why capturing a frame is insufficient by itself, and how Kanvas prevents the original renderer from presenting over the takeover surface.


Native Rendering​

JNI & Native Bridge​

The Java/Kotlin-to-C boundary, native renderer handles, attached windows, resource ownership, and backend recovery.

Draw Command Protocol​

The concrete frame batch format, JNI validation rules, texture revisioning, native copies, and presentation-status handling.

Backend Architecture​

Why the renderer-facing contract is backend-neutral and platform graphics APIs remain below JNI.

Swapchain & Presentation Policy​

Surface format, sRGB selection, present mode, image count, extent clamping, and why these decisions are tested outside the live Vulkan path.


Verification & Contracts​

Diagnostics & Renderer Audit​

The difference between environment diagnostics and renderer ownership diagnostics, plus the intended debugging order.

Compatibility Strategy​

Why version reporting, bytecode compatibility, JNI compatibility, and real hardware verification are treated as separate layers.

Design Decisions​

A consolidated explanation of the major architecture choices and their trade-offs.


Primary source entry points​

SubsystemSource
Gradle integrationKanvasPlugin.kt
Plugin DSLKanvasExtension.kt
Agent bootstrapKanvasAgent.java
Frame boundarySkikoFrameTransformer.java
Capture stateComposeCaptureHooks.java
Live takeoverLiveGpuTakeover.java
JNI boundaryjni_display_bridge.c
Swapchain policyswapchain_policy.c
Native build graphCMakeLists.txt