Fork me on GitHub
#membrane
<
2022-06-26
>
Kimo03:06:08

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.

phronmophobic03:06:26

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

phronmophobic03:06:44

what kind of backend are you trying to make?

phronmophobic03:06:03

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

Kimo11:06:46

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

Kimo12:06:00

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.

Kimo14:06:34

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

phronmophobic17:06:27

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.

phronmophobic17:06:44

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

phronmophobic17:06:02

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".

Kimo21:06:11

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.

phronmophobic22:06:28

@U02J388JDEG , 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

phronmophobic22:06:58

oh wait, you're using the skia backend?

phronmophobic22:06:42

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

Kimo22:06:43

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

phronmophobic22:06:04

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

Kimo22:06:22

the latter

phronmophobic22:06:12

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

phronmophobic22:06:52

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

Kimo22:06:22

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

phronmophobic22:06:41

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

Kimo22:06:41

oh yeah, haha

Kimo22:06:45

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

phronmophobic22:06:35

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

phronmophobic22:06:41

the two backends are not friendly with eachother

Kimo22:06:06

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

phronmophobic22:06:04

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

Kimo22:06:12

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

phronmophobic22:06:14

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

Kimo22:06:11

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

🎉 1
phronmophobic22:06:48

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

phronmophobic22:06:17

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

Kimo22:06:17

Yeah, it is.

gratitude 1
Kimo07:06:14

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

phronmophobic07:06:43

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

phronmophobic07:06:33

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

phronmophobic07:06:09

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.

Kimo07:06:23

Nice, I'll check it out

phronmophobic07:06:48

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

👍 1