Slide 1

Slide 1 text

Eric Butler @codebutler Droidcon San Francisco November 2017 FUN WITH NATIVE CODE

Slide 2

Slide 2 text

How This All Started

Slide 3

Slide 3 text

Problems with Existing Apps Complicated Setup Broken Controller Support Touchscreen Required Ugly UI Intrusive Ads Bad Performance etc…

Slide 4

Slide 4 text

Designing My Dream App Androidy TV Interface Works with game controllers Loads games from cloud Supports all retro consoles
 Leanback Support Library Android Input APIs Google Drive/Dropbox SDKs ??

Slide 5

Slide 5 text

open source retro video game emulator library Google Search I’m Feeling Unlucky

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

• Single API that abstracts many retro console emulators. • Specifically designed to make writing frontends easy. • Free, open-source. • Well documented API. • Lightweight cross-platform C library. • Perfect?

Slide 8

Slide 8 text

• Single API that abstracts many retro console emulators • Specifically designed to make writing frontends easy. • Free, open-source. • Well documented API. • Lightweight cross-platform C library. • Perfect? h no…

Slide 9

Slide 9 text

Foreign Function Interface ? FFI High-level language Low-level language

Slide 10

Slide 10 text

Foreign Function Interface Python CTypes C# P/Invoke Java Native Interface

Slide 11

Slide 11 text

JVM CODE

Slide 12

Slide 12 text

JVM CODE NATIVE CODE Image: http://www.cakesandcomics.com/#/stranger-things/

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

$ cat test.c #include void say_hello(const char* name) { printf("Hello %s!\n", name); } 
 $ clang -shared -o libtest.dylib test.c A simple C shared library

Slide 15

Slide 15 text

void say_hello(const char* name); $ cat test.py from ctypes import * lib = cdll.LoadLibrary("libtest.dylib") lib.say_hello("Droidcon") $ python test.py Hello Droidcon! Calling our native function with Python CTypes

Slide 16

Slide 16 text

void say_hello(const char* name); Calling our native function with C# PInvoke $ cat Test.cs using System.Runtime.InteropServices; static class LibTest { [DllImport("libtest.dylib")] public static extern void say_hello(string name); } LibTest.say_hello("Droidcon") $ csharp Test.cs Hello Droidcon!

Slide 17

Slide 17 text

void say_hello(const char* name); Calling our native function with Kotlin JNI…? $ cat test.kts
 external fun say_hello(name: String) System.loadLibrary("test") say_hello("Droidcon") $ kotlin -script test.kts java.lang.UnsatisfiedLinkError: Test.say_hello(Ljava/lang/String;)V at Test.say_hello(Native Method) at Test.(Unknown Source)

Slide 18

Slide 18 text

void say_hello(const char* name);

Slide 19

Slide 19 text

A simple C shared library $ cat test.c Z1Z1Z1Z1 
 $ clang -shared -o libtest.dylib test.c Z5Z5Z5Z5 #include Z2Z2Z2Z2 void say_hello(const char* name) {Z3Z3Z3Z3 printf("Hello %s!\n", name); }Z4Z4Z4Z4

Slide 20

Slide 20 text

$ cat test.cpp Z1Z1Z1Z1
 #include Z2Z2Z2Z2 #include extern "C" { void say_hello(const char* name) {Z3Z3Z3Z3 printf("Hello %s!\n", name); }Z4Z4Z4Z4 JNIEXPORT void JNICALL Java_Test_sayHello( JNIEnv *env, jclass cls, jstring name) { const char *nativeHello = env->GetStringUTFChars(name, JNI_FALSE); say_hello(nativeHello); env->ReleaseStringUTFChars(name, nativeHello); } } $ clang++ -shared -o libtest.dylib \ -I${JAVA_HOME}/include -I${JAVA_HOME}/include/darwin test.cpp Z5Z5Z5Z5 A (not so) simple JNI-compatible C++ shared library

Slide 21

Slide 21 text

JNIEXPORT void JNICALL Java_Test_sayHello( JNIEnv *env, jclass cls, jstring name); Calling our native function with Kotlin JNI $ cat test.kts
 external fun sayHello(name: String) System.loadLibrary("test") sayHello("Droidcon") $ kotlin -script test.kts Hello Droidcon!

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

call native code from java without jni Google Search I’m Feeling Hopeless

Slide 24

Slide 24 text

JNA: Java Native Access

Slide 25

Slide 25 text

“JNA provides Java programs easy access to native shared libraries without writing anything but Java code - no JNI or native code is required.
 This functionality is comparable to C#’s PInvoke and Python's ctypes.” github.com/java-native-access/jna JNA: Java Native Access Kotlin Kotlin

Slide 26

Slide 26 text

JNA Features Functions Methods C JVM Data Types Primitives Structs Classes Function Pointers Callback Interfaces

