Fork me on GitHub
Ben Sless12:03:55

Trying to play with viscous locally, how can I bring skia in as a dependency?


Skia isn't required. If Skia isn't available, it should use java swing to display everything. If you do want to use the skialib backend, you just need the skialib dependency that matches your OS and architecture. One of: • com.phronemophobic.membrane/skialib-linux-x86-64 {:mvn/version ""}com.phronemophobic.membrane/skialib-macosx-aarch64 {:mvn/version ""}com.phronemophobic.membrane/skialib-macosx-x86-64 {:mvn/version ""} You can also just add all 3 with no issue.

Ben Sless05:04:50

I have them on the class path but the toolkit isn't loaded


the skia toolkit? or any toolkit?

Ben Sless05:04:13

The skia toolkit, it starts with java2d instead


can you load (require 'membrane.skia) at the repl?

Ben Sless05:04:52

Tried it, not found

Ben Sless05:04:26

I can try to repro exactly when I'm at the computer and create a repository


ok, I think I found the issue


(defonce toolkit
     (if-let [tk (resolve 'membrane.skia/toolkit)]
       @(requiring-resolve 'membrane.java2d/toolkit))
pretty sure the resolve for skia should be requiring-resolve instead of resolve 😞


maybe I did that on purpose


so that it only uses skia if that namespace has already been loaded

Ben Sless05:04:01

Aha, so requiring resolve should fix it?


right but that would almost always use skia


because I think the skia namespace loads even if skialib isn't available


I think I did this as a hack because I didn't expect anyone else besides me to use the skia version


and I like to default to skia

Ben Sless05:04:27

Java2d looks terrible on Ubuntu 😄

😆 1

generally, loading a namespace isn't a big deal, but some UI libraries (cough AWT, GLFW) expect to be the only UI library to be running


hmmm, the issue is that I cheated and made monospaced, cell-width and cell-height top level defs. The right way to do it is to pass those values as arguments.


I can fix this, but I think my brain might be a little too tired to fix it tonight. I should be able to fix it tomorrow.


if you're running the code base locally, you can work around it by changing the toolkit definition in viscous.cljc to:

(require 'membrane.skia)
(defonce toolkit membrane.skia/toolkit)


or just requiring membrane.skia anywhere before the toolkit in viscous.cljc gets initialized


@UK0810AQ2 fyi, just updated viscous on github. As long as you require membrane.skia before opening the inspector, it should use membrane.skia now.

Ben Sless11:05:41

@U7RJTCH6J I'm happy to report this works and now I have a new fun one for you: try inspecting some pathological data structure like a tools.analyzer output then resize the window, program becomes functionally unresponsive

Ben Sless11:05:55

scratch that, resizing just behaves weird now


@UK0810AQ2 Interesting. Just tried some tools.analyzer output and didn't notice anything weird. Is there a repro I can follow? Big hairy data structures like tools.analyzer output are the kinds of data that viscous was made for so if that use case isn't working well, I'd like to fix it! The only datastructures I would consider truly pathological are infinitely empty sequences like (filter (constantly false) (range)) since there's no way to inspect it without calling first which loops forever.

Ben Sless17:05:42

It's every data structure, actually. Resize is just laggy


when you say resize, is that resizing the window or the viscous view?

Ben Sless17:05:34

The viscous view

Ben Sless17:05:50

Window resize is snappy


Is it slow for both skia and java2d or just skia backend?

Ben Sless17:05:37

Only checked skia


And do you recall if it was slow when shrinking and growing?


or possibly just growing?


and just to double check, do you know if the data structures you were inspecting were fully realized?


Were any other operations laggy or just grow/shrink? • hovering over child data structures • navigating • popping • etc

Ben Sless05:05:55

I think they were I can try to record a demo today

🙏 1

that would be great

Ben Sless05:05:57

You'll probably be sleeping by the time I record it

👍 1
Ben Sless08:05:48

Did some very rough profiling

Ben Sless08:05:32

I have flame graphs to share

Ben Sless08:05:16

key insight is that path->spec dominates

Ben Sless09:05:29

paths keep getting recompiled. Can work be front-loaded?


@UK0810AQ2 thanks for the extra info! 🙏 Yep, that's no bueno. I'm pretty surprised that it's showing path->spec as eating so many cycles. I'm sure path->spec could be more efficient, but that same code runs fine on my machine and in the browser, How are you starting the inspector? Are you specifying your own version of specter? Is anything being printed to stdout? I think I remember you saying you were running on linux? I'll see if I can reproduce that locally. I'm pretty puzzled by the symptoms so far. My initial guess is that some part of the event handling is running several times more often than it should be.

Ben Sless18:05:31

I ran this

(require 'membrane.skia)
(defonce toolkit membrane.skia/toolkit)
Then the example from README

👍 1
Ben Sless18:05:02

Let me try to clear my cpcache


Let me double check running the example from the README. That's not how I usually run it.

Ben Sless18:05:20

same after clearing cpcache

Ben Sless18:05:05

anyway, I find the paths get recompiled constantly and it hits a dynamic var thing in specter which incurs a clojure.core/walk which takes forever and a half and generates more garbage than all social media combined

Ben Sless18:05:51

on-mouse-move 😄

Ben Sless18:05:26

Some dark magic in defui could probably help with precompiling them

Ben Sless18:05:46

in the end you're just emitting specter navigators, yes?


Right, so the question is why this is slow for you, but runs well enough in clj and cljs locally

Ben Sless18:05:14

Let me be paranoid for a second, I'll clone viscous again


I'm not assuming that you're doing anything weird. I'm assuming membrane is doing something weird on linux


like calling on-mouse-move several times for each event for some reason


or having on-mouse-move trigger a redraw which triggers an on-mouse-move or something

Ben Sless18:05:25

okay, I managed to recreate on a fresh clone

Ben Sless18:05:48

If I do this first, then load an example, it's laggy

(require 'membrane.skia)
(defonce toolkit membrane.skia/toolkit)

Ben Sless18:05:58

If I just load the viscous namespace it works fine

Ben Sless18:05:18

How can I tell which toolkit was used?


it should say if you print com.phronemophobic.viscous/toolkit

Ben Sless18:05:38

Right, so it lags for skia and not for java2d


ok, I have an idea. one sec


ok, nevermind. I'm confused again

Ben Sless18:05:58

What sort of profiling information can I get you that'll help?

Ben Sless18:05:23

I can produce everything from JFR recording to GC and JIT logs


I'd love to see the clojure -Stree for the project


just to make sure that we're using the same versions of things

Ben Sless18:05:39

org.clojure/clojure 1.10.3
  . org.clojure/spec.alpha 0.2.194
  . org.clojure/core.specs.alpha 0.2.56
  . cnuernber/dtype-next 8.041
    . org.ow2.asm/asm 9.0
    . insn/insn 0.5.2
      . org.ow2.asm/asm 9.0
    . camel-snake-kebab/camel-snake-kebab 0.4.2
    . it.unimi.dsi/fastutil 8.2.1
    . org.xerial.larray/larray-mmap 0.4.1
      . org.xerial.larray/larray-buffer 0.4.1
    . org.apache.commons/commons-math3 3.6.1
    . org.roaringbitmap/RoaringBitmap 0.9.0
      . org.roaringbitmap/shims 0.9.0
    . com.github.wendykierp/JTransforms 3.1
      X org.apache.commons/commons-math3 3.5 :older-version
      . 1.5
        X org.apache.commons/commons-math3 3.5 :older-version
    . techascent/tech.resource 5.04
      . org.clojure/tools.logging 1.1.0
    . 30.1.1-jre
      . 1.0.1
      . 9999.0-empty-to-avoid-conflict-with-guava
      . 3.0.2
      . org.checkerframework/checker-qual 3.8.0
      . 2.5.1
      . 1.3
  . org.apache.commons/commons-text 1.9
    . org.apache.commons/commons-lang3 3.11
  . net.n01se/clojure-jna 1.0.0
    X 4.0.0 :older-version
  . 5.10.0
  . com.rpl/specter 1.1.3
    . riddley/riddley 0.1.12
  . org.clojure/core.async 1.4.627
    . org.clojure/tools.analyzer.jvm 1.2.0
      . org.clojure/tools.analyzer 1.1.0
      . org.clojure/core.memoize 1.0.236
        . org.clojure/core.cache 1.0.207
          . org.clojure/data.priority-map 1.0.0
      X org.ow2.asm/asm 5.2 :older-version
      . org.clojure/tools.reader 1.3.2
Now you'll tell me I'm using a bad version of skia linux


nope, that's the latest one


I guess it might still be bad, but if it is, there's not a better one yet

phronmophobic18:05:41 9999.0-empty-to-avoid-conflict-with-guava


So one of the things that is different is that locally, I use the monospaced Menlo font, but it should fall back to the system's monospaced if that's not available

Ben Sless18:05:22

I doubt that's it


however, the way to load the default monospaced font differs between java2d and skia. there's a way to do that correctly, but viscous currently does it the wrong way

Ben Sless18:05:14

That wasn't it. Tried with a font I have installed, same

Ben Sless18:05:31

Looks nicer, though 🙂


and it actually use the new font? nice

Ben Sless18:05:51

yup, Source Code Pro

Ben Sless18:05:59

If it's going to be slow, it can at least be nice to look at

😆 1

the spacing might be wrong since the sizes are hard-coded when the namespaces is loaded


fonts are annoying


ok, so there's some other difference between java2d and skia that is slowing things down a lot

Ben Sless18:05:50

There's a reason I stay away from UI/UX. You stumble in dark, bang little toe against couch. You had an experience. You're welcome

Ben Sless18:05:21

Yes, I can try to profile a bit, shake the Java tree, see what beans fall out


so if I could test this locally, I would just run something like:

(require '[membrane.ui :as ui])
(require '[membrane.skia :as backend]
 ;'[membrane.java2d :as backend]

(def pos (atom nil))
(defn test-mouse-move []
   (fn [pos']
     (println pos)
     (reset! pos pos')
   [(ui/with-style ::ui/style-stroke
      (ui/rectangle 400 400))
    (ui/label @pos)]))

(backend/run #'test-mouse-move)


and see if it's doing anything weird


see if 1) that minimal app is slow and 2) if it's printing out the same position multiple times.

Ben Sless19:05:29

1. yes 2. no


ok, so this isn't calling path->spec anywhere so the problem is elsewhere

Ben Sless19:05:33

I also added

(def ticker (java.util.concurrent.atomic.AtomicLong. 0))
(defn tick!
  ^long [^java.util.concurrent.atomic.AtomicLong n]
  (let [m (.get n)
        l (System/currentTimeMillis)]
    (.set n l)
    (- l m)))

Ben Sless19:05:46

I get pretty constant 25ms refresh rate

Ben Sless19:05:00

for the callback getting invoked, not the redraw

Ben Sless19:05:13

Is it capped at 40fps?


right now, it only redraws when there's an event. there's other ways to setup, but I think that's what it should be doing now


40 fps isn't impressive, but it shouldn't be unusable

Ben Sless19:05:22

It's getting a bit late so I'm tapping out for today, I'll profile it tomorrow in more detail and let you know if I found something weird


ok, thanks again!


fyi, I tried to reproduce locally, but it seemed to run fine. fwiw, I'm running linux inside of virtualbox so it might not be the best environment to reproduce.


Not sure how interested you are in falling down this rabbit hole with me, so obviously, feel free to ignore. To recap, the main symptom we have is sluggish UI and key difference we have is seems to work except on some linuxs with skia. I know sluggishness is generally debugged with profiling, but there's almost no platform specific code inside of membrane. Most of the platform specific code is inside of the skia graphics library and the glfw windowing library. At this point, I'm assuming that the skia and glfw libraries are correct and that the problem is that I've written some non-portable code that uses those libraries incorrectly. I think it's much more likely that the problem is with the glfw integration rather than the skia integration. If the bug is an integration issue, profiling may or may not be that helpful. > I get pretty constant 25ms refresh rate This is pretty weird since I think the events happen on the same thread as the repaint (membrane doesn't particularly care, but I know mac OS does and I think it's the same on linux). So if events were triggering every 25ms, you would expect the screen to update regularly as well.


Another theory is that the screen isn't being redrawn for some reason. This snippet should print on every redraw:

(require '[membrane.ui :as ui])
(require '[membrane.skia :as backend]
 ;'[membrane.java2d :as backend]

(def pos (atom nil))

(defn on-draw [f]
     (-origin [elem]
       [0 0])
     (-bounds [elem]
       [0 0])
     (draw [this]

(defn test-mouse-move []
   (fn [pos']
     #_(println pos')
     (reset! pos pos')
   [(ui/with-style ::ui/style-stroke
      (ui/rectangle 400 400))
    (on-draw #(println "draw!"))
    (ui/label @pos)]))

(backend/run #'test-mouse-move)