Я использую библиотеку CameraView для Android, чтобы захватывать кадры с камеры и обрабатывать их с помощью шейдеров OpenGL. Я столкнулся со странным поведением при попытке использовать вычислительные шейдеры. Я хочу получить кадр камеры в виде RGB-буфера с плавающей запятой, обработать его на ЦП, а затем отобразить с помощью OpenGL. Я наследую BaseFilter CameraView, настраиваю свою вычислительную программу и буфер SSBO, отправляю вычисления в onPreDraw () и, наконец, визуализирую кадр камеры на поверхности, как и все другие фильтры.
Все работает нормально, пока я либо начать запись видео ИЛИ переключить камеру с передней на заднюю. Независимо от того, как долго я запускаю фильтр, как только я переключаюсь лицом, я больше не могу получить содержимое моего SSBO с помощью glMapBufferRange. Вызов OpenGL завершается с ошибкой с кодом GL_OUT_OF_MEMORY, который, как я полагаю, означает случайную ошибку «не удалось получить буфер». текущая облицовка, но сбой сразу после следующего переключения. Удивительно, но установка фильтра на NONE перед переключением не помогает: я могу запустить свой фильтр с лицевой стороны, затем установить фильтр на NONE, затем переключить ориентацию, затем снова установить мой фильтр, и он не удастся. Не имеет значения, повторно использую ли я экземпляр своего фильтра ИЛИ создаю новый каждый раз, когда устанавливаю его на CameraView.
Некоторые случайные наблюдения:
- Эта проблема возникает только с Устройства Samsung, подтвержденные на Galaxy S8, Galaxy S10 и Galaxy Note 10 Lite. Эта проблема не может быть воспроизведена в Google Pixel 3
- Эта проблема не может быть воспроизведена, когда я изменяю свой вычислительный шейдер, чтобы не выполнять выборку кадра камеры через samplerExternalOES. Все работает нормально.
- Эта проблема не может быть воспроизведена, когда я изменяю свой вычислительный шейдер, чтобы не выполнять никакого вывода в SSBO. Все работает нормально.
Мой вычислительный шейдер выглядит так (пример кода для воспроизведения проблемы):
private fun getComputeShaderText(cx: Int, cy: Int, bind: Int) =
"""#version 310 es
#extension GL_OES_EGL_image_external_essl3: enable
precision mediump float;
layout(local_size_x = 8, local_size_y = 8) in;
layout(std430) buffer;
layout(binding = 0) uniform samplerExternalOES in_data;
layout(binding = ${bind}) buffer Input { float elements[]; } out_data;
void main() {
if (gl_GlobalInvocationID.x >= ${cx}u || gl_GlobalInvocationID.y >= ${cy}u) return;
float u = float(gl_GlobalInvocationID.x) / $cx.0;
float v = float(gl_GlobalInvocationID.y) / $cy.0;
vec3 texColor = texture(in_data, vec2(u,v)).rgb;
uint index = gl_GlobalInvocationID.x + ${cx}u * gl_GlobalInvocationID.y;
out_data.elements[index] = texColor.r;
}
"""
А вот весь класс фильтра, который может воспроизвести проблему (все проверки ошибок опущены для ясности, кроме соответствующей):
import android.opengl.GLES31
import android.util.Log
import com.otaliastudios.cameraview.filter.BaseFilter
class DummyCameraFilter : BaseFilter() {
private val ssboDimX = 64
private val ssboDimY = 64
private val ssboSize = ssboDimX * ssboDimY * 4 /* size of float */
private val ssboBind = 1 /* binding point */
private var renderProgram: Int = -1
private var computeShader: Int = -1
private var computeProgram: Int = -1
private val ssbo = IntArray(1)
override fun onCreate(programHandle: Int) {
super.onCreate(programHandle)
// keep program handle:
this.renderProgram = programHandle
// create shader:
computeShader = GLES31.glCreateShader(GLES31.GL_COMPUTE_SHADER)
GLES31.glShaderSource(computeShader, getComputeShaderText(ssboDimX, ssboDimY, ssboBind))
GLES31.glCompileShader(computeShader)
computeProgram = GLES31.glCreateProgram()
GLES31.glAttachShader(computeProgram, computeShader)
GLES31.glLinkProgram(computeProgram)
// create ssbo:
GLES31.glGenBuffers(1, ssbo, 0)
GLES31.glBindBuffer(GLES31.GL_SHADER_STORAGE_BUFFER, ssbo[0])
GLES31.glBufferData(GLES31.GL_SHADER_STORAGE_BUFFER, ssboSize, null, GLES31.GL_STREAM_COPY)
}
override fun onDestroy() {
GLES31.glDeleteBuffers(1, ssbo, 0)
GLES31.glDeleteShader(computeShader)
GLES31.glDeleteProgram(computeProgram)
super.onDestroy()
}
override fun onPreDraw(timestampUs: Long, transformMatrix: FloatArray) {
super.onPreDraw(timestampUs, transformMatrix)
// compute:
GLES31.glUseProgram(computeProgram)
GLES31.glBindBufferRange(GLES31.GL_SHADER_STORAGE_BUFFER, ssboBind, ssbo[0], 0, ssboSize)
GLES31.glDispatchCompute(64 / 8, 64 / 8, 1)
GLES31.glMemoryBarrier(GLES31.GL_SHADER_STORAGE_BARRIER_BIT)
// fetch data:
GLES31.glBindBuffer(GLES31.GL_SHADER_STORAGE_BUFFER, ssbo[0])
GLES31.glMapBufferRange(GLES31.GL_SHADER_STORAGE_BUFFER, 0, ssboSize, GLES31.GL_MAP_READ_BIT)
GLES31.glUnmapBuffer(GLES31.GL_SHADER_STORAGE_BUFFER)
if (GLES31.glGetError() != GLES31.GL_NO_ERROR)
{ Log.d("CameraView", "This starts failing after toggle facing!") }
}
override fun onDraw(timestampUs: Long) {
GLES31.glUseProgram(renderProgram)
super.onDraw(timestampUs)
}
override fun getFragmentShader()
= createDefaultFragmentShader()
}
Пожалуйста, помогите. Я что-то делаю не так с samplerExternalOES или SSBO или с обоими? Или это проблема библиотеки CameraView?