Slide 27

Slide 27 text

package com.sun.jna Library Native Structure Callback Pointer

Slide 28

Slide 28 text

Base type for interfaces containing native library function definitions. interface Library interface TestLibrary : Library { fun some_method() }

Slide 29

Slide 29 text

Static methods for loading native libraries, JNA configuration, and other utility tasks. final class Native val lib = Native.loadLibrary( "libName", TestLibrary::class.java) lib.some_method()

Slide 30

Slide 30 text

Base type for classes that are automatically mapped to/from native structs. abstract class Structure class example_struct : Structure() { } @JvmField var field1: String? = null @JvmField var field2: String? = null override fun getFieldOrder() = listOf("field1", “field2")

Slide 31

Slide 31 text

Base type for callback interfaces to be used as function pointers. Requires one public method. interface Callback interface my_callback : Callback { fun invoke() } interface TestLibrary : Library { fun set_callback(cb: my_callback) } val lib = Native.loadLibrary(/* … */) lib.set_callback(object : my_callback { override fun invoke() { /* TODO */ } })

Slide 32

Slide 32 text

Represents a native pointer, used for accessing arbitrary memory. class Pointer interface TestLibrary : Library { fun set_memory(pointer: Pointer) fun get_memory(): Pointer } interface my_callback : Callback { fun invoke(data: Pointer) }

Slide 33

Slide 33 text

Represents a native pointer, used for accessing arbitrary memory. class Pointer Pointer#getString(offset) Pointer#getInt(offset) Pointer#getLong(offset) Pointer#getByteArray(offset, length) Pointer#read(offset, buffer, index, length) // etc…

Slide 34

Slide 34 text

JNA Performance https://github.com/java-native-access/jna/blob/master/www/FrequentlyAskedQuestions.md • Call overhead up to 10x slower

Slide 35

Slide 35 text

JNA Performance https://github.com/java-native-access/jna/blob/master/www/FrequentlyAskedQuestions.md • Call overhead up to 10x slower • Ease of programing outweighs difference • Build, measure, optimize • Incrementally rewrite using JNI if needed

Slide 36

Slide 36 text

$ cat test.c #include void say_hello(const char* name) { printf("Hello %s!\n", name); } 
 $ clang -shared -o libtest.dylib test.c A simple C shared library, one last time

Slide 37

Slide 37 text

void say_hello(const char* name) Calling our function from Kotlin with JNA!

Slide 38

Slide 38 text

Calling our function from Kotlin with JNI…? external fun say_hello(name: String) System.loadLibrary("test") say_hello("Droidcon") void say_hello(const char* name)

Slide 39

Slide 39 text

Calling our function from Kotlin with JNA! import com.sun.jna.* 
 external fun say_hello(name: String) System.loadLibrary("test") say_hello("Droidcon") void say_hello(const char* name)

Slide 40

Slide 40 text

Calling our function from Kotlin with JNA! import com.sun.jna.* external fun say_hello(name: String) System.loadLibrary("test") say_hello("Droidcon") void say_hello(const char* name) interface TestLib: Library { }

Slide 41

Slide 41 text

Calling our function from Kotlin with JNA! import com.sun.jna.* fun say_hello(name: String) System.loadLibrary("test") say_hello("Droidcon") void say_hello(const char* name) interface TestLib: Library { }

Slide 42

Slide 42 text

Calling our function from Kotlin with JNA! import com.sun.jna.* fun say_hello(name: String) val lib = Native.loadLibrary("test", TestLib::class.java)ZZZ say_hello("Droidcon") void say_hello(const char* name) interface TestLib: Library { }

Slide 43

Slide 43 text

Calling our function from Kotlin with JNA! import com.sun.jna.* fun say_hello(name: String) val lib = Native.loadLibrary("test", TestLib::class.java)ZZZ lib.say_hello("Droidcon") void say_hello(const char* name) interface TestLib: Library { }

Slide 44

Slide 44 text

void say_hello(const char* name) Calling our function from Kotlin with JNA! $ cat test-jna.kts 
 import com.sun.jna.* interface TestLib: Library { fun say_hello(name: String) } val lib = Native.loadLibrary("test", TestLib::class.java) lib.say_hello("Droidcon") $ mvn dependency:get \ -Dartifact=net.java.dev.jna:jna:4.5.0 \ -Ddest=jna.jar $ kotlinc -cp jna.jar -script test-jna.kts Hello Droidcon!

Slide 45

Slide 45 text

My Dream App Androidy TV Interface Works with game controllers Loads games from cloud Supports all retro consoles
 Leanback Support Library Android Input APIs Google Drive/Dropbox SDKs ??

Slide 46

Slide 46 text

My Dream App Androidy TV Interface Works with game controllers Loads games from cloud Supports all retro consoles
 Leanback Support Library Android Input APIs Google Drive/Dropbox SDKs libretro + JNA

Slide 47

Slide 47 text

Learning Libretro Documentation C Headers Core Implementations Existing Frontends

Slide 48

Slide 48 text

Libretro Documentation bit.ly/libretro-pdf doc.libretro.com/specs/api

Slide 49

Slide 49 text

libretro.h • Single header file • 2178 lines • Mostly documentation comments! • 25 functions, 31 structs

Slide 50

Slide 50 text

Libretro Cores • “core”: An emulator that has been ported to the common libretro API. • Useful to see which APIs are actually needed. • https://github.com/libretro/snes9x/blob/master/libretro/libretro.cpp • https://github.com/libretro/Genesis-Plus-GX/blob/master/libretro/libretro.c

Slide 51

Slide 51 text

Libretro: Existing Frontends RetroArch C++ retroarch.com Phoenix Qt/C++ phoenix.vg GNOME Games C/Vala wiki.gnome.org/ Apps/Games

Slide 52

Slide 52 text

Libretro Architecture Init Callbacks Load Game ROM Run Game Loop Waveform https://thenounproject.com/icon/1345324/
 NES Cartridge https://thenounproject.com/icon/1127992/
 Refresh https://thenounproject.com/icon/974170/

Slide 53

Slide 53 text

Libretro Architecture: Callbacks Video Callback: Called once per frame with a pointer to video data. Input Callback: Called for every supported button on every frame. Return true if button is pressed. Audio Callback: Called once per frame with a pointer to audio data. “Environment” Callback: Generic callback for obscure functions. Mostly unused, per documentation.

Slide 54

Slide 54 text

Libretro Architecture: Loading Games Two Options 1. Core loads game, specify file path. 2. Frontend loads game, specify memory pointer.

Slide 55

Slide 55 text

Libretro Architecture: Game Loop Libretro runs one frame at a time. Frontend maintains the framerate.

Slide 56

Slide 56 text

libretro + JNA 1. Write library bindings. 2. Implement callbacks. 3. Call functions.

Slide 57

Slide 57 text

JNAerator github.com/nativelibs4java/JNAerator

Slide 58

Slide 58 text

build.gradle dependencies { implementation 'net.java.dev.jna:jna:4.5.0@aar' }

Slide 59

Slide 59 text

Native Libraries Go Here

Slide 60

Slide 60 text

Writing Bindings: Initialize void retro_init(void);

Slide 61

Slide 61 text

Writing Bindings: Initialize void retro_init(void); interface LibRetro : Library { fun retro_init() } LibRetro.kt libretro.h

Slide 62

Slide 62 text

Writing Bindings: Load Game bool retro_load_game(retro_game_info);

Slide 63

Slide 63 text

Writing Bindings: Load Game struct retro_game_info {AA const char *path; const void *data; size_t size; const char *meta; }; bool retro_load_game(retro_game_info);

Slide 64

Slide 64 text

Writing Bindings: Load Game struct retro_game_info {AA const char *path; const void *data; size_t size; const char *meta; }; bool retro_load_game(retro_game_info); LibRetro.kt libretro.h

Slide 65

Slide 65 text

Writing Bindings: Load Game struct retro_game_info { const char *path; const void *data; size_t size; const char *meta; }; bool retro_load_game(retro_game_info); class retro_game_info : Structure() { }AA struct retro_game_info 
 }; LibRetro.kt libretro.h

