├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── me
│ │ └── robot9
│ │ └── shared
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── aidl
│ │ └── me
│ │ │ └── robot9
│ │ │ └── shared
│ │ │ └── IOffscreenInterface.aidl
│ ├── java
│ │ └── me
│ │ │ └── robot9
│ │ │ └── shared
│ │ │ ├── EGLCore.java
│ │ │ ├── MainActivity.java
│ │ │ ├── OffscreenService.java
│ │ │ ├── OnscreenRenderView.java
│ │ │ └── QuadRenderer.java
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-anydpi-v33
│ │ └── ic_launcher.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── values-night
│ │ └── themes.xml
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── backup_rules.xml
│ │ └── data_extraction_rules.xml
│ └── test
│ └── java
│ └── me
│ └── robot9
│ └── shared
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── image
├── eglimage.png
└── eglsync.png
├── settings.gradle
└── shared
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
├── androidTest
└── java
│ └── me
│ └── robot9
│ └── shared
│ └── ExampleInstrumentedTest.java
├── main
├── AndroidManifest.xml
├── cpp
│ ├── CMakeLists.txt
│ ├── JSharedTexture.cpp
│ ├── JniLog.h
│ ├── SharedTexture.cpp
│ └── SharedTexture.h
└── java
│ └── me
│ └── robot9
│ └── shared
│ └── SharedTexture.java
└── test
└── java
└── me
└── robot9
└── shared
└── ExampleUnitTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 | .cxx
10 | local.properties
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 keith2018
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SharedTexture
2 |
3 | This project is a demo for multi-process sharing of OpenGL textures on Android, based on HardwareBuffer, supporting Android 8.0 and above, the two main functions are HardwareBuffer-based texture sharing and EGLSyncKHR-based cross-process synchronization. for more detail, see [https://robot9.me/hardwarebuffer-multi-process-rendering/](https://robot9.me/hardwarebuffer-multi-process-rendering/)
4 |
5 | ## License
6 | This code is licensed under the MIT License (see [LICENSE](LICENSE)).
7 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | }
4 |
5 | android {
6 | namespace 'me.robot9.shared'
7 | compileSdk 33
8 |
9 | defaultConfig {
10 | applicationId "me.robot9.shared"
11 | minSdk 26
12 | targetSdk 33
13 | versionCode 1
14 | versionName "1.0"
15 |
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | compileOptions {
26 | sourceCompatibility JavaVersion.VERSION_1_8
27 | targetCompatibility JavaVersion.VERSION_1_8
28 | }
29 | buildFeatures {
30 | viewBinding true
31 | }
32 | }
33 |
34 | dependencies {
35 |
36 | implementation 'androidx.appcompat:appcompat:1.6.1'
37 | implementation 'com.google.android.material:material:1.9.0'
38 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
39 |
40 | implementation project(path: ':shared')
41 |
42 | testImplementation 'junit:junit:4.13.2'
43 | androidTestImplementation 'androidx.test.ext:junit:1.1.5'
44 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
45 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/me/robot9/shared/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package me.robot9.shared;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.InstrumentationRegistry;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 | assertEquals("me.robot9.shared", appContext.getPackageName());
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/aidl/me/robot9/shared/IOffscreenInterface.aidl:
--------------------------------------------------------------------------------
1 | // OffscreenInterface.aidl
2 | package me.robot9.shared;
3 |
4 | // Declare any non-default types here with import statements
5 |
6 | interface IOffscreenInterface {
7 |
8 | void init(in HardwareBuffer buff, int width, int height);
9 |
10 | ParcelFileDescriptor drawFrame();
11 |
12 | void destroy();
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/me/robot9/shared/EGLCore.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SharedTexture
3 | * @author : keith@robot9.me
4 | *
5 | */
6 |
7 | package me.robot9.shared;
8 |
9 | import android.graphics.SurfaceTexture;
10 | import android.opengl.EGL14;
11 | import android.opengl.EGLConfig;
12 | import android.opengl.EGLContext;
13 | import android.opengl.EGLDisplay;
14 | import android.opengl.EGLExt;
15 | import android.opengl.EGLSurface;
16 | import android.opengl.GLES20;
17 | import android.util.Log;
18 | import android.view.Surface;
19 |
20 | public final class EGLCore {
21 |
22 | public final static int FLAG_RECORDABLE = 0x01;
23 | public final static int FLAG_TRY_GLES3 = 0x02;
24 | private final static String TAG = "EGLCore";
25 | private final static int EGL_RECORDABLE_ANDROID = 0x3142;
26 |
27 | private int mGlVersion = -1;
28 | private EGLConfig mEGLConfig = null;
29 | private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
30 | private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
31 |
32 | public EGLCore() {
33 | this(null, 0);
34 | }
35 |
36 | public EGLCore(EGLContext sharedContext, int flag) {
37 | mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
38 | int[] version = new int[2];
39 | if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
40 | throw new RuntimeException("unable to init EGL14");
41 | }
42 |
43 | if ((flag & FLAG_TRY_GLES3) != 0) {
44 | initEGLContext(sharedContext, flag, 3);
45 | }
46 | if (mEGLContext == EGL14.EGL_NO_CONTEXT) {
47 | initEGLContext(sharedContext, flag, 2);
48 | }
49 |
50 | int[] value = new int[1];
51 | EGL14.eglQueryContext(mEGLDisplay, mEGLContext, EGL14.EGL_CONTEXT_CLIENT_VERSION, value, 0);
52 | Log.i(TAG, "EGLContext client version=" + value[0]);
53 | }
54 |
55 | public static int createTexture(int target) {
56 | int[] textures = new int[1];
57 | GLES20.glGenTextures(textures.length, textures, 0);
58 | GLES20.glBindTexture(target, textures[0]);
59 | GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
60 | GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
61 | GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
62 | GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
63 | return textures[0];
64 | }
65 |
66 | public static void deleteTexture(int texture) {
67 | int[] textures = new int[]{texture};
68 | GLES20.glDeleteTextures(textures.length, textures, 0);
69 | }
70 |
71 | private void initEGLContext(EGLContext sharedContext, int flag, int version) {
72 | EGLConfig config = getConfig(flag, version);
73 | if (config == null) {
74 | throw new RuntimeException("unable to find suitable EGLConfig");
75 | }
76 |
77 | if (sharedContext == null) {
78 | sharedContext = EGL14.EGL_NO_CONTEXT;
79 | }
80 |
81 | int[] attributeList = {EGL14.EGL_CONTEXT_CLIENT_VERSION, version, EGL14.EGL_NONE};
82 |
83 | EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext, attributeList, 0);
84 | int error = EGL14.eglGetError();
85 | if (error == EGL14.EGL_SUCCESS) {
86 | mEGLConfig = config;
87 | mEGLContext = context;
88 | mGlVersion = version;
89 | } else {
90 | throw new RuntimeException("eglCreateContext failed:" + error);
91 | }
92 | }
93 |
94 | private EGLConfig getConfig(int flag, int version) {
95 | int renderType = EGL14.EGL_OPENGL_ES2_BIT;
96 | if (version >= 3) {
97 | renderType |= EGLExt.EGL_OPENGL_ES3_BIT_KHR;
98 | }
99 |
100 | int[] attributeList = {
101 | EGL14.EGL_RED_SIZE, 8,
102 | EGL14.EGL_GREEN_SIZE, 8,
103 | EGL14.EGL_BLUE_SIZE, 8,
104 | EGL14.EGL_ALPHA_SIZE, 8,
105 | //EGL14.EGL_DEPTH_SIZE, 16,
106 | //EGL14.EGL_STENCIL_SIZE, 8,
107 | EGL14.EGL_RENDERABLE_TYPE, renderType,
108 | EGL14.EGL_NONE, 0,
109 | EGL14.EGL_NONE
110 | };
111 |
112 | if ((flag & FLAG_RECORDABLE) != 0) {
113 | attributeList[attributeList.length - 3] = EGL_RECORDABLE_ANDROID;
114 | attributeList[attributeList.length - 2] = 1;
115 | }
116 | int[] numConfigs = new int[1];
117 | EGLConfig[] configs = new EGLConfig[1];
118 | if (!EGL14.eglChooseConfig(mEGLDisplay, attributeList, 0, configs,
119 | 0, configs.length, numConfigs, 0)) {
120 | Log.e(TAG, "unable to find RGB8888 / " + version + " EGLConfig");
121 | return null;
122 | }
123 | return configs[0];
124 | }
125 |
126 | public void release() {
127 | if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
128 | EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
129 | EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
130 | EGL14.eglReleaseThread();
131 | EGL14.eglTerminate(mEGLDisplay);
132 | }
133 | mEGLConfig = null;
134 | mEGLDisplay = EGL14.EGL_NO_DISPLAY;
135 | mEGLContext = EGL14.EGL_NO_CONTEXT;
136 | }
137 |
138 | @Override
139 | protected void finalize() throws Throwable {
140 | try {
141 | if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
142 | release();
143 | }
144 | } finally {
145 | super.finalize();
146 | }
147 | }
148 |
149 | public void releaseSurface(EGLSurface eglSurface) {
150 | if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
151 | EGL14.eglDestroySurface(mEGLDisplay, eglSurface);
152 | }
153 | }
154 |
155 | public EGLSurface createWindowSurface(Object surface) {
156 | if (!(surface instanceof Surface) && !(surface instanceof SurfaceTexture)) {
157 | throw new RuntimeException("invalid surface:" + surface);
158 | }
159 |
160 | int[] surfaceAttr = {EGL14.EGL_NONE};
161 | EGLSurface eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surface, surfaceAttr, 0);
162 | if (eglSurface == null) {
163 | throw new RuntimeException("window surface is null");
164 | }
165 | return eglSurface;
166 | }
167 |
168 | public EGLSurface createOffsetScreenSurface(int width, int height) {
169 | int[] surfaceAttr = {EGL14.EGL_WIDTH, width,
170 | EGL14.EGL_HEIGHT, height,
171 | EGL14.EGL_NONE};
172 | EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, surfaceAttr, 0);
173 | if (eglSurface == null) {
174 | throw new RuntimeException("offset-screen surface is null");
175 | }
176 | return eglSurface;
177 | }
178 |
179 | public void makeCurrent(EGLSurface eglSurface) {
180 | if (!EGL14.eglMakeCurrent(mEGLDisplay, eglSurface, eglSurface, mEGLContext)) {
181 | throw new RuntimeException("eglMakeCurrent failed!");
182 | }
183 | }
184 |
185 | public void makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) {
186 | if (!EGL14.eglMakeCurrent(mEGLDisplay, drawSurface, readSurface, mEGLContext)) {
187 | throw new RuntimeException("eglMakeCurrent failed!");
188 | }
189 | }
190 |
191 | public boolean swapBuffers(EGLSurface eglSurface) {
192 | return EGL14.eglSwapBuffers(mEGLDisplay, eglSurface);
193 | }
194 |
195 | public void setPresentationTime(EGLSurface eglSurface, long nsec) {
196 | EGLExt.eglPresentationTimeANDROID(mEGLDisplay, eglSurface, nsec);
197 | }
198 |
199 | public boolean isCurrent(EGLSurface eglSurface) {
200 | return mEGLContext.equals(EGL14.eglGetCurrentContext())
201 | && eglSurface.equals(EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW));
202 | }
203 |
204 | public int querySurface(EGLSurface eglSurface, int what) {
205 | int[] value = new int[1];
206 | EGL14.eglQuerySurface(mEGLDisplay, eglSurface, what, value, 0);
207 | return value[0];
208 | }
209 |
210 | public String queryString(int what) {
211 | return EGL14.eglQueryString(mEGLDisplay, what);
212 | }
213 |
214 | public int getVersion() {
215 | return mGlVersion;
216 | }
217 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/robot9/shared/MainActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SharedTexture
3 | * @author : keith@robot9.me
4 | *
5 | */
6 |
7 | package me.robot9.shared;
8 |
9 | import android.content.ComponentName;
10 | import android.content.Intent;
11 | import android.content.ServiceConnection;
12 | import android.opengl.GLES20;
13 | import android.os.Bundle;
14 | import android.os.IBinder;
15 | import android.os.ParcelFileDescriptor;
16 | import android.os.RemoteException;
17 | import android.util.Log;
18 | import android.widget.Toast;
19 |
20 | import androidx.appcompat.app.AppCompatActivity;
21 |
22 | import java.util.concurrent.atomic.AtomicBoolean;
23 |
24 | import me.robot9.shared.databinding.ActivityMainBinding;
25 |
26 | public class MainActivity extends AppCompatActivity {
27 |
28 | private static final String TAG = "MainActivity";
29 |
30 | private ActivityMainBinding binding;
31 | private OnscreenRenderView surfaceView;
32 |
33 | private ServiceConnection aidlConnection;
34 | private IOffscreenInterface aidlInterface;
35 |
36 | private QuadRenderer quadRenderer;
37 | private SharedTexture sharedTexture;
38 | private int renderWidth = 0;
39 | private int renderHeight = 0;
40 | private int texId = 0;
41 |
42 | private final AtomicBoolean aidlReady = new AtomicBoolean(false);
43 | private final AtomicBoolean renderReady = new AtomicBoolean(false);
44 |
45 | @Override
46 | protected void onCreate(Bundle savedInstanceState) {
47 | super.onCreate(savedInstanceState);
48 |
49 | binding = ActivityMainBinding.inflate(getLayoutInflater());
50 | setContentView(binding.getRoot());
51 |
52 | initGLSurfaceView();
53 |
54 | if (!SharedTexture.isAvailable()) {
55 | Toast.makeText(this, "shared texture not available !", Toast.LENGTH_SHORT).show();
56 | }
57 | }
58 |
59 | @Override
60 | protected void onStart() {
61 | super.onStart();
62 |
63 | if (SharedTexture.isAvailable()) {
64 | startOffscreenService();
65 | }
66 | surfaceView.onResume();
67 | }
68 |
69 | @Override
70 | protected void onStop() {
71 | super.onStop();
72 |
73 | if (SharedTexture.isAvailable()) {
74 | stopOffscreenService();
75 | }
76 | surfaceView.onPause();
77 | }
78 |
79 | private void initGLSurfaceView() {
80 | surfaceView = binding.surfaceView;
81 | surfaceView.setSurfaceViewCallback(new OnscreenRenderView.SurfaceViewCallback() {
82 | @Override
83 | public void onCreated(int width, int height) {
84 | renderWidth = width;
85 | renderHeight = height;
86 | }
87 |
88 | @Override
89 | public void onDrawFrame() {
90 | GLES20.glClearColor(0, 0, 0, 0);
91 | GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
92 |
93 | if (aidlReady.get()) {
94 | // init render context
95 | if (!renderReady.get()) {
96 | createRenderResources();
97 | offscreenRenderInit();
98 | }
99 |
100 | // draw frame
101 | offscreenRenderDrawFrame();
102 |
103 | // draw shared texture
104 | quadRenderer.drawTexture(texId, renderWidth, renderHeight);
105 | }
106 | }
107 |
108 | @Override
109 | public void onDestroy() {
110 | destroyRenderResources();
111 | }
112 | });
113 | }
114 |
115 | private void createRenderResources() {
116 | Log.d(TAG, "createRenderResources");
117 |
118 | quadRenderer = new QuadRenderer();
119 |
120 | texId = EGLCore.createTexture(GLES20.GL_TEXTURE_2D);
121 | sharedTexture = new SharedTexture(renderWidth, renderHeight);
122 | sharedTexture.bindTexture(texId);
123 |
124 | renderReady.set(true);
125 | }
126 |
127 | private void destroyRenderResources() {
128 | Log.d(TAG, "destroyRenderResources");
129 |
130 | renderReady.set(false);
131 |
132 | if (quadRenderer != null) {
133 | quadRenderer.release();
134 | quadRenderer = null;
135 | }
136 |
137 | EGLCore.deleteTexture(texId);
138 | if (sharedTexture != null) {
139 | sharedTexture.release();
140 | sharedTexture = null;
141 | }
142 | }
143 |
144 | private void offscreenRenderInit() {
145 | try {
146 | aidlInterface.init(sharedTexture.getHardwareBuffer(), renderWidth, renderHeight);
147 | } catch (RemoteException e) {
148 | e.printStackTrace();
149 | }
150 | }
151 |
152 | private void offscreenRenderDrawFrame() {
153 | try {
154 | ParcelFileDescriptor fence = aidlInterface.drawFrame();
155 | if (fence != null) {
156 | sharedTexture.waitFence(fence);
157 | }
158 | } catch (RemoteException e) {
159 | e.printStackTrace();
160 | }
161 | }
162 |
163 | private void offscreenRenderDestroy() {
164 | try {
165 | aidlInterface.destroy();
166 | } catch (RemoteException e) {
167 | e.printStackTrace();
168 | }
169 | }
170 |
171 | private void startOffscreenService() {
172 | Log.d(TAG, "startOffscreenService");
173 | if (aidlConnection == null) {
174 | aidlConnection = new ServiceConnection() {
175 | @Override
176 | public void onServiceConnected(ComponentName name, IBinder service) {
177 | Log.d(TAG, "onServiceConnected");
178 | aidlInterface = IOffscreenInterface.Stub.asInterface(service);
179 | aidlReady.set(true);
180 | }
181 |
182 | @Override
183 | public void onServiceDisconnected(ComponentName name) {
184 | Log.d(TAG, "onServiceDisconnected");
185 | }
186 | };
187 | }
188 |
189 | Intent it = new Intent(this, OffscreenService.class);
190 | bindService(it, aidlConnection, BIND_AUTO_CREATE | BIND_IMPORTANT);
191 | }
192 |
193 | private void stopOffscreenService() {
194 | Log.d(TAG, "stopOffscreenService");
195 |
196 | aidlReady.set(false);
197 |
198 | if (aidlInterface != null) {
199 | offscreenRenderDestroy();
200 | aidlInterface = null;
201 | }
202 |
203 | if (aidlConnection != null) {
204 | unbindService(aidlConnection);
205 | aidlConnection = null;
206 | }
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/app/src/main/java/me/robot9/shared/OffscreenService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SharedTexture
3 | * @author : keith@robot9.me
4 | *
5 | */
6 |
7 | package me.robot9.shared;
8 |
9 | import android.app.Service;
10 | import android.content.Intent;
11 | import android.hardware.HardwareBuffer;
12 | import android.opengl.EGL14;
13 | import android.opengl.EGLSurface;
14 | import android.opengl.GLES20;
15 | import android.os.IBinder;
16 | import android.os.ParcelFileDescriptor;
17 | import android.os.RemoteException;
18 | import android.util.Log;
19 |
20 | import androidx.annotation.Nullable;
21 |
22 | public class OffscreenService extends Service {
23 | private static final String TAG = "OffscreenService";
24 |
25 | private EGLCore outputEglCore = null;
26 | private EGLSurface outputEGLSurface = EGL14.EGL_NO_SURFACE;
27 |
28 | private SharedTexture sharedTexture;
29 | private int renderWidth = 0;
30 | private int renderHeight = 0;
31 | private int texId = 0;
32 | private int framebufferId = 0;
33 |
34 | private boolean renderReady = false;
35 |
36 | private float color = 0.f;
37 |
38 | @Nullable
39 | @Override
40 | public IBinder onBind(Intent intent) {
41 | return new IOffscreenInterface.Stub() {
42 |
43 | @Override
44 | public void init(HardwareBuffer buff, int width, int height) throws RemoteException {
45 | Log.d(TAG, "AIDL call: init");
46 | renderWidth = width;
47 | renderHeight = height;
48 |
49 | outputEglCore.makeCurrent(outputEGLSurface);
50 |
51 | // create gl texture and bind to SharedTexture
52 | texId = EGLCore.createTexture(GLES20.GL_TEXTURE_2D);
53 | sharedTexture = new SharedTexture(buff);
54 | sharedTexture.bindTexture(texId);
55 |
56 | // render to texture
57 | int[] framebuffers = new int[1];
58 | GLES20.glGenFramebuffers(1, framebuffers, 0);
59 | framebufferId = framebuffers[0];
60 |
61 | GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, framebufferId);
62 | GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, texId, 0);
63 |
64 | renderReady = true;
65 | }
66 |
67 | @Override
68 | public ParcelFileDescriptor drawFrame() throws RemoteException {
69 | if (!renderReady) {
70 | return null;
71 | }
72 |
73 | outputEglCore.makeCurrent(outputEGLSurface);
74 |
75 | GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, framebufferId);
76 | GLES20.glViewport(0, 0, renderWidth, renderHeight);
77 |
78 | // change clear color every frame
79 | color += 0.01f;
80 | if (color > 1.f) {
81 | color = 0.f;
82 | }
83 | GLES20.glClearColor(color, 0, 0, 1.0f);
84 | GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
85 |
86 | // TODO draw implementation
87 |
88 | GLES20.glFlush();
89 |
90 | return sharedTexture.createFence();
91 | }
92 |
93 | @Override
94 | public void destroy() throws RemoteException {
95 | Log.d(TAG, "AIDL call: destroy");
96 | if (!renderReady) {
97 | return;
98 | }
99 |
100 | sharedTexture.release();
101 | EGLCore.deleteTexture(texId);
102 |
103 | int[] frameBuffers = new int[]{framebufferId};
104 | GLES20.glDeleteFramebuffers(frameBuffers.length, frameBuffers, 0);
105 | }
106 | };
107 | }
108 |
109 | @Override
110 | public void onCreate() {
111 | super.onCreate();
112 | Log.d(TAG, "onCreate");
113 |
114 | // create egl context
115 | outputEglCore = new EGLCore(EGL14.EGL_NO_CONTEXT, EGLCore.FLAG_TRY_GLES3);
116 | outputEGLSurface = outputEglCore.createOffsetScreenSurface(renderWidth, renderHeight);
117 | }
118 |
119 | @Override
120 | public void onDestroy() {
121 | Log.d(TAG, "onDestroy");
122 |
123 | renderReady = false;
124 | outputEglCore.releaseSurface(outputEGLSurface);
125 | outputEglCore.release();
126 |
127 | outputEglCore = null;
128 | outputEGLSurface = EGL14.EGL_NO_SURFACE;
129 |
130 | super.onDestroy();
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/app/src/main/java/me/robot9/shared/OnscreenRenderView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SharedTexture
3 | * @author : keith@robot9.me
4 | *
5 | */
6 |
7 | package me.robot9.shared;
8 |
9 | import android.content.Context;
10 | import android.opengl.GLSurfaceView;
11 | import android.util.AttributeSet;
12 | import android.util.Log;
13 | import android.view.SurfaceHolder;
14 |
15 | import javax.microedition.khronos.egl.EGLConfig;
16 | import javax.microedition.khronos.opengles.GL10;
17 |
18 | public class OnscreenRenderView extends GLSurfaceView {
19 |
20 | private static final String TAG = "OnscreenRenderView";
21 |
22 | private int renderWidth = 0;
23 | private int renderHeight = 0;
24 |
25 | private boolean firstFrame = true;
26 | private SurfaceViewCallback callback;
27 |
28 | public OnscreenRenderView(Context context) {
29 | super(context);
30 | init();
31 | }
32 |
33 | public OnscreenRenderView(Context context, AttributeSet attrs) {
34 | super(context, attrs);
35 | init();
36 | }
37 |
38 | private void init() {
39 | setEGLContextClientVersion(3);
40 | setRenderer(new GLSurfaceView.Renderer() {
41 | @Override
42 | public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
43 | firstFrame = true;
44 | }
45 |
46 | @Override
47 | public void onSurfaceChanged(GL10 gl10, int width, int height) {
48 | renderWidth = width;
49 | renderHeight = height;
50 | }
51 |
52 | @Override
53 | public void onDrawFrame(GL10 gl10) {
54 | if (firstFrame) {
55 | firstFrame = false;
56 | if (callback != null) {
57 | callback.onCreated(renderWidth, renderHeight);
58 | }
59 | }
60 |
61 | if (callback != null) {
62 | callback.onDrawFrame();
63 | }
64 | }
65 | });
66 | }
67 |
68 | @Override
69 | public void surfaceCreated(SurfaceHolder holder) {
70 | Log.d(TAG, "surfaceCreated");
71 | super.surfaceCreated(holder);
72 | }
73 |
74 | @Override
75 | public void surfaceDestroyed(SurfaceHolder holder) {
76 | Log.d(TAG, "surfaceDestroyed");
77 | if (callback != null) {
78 | queueEvent(new Runnable() {
79 | @Override
80 | public void run() {
81 | callback.onDestroy();
82 | }
83 | });
84 | }
85 | super.surfaceDestroyed(holder);
86 | }
87 |
88 | public void setSurfaceViewCallback(SurfaceViewCallback callback) {
89 | this.callback = callback;
90 | }
91 |
92 | // all call back run in GLThread
93 | interface SurfaceViewCallback {
94 | void onCreated(int width, int height);
95 |
96 | void onDrawFrame();
97 |
98 | void onDestroy();
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/app/src/main/java/me/robot9/shared/QuadRenderer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SharedTexture
3 | * @author : keith@robot9.me
4 | *
5 | */
6 |
7 | package me.robot9.shared;
8 |
9 | import android.opengl.GLES20;
10 |
11 | import java.nio.ByteBuffer;
12 | import java.nio.ByteOrder;
13 | import java.nio.FloatBuffer;
14 |
15 | public class QuadRenderer {
16 | static float squareCoords[] = {
17 | -1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1
18 | };
19 | static float textureVertices[] = {
20 | 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0
21 | };
22 | static float textureVerticesFlipY[] = {
23 | 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1
24 | };
25 | private final String vertexShaderCode = "" +
26 | "precision mediump float;\n" +
27 | "attribute vec4 vPosition;\n" +
28 | "attribute vec4 inputTexCoordinate;\n" +
29 | "varying vec4 texCoordinate;\n" +
30 | "void main() {\n" +
31 | " gl_Position = vPosition;\n" +
32 | " texCoordinate = inputTexCoordinate;\n" +
33 | "}";
34 | private final String fragmentShaderCode = "" +
35 | "precision mediump float;\n" +
36 | "varying vec4 texCoordinate;\n" +
37 | "uniform sampler2D s_texture;\n" +
38 | "void main() {\n" +
39 | " gl_FragColor = texture2D(s_texture, vec2(texCoordinate.x,texCoordinate.y));\n" +
40 | "}";
41 | private FloatBuffer vertexBuffer, textureVerticesBuffer;
42 | private int mProgram;
43 | private int mPositionHandle;
44 | private int mTexCoordHandle;
45 | private int mTextureLocation;
46 | private int mFrameBuffer;
47 |
48 | public QuadRenderer() {
49 | this(false);
50 | }
51 |
52 | public QuadRenderer(boolean filpY) {
53 | // vertex
54 | ByteBuffer bb = ByteBuffer.allocateDirect(squareCoords.length * 4);
55 | bb.order(ByteOrder.nativeOrder());
56 | vertexBuffer = bb.asFloatBuffer();
57 | vertexBuffer.put(squareCoords);
58 | vertexBuffer.position(0);
59 |
60 | // texture
61 | float[] targetTextureVertices = filpY ? textureVerticesFlipY : textureVertices;
62 | ByteBuffer bb2 = ByteBuffer.allocateDirect(targetTextureVertices.length * 4);
63 | bb2.order(ByteOrder.nativeOrder());
64 | textureVerticesBuffer = bb2.asFloatBuffer();
65 | textureVerticesBuffer.put(targetTextureVertices);
66 | textureVerticesBuffer.position(0);
67 |
68 | // shader
69 | int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
70 | int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
71 | mProgram = GLES20.glCreateProgram();
72 | GLES20.glAttachShader(mProgram, vertexShader);
73 | GLES20.glAttachShader(mProgram, fragmentShader);
74 | GLES20.glLinkProgram(mProgram);
75 | GLES20.glDeleteShader(vertexShader);
76 | GLES20.glDeleteShader(fragmentShader);
77 |
78 | GLES20.glUseProgram(mProgram);
79 | mTextureLocation = GLES20.glGetUniformLocation(mProgram, "s_texture");
80 | mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
81 | mTexCoordHandle = GLES20.glGetAttribLocation(mProgram, "inputTexCoordinate");
82 |
83 | // fbo
84 | int[] frameBuffers = new int[1];
85 | GLES20.glGenFramebuffers(1, frameBuffers, 0);
86 | mFrameBuffer = frameBuffers[0];
87 | }
88 |
89 | public void drawTexture(int inputTexture, int width, int height) {
90 | GLES20.glUseProgram(mProgram);
91 |
92 | GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
93 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, inputTexture);
94 | GLES20.glUniform1i(mTextureLocation, 0);
95 |
96 | GLES20.glEnableVertexAttribArray(mPositionHandle);
97 | GLES20.glVertexAttribPointer(mPositionHandle, 2, GLES20.GL_FLOAT, false, 0, vertexBuffer);
98 |
99 | GLES20.glEnableVertexAttribArray(mTexCoordHandle);
100 | GLES20.glVertexAttribPointer(mTexCoordHandle, 2, GLES20.GL_FLOAT, false, 0, textureVerticesBuffer);
101 |
102 | // draw
103 | GLES20.glViewport(0, 0, width, height);
104 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 6);
105 |
106 | GLES20.glDisableVertexAttribArray(mPositionHandle);
107 | GLES20.glDisableVertexAttribArray(mTexCoordHandle);
108 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
109 | GLES20.glUseProgram(0);
110 | }
111 |
112 | private int loadShader(int type, String shaderCode) {
113 | int shader = GLES20.glCreateShader(type);
114 | GLES20.glShaderSource(shader, shaderCode);
115 | GLES20.glCompileShader(shader);
116 | return shader;
117 | }
118 |
119 | public void release() {
120 | int[] frameBuffers = new int[]{mFrameBuffer};
121 | GLES20.glDeleteFramebuffers(frameBuffers.length, frameBuffers, 0);
122 | GLES20.glDeleteProgram(mProgram);
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/keith2018/SharedTexture/1995d41956b15879b26516fdb4e23414ee86f28d/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/keith2018/SharedTexture/1995d41956b15879b26516fdb4e23414ee86f28d/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/keith2018/SharedTexture/1995d41956b15879b26516fdb4e23414ee86f28d/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/keith2018/SharedTexture/1995d41956b15879b26516fdb4e23414ee86f28d/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/keith2018/SharedTexture/1995d41956b15879b26516fdb4e23414ee86f28d/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/keith2018/SharedTexture/1995d41956b15879b26516fdb4e23414ee86f28d/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/keith2018/SharedTexture/1995d41956b15879b26516fdb4e23414ee86f28d/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/keith2018/SharedTexture/1995d41956b15879b26516fdb4e23414ee86f28d/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/keith2018/SharedTexture/1995d41956b15879b26516fdb4e23414ee86f28d/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/keith2018/SharedTexture/1995d41956b15879b26516fdb4e23414ee86f28d/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | SharedTexture Demo
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/test/java/me/robot9/shared/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package me.robot9.shared;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | id 'com.android.application' version '7.4.2' apply false
4 | id 'com.android.library' version '7.4.2' apply false
5 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Enables namespacing of each library's R class so that its R class includes only the
19 | # resources declared in the library itself and none from the library's dependencies,
20 | # thereby reducing the size of the R class for that library
21 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/keith2018/SharedTexture/1995d41956b15879b26516fdb4e23414ee86f28d/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri May 12 12:43:41 CST 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/image/eglimage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/keith2018/SharedTexture/1995d41956b15879b26516fdb4e23414ee86f28d/image/eglimage.png
--------------------------------------------------------------------------------
/image/eglsync.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/keith2018/SharedTexture/1995d41956b15879b26516fdb4e23414ee86f28d/image/eglsync.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "Demo"
16 | include ':app'
17 | include ':shared'
18 |
--------------------------------------------------------------------------------
/shared/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/shared/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | }
4 |
5 | android {
6 | namespace 'me.robot9.shared'
7 | compileSdk 33
8 |
9 | defaultConfig {
10 | minSdk 26
11 | targetSdk 33
12 |
13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
14 | consumerProguardFiles "consumer-rules.pro"
15 |
16 | externalNativeBuild {
17 | cmake {
18 | cppFlags '-std=c++11'
19 | }
20 | }
21 | }
22 |
23 | buildTypes {
24 | release {
25 | minifyEnabled false
26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
27 | }
28 | }
29 | compileOptions {
30 | sourceCompatibility JavaVersion.VERSION_1_8
31 | targetCompatibility JavaVersion.VERSION_1_8
32 | }
33 | externalNativeBuild {
34 | cmake {
35 | path file('src/main/cpp/CMakeLists.txt')
36 | version '3.22.1'
37 | }
38 | }
39 | }
40 |
41 | dependencies {
42 |
43 | implementation 'androidx.appcompat:appcompat:1.6.1'
44 | implementation 'com.google.android.material:material:1.9.0'
45 |
46 | testImplementation 'junit:junit:4.13.2'
47 | androidTestImplementation 'androidx.test.ext:junit:1.1.5'
48 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
49 | }
--------------------------------------------------------------------------------
/shared/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/keith2018/SharedTexture/1995d41956b15879b26516fdb4e23414ee86f28d/shared/consumer-rules.pro
--------------------------------------------------------------------------------
/shared/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/shared/src/androidTest/java/me/robot9/shared/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package me.robot9.shared;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.InstrumentationRegistry;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 | assertEquals("me.robot9.shared.test", appContext.getPackageName());
25 | }
26 | }
--------------------------------------------------------------------------------
/shared/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shared/src/main/cpp/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # For more information about using CMake with Android Studio, read the
2 | # documentation: https://d.android.com/studio/projects/add-native-code.html
3 |
4 | # Sets the minimum version of CMake required to build the native library.
5 |
6 | cmake_minimum_required(VERSION 3.10.2)
7 |
8 | project("shared-texture")
9 |
10 | add_library(shared-texture SHARED
11 | SharedTexture.cpp
12 | JSharedTexture.cpp)
13 |
14 | find_library(android-lib android)
15 | find_library(log-lib log)
16 |
17 | target_link_libraries(shared-texture
18 | GLESv2
19 | EGL
20 | jnigraphics
21 | ${android-lib}
22 | ${log-lib})
23 |
--------------------------------------------------------------------------------
/shared/src/main/cpp/JSharedTexture.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * SharedTexture
3 | * @author : keith@robot9.me
4 | *
5 | */
6 |
7 | #include "SharedTexture.h"
8 | #include "JniLog.h"
9 |
10 | namespace robot9 {
11 |
12 | class SharedTextureContext {
13 | public:
14 | std::shared_ptr buffer;
15 | };
16 |
17 | }
18 |
19 | extern "C"
20 | JNIEXPORT jboolean JNICALL
21 | Java_me_robot9_shared_SharedTexture_available(JNIEnv *env, jclass clazz) {
22 | return robot9::SharedTexture::available() ? JNI_TRUE : JNI_FALSE;
23 | }
24 |
25 | extern "C"
26 | JNIEXPORT jlong JNICALL
27 | Java_me_robot9_shared_SharedTexture_create(JNIEnv *env, jobject /* this */,
28 | jint width, jint height) {
29 | auto SharedTexture = robot9::SharedTexture::Make(width, height);
30 | if (SharedTexture) {
31 | auto *ctx = new robot9::SharedTextureContext();
32 | ctx->buffer = std::move(SharedTexture);
33 | return reinterpret_cast(ctx);
34 | }
35 | LOGE("create SharedTexture failed");
36 | return 0;
37 | }
38 |
39 | extern "C"
40 | JNIEXPORT jlong JNICALL
41 | Java_me_robot9_shared_SharedTexture_createFromBuffer(JNIEnv *env, jobject /* this */,
42 | jobject buffer) {
43 | auto SharedTexture = robot9::SharedTexture::MakeAdoptedJObject(env, buffer);
44 | if (SharedTexture) {
45 | auto *ctx = new robot9::SharedTextureContext();
46 | ctx->buffer = std::move(SharedTexture);
47 | return reinterpret_cast(ctx);
48 | }
49 | LOGE("create SharedTexture failed");
50 | return 0;
51 | }
52 |
53 | extern "C"
54 | JNIEXPORT jobject JNICALL
55 | Java_me_robot9_shared_SharedTexture_getBuffer(JNIEnv *env, jobject /* this */,
56 | jlong ctx) {
57 | if (ctx == 0) {
58 | return nullptr;
59 | }
60 |
61 | auto *_ctx = reinterpret_cast(ctx);
62 | if (!_ctx->buffer) {
63 | return nullptr;
64 | }
65 | return _ctx->buffer->getBufferJObject(env);
66 | }
67 |
68 | extern "C"
69 | JNIEXPORT jboolean JNICALL
70 | Java_me_robot9_shared_SharedTexture_bindTexture(JNIEnv *env, jobject /* this */,
71 | jlong ctx, jint texId) {
72 | if (ctx == 0) {
73 | return JNI_FALSE;
74 | }
75 |
76 | auto *_ctx = reinterpret_cast(ctx);
77 | if (!_ctx->buffer) {
78 | return JNI_FALSE;
79 | }
80 | return _ctx->buffer->bindTexture(texId) ? JNI_TRUE : JNI_FALSE;
81 | }
82 |
83 | extern "C"
84 | JNIEXPORT jint JNICALL
85 | Java_me_robot9_shared_SharedTexture_getWidth(JNIEnv *env, jobject /* this */,
86 | jlong ctx) {
87 | if (ctx == 0) {
88 | return 0;
89 | }
90 |
91 | auto *_ctx = reinterpret_cast(ctx);
92 | if (!_ctx->buffer) {
93 | return 0;
94 | }
95 | return _ctx->buffer->getWidth();
96 | }
97 |
98 | extern "C"
99 | JNIEXPORT jint JNICALL
100 | Java_me_robot9_shared_SharedTexture_getHeight(JNIEnv *env, jobject /* this */,
101 | jlong ctx) {
102 | if (ctx == 0) {
103 | return 0;
104 | }
105 |
106 | auto *_ctx = reinterpret_cast(ctx);
107 | if (!_ctx->buffer) {
108 | return 0;
109 | }
110 | return _ctx->buffer->getHeight();
111 | }
112 |
113 | extern "C"
114 | JNIEXPORT jint JNICALL
115 | Java_me_robot9_shared_SharedTexture_getBindTexture(JNIEnv *env, jobject /* this */,
116 | jlong ctx) {
117 | if (ctx == 0) {
118 | return 0;
119 | }
120 |
121 | auto *_ctx = reinterpret_cast(ctx);
122 | if (!_ctx->buffer) {
123 | return 0;
124 | }
125 | return static_cast(_ctx->buffer->getBindTexture());
126 | }
127 |
128 | extern "C"
129 | JNIEXPORT jint JNICALL
130 | Java_me_robot9_shared_SharedTexture_createEGLFence(JNIEnv *env, jclass clazz) {
131 | return robot9::SharedTexture::createEGLFence();
132 | }
133 |
134 | extern "C"
135 | JNIEXPORT jboolean JNICALL
136 | Java_me_robot9_shared_SharedTexture_waitEGLFence(JNIEnv *env, jclass clazz,
137 | jint fenceFd) {
138 | return robot9::SharedTexture::waitEGLFence(fenceFd) ? JNI_TRUE : JNI_FALSE;
139 | }
140 |
141 | extern "C"
142 | JNIEXPORT void JNICALL
143 | Java_me_robot9_shared_SharedTexture_destroy(JNIEnv *env, jobject /* this */,
144 | jlong ctx) {
145 | if (ctx == 0) {
146 | return;
147 | }
148 | auto *_ctx = reinterpret_cast(ctx);
149 | delete _ctx;
150 | }
151 |
--------------------------------------------------------------------------------
/shared/src/main/cpp/JniLog.h:
--------------------------------------------------------------------------------
1 | /*
2 | * SharedTexture
3 | * @author : keith@robot9.me
4 | *
5 | */
6 |
7 | #include
8 | #include
9 |
10 | #define LOG_TAG "SharedTexture-JNI"
11 | #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
12 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
13 | #define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
14 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
15 | #define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__)
16 |
--------------------------------------------------------------------------------
/shared/src/main/cpp/SharedTexture.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * SharedTexture
3 | * @author : keith@robot9.me
4 | *
5 | */
6 |
7 | #include "SharedTexture.h"
8 | #include "JniLog.h"
9 |
10 | #include
11 | #include
12 | #include
13 |
14 | namespace robot9 {
15 |
16 | namespace glext {
17 |
18 | PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC eglGetNativeClientBufferANDROID = nullptr;
19 | PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = nullptr;
20 | PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR = nullptr;
21 | PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR = nullptr;
22 | PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR = nullptr;
23 | PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR = nullptr;
24 | PFNEGLWAITSYNCKHRPROC eglWaitSyncKHR = nullptr;
25 | PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID = nullptr;
26 |
27 | }
28 | using namespace glext;
29 |
30 | std::once_flag glProcOnceFlag;
31 | static bool initGLExtProc() noexcept {
32 | std::call_once(glProcOnceFlag, []() {
33 | glext::eglGetNativeClientBufferANDROID = (PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC) eglGetProcAddress("eglGetNativeClientBufferANDROID");
34 | glext::glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) eglGetProcAddress("glEGLImageTargetTexture2DOES");
35 | glext::eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC) eglGetProcAddress("eglCreateImageKHR");
36 | glext::eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC) eglGetProcAddress("eglDestroyImageKHR");
37 | glext::eglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC) eglGetProcAddress("eglCreateSyncKHR");
38 | glext::eglDestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC) eglGetProcAddress("eglDestroySyncKHR");
39 | glext::eglWaitSyncKHR = (PFNEGLWAITSYNCKHRPROC) eglGetProcAddress("eglWaitSyncKHR");
40 | glext::eglDupNativeFenceFDANDROID = (PFNEGLDUPNATIVEFENCEFDANDROIDPROC) eglGetProcAddress("eglDupNativeFenceFDANDROID");
41 | });
42 | return glext::eglGetNativeClientBufferANDROID
43 | && glext::glEGLImageTargetTexture2DOES
44 | && glext::eglCreateImageKHR
45 | && glext::eglDestroyImageKHR
46 | && glext::eglCreateSyncKHR
47 | && glext::eglDestroySyncKHR
48 | && glext::eglWaitSyncKHR
49 | && glext::eglDupNativeFenceFDANDROID;
50 | }
51 |
52 | typedef int (*Func_AHardwareBuffer_allocate)(const AHardwareBuffer_Desc *, AHardwareBuffer **);
53 | typedef void (*Func_AHardwareBuffer_release)(AHardwareBuffer *);
54 | typedef int (*Func_AHardwareBuffer_lock)(AHardwareBuffer *buffer, uint64_t usage, int32_t fence, const ARect *rect, void **outVirtualAddress);
55 | typedef int (*Func_AHardwareBuffer_unlock)(AHardwareBuffer *buffer, int32_t *fence);
56 | typedef void (*Func_AHardwareBuffer_describe)(const AHardwareBuffer *buffer, AHardwareBuffer_Desc *outDesc);
57 | typedef void (*Func_AHardwareBuffer_acquire)(AHardwareBuffer *buffer);
58 |
59 | typedef AHardwareBuffer *(*Func_AHardwareBuffer_fromHardwareBuffer)(JNIEnv *env, jobject hardwareBufferObj);
60 | typedef jobject (*Func_AHardwareBuffer_toHardwareBuffer)(JNIEnv *env, AHardwareBuffer *hardwareBuffer);
61 |
62 | class HWDriver {
63 | public:
64 | static Func_AHardwareBuffer_allocate AHardwareBuffer_allocate;
65 | static Func_AHardwareBuffer_release AHardwareBuffer_release;
66 | static Func_AHardwareBuffer_lock AHardwareBuffer_lock;
67 | static Func_AHardwareBuffer_unlock AHardwareBuffer_unlock;
68 | static Func_AHardwareBuffer_describe AHardwareBuffer_describe;
69 | static Func_AHardwareBuffer_acquire AHardwareBuffer_acquire;
70 | static Func_AHardwareBuffer_fromHardwareBuffer AHardwareBuffer_fromHardwareBuffer;
71 | static Func_AHardwareBuffer_toHardwareBuffer AHardwareBuffer_toHardwareBuffer;
72 |
73 | template
74 | static void loadSymbol(T *&pfn, const char *symbol) {
75 | pfn = (T *) dlsym(RTLD_DEFAULT, symbol);
76 | }
77 |
78 | static bool initFunctions() noexcept {
79 | loadSymbol(AHardwareBuffer_allocate, "AHardwareBuffer_allocate");
80 | loadSymbol(AHardwareBuffer_release, "AHardwareBuffer_release");
81 | loadSymbol(AHardwareBuffer_lock, "AHardwareBuffer_lock");
82 | loadSymbol(AHardwareBuffer_unlock, "AHardwareBuffer_unlock");
83 | loadSymbol(AHardwareBuffer_describe, "AHardwareBuffer_describe");
84 | loadSymbol(AHardwareBuffer_acquire, "AHardwareBuffer_acquire");
85 | loadSymbol(AHardwareBuffer_fromHardwareBuffer, "AHardwareBuffer_fromHardwareBuffer");
86 | loadSymbol(AHardwareBuffer_toHardwareBuffer, "AHardwareBuffer_toHardwareBuffer");
87 |
88 | return AHardwareBuffer_allocate && AHardwareBuffer_release
89 | && AHardwareBuffer_lock && AHardwareBuffer_unlock
90 | && AHardwareBuffer_describe && AHardwareBuffer_acquire
91 | && AHardwareBuffer_fromHardwareBuffer && AHardwareBuffer_toHardwareBuffer;
92 | }
93 | };
94 |
95 | Func_AHardwareBuffer_allocate HWDriver::AHardwareBuffer_allocate = nullptr;
96 | Func_AHardwareBuffer_release HWDriver::AHardwareBuffer_release = nullptr;
97 | Func_AHardwareBuffer_lock HWDriver::AHardwareBuffer_lock = nullptr;
98 | Func_AHardwareBuffer_unlock HWDriver::AHardwareBuffer_unlock = nullptr;
99 | Func_AHardwareBuffer_describe HWDriver::AHardwareBuffer_describe = nullptr;
100 | Func_AHardwareBuffer_acquire HWDriver::AHardwareBuffer_acquire = nullptr;
101 | Func_AHardwareBuffer_fromHardwareBuffer HWDriver::AHardwareBuffer_fromHardwareBuffer = nullptr;
102 | Func_AHardwareBuffer_toHardwareBuffer HWDriver::AHardwareBuffer_toHardwareBuffer = nullptr;
103 |
104 | static const char *getEGLError() {
105 | switch (eglGetError()) {
106 | case EGL_SUCCESS:return "EGL_SUCCESS";
107 | case EGL_NOT_INITIALIZED:return "EGL_NOT_INITIALIZED";
108 | case EGL_BAD_ACCESS:return "EGL_BAD_ACCESS";
109 | case EGL_BAD_ALLOC:return "EGL_BAD_ALLOC";
110 | case EGL_BAD_ATTRIBUTE:return "EGL_BAD_ATTRIBUTE";
111 | case EGL_BAD_CONTEXT:return "EGL_BAD_CONTEXT";
112 | case EGL_BAD_CONFIG:return "EGL_BAD_CONFIG";
113 | case EGL_BAD_CURRENT_SURFACE:return "EGL_BAD_CURRENT_SURFACE";
114 | case EGL_BAD_DISPLAY:return "EGL_BAD_DISPLAY";
115 | case EGL_BAD_SURFACE:return "EGL_BAD_SURFACE";
116 | case EGL_BAD_MATCH:return "EGL_BAD_MATCH";
117 | case EGL_BAD_PARAMETER:return "EGL_BAD_PARAMETER";
118 | case EGL_BAD_NATIVE_PIXMAP:return "EGL_BAD_NATIVE_PIXMAP";
119 | case EGL_BAD_NATIVE_WINDOW:return "EGL_BAD_NATIVE_WINDOW";
120 | case EGL_CONTEXT_LOST:return "EGL_CONTEXT_LOST";
121 | default:return "Unknown error";
122 | }
123 | }
124 |
125 | bool SharedTexture::AVAILABLE = initGLExtProc() && HWDriver::initFunctions();
126 |
127 | bool SharedTexture::available() {
128 | return AVAILABLE;
129 | }
130 |
131 | std::shared_ptr SharedTexture::Make(int width, int height) {
132 | if (!AVAILABLE) {
133 | LOGE("Make failed: not AVAILABLE");
134 | return nullptr;
135 | }
136 | AHardwareBuffer *buffer = nullptr;
137 | AHardwareBuffer_Desc desc = {
138 | static_cast(width),
139 | static_cast(height),
140 | 1,
141 | AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
142 | AHARDWAREBUFFER_USAGE_CPU_READ_NEVER | AHARDWAREBUFFER_USAGE_CPU_WRITE_NEVER |
143 | AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT,
144 | 0,
145 | 0,
146 | 0};
147 | int errCode = HWDriver::AHardwareBuffer_allocate(&desc, &buffer);
148 | if (errCode != 0 || !buffer) {
149 | LOGE("Make failed: AHardwareBuffer_allocate error: %d", errCode);
150 | return nullptr;
151 | }
152 | return std::shared_ptr(new SharedTexture(buffer, static_cast(desc.width), static_cast(desc.height)));
153 | }
154 |
155 | std::shared_ptr SharedTexture::MakeAdopted(AHardwareBuffer *buffer) {
156 | if (!AVAILABLE) {
157 | LOGE("MakeAdopted failed: not AVAILABLE");
158 | return nullptr;
159 | }
160 | if (!buffer) {
161 | LOGE("MakeAdopted failed: buffer null");
162 | return nullptr;
163 | }
164 | AHardwareBuffer_Desc desc;
165 | HWDriver::AHardwareBuffer_describe(buffer, &desc);
166 | HWDriver::AHardwareBuffer_acquire(buffer);
167 | return std::shared_ptr(new SharedTexture(buffer, static_cast(desc.width), static_cast(desc.height)));
168 | }
169 |
170 | std::shared_ptr SharedTexture::MakeAdoptedJObject(JNIEnv *env, jobject buffer) {
171 | if (!AVAILABLE) {
172 | LOGE("MakeAdoptedJObject failed: not AVAILABLE");
173 | return nullptr;
174 | }
175 | if (!buffer) {
176 | LOGE("MakeAdoptedJObject failed: buffer null");
177 | return nullptr;
178 | }
179 | AHardwareBuffer *hwBuffer = HWDriver::AHardwareBuffer_fromHardwareBuffer(env, buffer);
180 | return MakeAdopted(hwBuffer);
181 | }
182 |
183 | SharedTexture::SharedTexture(AHardwareBuffer *buffer, int width, int height)
184 | : buffer_(buffer), width_(width), height_(height) {
185 | LOGD("SharedTexture(%d, %d)", width, height);
186 | }
187 |
188 | SharedTexture::~SharedTexture() {
189 | LOGD("~SharedTexture");
190 | EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
191 | if (eglImage_ != EGL_NO_IMAGE_KHR) {
192 | glext::eglDestroyImageKHR(display, eglImage_);
193 | eglImage_ = EGL_NO_IMAGE_KHR;
194 | }
195 | if (buffer_) {
196 | HWDriver::AHardwareBuffer_release(buffer_);
197 | }
198 | }
199 |
200 | bool SharedTexture::bindTexture(unsigned texId) {
201 | if (!AVAILABLE) {
202 | LOGE("bindTexture failed: not AVAILABLE");
203 | return false;
204 | }
205 |
206 | if (!buffer_ || texId == 0) {
207 | LOGE("bindTexture failed: buffer_ or texId null");
208 | return false;
209 | }
210 |
211 | EGLClientBuffer clientBuffer = glext::eglGetNativeClientBufferANDROID(buffer_);
212 | if (!clientBuffer) {
213 | LOGE("bindTexture failed: clientBuffer null");
214 | return false;
215 | }
216 |
217 | EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
218 |
219 | if (eglImage_ != EGL_NO_IMAGE_KHR) {
220 | glext::eglDestroyImageKHR(display, eglImage_);
221 | }
222 |
223 | EGLint eglImageAttributes[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
224 | eglImage_ = glext::eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,
225 | clientBuffer, eglImageAttributes);
226 | if (eglImage_ == EGL_NO_IMAGE_KHR) {
227 | LOGE("bindTexture failed: eglCreateImageKHR null: %s", getEGLError());
228 | return false;
229 | }
230 |
231 | glBindTexture(GL_TEXTURE_2D, texId);
232 | glext::glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES) eglImage_);
233 |
234 | bindTexId_ = texId;
235 | return true;
236 | }
237 |
238 | AHardwareBuffer *SharedTexture::getBuffer() const {
239 | return buffer_;
240 | }
241 |
242 | jobject SharedTexture::getBufferJObject(JNIEnv *env) const {
243 | if (!AVAILABLE) {
244 | LOGE("getBufferJObject null: not AVAILABLE");
245 | return nullptr;
246 | }
247 | if (!buffer_) {
248 | LOGE("getBufferJObject null: buffer_ null");
249 | return nullptr;
250 | }
251 | return HWDriver::AHardwareBuffer_toHardwareBuffer(env, buffer_);
252 | }
253 |
254 | int SharedTexture::getWidth() const {
255 | return width_;
256 | }
257 |
258 | int SharedTexture::getHeight() const {
259 | return height_;
260 | }
261 |
262 | unsigned SharedTexture::getBindTexture() const {
263 | return bindTexId_;
264 | }
265 |
266 | int SharedTexture::createEGLFence() {
267 | if (!AVAILABLE) {
268 | LOGE("createEGLFence null: not AVAILABLE");
269 | return EGL_NO_NATIVE_FENCE_FD_ANDROID;
270 | }
271 |
272 | EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
273 | EGLSyncKHR eglSync = glext::eglCreateSyncKHR(display, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
274 | if (eglSync == EGL_NO_SYNC_KHR) {
275 | LOGE("createEGLFence null: eglCreateSyncKHR null: %s", getEGLError());
276 | return EGL_NO_NATIVE_FENCE_FD_ANDROID;
277 | }
278 |
279 | // need flush before wait
280 | glFlush();
281 |
282 | int fenceFd = glext::eglDupNativeFenceFDANDROID(display, eglSync);
283 | glext::eglDestroySyncKHR(display, eglSync);
284 |
285 | if (fenceFd == EGL_NO_NATIVE_FENCE_FD_ANDROID) {
286 | LOGE("createEGLFence null: eglDupNativeFenceFDANDROID error: %s", getEGLError());
287 | }
288 |
289 | return fenceFd;
290 | }
291 |
292 | bool SharedTexture::waitEGLFence(int fenceFd) {
293 | if (!AVAILABLE) {
294 | LOGE("waitEGLFence failed: not AVAILABLE");
295 | return false;
296 | }
297 |
298 | EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
299 | EGLint attribs[] = {EGL_SYNC_NATIVE_FENCE_FD_ANDROID, fenceFd, EGL_NONE};
300 | EGLSyncKHR eglSync = glext::eglCreateSyncKHR(display, EGL_SYNC_NATIVE_FENCE_ANDROID, attribs);
301 | if (eglSync == EGL_NO_SYNC_KHR) {
302 | LOGE("waitEGLFence failed: eglCreateSyncKHR null: %s", getEGLError());
303 | close(fenceFd);
304 | return false;
305 | }
306 |
307 | EGLint success = glext::eglWaitSyncKHR(display, eglSync, 0);
308 | glext::eglDestroySyncKHR(display, eglSync);
309 |
310 | if (success == EGL_FALSE) {
311 | LOGE("waitEGLFence failed: eglWaitSyncKHR fail: %s", getEGLError());
312 | return false;
313 | }
314 |
315 | return true;
316 | }
317 |
318 | }
319 |
--------------------------------------------------------------------------------
/shared/src/main/cpp/SharedTexture.h:
--------------------------------------------------------------------------------
1 | /*
2 | * SharedTexture
3 | * @author : keith@robot9.me
4 | *
5 | */
6 |
7 | #pragma once
8 |
9 | #include
10 | #include
11 |
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 |
19 | namespace robot9 {
20 |
21 | class SharedTexture {
22 | public:
23 | static bool available();
24 |
25 | static std::shared_ptr Make(int width, int height);
26 |
27 | static std::shared_ptr MakeAdopted(AHardwareBuffer *buffer);
28 |
29 | static std::shared_ptr MakeAdoptedJObject(JNIEnv *env, jobject buffer);
30 |
31 | virtual ~SharedTexture();
32 |
33 | bool bindTexture(unsigned texId);
34 |
35 | AHardwareBuffer *getBuffer() const;
36 |
37 | jobject getBufferJObject(JNIEnv *env) const;
38 |
39 | int getWidth() const;
40 |
41 | int getHeight() const;
42 |
43 | unsigned getBindTexture() const;
44 |
45 | static int createEGLFence();
46 |
47 | static bool waitEGLFence(int fenceFd);
48 |
49 | private:
50 | SharedTexture(AHardwareBuffer *buffer, int width, int height);
51 |
52 | private:
53 | static bool AVAILABLE;
54 |
55 | AHardwareBuffer *buffer_ = nullptr;
56 | EGLImageKHR eglImage_ = EGL_NO_IMAGE_KHR;
57 | int width_ = 0;
58 | int height_ = 0;
59 | GLuint bindTexId_ = 0;
60 | };
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/shared/src/main/java/me/robot9/shared/SharedTexture.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SharedTexture
3 | * @author : keith@robot9.me
4 | *
5 | */
6 |
7 | package me.robot9.shared;
8 |
9 | import android.hardware.HardwareBuffer;
10 | import android.os.ParcelFileDescriptor;
11 | import android.util.Log;
12 |
13 |
14 | public class SharedTexture {
15 |
16 | private static final String TAG = "SharedTexture";
17 | private static final int EGL_NO_NATIVE_FENCE_FD_ANDROID = -1;
18 | private static boolean available = false;
19 |
20 | static {
21 | System.loadLibrary("shared-texture");
22 | }
23 |
24 | static {
25 | try {
26 | available = available();
27 | } catch (Throwable t) {
28 | Log.e(TAG, "SharedTexture check available error: " + t);
29 | }
30 |
31 | Log.d(TAG, "SharedTexture available: " + available);
32 | if (!available) {
33 | Log.e(TAG, "SharedTexture not available");
34 | }
35 | }
36 |
37 | private long nativeContext = 0;
38 | private HardwareBuffer buffer = null;
39 |
40 | public SharedTexture(HardwareBuffer buff) {
41 | Log.d(TAG, "create SharedTexture from buffer");
42 | nativeContext = createFromBuffer(buff);
43 | buffer = buff;
44 | }
45 |
46 | public SharedTexture(int width, int height) {
47 | Log.d(TAG, "create new SharedTexture:(" + width + "," + height + ")");
48 | nativeContext = create(width, height);
49 | }
50 |
51 | public static boolean isAvailable() {
52 | return available;
53 | }
54 |
55 | public HardwareBuffer getHardwareBuffer() {
56 | if (nativeContext != 0) {
57 | if (buffer != null) {
58 | buffer.close();
59 | buffer = null;
60 | }
61 | buffer = getBuffer(nativeContext);
62 | return buffer;
63 | }
64 | return null;
65 | }
66 |
67 | public boolean bindTexture(int texId) {
68 | if (nativeContext != 0) {
69 | return bindTexture(nativeContext, texId);
70 | }
71 | return false;
72 | }
73 |
74 | public int getBufferWidth() {
75 | if (nativeContext != 0) {
76 | return getWidth(nativeContext);
77 | }
78 | return 0;
79 | }
80 |
81 | public int getBufferHeight() {
82 | if (nativeContext != 0) {
83 | return getHeight(nativeContext);
84 | }
85 | return 0;
86 | }
87 |
88 | public int getBindTexture() {
89 | if (nativeContext != 0) {
90 | return getBindTexture(nativeContext);
91 | }
92 | return 0;
93 | }
94 |
95 | public ParcelFileDescriptor createFence() {
96 | int fd = createEGLFence();
97 | if (fd != EGL_NO_NATIVE_FENCE_FD_ANDROID) {
98 | return ParcelFileDescriptor.adoptFd(fd);
99 | }
100 | return null;
101 | }
102 |
103 | public boolean waitFence(ParcelFileDescriptor pfd) {
104 | if (pfd != null) {
105 | int fd = pfd.detachFd();
106 | if (fd != EGL_NO_NATIVE_FENCE_FD_ANDROID) {
107 | return waitEGLFence(fd);
108 | }
109 | }
110 |
111 | return false;
112 | }
113 |
114 | public void release() {
115 | if (nativeContext != 0) {
116 | Log.d(TAG, "destroy");
117 | if (buffer != null) {
118 | buffer.close();
119 | buffer = null;
120 | }
121 | destroy(nativeContext);
122 | nativeContext = 0;
123 | }
124 | }
125 |
126 | @Override
127 | protected void finalize() throws Throwable {
128 | super.finalize();
129 | release();
130 | }
131 |
132 | private static native boolean available();
133 |
134 | private static native int createEGLFence();
135 |
136 | private static native boolean waitEGLFence(int fenceFd);
137 |
138 | private native long create(int width, int height);
139 |
140 | private native long createFromBuffer(HardwareBuffer buffer);
141 |
142 | private native HardwareBuffer getBuffer(long ctx);
143 |
144 | private native boolean bindTexture(long ctx, int texId);
145 |
146 | private native int getWidth(long ctx);
147 |
148 | private native int getHeight(long ctx);
149 |
150 | private native int getBindTexture(long ctx);
151 |
152 | private native void destroy(long ctx);
153 | }
154 |
--------------------------------------------------------------------------------
/shared/src/test/java/me/robot9/shared/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package me.robot9.shared;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------