Serhii Yaremych

Serhii Yaremych

@desugar.bsky.social

Android Developer passionate about GPU graphics and Jetpack Compose. Building Imla - an experimental GPU-accelerated blur solution for Android. Exploring OpenGL and rendering algorithms to create smooth visual experiences.

102 followers197 following254 posts

Pinned

Okay, I finally had time to update #Imla, my experimental GPU backdrop blur for Jetpack Compose. The main change is how it renders: instead of a surface per blurred region, the whole UI is captured once per frame into a HardwareBuffer that every blur layer samples on the GPU. 🧵

Serhii Yaremych reposted

Here's a little preview of the new glass effect coming to Haze 2.0 Should be in the next alpha release

Added two little surface-light tricks to the glass: a specular hotspot, the sharp shine that slides around, and a soft fresnel sheen that lights up the edges at grazing angles. Together they make it feel less like a flat pane and more like something rounded you could squish

Serhii Yaremych@desugar.bsky.social

My take on the iOS Siri liquid glass, running on Android. All real-time on the GPU, no video, no assets. Lens refraction, colored ribbons, fresnel rim, specular sheen, and a contact shadow through the glass. The shader is very messy, but I hope I manage to clean it up sometime. RenderEffectX

My take on the iOS Siri liquid glass, running on Android. All real-time on the GPU, no video, no assets. Lens refraction, colored ribbons, fresnel rim, specular sheen, and a contact shadow through the glass. The shader is very messy, but I hope I manage to clean it up sometime. RenderEffectX

Demo app

Serhii Yaremych@desugar.bsky.social

A for-fun Android project: one flat photo in, a tilt-reactive 2.5D depth card out. Depth estimation, subject cutout, background inpaint, per-pixel parallax, all on-device, mostly CPU. Kotlin+Jetpack Compose, tested on a 2019 Xiaomi Mi 9. Thread, with the how-it-works. 🧵

A for-fun Android project: one flat photo in, a tilt-reactive 2.5D depth card out. Depth estimation, subject cutout, background inpaint, per-pixel parallax, all on-device, mostly CPU. Kotlin+Jetpack Compose, tested on a 2019 Xiaomi Mi 9. Thread, with the how-it-works. 🧵

View full thread

Bonus: the depth map read as a relief. The slope at each pixel tells which way that spot faces, that's a normal map. Tilt moves a virtual light: spots facing the light catch a sheen, spots leaning away go dark(Blinn-Phong). Full-frame it read as hammered metal, so it stayed a sandbox.

A for-fun Android project: one flat photo in, a tilt-reactive 2.5D depth card out. Depth estimation, subject cutout, background inpaint, per-pixel parallax, all on-device, mostly CPU. Kotlin+Jetpack Compose, tested on a 2019 Xiaomi Mi 9. Thread, with the how-it-works. 🧵

View full thread

The payoff. Per frame, the inpainted plate is backward-warped by depth, pivoted at the subject's depth: the subject plane holds still, far pixels drift one way, near ones the other. The cutout stays rigid on top, warping a flat person in 3D reads uncanny; moving only the background reads natural.

The loading FX reuse the same depth field. One idea: a front travels through the scene far->near, and each pixel compares its depth to it. Left: a gaussian glow around the current depth slice. Right: a smoothstep dissolve in depth order; the subject mask decides what fades last.

A for-fun Android project: one flat photo in, a tilt-reactive 2.5D depth card out. Depth estimation, subject cutout, background inpaint, per-pixel parallax, all on-device, mostly CPU. Kotlin+Jetpack Compose, tested on a 2019 Xiaomi Mi 9. Thread, with the how-it-works. 🧵

View full thread

Stage 2 — cut out the subject, rebuild the background. BiRefNet gives the mask; a guided filter refines it only in a thin band around the edge, full-frame refining adds halo. The hole is filled with a push-pull pyramid: average known colors down to 1×1, refill on the way up. No model, runs once.