Slide 66

Slide 66 text

Writing Bindings: Load Game struct retro_game_info { const char *path; const void *data; size_t size; const char *meta; }; bool retro_load_game(retro_game_info); class retro_game_info : Structure() { }AA const char *path; const void *data; size_t size; const char *meta; @JvmField var path: String? = null @JvmField var data: Pointer? = null @JvmField var size: SizeT = SizeT() @JvmField var meta: String? = null LibRetro.kt libretro.h

Slide 67

Slide 67 text

Writing Bindings: Load Game struct retro_game_info { const char *path; const void *data; size_t size; const char *meta; }; bool retro_load_game(retro_game_info); class retro_game_info : Structure() { @JvmField var path: String? = null @JvmField var data: Pointer? = null @JvmField var size: SizeT = SizeT() @JvmField var meta: String? = null }AAA override fun getFieldOrder() = listOf("path", "data", "size", "meta") LibRetro.kt libretro.h

Slide 68

Slide 68 text

Writing Bindings: Load Game struct retro_game_info { const char *path; const void *data; size_t size; const char *meta; }; bool retro_load_game(retro_game_info); class retro_game_info : Structure() { @JvmField var path: String? = null @JvmField var data: Pointer? = null @JvmField var size: SizeT = SizeT() @JvmField var meta: String? = null override fun getFieldOrder() = listOf("path", "data", "size", "meta") }AAA bool retro_load_game(retro_game_info); interface LibRetro : Library { fun retro_load_game(info: retro_game_info): Boolean } LibRetro.kt libretro.h

