diff --git a/.gitignore b/.gitignore index 750dc9e4..9755af8c 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,8 @@ sled.wasm.map # nix default result symlink (from nix-build) /result + +# JNI build files +src/os/JniSled.class +src/os/sh_tty_sled_JniSled.h +/sled.jar diff --git a/GNUmakefile b/GNUmakefile index 45a022df..18b4ff12 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -171,6 +171,7 @@ include Makefiles/card10.GNUmakefile include Makefiles/esp32.GNUmakefile include Makefiles/ndless.GNUmakefile include Makefiles/emscripten.GNUmakefile +include Makefiles/unix_jni.GNUmakefile # --- All/Cleaning begins here --- diff --git a/Makefiles/sledconf.unix_jni b/Makefiles/sledconf.unix_jni new file mode 100644 index 00000000..a49a1c2f --- /dev/null +++ b/Makefiles/sledconf.unix_jni @@ -0,0 +1,21 @@ +# Example sledconf for building sled as a JNI library + +# Build with `make sled.jar` + +PROJECT := sled + +DEBUG := 0 + +# I've only validated it to work in static form. Dynamic will need extra handling/work in unix_jni.GNUmakefile +STATIC := 1 +PLATFORM := unix_jni + +# Avoid touching CFLAGS too much. + +DEFAULT_OUTMOD := jnibuf +DEFAULT_MODULEDIR := "./modules" +MODULES := $(MODULES_DEFAULT) out_$(DEFAULT_OUTMOD) + +# TODO these are useless now. Set them in src/os/JniSled.java instead. +#MATRIX_X := 256 +#MATRIX_Y := 256 diff --git a/Makefiles/unix_jni.GNUmakefile b/Makefiles/unix_jni.GNUmakefile new file mode 100644 index 00000000..b144ce69 --- /dev/null +++ b/Makefiles/unix_jni.GNUmakefile @@ -0,0 +1,34 @@ +ifeq ($(PLATFORM),unix_jni) + +ifeq (,${JAVA_HOME}) + #$(error Please set JAVA_HOME) + JAVA_HOME := /usr/lib/jvm/java-17-openjdk +endif + +STATIC := 1 + +CFLAGS += -fPIC + +CFLAGS += -I${JAVA_HOME}/include/ +CFLAGS += -I${JAVA_HOME}/include/linux + + +src/os/sh_tty_sled_JniSled.h src/os/JniSled.class: src/os/JniSled.java + javac -h src/os $^ + +sh/tty/sled/JniSled.class: src/os/JniSled.class + mkdir -p $(shell dirname $@) + cp $^ $@ + +JNI_PROJECT := sled + +#OBJECTS += sh_tty_sled_JniSled.h + +#PROJECT := $(JNI_PROJECT).jar + +lib$(PROJECT).so: $(OBJECTS) $(ML_OBJECTS) + $(CC) -shared $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LIBS) $(shell cat $(PLATFORM_LIBS) $(MODULES_STATIC_LIBS) 2>/dev/null || true) + +$(PROJECT).jar: lib$(PROJECT).so sh/tty/sled/JniSled.class + zip $@ $^ +endif diff --git a/src/modules/out_jnibuf.c b/src/modules/out_jnibuf.c new file mode 100644 index 00000000..e5877905 --- /dev/null +++ b/src/modules/out_jnibuf.c @@ -0,0 +1,156 @@ +// Dummy output. +// +// Copyright (c) 2021, fridtjof +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +#include "../os/sh_tty_sled_JniSled.h" + +#include +#include +#include +#include +#include +#include "stdio.h" + +#define PPOS(x, y) (x + (y * matx)) + +static size_t JNI_BUFFER_SIZE; +static jintArray buffers[2]; +static jint* working_buffer; +static jint* currentBufferPtr; +static int currentBuffer = -1; +#define NEXTBUFFER ((currentBuffer + 1) % 2) + +extern JNIEnv *pJniEnv; +#define JniEnv (*pJniEnv) +extern jobject jo_jni_sled; + +static jclass jc_jni_sled; +static jmethodID jm_setCurrentBuffer; +static jfieldID jf_matx; +static jfieldID jf_maty; + +static int matx; +static int maty; + +void print_any_exceptions() { + if (JniEnv->ExceptionCheck(pJniEnv)) { + // this doesn't actually output anything?? + JniEnv->ExceptionDescribe(pJniEnv); + JniEnv->ExceptionClear(pJniEnv); + } +} + +void swapBuffers() { + currentBuffer = NEXTBUFFER; + JniEnv->CallVoidMethodA(pJniEnv, jo_jni_sled, jm_setCurrentBuffer, (jvalue *) &buffers[currentBuffer]); + + print_any_exceptions(); +} + +inline unsigned int rgb2uint(RGB color) { + return (color.blue << 0) | (color.green << 8) | (color.red << 16) | (color.alpha << 24); +} + +inline RGB uint2rgb(unsigned int color) { + RGB col; + col.blue = (color << 0) & 0xFF; + col.green = (color << 8) & 0xFF; + col.red = (color << 16) & 0xFF; + col.alpha = (color << 24) & 0xFF; + + return col; +} + +int init(void) { + jc_jni_sled = JniEnv->GetObjectClass(pJniEnv, jo_jni_sled); + + // determine sizes + jf_matx = JniEnv->GetFieldID(pJniEnv, jc_jni_sled, "matrixX", "I"); + jf_maty = JniEnv->GetFieldID(pJniEnv, jc_jni_sled, "matrixY", "I"); + matx = JniEnv->GetIntField(pJniEnv, jo_jni_sled, jf_matx); + maty = JniEnv->GetIntField(pJniEnv, jo_jni_sled, jf_maty); + JNI_BUFFER_SIZE = matx * maty * sizeof(jint); + + // allocate output buffers + working_buffer = malloc(JNI_BUFFER_SIZE); + buffers[0] = JniEnv->NewIntArray(pJniEnv, matx * maty); + buffers[1] = JniEnv->NewIntArray(pJniEnv, matx * maty); + + // get ready for rendering + jm_setCurrentBuffer = JniEnv->GetMethodID(pJniEnv, jc_jni_sled, "setCurrentBuffer", "([I)V"); + swapBuffers(); + + return 0; +} + +int getx(int _modno) { + return matx; +} +int gety(int _modno) { + return maty; +} + +static inline void bounds_check(int x, int y) { + assert(x >= 0); + assert(y >= 0); + assert(x < matx); + assert(y < maty); +} + +int set(int _modno, int x, int y, RGB color) { + bounds_check(x, y); + working_buffer[PPOS(x, y)] = rgb2uint(color); + return 0; +} + +RGB get(int _modno, int x, int y) { + bounds_check(x, y); + return uint2rgb((unsigned int) working_buffer[PPOS(x, y)]); +} + +int clear(int _modno) { + memset(working_buffer, 0, JNI_BUFFER_SIZE); + return 0; +} + +int render(void) { + // prepare for working on the next frame + currentBufferPtr = JniEnv->GetIntArrayElements(pJniEnv, buffers[NEXTBUFFER], 0 /*ptr bool*/); + memcpy(currentBufferPtr, working_buffer, JNI_BUFFER_SIZE); + JniEnv->ReleaseIntArrayElements(pJniEnv, buffers[NEXTBUFFER], currentBufferPtr, 0); + swapBuffers(); + // notify? prob not needed i think? + return 0; +} + +oscore_time wait_until(int _modno, oscore_time desired_usec) { + // Hey, we can just delegate work to someone else. Yay! +#ifdef CIMODE + return desired_usec; +#else + return timers_wait_until_core(desired_usec); +#endif +} + +void wait_until_break(int _modno) { +#ifndef CIMODE + timers_wait_until_break_core(); +#endif +} + +void deinit(int _modno) { + // TODO actually do stuff here. Sled and/or jvm won't exit properly. + // maybe this needs some more fiddling in the os_ module too? +} diff --git a/src/os/JniSled.java b/src/os/JniSled.java new file mode 100644 index 00000000..830054f6 --- /dev/null +++ b/src/os/JniSled.java @@ -0,0 +1,48 @@ +package sh.tty.sled; + +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; + +public class JniSled implements Runnable { + static { + System.loadLibrary("sled"); + } + + private int matrixX = 256; + private int matrixY = 256; + + private int[] currentBuf; + + public JniSled(int x, int y) { + matrixX = x; + matrixY = y; + } + + public native void main(); + + @Override + public void run() { + main(); + } + + public int getMatrixX() { + return matrixX; + } + + public int getMatrixY() { + return matrixY; + } + + public int[] getCurrentBuffer() { + return currentBuf; + } + + // called from out_jnibuf + // TODO: maybe make this trigger a callback optionally? + // could use that to let the user immediately render, + // "pushing" instead of polling + // this would let us have a variable frame rate dictated by sled. + private void setCurrentBuffer(int[] buf) { + currentBuf = buf; + } +} \ No newline at end of file diff --git a/src/os/os_unix_jni.c b/src/os/os_unix_jni.c new file mode 100644 index 00000000..1614aa0c --- /dev/null +++ b/src/os/os_unix_jni.c @@ -0,0 +1,31 @@ +// os_unix_jni +// you're going to hate this +// +// Copyright (c) 2021, fridtjof +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +#include "os_unix.c" + +#include "sh_tty_sled_JniSled.h" + +JNIEnv *pJniEnv; +jobject jo_jni_sled; + +JNIEXPORT void JNICALL Java_sh_tty_sled_JniSled_main(JNIEnv * env, jobject obj) { + pJniEnv = env; + jo_jni_sled = obj; + + char * args[] = {"sled"}; + sled_main(1,args); +} \ No newline at end of file diff --git a/src/os/os_unix_jni.libs b/src/os/os_unix_jni.libs new file mode 100644 index 00000000..1d2c98f4 --- /dev/null +++ b/src/os/os_unix_jni.libs @@ -0,0 +1 @@ +-lpthread