membrane

Kimo 2022-06-26T03:00:08.902989Z

Hey there, How can I start writing a membrane backend? I tried copying skia.clj into my clojure project, and forking it in a git submodule. Either way, it can't get a working classpath. Missing membraneskia.so and com.phronemophobic.membrane.Skia. Tried running clj -M:deploy in the submodule, thinking it might build Skia, but it's missing a pom.xml.

Kimo 2022-06-27T07:38:14.345149Z

By the way, still interested in figuring out how to build membrane/skia. I'd like to contribute to issues like https://github.com/phronmophobic/membrane/issues/23

phronmophobic 2022-06-27T07:48:43.923139Z

if you're looking to build the native dependencies, you can check out the github workflow, https://github.com/phronmophobic/membrane/blob/master/.github/workflows/build.yml#L77

phronmophobic 2022-06-27T07:49:33.394219Z

I think the issue with vsync is essentially a glfw issue (on mac osx)

phronmophobic 2022-06-27T07:56:09.598479Z

on linux, it might be as simple as: • setting the glfwswapinterval to 1 https://github.com/phronmophobic/membrane/blob/master/src/membrane/skia.clj#L1763, https://www.glfw.org/docs/latest/group__context.html#ga6d4e0cdf151b5e579bd67f13202994ed • use glfwPollEvents to wait for events instead of glfwWaitEventsTimeout , see https://github.com/phronmophobic/membrane/blob/master/src/membrane/skia.clj#L1986 • for normal UI that doesn't normally change every frame, this will unnecessarily consume a bunch of cpu, so it's probably a good idea to only redraw if the view has changed since the last frame.

Kimo 2022-06-27T07:56:23.784859Z

Nice, I'll check it out

phronmophobic 2022-06-27T07:57:48.350909Z

I'd be open to a pull request that makes this configurable as long as the defaults "just work" on linux and mac

👍 1
phronmophobic 2022-06-26T03:23:58.700769Z

Hi @kimo741,

phronmophobic 2022-06-26T03:24:26.569089Z

writing a membrane backend isn't really documented, but I can try to help throw together some resources

phronmophobic 2022-06-26T03:24:44.286599Z

what kind of backend are you trying to make?

phronmophobic 2022-06-26T03:25:03.648439Z

as a template, I would start with the membrane.java2d namespace since it doesn't have any external dependencies

Kimo 2022-06-26T11:23:46.745369Z

Thanks, good idea. I just got a local java2d fork working. Hoping to experiment with a game loop.

Kimo 2022-06-26T12:11:00.534279Z

Not sure if I need a game loop. I like the idea of writing an app that could work on any backend. But I'm not sure how to get an animation to run while no input events are happening. Maybe just changing to (glfw-call void glfwWaitEventsTimeout (double 1/60)) would be enough.

Kimo 2022-06-26T14:28:34.279519Z

Oh! I just found the new skia repaint function. Maybe that's what I needed.

phronmophobic 2022-06-26T17:49:27.802119Z

I wouldn't mix and match functions between membrane.skia and membrane.java2d. When you run a ui with membrane.java2d/run, it returns a map of window info that includes a repaint function and the jframe window. (see https://github.com/phronmophobic/membrane/blob/master/src/membrane/java2d.clj#L946) In general, I probably wouldn't recommend calling any of the internal membrane.skia functions unless you're familiar with skia and native programming. The native calls in membrane.skia are a little trickier to use since incorrect usage can cause memory corruption or hard crash the jvm.

phronmophobic 2022-06-26T17:51:44.208849Z

Another interesting option might be to create a http://www.quil.info/ based backend.

phronmophobic 2022-06-26T17:53:02.173179Z

Or depending on what you're looking for, you might not need membrane at all, but the benefit of creating a quil based backend is that you get a bunch of membrane tools and components "for free".

Kimo 2022-06-26T21:56:11.994859Z

Yeah, I was thinking about quil. I would like to implement more components for drawing. I'm also wondering if membrane could abstract over something like https://github.com/oakes/play-cljc. Making progress on the game loop. It's amazing how concise membrane can be:

(ns kimok.membrane-game
  (:require [clojure.core.async :refer [<! timeout go-loop]]
            [tick.core :as time]
	        [membrane.ui   :as ui]
            [membrane.skia :as skia]))

(def state (atom {:frame-number 0
                  :last-time (time/now)}))

