membrane 2022-03-31

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

@smith.adriane 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

scratch that, resizing just behaves weird now

@ben.sless 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.

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

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

The viscous view

Window resize is snappy

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

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

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

🙏 1

that would be great

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

👍 1

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 "0.9.31.0-beta"}com.phronemophobic.membrane/skialib-macosx-aarch64 {:mvn/version "0.9.31.0-beta"}com.phronemophobic.membrane/skialib-macosx-x86-64 {:mvn/version "0.9.31.0-beta"} You can also just add all 3 with no issue.

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

the skia toolkit? or any toolkit?

The skia toolkit, it starts with java2d instead

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

Tried it, not found

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
  #?(:clj
     (if-let [tk (resolve 'membrane.skia/toolkit)]
       @tk
       @(requiring-resolve 'membrane.java2d/toolkit))
     :cljs
     nil))
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

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

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

Did some very rough profiling

I have flame graphs to share

key insight is that path->spec dominates

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

@ben.sless 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, https://phronmophobic.github.io/viscous/. 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.

I ran this

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

👍 1

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.

same after clearing cpcache

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

impressive

on-mouse-move 😄

Some dark magic in defui could probably help with precompiling them

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

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

okay, I managed to recreate on a fresh clone

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

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

If I just load the viscous namespace it works fine

How can I tell which toolkit was used?

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

Right, so it lags for skia and not for java2d

ok, I have an idea. one sec

ok, nevermind. I'm confused again

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

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

org.clojure/clojure 1.10.3
  . org.clojure/spec.alpha 0.2.194
  . org.clojure/core.specs.alpha 0.2.56
com.phronemophobic.membrane/skialib-linux-x86-64 0.9.31.0-beta
com.phronemophobic/membrane 0.9.31.8-beta
  . 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
      . pl.edu.icm/JLargeArrays 1.5
        X org.apache.commons/commons-math3 3.5 :older-version
    . techascent/tech.resource 5.04
      . org.clojure/tools.logging 1.1.0
    . com.google.guava/guava 30.1.1-jre
      . com.google.guava/failureaccess 1.0.1
      . com.google.guava/listenablefuture 9999.0-empty-to-avoid-conflict-with-guava
      . com.google.code.findbugs/jsr305 3.0.2
      . org.checkerframework/checker-qual 3.8.0
      . com.google.errorprone/error_prone_annotations 2.5.1
      . com.google.j2objc/j2objc-annotations 1.3
  . org.apache.commons/commons-text 1.9
    . org.apache.commons/commons-lang3 3.11
  . net.n01se/clojure-jna 1.0.0
    X net.java.dev.jna/jna 4.0.0 :older-version
  . net.java.dev.jna/jna 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

com.google.guava/listenablefuture 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

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

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

Looks nicer, though 🙂

and it actually use the new font? nice

yup, Source Code Pro

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

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

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 []
  (ui/on
   :mouse-move
   (fn [pos']
     (println pos)
     (reset! pos pos')
     nil)
   [(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.

1. yes 2. no

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

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)))

I get pretty constant 25ms refresh rate

for the callback getting invoked, not the redraw

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

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]
  (ui/no-events
   (reify
     ui/IOrigin
     (-origin [elem]
       [0 0])
     ui/IBounds
     (-bounds [elem]
       [0 0])
     backend/IDraw
     (draw [this]
       (f)))))

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

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

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