Slide 69

Slide 69 text

Writing Bindings: Audio Callback void retro_set_audio_sample_batch(retro_audio_sample_batch_t);

Slide 70

Slide 70 text

Writing Bindings: Audio Callback typedef size_t (*retro_audio_sample_batch_t)( const int16_t *data, size_t frames); void retro_set_audio_sample_batch(retro_audio_sample_batch_t);

Slide 71

Slide 71 text

Writing Bindings: Audio Callback void retro_set_audio_sample_batch(retro_audio_sample_batch_t); interface retro_audio_sample_batch : Callback { fun invoke(data: Pointer, frames: SizeT): SizeT }AA typedef size_t (*retro_audio_sample_batch_t)( const int16_t *data, size_t frames); LibRetro.kt libretro.h

Slide 72

Slide 72 text

Writing Bindings: Audio Callback void retro_set_audio_sample_batch(retro_audio_sample_batch_t); typedef size_t (*retro_audio_sample_batch_t)( const int16_t *data, size_t frames); interface retro_audio_sample_batch : Callback { fun invoke(data: Pointer, frames: SizeT): SizeT }AA interface LibRetro : Library { fun retro_set_audio_sample_batch(cb: retro_audio_sample_batch) } LibRetro.kt libretro.h

Slide 73

Slide 73 text

Writing Bindings: Video Callback void retro_set_video_refresh(retro_video_refresh_t);

Slide 74

Slide 74 text

Writing Bindings: Video Callback typedef void (*retro_video_refresh_t) ( const void *data, unsigned width, unsigned height, size_t pitch); void retro_set_video_refresh(retro_video_refresh_t);

Slide 75

Slide 75 text

Writing Bindings: Video Callback void retro_set_video_refresh(retro_video_refresh_t); interface retro_video_refresh : Callback { fun invoke( data: Pointer, width: UnsignedInt, height: UnsignedInt,
 pitch: SizeT) } typedef void (*retro_video_refresh_t) ( const void *data, unsigned width, unsigned height, size_t pitch); LibRetro.kt libretro.h

Slide 76

Slide 76 text

Writing Bindings: Video Callback interface retro_video_refresh : Callback { fun invoke( data: Pointer, width: UnsignedInt, height: UnsignedInt,
 pitch: SizeT) } void retro_set_video_refresh(retro_video_refresh_t); typedef void (*retro_video_refresh_t) ( const void *data, unsigned width, unsigned height, size_t pitch); interface LibRetro : Library { fun retro_set_video_refresh(cb: retro_video_refresh) } LibRetro.kt libretro.h

Slide 77

Slide 77 text

void retro_set_input_state(retro_input_state_t); Writing Bindings: Input State Callback

Slide 78

Slide 78 text

void retro_set_input_state(retro_input_state_t); typedef int16_t (*retro_input_state_t)( unsigned port, unsigned device, unsigned index, unsigned id); Writing Bindings: Input State Callback

Slide 79

Slide 79 text

interface retro_input_state : Callback { fun invoke( port: UInt, device: UInt, index: UInt, id: UInt): Short } void retro_set_input_state(retro_input_state_t); typedef int16_t (*retro_input_state_t)( unsigned port, unsigned device, unsigned index, unsigned id); interface LibRetro : Library { fun retro_set_input_state(cb: retro_input_state) } LibRetro.kt libretro.h Writing Bindings: Input State Callback

Slide 80

Slide 80 text

interface retro_input_state : Callback { fun invoke( port: UInt, device: UInt, index: UInt, id: UInt): Short } void retro_set_input_state(retro_input_state_t); typedef int16_t (*retro_input_state_t)( unsigned port, unsigned device, unsigned index, unsigned id); interface LibRetro : Library { fun retro_set_input_state(cb: retro_input_state) } LibRetro.kt libretro.h Writing Bindings: Input State Callback

Slide 81

Slide 81 text

#define RETRO_DEVICE_JOYPAD 1 #define RETRO_DEVICE_ID_JOYPAD_START 3 #define RETRO_DEVICE_ID_JOYPAD_UP 4 #define RETRO_DEVICE_ID_JOYPAD_DOWN 5 #define RETRO_DEVICE_ID_JOYPAD_LEFT 6 #define RETRO_DEVICE_ID_JOYPAD_RIGHT 7 const val RETRO_DEVICE_JOYPAD = 1 const val RETRO_DEVICE_ID_JOYPAD_START = 3 const val RETRO_DEVICE_ID_JOYPAD_UP = 4 const val RETRO_DEVICE_ID_JOYPAD_DOWN = 5 const val RETRO_DEVICE_ID_JOYPAD_LEFT = 6 const val RETRO_DEVICE_ID_JOYPAD_RIGHT = 7 LibRetro.kt libretro.h Writing Bindings: Input State Callback

