OpenGlRenderer.java
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.camera.core.processing;
import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.WorkerThread;
import androidx.core.util.Preconditions;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* OpenGLRenderer renders texture image to the output surface.
*
* <p>OpenGLRenderer's methods must run on the same thread, so called GL thread. The GL thread is
* locked as the thread running the {@link #init(ShaderProvider)} method, otherwise an
* {@link IllegalStateException} will be thrown when other methods are called.
*/
@WorkerThread
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public final class OpenGlRenderer {
static {
System.loadLibrary("camerax_core_opengl_renderer_jni");
}
private final AtomicBoolean mInitialized = new AtomicBoolean(false);
private final ThreadLocal<Long> mNativeContext = new ThreadLocal<>();
/**
* Initializes the OpenGLRenderer
*
* <p>Initialization must be done before calling other methods, otherwise an
* {@link IllegalStateException} will be thrown. Following methods must run on the same
* thread as this method, so called GL thread, otherwise an {@link IllegalStateException}
* will be thrown.
*
* @throws IllegalStateException if the renderer is already initialized.
* @throws IllegalArgumentException if the ShaderProvider fails to create shader or provides
* invalid shader string.
*/
public void init(@NonNull ShaderProvider shaderProvider) {
checkInitializedOrThrow(false);
long nativeContext;
if (shaderProvider == ShaderProvider.DEFAULT) {
nativeContext = initContext(null);
} else {
List<String> varNames = getShaderVariableNames();
Preconditions.checkState(varNames.size() == 2);
String fragmentCoords = varNames.get(0);
String sampler = varNames.get(1);
String fragmentShader;
try {
fragmentShader = shaderProvider.createFragmentShader(sampler, fragmentCoords);
} catch (Throwable t) {
throw new IllegalArgumentException("Unable to create custom fragment shader", t);
}
nativeContext = initContext(fragmentShader);
}
mNativeContext.set(nativeContext);
mInitialized.set(true);
}
/**
* Releases the OpenGLRenderer
*
* @throws IllegalStateException if the caller doesn't run on the GL thread.
*/
public void release() {
if (!mInitialized.getAndSet(false)) {
return;
}
long nativeContext = getNativeContextOrThrow();
closeContext(nativeContext);
mNativeContext.remove();
}
/**
* Set the output surface.
*
* @throws IllegalStateException if the renderer is not initialized or the caller doesn't run
* on the GL thread.
*/
public void setOutputSurface(@NonNull Surface surface) {
checkInitializedOrThrow(true);
long nativeContext = getNativeContextOrThrow();
setWindowSurface(nativeContext, surface);
}
/**
* Gets the texture name.
*
* @return the texture name
* @throws IllegalStateException if the renderer is not initialized or the caller doesn't run
* on the GL thread.
*/
public int getTextureName() {
checkInitializedOrThrow(true);
long nativeContext = getNativeContextOrThrow();
return getTexName(nativeContext);
}
/**
* Renders the texture image to the output surface.
*
* @throws IllegalStateException if the renderer is not initialized or the caller doesn't run
* on the GL thread.
*/
public void render(long timestampNs, @NonNull float[] textureTransform) {
checkInitializedOrThrow(true);
long nativeContext = getNativeContextOrThrow();
renderTexture(nativeContext, timestampNs, textureTransform);
}
private void checkInitializedOrThrow(boolean shouldInitialized) {
boolean result = shouldInitialized == mInitialized.get();
String message = shouldInitialized ? "OpenGlRenderer is not initialized"
: "OpenGlRenderer is already initialized";
Preconditions.checkState(result, message);
}
private long getNativeContextOrThrow() {
Long nativeContext = mNativeContext.get();
Preconditions.checkState(nativeContext != null,
"Method call must be called on the GL thread.");
return nativeContext;
}
@NonNull
private static native List<String> getShaderVariableNames();
private static native long initContext(@Nullable String fragmentShader);
private static native boolean setWindowSurface(long nativeContext, @Nullable Surface surface);
private static native int getTexName(long nativeContext);
private static native boolean renderTexture(
long nativeContext,
long timestampNs,
@NonNull float[] textureTransform);
private static native void closeContext(long nativeContext);
}