(def window (skia/run #(ui/label @state)))

(defn on-frame []
  (let [now (time/now)
        {:keys [frame-number last-time]} @state
        dt (time/micros (time/between last-time now))]
    (reset! state {:frame-number (inc frame-number)
                   :fps (int (/ 1000000 dt))
                   :last-time now
                   :dt (float (/ dt 1000000))})))

(go-loop []
  (<! (timeout 1000/60))
  (on-frame)
  ((::skia/repaint window))
  (recur))
Not sure if the metrics are accurate to what's really happening. It would be nice if the backend could pass them somehow. I tried the same with java2d on 0.9.31.5-beta, but it still seems to wait on input events.

👀 1
phronmophobic 2022-06-26T22:00:28.807349Z

@kimo741 , I came across a stackoverflow post recently that said you might need to call https://docs.oracle.com/javase/7/docs/api/java/awt/Toolkit.html#sync() to get the screen to repaint on linux

phronmophobic 2022-06-26T22:00:58.384369Z

oh wait, you're using the skia backend?

phronmophobic 2022-06-26T22:02:42.000939Z

I'm sure you could extend support for membrane to play-clj.

Kimo 2022-06-26T22:02:43.268129Z

Yeah, it does crash the repl here and there but this sketch seems stable.

phronmophobic 2022-06-26T22:04:04.630719Z

when you say crash, do you mean "throw an exception and close the window" or jvm dies?

Kimo 2022-06-26T22:04:22.354589Z

the latter

phronmophobic 2022-06-26T22:05:12.781029Z

I've heard there's some issues on linux, but I've been unable to reproduce them

phronmophobic 2022-06-26T22:05:52.487229Z

would you be able to share what you're using for clojure -Stree?

Kimo 2022-06-26T22:18:22.402069Z

org.clojure/clojure 1.10.3 . org.clojure/spec.alpha 0.2.194 . org.clojure/core.specs.alpha 0.2.56

phronmophobic 2022-06-26T22:19:41.544089Z

I'm more interested in the which membrane version and which skialib you're using

Kimo 2022-06-26T22:20:41.840469Z

oh yeah, haha

Kimo 2022-06-26T22:22:45.435749Z

org.clojure/clojure 1.11.1
  . org.clojure/spec.alpha 0.3.218
  . org.clojure/core.specs.alpha 0.2.62
net.sekao/odoyle-rules 0.11.0
  . expound/expound 0.7.2
com.phronemophobic.membrane/skialib-linux-x86-64 0.9.31.0-beta
com.phronemophobic.membrane/skialib-macosx-aarch64 0.9.31.0-beta
com.phronemophobic.membrane/skialib-macosx-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
tick/tick 0.5.0-RC6
  . com.widdindustries/cljc.java-time 0.1.21
    . com.widdindustries/cljs.java-time 0.1.20
  . cljsjs/js-joda-timezone 2.2.0-0
  . cljsjs/js-joda-locale-en-us 3.1.1-1
  . com.widdindustries/time-literals 0.1.10

phronmophobic 2022-06-26T22:23:35.750249Z

when it crashed, did you switch between java2d and skia without restarting the repl?

phronmophobic 2022-06-26T22:27:41.917579Z

the two backends are not friendly with eachother

Kimo 2022-06-26T22:29:06.711589Z

Actually, swiching between them or running both at once seems to work pretty well here.

phronmophobic 2022-06-26T22:30:04.718289Z

I think that might work for a bit, but my guess is that might the cause leading to eventual crashes

Kimo 2022-06-26T22:30:12.503919Z

Not sure if I can pinpoint the cause, but I'll let you know if I can reproduce something.

phronmophobic 2022-06-26T22:31:14.123119Z

this works for me on swing. I'm wondering if adding a call to .sync would help the java2d issues on linux Something like:

(ns kimo
  (:require [clojure.core.async :refer [<! timeout go-loop]
             :as async]
            [tick.core :as time]
	    [membrane.ui   :as ui]
            [membrane.java2d :as backend])
  (:import java.awt.Toolkit))

(def default-toolkit (Toolkit/getDefaultToolkit))

(def state (atom {:frame-number 0
                  :last-time (time/now)}))



(defn on-frame [now]
  (let [{:keys [frame-number last-time]} @state
        dt (time/micros (time/between last-time now))]
    (reset! state {:frame-number (inc frame-number)
                   :fps (int (/ 1000000 dt))
                   :last-time now
                   :dt (float (/ dt 1000000))})))

(defonce running? (atom false))

(comment

  (def window (backend/run #(ui/label @state)))

  (async/thread
    (reset! running? true)
    (loop []
      (let [t (time/now)]

        (on-frame t)
        (async/<!! (timeout (- 1000/80
                               (/ (time/micros
                                   (time/between t (time/now)))
                                  1000))))
        ((::backend/repaint window))
        (.sync ^Toolkit default-toolkit))

      (when @running?
        (recur))))
  ,
  )

Kimo 2022-06-26T22:39:11.202179Z

Thanks, I'll study this. Works so far.

🎉 1
phronmophobic 2022-06-26T22:39:48.976739Z

can you confirm that the call to .sync is the fix?

phronmophobic 2022-06-26T22:40:17.323919Z

if that's the case, I'll probably make an update to the java2d backend to call .sync on repaint

Kimo 2022-06-26T22:42:17.397439Z

Yeah, it is.

1