SadSido
SadSido

Reputation: 2551

glMapBufferRange fails with GL_OUT_OF_MEMORY on Samsung Galaxy S8 / S10 / Note 10 Lite

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:

  1. This issue happens only with Samsung devices, confirmed on Galaxy S8, Galaxy S10 and Galaxy Note 10 Lite. This issue cannot be reproduced on Google Pixel 3
  2. This issue cannot be reproduced when I modify my compute shader to not sample camera frame via samplerExternalOES. Everything works fine.
  3. This issue cannot be reproduced when I modify my compute shader to not perform any output to SSBO. Everything works fine.

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

Answers (1)

SadSido
SadSido

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

Related Questions