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.
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.
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.
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.
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.
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
| Subsystem | Source |
|---|---|
| Gradle integration | KanvasPlugin.kt |
| Plugin DSL | KanvasExtension.kt |
| Agent bootstrap | KanvasAgent.java |
| Frame boundary | SkikoFrameTransformer.java |
| Capture state | ComposeCaptureHooks.java |
| Live takeover | LiveGpuTakeover.java |
| JNI boundary | jni_display_bridge.c |
| Swapchain policy | swapchain_policy.c |
| Native build graph | CMakeLists.txt |