Slide 82

Slide 82 text

void retro_run(void); interface LibRetro : Library { fun retro_run() } LibRetro.kt libretro.h Writing Bindings: Run Game Loop

Slide 83

Slide 83 text

class retro_game_info : Structure() { @JvmField var path: String? = null @JvmField var data: Pointer? = null @JvmField var size: SizeT = SizeT() @JvmField var meta: String? = null override fun getFieldOrder() = listOf("path", "data", "size", "meta") } interface retro_audio_sample_batch : Callback { fun invoke(data: Pointer, frames: SizeT): SizeT } interface retro_video_refresh : Callback { fun invoke(data: Pointer, width: UInt, height: UInt, pitch: SizeT) } interface retro_input_state : Callback { fun invoke(port: UInt, device: UInt, index: UInt, id: UInt): Short } interface retro_input_poll : Callback { fun invoke() } interface LibRetro : Library {AA fun retro_init() fun retro_load_game(game: retro_game_info): Boolean fun retro_set_audio_sample_batch(cb: retro_audio_sample_batch) fun retro_set_video_refresh(cb: retro_video_refresh) fun retro_set_input_state(cb: retro_input_state) fun retro_set_input_poll(cb: retro_input_poll) fun retro_run() }BB

Slide 84

Slide 84 text

libretro + JNA 1. Write library bindings. 2. Implement callbacks. 3. Call functions.

Slide 85

Slide 85 text

Implementing Callbacks: Audio private val audioSampleBatch = object : retro_audio_sample_batch { override fun invoke(data: Pointer, frames: SizeT): SizeT { }AA }BB

Slide 86

Slide 86 text

Implementing Callbacks: Audio private val audioBytesPerFrame = 4 // 16-bit stereo private val audioSampleBatch = object : retro_audio_sample_batch { override fun invoke(data: Pointer, frames: SizeT): SizeT { val length = frames * audioBytesPerFrame }AA }BB

Slide 87

Slide 87 text

Implementing Callbacks: Audio private val audioBytesPerFrame = 4 // 16-bit stereo private val audioSampleBatch = object : retro_audio_sample_batch { override fun invoke(data: Pointer, frames: SizeT): SizeT { val length = frames * audioBytesPerFrame val buffer = data.getByteArray(0, length) }AA }BB

Slide 88

Slide 88 text

Implementing Callbacks: Audio import android.media.AudioTrack private val audioTrack = AudioTrack.Builder() .setAudioFormat(AudioFormat.Builder() .setEncoding(AudioFormat.ENCODING_PCM_16BIT) .setSampleRate(32040) .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO) .build()) .build() private val audioBytesPerFrame = 4 // 16-bit stereo private val audioSampleBatch = object : retro_audio_sample_batch { override fun invoke(data: Pointer, frames: SizeT): SizeT { val length = frames * audioBytesPerFrame val buffer = data.getByteArray(0, length) }AA }BB

Slide 89

Slide 89 text

Implementing Callbacks: Audio import android.media.AudioTrack private val audioTrack = AudioTrack.Builder() .setAudioFormat(AudioFormat.Builder() .setEncoding(AudioFormat.ENCODING_PCM_16BIT) .setSampleRate(32040) .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO) .build()) .build() private val audioBytesPerFrame = 4 // 16-bit stereo private val audioSampleBatch = object : retro_audio_sample_batch { override fun invoke(data: Pointer, frames: SizeT): SizeT { val length = frames * audioBytesPerFrame val buffer = data.getByteArray(0, length) val written = audioTrack.write(buffer, 0, buffer.size) audioTrack.play() return SizeT(written) }AA }BB

Slide 90

Slide 90 text

Implementing Callbacks: Video private val videoRefresh = object : retro_video_refresh { override fun invoke(data: Pointer, width: UInt, height: UInt, pitch: SizeT) { }AA }BB

Slide 91

Slide 91 text

Implementing Callbacks: Video private val videoBytesPerPixel = 2 // RGB 565 private val videoRefresh = object : retro_video_refresh { override fun invoke(data: Pointer, width: UInt, height: UInt, pitch: SizeT) { val widthBytes = width * videoBytesPerPixel val totalBytes = width * height * videoBytesPerPixel }AA }BB

Slide 92

Slide 92 text

Implementing Callbacks: Video private val videoBytesPerPixel = 2 // RGB 565 private val videoRefresh = object : retro_video_refresh { override fun invoke(data: Pointer, width: UInt, height: UInt, pitch: SizeT) { val widthBytes = width * videoBytesPerPixel val totalBytes = width * height * videoBytesPerPixel val buffer = ByteArray(totalBytes) for (i in 0 until height.toInt()) { data.read(i * pitch.toLong(), buffer, i * widthBytes, widthBytes) }FF }AA }BB

