Reputation: 2551
I am using CameraView library for Android to capture frames from the camera and process them with OpenGL shaders. I have encountered some strange behavior while trying to use compute shaders. I want to grab a camera frame as an RGB buffer of floating points, process it on CPU and then render it with OpenGL. I inherit CameraView's BaseFilter, setup my compute program and SSBO buffer, dispatch computation in onPreDraw(), and finally render camera frame on the surface, just like all the other filters do.
Everything works fine, until I either start recording video OR toggle camera facing between front and back. No matter how long I run the filter, as soon as I toggle facing, I am no longer able to get the contents of my SSBO with glMapBufferRange. OpenGL call fails with error code GL_OUT_OF_MEMORY, which I believe stands for a random "failed to obtain buffer" error.
Toggling between front and back BEFORE setting the filter has no effect - the filter will start running normally with the current facing, but fail immediately after the next toggle. Surprisingly, setting filter to NONE before toggling does not help: I can start my filter with front facing, then set filter to NONE, then toggle facing, then set my filter again, and it will fail. It does not matter if I re-use the instance of my filter OR create new one every time I set it to CameraView.
Some random observations:
My compute shader looks like this (sample code to reproduce the issue):
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;
}
"""
And here is the entire filter class, which can reproduce the issue (all error checks are omitted for clarity, except for the relevant one):
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()
}
Please help. Am I doing something wrong with samplerExternalOES or SSBO or both? Or is it a CameraView library issue?
Upvotes: 1
Views: 603
Reputation: 2551
The best workaround so far, suggested by the CameraView owner, is to add a render pass, dumping OES texture to frame buffer, then access the frame buffer via sampler2D uniform from the compute shader. It works fine, although eating some FPS due to the extra step.
So, the problem is weird interaction between compute shaders, SSBO and samplerExternalOES. I hope this workaround can save some time for someone, messing with the same problem
Upvotes: 2