The translation of the article was prepared in anticipation of the start of the courses " Android Developer. Basic " and " Android Developer. Professional " .
, , , , Canvas. , - . RenderScript, ?
GLSL OpenGL view, Android View (android.view.View). , - :
.
.
.
- , (blurring), (distortion), . .
.
, . , !
, , , Android View (android.view.View), c OpenGL .
- ShaderViews. .
, , , . :
β GLSL OpenGL.
β , view.
β , Render. , , , .
β view-, / .
β Activity Fragment android.
view view- (, view-
ShaderView
). :SurfaceView
TextureView
. .
Render
,view
.
3D- (quadrangle), view (3D, OpenGL 3D-). ; , .
SurfaceView TextureView
SurfaceView
TextureView
Android View, .
SurfaceView
, OpenGL . GLSurfaceView
. , GLSurfaceView
. , , , view
.
TextureView
android.view.View
, , , . , SurfaceView
, ( 1β3 ).
, , view view Android, TextureView
.
, , OpenGL
render TextureView
. β GLSurfaceView
, , SurfaceView
, , GLTextureView
.
GLTextureView
, GLSurfaceView
, OpenGL. .
GLTextureView.kt
,TextureView
TextureView.SurfaceTextureListener
View.OnLayoutChangeListener
. .
open class GLTextureView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) :
TextureView(context, attrs, defStyleAttr),
TextureView.SurfaceTextureListener,
View.OnLayoutChangeListener {
}
5. finalize()
Kotlin. ( , ).
6. SurfaceHolder
SurfaceTexture
.
7. GLSurfaceView
GLTextureView
.
8. , GLSurfaceView
. , GLSurfaceView
.
9. Java Kotlin. , NULL (, egl: EGL10
egl: EGL10?
).
10. - .
11. .
12. SurfaceTextureListener
.
override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
surfaceCreated(surface)
surfaceChanged(surface, 0, width, height)
}
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {
surfaceChanged(surface, 0, width, height)
}
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
surfaceDestroyed(surface)
return true
}
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {
}
13. createSurface()
, view.holder
view.surfaceTexture
.
14. onLayoutChange
.
override fun onLayoutChange(
v: View?, left: Int, top: Int,
right: Int,
bottom: Int,
oldLeft: Int,
oldTop: Int,
oldRight: Int,
oldBottom: Int
) {
surfaceChanged(surfaceTexture, 0, right - left, bottom - top)
}
, , .
fun Resources.getRawTextFile(@RawRes resource: Int): String =
openRawResource(resource).bufferedReader().use { it.readText() }
(Vertex Shader)
( ).
#version 300 es
uniform mat4 uMVPMatrix;
uniform mat4 uSTMatrix;
in vec3 inPosition;
in vec2 inTextureCoord;
out vec2 textureCoord;
void main() {
gl_Position = uMVPMatrix * vec4(inPosition.xyz, 1);
textureCoord = (uSTMatrix * vec4(inTextureCoord.xy, 0, 0)).xy;
}
/ (Fragment Shader)
, , .
, GLSL.
#version 300 es
, .
uniform vec4 uMyUniform;
. In β ( ), out β ( ).
in vec2 textureCoord;
out vec4 fragColor;
, Android View .
void main() {
fragColor = vec4(textureCoord.x, textureCoord.y, 1.0, 1.0) * uMyUniform;
}
:
#version 300 es
precision mediump float;
uniform vec4 uMyUniform;
in vec2 textureCoord;
out vec4 fragColor;
void main() {
fragColor = vec4(textureCoord.x, textureCoord.y, 1.0, 1.0) * uMyUniform;
}
QuadRender
, , β . ShaderView
.
OpenGL . β , .
GLTextureView.Renderer
:
onSurfaceCreated()
β , (uniform) .
onDrawFrame()
β . .
onSurfaceChanged()
β .
, . , OpenGL, . , , .
.
private const val FLOAT_SIZE_BYTES = 4 // Float
private const val TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES // 5 floatβ (3 floatβ 2 )
private const val TRIANGLE_VERTICES_DATA_POS_OFFSET = 0 //
private const val TRIANGLE_VERTICES_DATA_UV_OFFSET = 3 // 3- floatβ (4- 5- floatβ)
//
const val VERTEX_SHADER_IN_POSITION = "inPosition"
const val VERTEX_SHADER_IN_TEXTURE_COORD = "inTextureCoord"
const val VERTEX_SHADER_UNIFORM_MATRIX_MVP = "uMVPMatrix"
const val VERTEX_SHADER_UNIFORM_MATRIX_STM = "uSTMatrix"
const val FRAGMENT_SHADER_UNIFORM_MY_UNIFORM = "uMyUniform"
private const val UNKNOWN_PROGRAM = -1
private const val UNKNOWN_ATTRIBUTE = -1
, .
private var vertexShaderSource : String, //
private var fragmentShaderSource : String, //
QuadRender.kt GitHub
.
private val quadVertices: FloatBuffer
init {
//
val quadVerticesData = floatArrayOf(
// [x,y,z, U,V]
-1.0f, -1.0f, 0f, 0f, 1f,
1.0f, -1.0f, 0f, 1f, 1f,
-1.0f, 1.0f, 0f, 0f, 0f,
1.0f, 1.0f, 0f, 1f, 0f
)
quadVertices = ByteBuffer
.allocateDirect(quadVerticesData.size * FLOAT_SIZE_BYTES)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.apply {
put(quadVerticesData).position(0)
}
}
.
private val matrixMVP = FloatArray(16)
private val matrixSTM = FloatArray(16)
init{}
.
init {
// ,
Matrix.setIdentityM(matrixSTM, 0)
}
, .
private var inPositionHandle = UNKNOWN_ATTRIBUTE
private var inTextureHandle = UNKNOWN_ATTRIBUTE
private var uMVPMatrixHandle = UNKNOWN_ATTRIBUTE
private var uSTMatrixHandle = UNKNOWN_ATTRIBUTE
private var uMyUniform = UNKNOWN_ATTRIBUTE
.
private var program = UNKNOWN_PROGRAM
, . onSurfaceCreated()
. , uMyUniform
, .
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
//
createProgram(vertexShaderSource, fragmentShaderSource)
//
inPositionHandle = GLES20.glGetAttribLocation(program, VERTEX_SHADER_IN_POSITION)
checkGlError("glGetAttribLocation $VERTEX_SHADER_IN_POSITION")
if (inPositionHandle == UNKNOWN_ATTRIBUTE) throw RuntimeException("Could not get attrib location for $VERTEX_SHADER_IN_POSITION")
inTextureHandle = GLES20.glGetAttribLocation(program, VERTEX_SHADER_IN_TEXTURE_COORD)
checkGlError("glGetAttribLocation $VERTEX_SHADER_IN_TEXTURE_COORD")
if (inTextureHandle == UNKNOWN_ATTRIBUTE) throw RuntimeException("Could not get attrib location for $VERTEX_SHADER_IN_TEXTURE_COORD")
uMVPMatrixHandle = GLES20.glGetUniformLocation(program, VERTEX_SHADER_UNIFORM_MATRIX_MVP)
checkGlError("glGetUniformLocation $VERTEX_SHADER_UNIFORM_MATRIX_MVP")
if (uMVPMatrixHandle == UNKNOWN_ATTRIBUTE) throw RuntimeException("Could not get uniform location for $VERTEX_SHADER_UNIFORM_MATRIX_MVP")
uSTMatrixHandle = GLES20.glGetUniformLocation(program, VERTEX_SHADER_UNIFORM_MATRIX_STM)
checkGlError("glGetUniformLocation $VERTEX_SHADER_UNIFORM_MATRIX_STM")
if (uSTMatrixHandle == UNKNOWN_ATTRIBUTE) throw RuntimeException("Could not get uniform location for $VERTEX_SHADER_UNIFORM_MATRIX_STM")
// (!)
uMyUniform = GLES30.glGetUniformLocation(program, FRAGMENT_SHADER_UNIFORM_MY_UNIFORM)
checkGlError("glGetUniformLocation $FRAGMENT_SHADER_UNIFORM_MY_UNIFORM")
if (uMyUniform == UNKNOWN_ATTRIBUTE) throw RuntimeException("Could not get uniform location for $FRAGMENT_SHADER_UNIFORM_MY_UNIFORM")
}
, (uMyUniform
) . .
onSurfaceCreated()
.
/**
*
*/
private fun createProgram(vertexSource: String, fragmentSource: String): Boolean {
if (program != UNKNOWN_PROGRAM) {
//
GLES30.glDeleteProgram(program)
program = UNKNOWN_PROGRAM
}
//
val vertexShader = loadShader(GLES30.GL_VERTEX_SHADER, vertexSource)
if (vertexShader == UNKNOWN_PROGRAM) {
return false
}
//
val pixelShader = loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentSource)
if (pixelShader == UNKNOWN_PROGRAM) {
return false
}
program = GLES30.glCreateProgram()
if (program != UNKNOWN_PROGRAM) {
GLES30.glAttachShader(program, vertexShader)
checkGlError("glAttachShader: vertex")
GLES30.glAttachShader(program, pixelShader)
checkGlError("glAttachShader: pixel")
return linkProgram()
}
return true
}
private fun linkProgram(): Boolean {
if (program == UNKNOWN_PROGRAM) {
return false
}
GLES30.glLinkProgram(program)
val linkStatus = IntArray(1)
GLES30.glGetProgramiv(program, GLES30.GL_LINK_STATUS, linkStatus, 0)
if (linkStatus[0] != GLES30.GL_TRUE) {
Log.e(TAG, "Could not link program: ")
Log.e(TAG, GLES30.glGetProgramInfoLog(program))
GLES30.glDeleteProgram(program)
program = UNKNOWN_PROGRAM
return false
}
return true
}
private fun loadShader(shaderType: Int, source: String): Int {
var shader = GLES30.glCreateShader(shaderType)
if (shader != UNKNOWN_PROGRAM) {
GLES30.glShaderSource(shader, source)
GLES30.glCompileShader(shader)
val compiled = IntArray(1)
GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, compiled, 0)
if (compiled[0] == UNKNOWN_PROGRAM) {
Log.e(TAG, "Could not compile shader $shaderType:")
Log.e(TAG, GLES30.glGetShaderInfoLog(shader))
GLES30.glDeleteShader(shader)
shader = UNKNOWN_PROGRAM
}
}
return shader
}
private fun checkGlError(op: String) {
var error: Int
while (GLES30.glGetError().also { error = it } != GLES30.GL_NO_ERROR) {
Log.e(TAG, "$op: glError $error")
throw RuntimeException("$op: glError $error")
}
, , β onDrawFrame()
.
override fun onDrawFrame(gl: GL10?) {
// ""
GLES30.glClearColor(0.0f, 0.0f, 0.0f, 0.0f)
GLES30.glClear(GLES30.GL_DEPTH_BUFFER_BIT or GLES30.GL_COLOR_BUFFER_BIT)
//
GLES30.glUseProgram(program)
// ( )
setAttribute(inPositionHandle, VERTEX_SHADER_IN_POSITION, 3, TRIANGLE_VERTICES_DATA_POS_OFFSET) // 3 3 floatβ
setAttribute(inTextureHandle, VERTEX_SHADER_IN_TEXTURE_COORD, 2, TRIANGLE_VERTICES_DATA_UV_OFFSET) // 2 2 floatβ
//
Matrix.setIdentityM(matrixMVP, 0)
GLES30.glUniformMatrix4fv(uMVPMatrixHandle, 1, false, matrixMVP, 0)
GLES30.glUniformMatrix4fv(uSTMatrixHandle, 1, false, matrixSTM, 0)
// (!)
val uMyUniformValue = floatArrayOf(1.0f, 0.75f, 0.95f, 1f) // ,
GLES30.glUniform4fv(uMyUniform, 1, uMyUniformValue, 0)
// ( )
GLES30.glBlendFunc(GLES30.GL_SRC_ALPHA, GLES30.GL_ONE_MINUS_SRC_ALPHA)
GLES30.glEnable(GLES20.GL_BLEND)
//
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4)
checkGlError("glDrawArrays")
GLES30.glFinish()
}
, (uMyUniformValue
) (uMyUniform
) .
, surfaceChange()
β .
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
GLES30.glViewport(0, 0, width, height)
}
ShaderView
, , ShaderView, . ! ShaderView.
private const val OPENGL_VERSION = 3
class ShaderView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) :
GLTextureView(context, attrs, defStyleAttr) {
init {
// OpenGL
setEGLContextClientVersion(OPENGL_VERSION)
//
val vsh = context.resources.getRawTextFile(R.raw.vertex_shader)
val fsh = context.resources.getRawTextFile(R.raw.fragment_shader)
//
setRenderer(QuadRender(vertexShaderSource = vsh, fragmentShaderSource = fsh))
// RENDERMODE_WHEN_DIRTY RENDERMODE_CONTINUOUSLY
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY) // GLSurfaceView.RENDERMODE_CONTINUOUSLY
}
}
:
, , (, ).
sampler2D
texture()
GLSL.
.
#version 300 es
precision mediump float;
uniform sampler2D uTexture;
in vec2 textureCoord;
out vec4 fragColor;
void main() {
fragColor = texture(uTexture, textureCoord);
}
fragment_texture_shader.fsh GitHub
OpenGL.
fun Resources.loadBitmapForTexture(@DrawableRes drawableRes: Int): Bitmap {
val options = BitmapFactory.Options()
options.inScaled = false // true. false,
//
return BitmapFactory.decodeResource(this, drawableRes, options)
}
/**
* Bitmap
* @needToRecycle - Bitmap, GPI?
*/
@Throws(RuntimeException::class)
fun Bitmap.toGlTexture(needToRecycle: Boolean = true, textureSlot: Int = GLES30.GL_TEXTURE0): Int {
//
val textureIds = IntArray(1)
GLES30.glGenTextures(1, textureIds, 0) // ID
if (textureIds[0] == 0) {
throw java.lang.RuntimeException("It's not possible to generate ID for texture")
}
GLES30.glActiveTexture(textureSlot) // #0
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureIds[0]) // ID
//
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR)
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR)
// GPU
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, this, 0)
//
if (needToRecycle) {
this.recycle()
}
//
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
return textureIds[0]
}
(bitmap), loadBitmapForTexture()
, QuadRender.onSurfaceCreated()
. OpenGL ( GL_TEXTURE0
GL_TEXTURE31
).
, .
uTextureHandle = GLES30.glGetUniformLocation(program, FRAGMENT_SHADER_UNIFORM_TEXTURE)
uTextureId = textureBitmap.toGlTexture(needToRecycle = true, GLES30.GL_TEXTURE0)
, QuadRender.onDrawFrame()
.
GLES30.glUniform1i(uTextureHandle, 0) // 0 as far as it's slot number 0// 0 0
GLES30.glActiveTexture(GLES30.GL_TEXTURE0) // ,
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, uTextureId)
ShaderView API .
: "Android Developer. Basic" / "Android Developer. Professional".
:
1) Android:
-
- Canvas, Path, Paint
-
2) - β Android 2 , β Kotlin.