Slide 93

Slide 93 text

Implementing Callbacks: Video import android.graphics.Bitmap private val videoBytesPerPixel = 2 // RGB 565 private val videoRefresh = object : retro_video_refresh { override fun invoke(data: Pointer, width: UInt, height: UInt, pitch: SizeT) { val widthBytes = width * videoBytesPerPixel val totalBytes = width * height * videoBytesPerPixel val buffer = ByteArray(totalBytes) for (i in 0 until height.toInt()) { data.read(i * pitch.toLong(), buffer, i * widthBytes, widthBytes) }FF val bitmap = Bitmap.createBitmap( width.toInt(), height.toInt(), Bitmap.Config.RGB_565) bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(buffer)) }AA }BB

Slide 94

Slide 94 text

Implementing Callbacks: Video import android.graphics.Bitmap private val imageView by lazy { findViewById(R.id.imageView) } private val videoBytesPerPixel = 2 // RGB 565 private val videoRefresh = object : retro_video_refresh { override fun invoke(data: Pointer, width: UInt, height: UInt, pitch: SizeT) { val widthBytes = width * videoBytesPerPixel val totalBytes = width * height * videoBytesPerPixel val buffer = ByteArray(totalBytes) for (i in 0 until height.toInt()) { data.read(i * pitch.toLong(), buffer, i * widthBytes, widthBytes) }FF val bitmap = Bitmap.createBitmap( width.toInt(), height.toInt(), Bitmap.Config.RGB_565) bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(buffer)) imageView.post { imageView.setImageBitmap(bitmap) } }AA }BB

Slide 95

Slide 95 text

Implementing Callbacks: Input State private val inputState = object : retro_input_state { override fun invoke(port: UInt, device: UInt, index: UInt, id: UInt): Short { }BB }ZZ

Slide 96

Slide 96 text

Implementing Callbacks: Input State private val inputState = object : retro_input_state { override fun invoke(port: UInt, device: UInt, index: UInt, id: UInt): Short { }BB }ZZ // TODO: Multiplayer support if (port.toInt() != 0) { return 0AA }AA

Slide 97

Slide 97 text

Implementing Callbacks: Input State private val inputState = object : retro_input_state { override fun invoke(port: UInt, device: UInt, index: UInt, id: UInt): Short { if (port.toInt() != 0) { return 0AA }AA }BB }ZZ if (device.toInt() != RETRO_DEVICE_JOYPAD) { return 0JJ }DD

Slide 98

Slide 98 text

Implementing Callbacks: Input State private val inputState = object : retro_input_state { override fun invoke(port: UInt, device: UInt, index: UInt, id: UInt): Short { if (port.toInt() != 0) { return 0 }AA if (device.toInt() != RETRO_DEVICE_JOYPAD) { return 0JJ }DD }BB }ZZ private val pressedButtons = mutableSetOf()

Slide 99

Slide 99 text

Implementing Callbacks: Input State private val pressedButtons = mutableSetOf() private val inputState = object : retro_input_state { override fun invoke(port: UInt, device: UInt, index: UInt, id: UInt): Short { if (port.toInt() != 0) { return 0 RR }AA if (device.toInt() != RETRO_DEVICE_JOYPAD) { return 0 GG }DD }BB }ZZ val isPressed = when (id.toInt()) { RETRO_DEVICE_ID_JOYPAD_START -> pressedButtons.contains(KeyEvent.KEYCODE_BUTTON_START) RETRO_DEVICE_ID_JOYPAD_UP -> pressedButtons.contains(KeyEvent.KEYCODE_DPAD_UP) RETRO_DEVICE_ID_JOYPAD_DOWN -> pressedButtons.contains(KeyEvent.KEYCODE_DPAD_DOWN) RETRO_DEVICE_ID_JOYPAD_LEFT -> pressedButtons.contains(KeyEvent.KEYCODE_DPAD_LEFT) RETRO_DEVICE_ID_JOYPAD_RIGHT -> pressedButtons.contains(KeyEvent.KEYCODE_DPAD_RIGHT) else -> false } return if (isPressed) 1 else 0 import android.view.KeyEvent

Slide 100

Slide 100 text