Stage 3 — the depth warp, the heart of the effect. ONE background image; every pixel shifts by (its depth − the subject's) × tilt. A far pixel (0.1) slides 24px, a near one (0.9) a few px the other way, the subject (0.7) exactly 0 pinned. The warp never knows WHAT a pixel is, only how far it is.

A for-fun Android project: one flat photo in, a tilt-reactive 2.5D depth card out. Depth estimation, subject cutout, background inpaint, per-pixel parallax, all on-device, mostly CPU. Kotlin+Jetpack Compose, tested on a 2019 Xiaomi Mi 9. Thread, with the how-it-works. 🧵

The whole thing, coarse. Two small models run once per photo, depth (how far is every pixel) and the subject mask, then classic image processing: refine the mask edge, cut the subject out, fill the hole behind it. Per frame it's just pixels: the warp, plus a glare and rim light riding the tilt.

Stage 1 — depth, and how a model actually runs on a phone. The .tflite is mmap'd straight from APK assets, runs on the GPU delegate (CPU fallback). In/out are direct ByteBuffers, allocated once and reused native memory the runtime reads in place, no JNI copy per run. Left: the real output.

Yep, shaders are cool 😎 even on Android 10 (API 29), no AGSL, no RuntimeShader (API 33+). RenderEffectX runs GLSL straight on live Compose content over the HardwareBuffer path. Shader by Shane 🙏 shadertoy.com/view/4l2XWK

Jorge Castillo@jorgecastillo.dev

You can do cool things with shaders in Compose using RuntimeShader and AGSL (Android Graphics Shading Language). Here is a warping effect shader 😵‍💫 This code runs directly on the GPU using Android's RenderEngine.

Jetpack Compose RuntimeEffectX

Serhii Yaremych@desugar.bsky.social

RenderEffectX is my follow-on from Imla. Imla captures the Compose tree into a HardwareBuffer but presents it through a SurfaceView. This one keeps that pipeline, drops the surface, and draws the result inline. Same idea as RenderEffect, but down to API 29. #AndroidDev

RenderEffectX is my follow-on from Imla. Imla captures the Compose tree into a HardwareBuffer but presents it through a SurfaceView. This one keeps that pipeline, drops the surface, and draws the result inline. Same idea as RenderEffect, but down to API 29. #AndroidDev

View full thread

Three animatable demo scenes, each a fragment shader rebuilt per frame: a music player pushed through a CRT (adapted from a Shadertoy shader), a glitch tearing a real photo with channel split and dropout, and a chromatic ripple that radiates from your touch.

RenderEffectX is my follow-on from Imla. Imla captures the Compose tree into a HardwareBuffer but presents it through a SurfaceView. This one keeps that pipeline, drops the surface, and draws the result inline. Same idea as RenderEffect, but down to API 29. #AndroidDev

View full thread

The pipeline: record the composable into a HardwareBuffer (HardwareRenderer + ImageReader, no glReadPixels), import it zero-copy as an EGLImage on a GL thread, run a GLSL ES fragment shader, wrap the result as a hardware Bitmap (Bitmap.wrapHardwareBuffer, API 29+), draw it inline.

Not a backport of AGSL RuntimeShader (that is API 33+). Just a narrow subset: one Modifier, one GL pass, GLSL ES strings. But it runs per-pixel effects on the composable's own content, with no surface in the way, down to API 29.

RenderEffectX is my follow-on from Imla. Imla captures the Compose tree into a HardwareBuffer but presents it through a SurfaceView. This one keeps that pipeline, drops the surface, and draws the result inline. Same idea as RenderEffect, but down to API 29. #AndroidDev

A SurfaceView is a separate window on its own z-layer. It fights clipping, transforms, alpha and scrolling, and never composites cleanly into a Compose tree. So the question: keep the HardwareBuffer pipeline, but skip the surface entirely?

Serhii Yaremych reposted

Okay, I finally had time to update #Imla, my experimental GPU backdrop blur for Jetpack Compose. The main change is how it renders: instead of a surface per blurred region, the whole UI is captured once per frame into a HardwareBuffer that every blur layer samples on the GPU. 🧵

View full thread

The fun part is the output: instead of rendering into the default framebuffer and eglSwapBuffers, I hand the finished HardwareBuffer straight to SurfaceFlinger via a SurfaceControl transaction (API 29+). I rarely see SurfaceControl used this way in apps.

Okay, I finally had time to update #Imla, my experimental GPU backdrop blur for Jetpack Compose. The main change is how it renders: instead of a surface per blurred region, the whole UI is captured once per frame into a HardwareBuffer that every blur layer samples on the GPU. 🧵

What's actually new: - separable Gaussian blur (replacing my old custom Kawase) - progressive blur: sharp top, blurred bottom, via a brush mask - a modifier DSL: ImlaHost + Modifier.effectGroup() / effectLayer { }

How it works: I render the UI into a HardwareBuffer with HardwareRenderer, then import it on the GL side as a normal GL_TEXTURE_2D (via EGLExt) and blur it. No glReadPixels, and no external texture / samplerExternalOES, so it also works as a render target.

Serhii Yaremych reposted

Swift 6.3 has landed! 🚀 This release brings community-driven Android support into the official toolchain, along with major improvements to C interoperability, the build system, and embedded Swift. Read more: www.swift.org/blog/swift-6... #AndroidDev #iOSDev

Imla update, back after a long break and feeling inspired again. Reworked the renderer and layer composition, made effect layers aware of each other, reduced temporal flicker, and switched to a real progressive blur instead of alpha masking. Lots more planned 😊 #AndroidDev