summaryrefslogtreecommitdiff
path: root/src/contrib/SDL-3.2.20/docs/README-android.md
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-08-30 16:53:58 -0700
committer3gg <3gg@shellblade.net>2025-08-30 16:53:58 -0700
commit6aaedb813fa11ba0679c3051bc2eb28646b9506c (patch)
tree34acbfc9840e02cb4753e6306ea7ce978bf8b58e /src/contrib/SDL-3.2.20/docs/README-android.md
parent8f228ade99dd3d4c8da9b78ade1815c9adf85c8f (diff)
Update to SDL3
Diffstat (limited to 'src/contrib/SDL-3.2.20/docs/README-android.md')
-rw-r--r--src/contrib/SDL-3.2.20/docs/README-android.md653
1 files changed, 653 insertions, 0 deletions
diff --git a/src/contrib/SDL-3.2.20/docs/README-android.md b/src/contrib/SDL-3.2.20/docs/README-android.md
new file mode 100644
index 0000000..e57ca81
--- /dev/null
+++ b/src/contrib/SDL-3.2.20/docs/README-android.md
@@ -0,0 +1,653 @@
1Android
2================================================================================
3
4Matt Styles wrote a tutorial on building SDL for Android with Visual Studio:
5http://trederia.blogspot.de/2017/03/building-sdl2-for-android-with-visual.html
6
7The rest of this README covers the Android gradle style build process.
8
9
10Requirements
11================================================================================
12
13Android SDK (version 35 or later)
14https://developer.android.com/sdk/index.html
15
16Android NDK r15c or later
17https://developer.android.com/tools/sdk/ndk/index.html
18
19Minimum API level supported by SDL: 21 (Android 5.0)
20
21
22How the port works
23================================================================================
24
25- Android applications are Java-based, optionally with parts written in C
26- As SDL apps are C-based, we use a small Java shim that uses JNI to talk to
27 the SDL library
28- This means that your application C code must be placed inside an Android
29 Java project, along with some C support code that communicates with Java
30- This eventually produces a standard Android .apk package
31
32The Android Java code implements an "Activity" and can be found in:
33android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
34
35The Java code loads your game code, the SDL shared library, and
36dispatches to native functions implemented in the SDL library:
37src/core/android/SDL_android.c
38
39
40Building a simple app
41================================================================================
42
43For simple projects you can use the script located at build-scripts/create-android-project.py
44
45There's two ways of using it:
46
47 ./create-android-project.py com.yourcompany.yourapp < sources.list
48 ./create-android-project.py com.yourcompany.yourapp source1.c source2.c ...sourceN.c
49
50sources.list should be a text file with a source file name in each line
51Filenames should be specified relative to the current directory, for example if
52you are in the build-scripts directory and want to create the testgles.c test, you'll
53run:
54
55 ./create-android-project.py org.libsdl.testgles ../test/testgles.c
56
57One limitation of this script is that all sources provided will be aggregated into
58a single directory, thus all your source files should have a unique name.
59
60Once the project is complete the script will tell you how to build the project.
61If you want to create a signed release APK, you can use the project created by this
62utility to generate it.
63
64Running the script with `--help` will list all available options, and their purposes.
65
66Finally, a word of caution: re running create-android-project.py wipes any changes you may have
67done in the build directory for the app!
68
69
70Building a more complex app
71================================================================================
72
73For more complex projects, follow these instructions:
74
751. Get the source code for SDL and copy the 'android-project' directory located at SDL/android-project to a suitable location in your project.
76
77 The 'android-project' directory can basically be seen as a sort of starting point for the android-port of your project. It contains the glue code between the Android Java 'frontend' and the SDL code 'backend'. It also contains some standard behaviour, like how events should be handled, which you will be able to change.
78
792. If you are _not_ already building SDL as a part of your project (e.g. via CMake add_subdirectory() or FetchContent) move or [symlink](https://en.wikipedia.org/wiki/Symbolic_link) the SDL directory into the 'android-project/app/jni' directory. Alternatively you can [use the SDL3 Android Archive (.aar)](#using-the-sdl3-android-archive-aar), see bellow for more details.
80
81 This is needed as SDL has to be compiled by the Android compiler.
82
833. Edit 'android-project/app/build.gradle' to include any assets that your app needs by adding 'assets.srcDirs' in 'sourceSets.main'.
84
85 For example: `assets.srcDirs = ['../../assets', '../../shaders']`
86
87If using CMake:
88
894. Edit 'android-project/app/build.gradle' to set 'buildWithCMake' to true and set 'externalNativeBuild' cmake path to your top level CMakeLists.txt.
90
91 For example: `path '../../CMakeLists.txt'`
92
935. Change the target containing your main function to be built as a shared library called "main" when compiling for Android. (e.g. add_executable(MyGame main.c) should become add_library(main SHARED main.c) on Android)
94
95If using Android Makefiles:
96
974. Edit 'android-project/app/jni/src/Android.mk' to include your source files. They should be separated by spaces after the 'LOCAL_SRC_FILES := ' declaration.
98
99To build your app, run `./gradlew installDebug` or `./gradlew installRelease` in the project directory. It will build and install your .apk on any connected Android device. If you want to use Android Studio, simply open your 'android-project' directory and start building.
100
101Additionally the [SDL_helloworld](https://github.com/libsdl-org/SDL_helloworld) project contains a small example program with a functional Android port that you can use as a reference.
102
103Here's an explanation of the files in the Android project, so you can customize them:
104
105 android-project/app
106 build.gradle - build info including the application version and SDK
107 src/main/AndroidManifest.xml - package manifest. Among others, it contains the class name of the main Activity and the package name of the application.
108 jni/ - directory holding native code
109 jni/Application.mk - Application JNI settings, including target platform and STL library
110 jni/Android.mk - Android makefile that can call recursively the Android.mk files in all subdirectories
111 jni/CMakeLists.txt - Top-level CMake project that adds SDL as a subproject
112 jni/SDL/ - (symlink to) directory holding the SDL library files
113 jni/SDL/Android.mk - Android makefile for creating the SDL shared library
114 jni/src/ - directory holding your C/C++ source
115 jni/src/Android.mk - Android makefile that you should customize to include your source code and any library references
116 jni/src/CMakeLists.txt - CMake file that you may customize to include your source code and any library references
117 src/main/assets/ - directory holding asset files for your application
118 src/main/res/ - directory holding resources for your application
119 src/main/res/mipmap-* - directories holding icons for different phone hardware
120 src/main/res/values/strings.xml - strings used in your application, including the application name
121 src/main/java/org/libsdl/app/SDLActivity.java - the Java class handling the initialization and binding to SDL. Be very careful changing this, as the SDL library relies on this implementation. You should instead subclass this for your application.
122
123
124Using the SDL3 Android Archive (.aar)
125================================================================================
126
127The Android archive allows use of SDL3 in your Android project, without needing to copy any SDL C or JAVA source into your project.
128For integration with CMake/ndk-build, it uses [prefab](https://google.github.io/prefab/).
129
130Copy the archive to a `app/libs` directory in your project and add the following to `app/gradle.build`:
131```
132android {
133 /* ... */
134 buildFeatures {
135 prefab true
136 }
137}
138dependencies {
139 implementation files('libs/SDL3-X.Y.Z.aar') /* Replace with the filename of the actual SDL3-x.y.z.aar file you downloaded */
140 /* ... */
141}
142```
143
144If you use CMake, add the following to your CMakeLists.txt:
145```
146find_package(SDL3 REQUIRED CONFIG)
147target_link_libraries(yourgame PRIVATE SDL3::SDL3)
148```
149
150If you use ndk-build, add the following before `include $(BUILD_SHARED_LIBRARY)` to your `Android.mk`:
151```
152LOCAL_SHARED_LIBARARIES := SDL3 SDL3-Headers
153```
154And add the following at the bottom:
155```
156# https://google.github.io/prefab/build-systems.html
157# Add the prefab modules to the import path.
158$(call import-add-path,/out)
159# Import @PROJECT_NAME@ so we can depend on it.
160$(call import-module,prefab/@PROJECT_NAME@)
161```
162
163The `build-scripts/create-android-project.py` script can create a project using Android aar-chives from scratch:
164```
165build-scripts/create-android-project.py --variant aar com.yourcompany.yourapp < sources.list
166```
167
168Customizing your application name
169================================================================================
170
171To customize your application name, edit AndroidManifest.xml and build.gradle to replace
172"org.libsdl.app" with an identifier for your product package.
173
174Then create a Java class extending SDLActivity and place it in a directory
175under src matching your package, e.g.
176
177 app/src/main/java/com/gamemaker/game/MyGame.java
178
179Here's an example of a minimal class file:
180
181 --- MyGame.java --------------------------
182 package com.gamemaker.game;
183
184 import org.libsdl.app.SDLActivity;
185
186 /**
187 * A sample wrapper class that just calls SDLActivity
188 */
189
190 public class MyGame extends SDLActivity { }
191
192 ------------------------------------------
193
194Then replace "SDLActivity" in AndroidManifest.xml with the name of your
195class, .e.g. "MyGame"
196
197
198Customizing your application icon
199================================================================================
200
201Conceptually changing your icon is just replacing the "ic_launcher.png" files in
202the drawable directories under the res directory. There are several directories
203for different screen sizes.
204
205
206Loading assets
207================================================================================
208
209Any files you put in the "app/src/main/assets" directory of your project
210directory will get bundled into the application package and you can load
211them using the standard functions in SDL_iostream.h.
212
213There are also a few Android specific functions that allow you to get other
214useful paths for saving and loading data:
215* SDL_GetAndroidInternalStoragePath()
216* SDL_GetAndroidExternalStorageState()
217* SDL_GetAndroidExternalStoragePath()
218* SDL_GetAndroidCachePath()
219
220See SDL_system.h for more details on these functions.
221
222The asset packaging system will, by default, compress certain file extensions.
223SDL includes two asset file access mechanisms, the preferred one is the so
224called "File Descriptor" method, which is faster and doesn't involve the Dalvik
225GC, but given this method does not work on compressed assets, there is also the
226"Input Stream" method, which is automatically used as a fall back by SDL. You
227may want to keep this fact in mind when building your APK, specially when large
228files are involved.
229For more information on which extensions get compressed by default and how to
230disable this behaviour, see for example:
231
232http://ponystyle.com/blog/2010/03/26/dealing-with-asset-compression-in-android-apps/
233
234
235Activity lifecycle
236================================================================================
237
238On Android the application goes through a fixed life cycle and you will get
239notifications of state changes via application events. When these events
240are delivered you must handle them in an event callback because the OS may
241not give you any processing time after the events are delivered.
242
243e.g.
244
245 bool HandleAppEvents(void *userdata, SDL_Event *event)
246 {
247 switch (event->type)
248 {
249 case SDL_EVENT_TERMINATING:
250 /* Terminate the app.
251 Shut everything down before returning from this function.
252 */
253 return false;
254 case SDL_EVENT_LOW_MEMORY:
255 /* You will get this when your app is paused and iOS wants more memory.
256 Release as much memory as possible.
257 */
258 return false;
259 case SDL_EVENT_WILL_ENTER_BACKGROUND:
260 /* Prepare your app to go into the background. Stop loops, etc.
261 This gets called when the user hits the home button, or gets a call.
262
263 You should not make any OpenGL graphics calls or use the rendering API,
264 in addition, you should set the render target to NULL, if you're using
265 it, e.g. call SDL_SetRenderTarget(renderer, NULL).
266 */
267 return false;
268 case SDL_EVENT_DID_ENTER_BACKGROUND:
269 /* Your app is NOT active at this point. */
270 return false;
271 case SDL_EVENT_WILL_ENTER_FOREGROUND:
272 /* This call happens when your app is coming back to the foreground.
273 Restore all your state here.
274 */
275 return false;
276 case SDL_EVENT_DID_ENTER_FOREGROUND:
277 /* Restart your loops here.
278 Your app is interactive and getting CPU again.
279
280 You have access to the OpenGL context or rendering API at this point.
281 However, there's a chance (on older hardware, or on systems under heavy load),
282 where the graphics context can not be restored. You should listen for the
283 event SDL_EVENT_RENDER_DEVICE_RESET and recreate your OpenGL context and
284 restore your textures when you get it, or quit the app.
285 */
286 return false;
287 default:
288 /* No special processing, add it to the event queue */
289 return true;
290 }
291 }
292
293 int main(int argc, char *argv[])
294 {
295 SDL_SetEventFilter(HandleAppEvents, NULL);
296
297 ... run your main loop
298
299 return 0;
300 }
301
302
303Note that if you are using main callbacks instead of a standard C main() function,
304your SDL_AppEvent() callback will run as these events arrive and you do not need to
305use SDL_SetEventFilter.
306
307If SDL_HINT_ANDROID_BLOCK_ON_PAUSE hint is set (the default),
308the event loop will block itself when the app is paused (ie, when the user
309returns to the main Android dashboard). Blocking is better in terms of battery
310use, and it allows your app to spring back to life instantaneously after resume
311(versus polling for a resume message).
312
313You can control activity re-creation (eg. onCreate()) behaviour. This allows you
314to choose whether to keep or re-initialize java and native static datas, see
315SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY in SDL_hints.h.
316
317
318Insets and Safe Areas
319================================================================================
320
321As of Android 15, SDL windows cover the entire screen, extending under notches
322and system bars. The OS expects you to take those into account when displaying
323content and SDL provides the function SDL_GetWindowSafeArea() so you know what
324area is available for interaction. Outside of the safe area can be potentially
325covered by system bars or used by OS gestures.
326
327
328Mouse / Touch events
329================================================================================
330
331In some case, SDL generates synthetic mouse (resp. touch) events for touch
332(resp. mouse) devices.
333To enable/disable this behavior, see SDL_hints.h:
334- SDL_HINT_TOUCH_MOUSE_EVENTS
335- SDL_HINT_MOUSE_TOUCH_EVENTS
336
337
338Misc
339================================================================================
340
341For some device, it appears to works better setting explicitly GL attributes
342before creating a window:
343 SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
344 SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
345 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
346
347
348Threads and the Java VM
349================================================================================
350
351For a quick tour on how Linux native threads interoperate with the Java VM, take
352a look here: https://developer.android.com/guide/practices/jni.html
353
354If you want to use threads in your SDL app, it's strongly recommended that you
355do so by creating them using SDL functions. This way, the required attach/detach
356handling is managed by SDL automagically. If you have threads created by other
357means and they make calls to SDL functions, make sure that you call
358Android_JNI_SetupThread() before doing anything else otherwise SDL will attach
359your thread automatically anyway (when you make an SDL call), but it'll never
360detach it.
361
362
363If you ever want to use JNI in a native thread (created by "SDL_CreateThread()"),
364it won't be able to find your java class and method because of the java class loader
365which is different for native threads, than for java threads (eg your "main()").
366
367the work-around is to find class/method, in you "main()" thread, and to use them
368in your native thread.
369
370see:
371https://developer.android.com/training/articles/perf-jni#faq:-why-didnt-findclass-find-my-class
372
373
374Using STL
375================================================================================
376
377You can use STL in your project by creating an Application.mk file in the jni
378folder and adding the following line:
379
380 APP_STL := c++_shared
381
382For more information go here:
383 https://developer.android.com/ndk/guides/cpp-support
384
385
386Using the emulator
387================================================================================
388
389There are some good tips and tricks for getting the most out of the
390emulator here: https://developer.android.com/tools/devices/emulator.html
391
392Especially useful is the info on setting up OpenGL ES 2.0 emulation.
393
394Notice that this software emulator is incredibly slow and needs a lot of disk space.
395Using a real device works better.
396
397
398Troubleshooting
399================================================================================
400
401You can see if adb can see any devices with the following command:
402
403 adb devices
404
405You can see the output of log messages on the default device with:
406
407 adb logcat
408
409You can push files to the device with:
410
411 adb push local_file remote_path_and_file
412
413You can push files to the SD Card at /sdcard, for example:
414
415 adb push moose.dat /sdcard/moose.dat
416
417You can see the files on the SD card with a shell command:
418
419 adb shell ls /sdcard/
420
421You can start a command shell on the default device with:
422
423 adb shell
424
425You can remove the library files of your project (and not the SDL lib files) with:
426
427 ndk-build clean
428
429You can do a build with the following command:
430
431 ndk-build
432
433You can see the complete command line that ndk-build is using by passing V=1 on the command line:
434
435 ndk-build V=1
436
437If your application crashes in native code, you can use ndk-stack to get a symbolic stack trace:
438 https://developer.android.com/ndk/guides/ndk-stack
439
440If you want to go through the process manually, you can use addr2line to convert the
441addresses in the stack trace to lines in your code.
442
443For example, if your crash looks like this:
444
445 I/DEBUG ( 31): signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 400085d0
446 I/DEBUG ( 31): r0 00000000 r1 00001000 r2 00000003 r3 400085d4
447 I/DEBUG ( 31): r4 400085d0 r5 40008000 r6 afd41504 r7 436c6a7c
448 I/DEBUG ( 31): r8 436c6b30 r9 435c6fb0 10 435c6f9c fp 4168d82c
449 I/DEBUG ( 31): ip 8346aff0 sp 436c6a60 lr afd1c8ff pc afd1c902 cpsr 60000030
450 I/DEBUG ( 31): #00 pc 0001c902 /system/lib/libc.so
451 I/DEBUG ( 31): #01 pc 0001ccf6 /system/lib/libc.so
452 I/DEBUG ( 31): #02 pc 000014bc /data/data/org.libsdl.app/lib/libmain.so
453 I/DEBUG ( 31): #03 pc 00001506 /data/data/org.libsdl.app/lib/libmain.so
454
455You can see that there's a crash in the C library being called from the main code.
456I run addr2line with the debug version of my code:
457
458 arm-eabi-addr2line -C -f -e obj/local/armeabi/libmain.so
459
460and then paste in the number after "pc" in the call stack, from the line that I care about:
461000014bc
462
463I get output from addr2line showing that it's in the quit function, in testspriteminimal.c, on line 23.
464
465You can add logging to your code to help show what's happening:
466
467 #include <android/log.h>
468
469 __android_log_print(ANDROID_LOG_INFO, "foo", "Something happened! x = %d", x);
470
471If you need to build without optimization turned on, you can create a file called
472"Application.mk" in the jni directory, with the following line in it:
473
474 APP_OPTIM := debug
475
476
477Memory debugging
478================================================================================
479
480The best (and slowest) way to debug memory issues on Android is valgrind.
481Valgrind has support for Android out of the box, just grab code using:
482
483 git clone https://sourceware.org/git/valgrind.git
484
485... and follow the instructions in the file `README.android` to build it.
486
487One thing I needed to do on macOS was change the path to the toolchain,
488and add ranlib to the environment variables:
489export RANLIB=$NDKROOT/toolchains/arm-linux-androideabi-4.4.3/prebuilt/darwin-x86/bin/arm-linux-androideabi-ranlib
490
491Once valgrind is built, you can create a wrapper script to launch your
492application with it, changing org.libsdl.app to your package identifier:
493
494 --- start_valgrind_app -------------------
495 #!/system/bin/sh
496 export TMPDIR=/data/data/org.libsdl.app
497 exec /data/local/Inst/bin/valgrind --log-file=/sdcard/valgrind.log --error-limit=no $*
498 ------------------------------------------
499
500Then push it to the device:
501
502 adb push start_valgrind_app /data/local
503
504and make it executable:
505
506 adb shell chmod 755 /data/local/start_valgrind_app
507
508and tell Android to use the script to launch your application:
509
510 adb shell setprop wrap.org.libsdl.app "logwrapper /data/local/start_valgrind_app"
511
512If the setprop command says "could not set property", it's likely that
513your package name is too long and you should make it shorter by changing
514AndroidManifest.xml and the path to your class file in android-project/src
515
516You can then launch your application normally and waaaaaaaiiittt for it.
517You can monitor the startup process with the logcat command above, and
518when it's done (or even while it's running) you can grab the valgrind
519output file:
520
521 adb pull /sdcard/valgrind.log
522
523When you're done instrumenting with valgrind, you can disable the wrapper:
524
525 adb shell setprop wrap.org.libsdl.app ""
526
527
528Graphics debugging
529================================================================================
530
531If you are developing on a compatible Tegra-based tablet, NVidia provides
532Tegra Graphics Debugger at their website. Because SDL3 dynamically loads EGL
533and GLES libraries, you must follow their instructions for installing the
534interposer library on a rooted device. The non-rooted instructions are not
535compatible with applications that use SDL3 for video.
536
537The Tegra Graphics Debugger is available from NVidia here:
538https://developer.nvidia.com/tegra-graphics-debugger
539
540
541A note regarding the use of the "dirty rectangles" rendering technique
542================================================================================
543
544If your app uses a variation of the "dirty rectangles" rendering technique,
545where you only update a portion of the screen on each frame, you may notice a
546variety of visual glitches on Android, that are not present on other platforms.
547This is caused by SDL's use of EGL as the support system to handle OpenGL ES/ES2
548contexts, in particular the use of the eglSwapBuffers function. As stated in the
549documentation for the function "The contents of ancillary buffers are always
550undefined after calling eglSwapBuffers".
551
552
553Ending your application
554================================================================================
555
556Two legitimate ways:
557
558- return from your main() function. Java side will automatically terminate the
559Activity by calling Activity.finish().
560
561- Android OS can decide to terminate your application by calling onDestroy()
562(see Activity life cycle). Your application will receive an SDL_EVENT_QUIT you
563can handle to save things and quit.
564
565Don't call exit() as it stops the activity badly.
566
567NB: "Back button" can be handled as a SDL_EVENT_KEY_DOWN/UP events, with Keycode
568SDLK_AC_BACK, for any purpose.
569
570
571Known issues
572================================================================================
573
574- The number of buttons reported for each joystick is hardcoded to be 36, which
575is the current maximum number of buttons Android can report.
576
577
578Building the SDL tests
579================================================================================
580
581SDL's CMake build system can create APK's for the tests.
582It can build all tests with a single command without a dependency on gradle or Android Studio.
583The APK's are signed with a debug certificate.
584The only caveat is that the APK's support a single architecture.
585
586### Requirements
587- SDL source tree
588- CMake
589- ninja or make
590- Android Platform SDK
591- Android NDK
592- Android Build tools
593- Java JDK (version should be compatible with Android)
594- keytool (usually provided with the Java JDK), used for generating a debug certificate
595- zip
596
597### CMake configuration
598
599When configuring the CMake project, you need to use the Android NDK CMake toolchain, and pass the Android home path through `SDL_ANDROID_HOME`.
600```
601cmake .. -DCMAKE_TOOLCHAIN_FILE=<path/to/android.toolchain.cmake> -DANDROID_ABI=<android-abi> -DSDL_ANDROID_HOME=<path-to-android-sdk-home> -DANDROID_PLATFORM=23 -DSDL_TESTS=ON
602```
603
604Remarks:
605- `android.toolchain.cmake` can usually be found at `$ANDROID_HOME/ndk/x.y.z/build/cmake/android.toolchain.cmake`
606- `ANDROID_ABI` should be one of `arm64-v8a`, `armeabi-v7a`, `x86` or `x86_64`.
607- When CMake is unable to find required paths, use `cmake-gui` to override required `SDL_ANDROID_` CMake cache variables.
608
609### Building the APK's
610
611For the `testsprite` executable, the `testsprite-apk` target will build the associated APK:
612```
613cmake --build . --target testsprite-apk
614```
615
616APK's of all tests can be built with the `sdl-test-apks` target:
617```
618cmake --build . --target sdl-test-apks
619```
620
621### Installation/removal of the tests
622
623`testsprite.apk` APK can be installed on your Android machine using the `install-testsprite` target:
624```
625cmake --build . --target install-testsprite
626```
627
628APK's of all tests can be installed with the `install-sdl-test-apks` target:
629```
630cmake --build . --target install-sdl-test-apks
631```
632
633All SDL tests can be uninstalled with the `uninstall-sdl-test-apks` target:
634```
635cmake --build . --target uninstall-sdl-test-apks
636```
637
638### Starting the tests
639
640After installation, the tests can be started using the Android Launcher GUI.
641Alternatively, they can also be started using CMake targets.
642
643This command will start the testsprite executable:
644```
645cmake --build . --target start-testsprite
646```
647
648There is also a convenience target which will build, install and start a test:
649```
650cmake --build . --target build-install-start-testsprite
651```
652
653Not all tests provide a GUI. For those, you can use `adb logcat` to read the output.