Implementing Callbacks: Input State private val inputState = object : retro_input_state { override fun invoke(port: UInt, device: UInt, index: UInt, id: UInt): Short { if (port.toInt() != 0) { return 0 RR }AA if (device.toInt() != RETRO_DEVICE_JOYPAD) { return 0 GG }DD }BB }ZZ val isPressed = when (id.toInt()) { RETRO_DEVICE_ID_JOYPAD_START -> pressedButtons.contains(KeyEvent.KEYCODE_BUTTON_START) RETRO_DEVICE_ID_JOYPAD_UP -> pressedButtons.contains(KeyEvent.KEYCODE_DPAD_UP) RETRO_DEVICE_ID_JOYPAD_DOWN -> pressedButtons.contains(KeyEvent.KEYCODE_DPAD_DOWN) RETRO_DEVICE_ID_JOYPAD_LEFT -> pressedButtons.contains(KeyEvent.KEYCODE_DPAD_LEFT) RETRO_DEVICE_ID_JOYPAD_RIGHT -> pressedButtons.contains(KeyEvent.KEYCODE_DPAD_RIGHT) else -> false } return if (isPressed) 1 else 0 import android.view.KeyEvent private val pressedButtons = mutableSetOf()

Slide 101

Slide 101 text

Implementing Callbacks: Input State import android.view.KeyEvent private val pressedButtons = mutableSetOf() override fun dispatchKeyEvent(event: KeyEvent): Boolean { }ZZ

Slide 102

Slide 102 text

Implementing Callbacks: Input State private val pressedButtons = mutableSetOf() override fun dispatchKeyEvent(event: KeyEvent): Boolean { }ZZ import android.view.KeyEvent super.dispatchKeyEvent(event) when (event.action) { KeyEvent.ACTION_DOWN -> pressedButtons.add(event.keyCode) KeyEvent.ACTION_UP -> pressedButtons.remove(event.keyCode) } return true

Slide 103

Slide 103 text

libretro + JNA 1. Write library bindings. 2. Implement callbacks. 3. Call functions.

Slide 104

Slide 104 text

Calling Functions val lib = Native.loadLibrary(
 "snes9x_libretro_android",
 LibRetro::class.java) lib.retro_set_audio_sample_batch(audioSampleBatch)
 lib.retro_set_video_refresh(videoRefresh)
 lib.retro_set_input_poll(inputPoll)
 lib.retro_set_input_state(inputState)
 lib.retro_init() val gameInfo = retro_game_info()
 gameInfo.path = "/sdcard/allstars.smc"
 lib.retro_load_game(gameInfo) fixedRateTimer(period = 1000L / 60L) {
 lib.retro_run()
 }

Slide 105

Slide 105 text

No content

Slide 106

Slide 106 text

wat libc F Fatal signal 11 (SIGSEGV), code 1, fault addr 0x0 in tid 12676 ( DEBUG F #00 pc 00000000 F #01 pc 00010fc9 /data/app/com.codebutler.funwithnativecode-n1T9 F #02 pc 00010c40 /data/app/com.codebutler.funwithnativecode-n1T9 F #03 pc 000051e0 /data/app/com.codebutler.funwithnativecode-n1T9 F #04 pc 00007c8f /data/app/com.codebutler.funwithnativecode-n1T9 F #05 pc 0063ec67 /system/lib/libart.so (art_quick_generic_jni_tr F #06 pc 00638ea2 /system/lib/libart.so (art_quick_invoke_static_ F #07 pc 00112b92 /system/lib/libart.so (_ZN3art9ArtMethod6Invoke F #08 pc 003231ff /system/lib/libart.so (_ZN3art11interpreter34Ar F #09 pc 0031bde1 /system/lib/libart.so (_ZN3art11interpreter6DoC F #10 pc 0061faf4 /system/lib/libart.so (MterpInvokeStatic+484)

Slide 107

Slide 107 text

wat

Slide 108

Slide 108 text

wat

Slide 109

Slide 109 text

interface retro_environment : Callback { fun invoke(cmd: UInt, data: Pointer): Boolean }EE LibRetro.kt libretro.h interface LibRetro : Library { } void retro_set_environment(retro_environment_t); typedef bool (*retro_environment_t)(unsigned cmd, void *data); Writing Bindings: Environment Callback fun retro_set_environment(cb: retro_environment)

Slide 110

Slide 110 text

class retro_game_info : Structure() { @JvmField var path: String? = null @JvmField var data: Pointer? = null @JvmField var size: SizeT = SizeT() @JvmField var meta: String? = null override fun getFieldOrder() = listOf("path", "data", "size", "meta") } interface retro_audio_sample_batch : Callback { fun invoke(data: Pointer, frames: SizeT): SizeT } interface retro_video_refresh : Callback { fun invoke(data: Pointer, width: UInt, height: UInt, pitch: SizeT) } interface retro_input_state : Callback { fun invoke(port: UInt, device: UInt, index: UInt, id: UInt): Short } interface retro_input_poll : Callback { fun invoke() } interface LibRetro : Library {AA fun retro_init() fun retro_load_game(game: retro_game_info): Boolean fun retro_set_audio_sample_batch(cb: retro_audio_sample_batch) fun retro_set_video_refresh(cb: retro_video_refresh) fun retro_set_input_state(cb: retro_input_state) fun retro_set_input_poll(cb: retro_input_poll) fun retro_run() }BB fun retro_set_environment(cb: retro_environment) interface retro_environment : Callback { fun invoke(cmd: UInt, data: Pointer): Boolean }EE

Slide 111

Slide 111 text

class retro_game_info : Structure() { @JvmField var path: String? = null @JvmField var data: Pointer? = null @JvmField var size: SizeT = SizeT() @JvmField var meta: String? = null override fun getFieldOrder() = listOf("path", "data", "size", "meta") } interface retro_environment : Callback { fun invoke(cmd: UInt, data: Pointer): Boolean } interface retro_audio_sample_batch : Callback { fun invoke(data: Pointer, frames: SizeT): SizeT } interface retro_video_refresh : Callback { fun invoke(data: Pointer, width: UInt, height: UInt, pitch: SizeT) } interface retro_input_state : Callback { fun invoke(port: UInt, device: UInt, index: UInt, id: UInt): Short } interface retro_input_poll : Callback { fun invoke() } interface LibRetro : Library {AA fun retro_init() fun retro_load_game(game: retro_game_info): Boolean fun retro_set_environment(cb: retro_input_poll) fun retro_set_audio_sample_batch(cb: retro_audio_sample_batch) fun retro_set_video_refresh(cb: retro_video_refresh) fun retro_set_input_state(cb: retro_input_state) fun retro_set_input_poll(cb: retro_input_poll) fun retro_run() }BB

Slide 112

Slide 112 text

private val environment = object : retro_environment { override fun invoke(cmd: UInt, data: Pointer): Boolean = false } Implementing Callbacks: Environment

Slide 113

Slide 113 text

val lib = Native.loadLibrary( "snes9x_libretro_android", LibRetro::class.java) lib.retro_set_audio_sample_batch(audioSampleBatch) lib.retro_set_video_refresh(videoRefresh) lib.retro_set_input_poll(inputPoll) lib.retro_set_input_state(inputState) lib.retro_init() val gameInfo = retro_game_info() gameInfo.path = "/sdcard/allstars.smc" lib.retro_load_game(gameInfo) fixedRateTimer(period = 1000L / 60L) { lib.retro_run() }AA

Slide 114

Slide 114 text

val lib = Native.loadLibrary( "snes9x_libretro_android", LibRetro::class.java) lib.retro_set_audio_sample_batch(audioSampleBatch) lib.retro_set_video_refresh(videoRefresh) lib.retro_set_input_poll(inputPoll) lib.retro_set_input_state(inputState) lib.retro_init() val gameInfo = retro_game_info() gameInfo.path = "/sdcard/allstars.smc" lib.retro_load_game(gameInfo) fixedRateTimer(period = 1000L / 60L) { lib.retro_run() }AA lib.retro_set_environment(environment)

Slide 115

Slide 115 text

No content

Slide 116

Slide 116 text

No content

Slide 117

Slide 117 text

val lib = Native.loadLibrary( "snes9x_libretro_android", LibRetro::class.java) lib.retro_set_environment(environment) lib.retro_set_audio_sample_batch(audioSampleBatch) lib.retro_set_video_refresh(videoRefresh) lib.retro_set_input_poll(inputPoll) lib.retro_set_input_state(inputState) lib.retro_init() val gameInfo = retro_game_info() gameInfo.path = "/sdcard/allstars.smc" lib.retro_load_game(gameInfo) fixedRateTimer(period = 1000L / 60L) { lib.retro_run() }

Slide 118

Slide 118 text

val lib = Native.loadLibrary( LibRetro::class.java) lib.retro_set_environment(environment) lib.retro_set_audio_sample_batch(audioSampleBatch) lib.retro_set_video_refresh(videoRefresh) lib.retro_set_input_poll(inputPoll) lib.retro_set_input_state(inputState) lib.retro_init() val gameInfo = retro_game_info() lib.retro_load_game(gameInfo) fixedRateTimer(period = 1000L / 60L) { lib.retro_run() } "genesis_plus_gx_libretro_android", gameInfo.path = "/sdcard/sonic3.md"

Slide 119

Slide 119 text

No content

Slide 120

Slide 120 text

MY DREAM APP...

Slide 121

Slide 121 text

MY APP...!

Slide 122

Slide 122 text

No content

Slide 123

Slide 123 text

ODYSSEY github.com/codebutler/odyssey Current Features 7 Console Systems Early Leanback UI Controller Support Game Covert Art Game Save/Restore WebDAV Future Features More Console Systems Better UI Local Multiplayer Game State Save/Restore Dropbox/GDrive Cheat Codes Internet Multiplayer Twitch/YouTube (Named after the Magnavox Odyssey, the first commercial home video game console released in 1972)

Slide 124

Slide 124 text

THANKS! Slides & Code bit.ly/FunWithNativeCode Follow Me @codebutler