YaGL: Workaround broken UBO on some platforms 82/18482/1
authorStanislav Vorobiov <s.vorobiov@samsung.com>
Mon, 24 Mar 2014 07:52:52 +0000 (11:52 +0400)
committerStanislav Vorobiov <s.vorobiov@samsung.com>
Mon, 24 Mar 2014 13:51:53 +0000 (17:51 +0400)
Some platforms (like Mac OS X 10.8) have broken UBO support.
The problem is that if an UB is unnamed it still requires a name, i.e.:

uniform myUB
{
    mat4 myMatrix;
};

API should be able to reference myMatrix as "myMatrix", but broken
UBO platform expects "myUB.myMatrix".
For named UBs there's no error however:

uniform myUB
{
    mat4 myMatrix;
} myUBName;

Here myMatrix is referenced as "myUB.myMatrix" (yes, myUB, not myUBName)
and this is correct.

To work around the problem we must patch index lookups
by UB name when needed

Change-Id: I0e73f32b116469d2bffbf8fee2a2129f7cff8cb7
Signed-off-by: Stanislav Vorobiov <s.vorobiov@samsung.com>
hw/yagl/yagl_apis/gles/yagl_gles_api.h
hw/yagl/yagl_apis/gles/yagl_gles_api_ts.c
hw/yagl/yagl_apis/gles/yagl_gles_api_ts.h
hw/yagl/yagl_apis/gles/yagl_host_gles_calls.c

index 5f196a8e84030038597cb18f9f8c9a18aafc381a..ef6946ff5d729a54690729eb6d856f91b0778ee6 100644 (file)
@@ -39,6 +39,11 @@ struct yagl_gles_api
     struct yagl_api base;
 
     struct yagl_gles_driver *driver;
+
+    bool checked;
+
+    bool use_map_buffer_range;
+    bool broken_ubo;
 };
 
 /*
index 01e0e3d14beccec674160dfc446046c412ebb0af..7285176dd84d42d9459e825b816a98dd1e0d6515 100644 (file)
 
 #include <GL/gl.h>
 #include "yagl_gles_api_ts.h"
+#include "yagl_gles_api_ps.h"
+#include "yagl_gles_api.h"
 #include "yagl_gles_driver.h"
 #include "yagl_process.h"
 #include "yagl_thread.h"
+#include "yagl_log.h"
+
+static GLuint yagl_gles_api_ts_create_shader(struct yagl_gles_driver *driver,
+                                             const char *source,
+                                             GLenum type)
+{
+    GLuint shader = driver->CreateShader(type);
+    GLint tmp = 0;
+
+    YAGL_LOG_FUNC_SET(yagl_gles_api_ts_create_shader);
+
+    if (!shader) {
+        YAGL_LOG_ERROR("Unable to create shader type %d", type);
+        return 0;
+    }
+
+    driver->ShaderSource(shader, 1, &source, NULL);
+    driver->CompileShader(shader);
+    driver->GetShaderiv(shader, GL_COMPILE_STATUS, &tmp);
+
+    if (!tmp) {
+        char *buff;
+
+        tmp = 0;
+
+        driver->GetShaderiv(shader, GL_INFO_LOG_LENGTH, &tmp);
+
+        buff = g_malloc0(tmp);
+
+        driver->GetShaderInfoLog(shader, tmp, NULL, buff);
+
+        YAGL_LOG_ERROR("Unable to compile shader (type = %d) - %s",
+                       type, buff);
+
+        driver->DeleteShader(shader);
+
+        g_free(buff);
+
+        return 0;
+    }
+
+    return shader;
+}
+
+/*
+ * Some platforms (like Mac OS X 10.8) have broken UBO support.
+ * The problem is that if an UB is unnamed it still requires a name, i.e.:
+ *
+ * uniform myUB
+ * {
+ *     mat4 myMatrix;
+ * };
+ *
+ * API should be able to reference myMatrix as "myMatrix", but broken
+ * UBO platform expects "myUB.myMatrix".
+ * For named UBs there's no error however:
+ *
+ * uniform myUB
+ * {
+ *     mat4 myMatrix;
+ * } myUBName;
+ *
+ * Here myMatrix is referenced as "myUB.myMatrix" (yes, myUB, not myUBName)
+ * and this is correct.
+ *
+ * To (partially) work around the problem we must patch
+ * index lookups by UB name.
+ */
+static bool yagl_gles_api_ts_broken_ubo_test(struct yagl_gles_driver *driver)
+{
+    static const char *vs_source_es3 =
+        "#version 300 es\n\n"
+        "uniform myUB\n"
+        "{\n"
+        "    mediump mat4 myMatrix;\n"
+        "};\n"
+        "in mediump vec4 vertCoord;\n"
+        "void main()\n"
+        "{\n"
+        "    gl_Position = myMatrix * vertCoord;\n"
+        "}\n";
+
+    static const char *fs_source_es3 =
+        "#version 300 es\n\n"
+        "out mediump vec4 FragColor;\n"
+        "void main()\n"
+        "{\n"
+        "    FragColor = vec4(0, 0, 0, 0);\n"
+        "}\n";
+
+    static const char *vs_source_3_2 =
+        "#version 150\n\n"
+        "uniform myUB\n"
+        "{\n"
+        "    mat4 myMatrix;\n"
+        "};\n"
+        "in vec4 vertCoord;\n"
+        "void main()\n"
+        "{\n"
+        "    gl_Position = myMatrix * vertCoord;\n"
+        "}\n";
+
+    static const char *fs_source_3_2 =
+        "#version 150\n\n"
+        "out vec4 FragColor;\n"
+        "void main()\n"
+        "{\n"
+        "    FragColor = vec4(0, 0, 0, 0);\n"
+        "}\n";
+
+    static const GLchar *good_name = "myMatrix";
+    static const GLchar *bad_name = "myUB.myMatrix";
+
+    bool res = false;
+    GLuint vs, fs;
+    GLuint program;
+    GLint tmp = 0;
+    GLuint index = GL_INVALID_INDEX;
+
+    YAGL_LOG_FUNC_SET(yagl_gles_api_ts_broken_ubo_test);
+
+    vs = yagl_gles_api_ts_create_shader(driver,
+        ((driver->gl_version == yagl_gl_3_1_es3) ? vs_source_es3 : vs_source_3_2),
+        GL_VERTEX_SHADER);
+
+    if (!vs) {
+        goto out1;
+    }
+
+    fs = yagl_gles_api_ts_create_shader(driver,
+        ((driver->gl_version == yagl_gl_3_1_es3) ? fs_source_es3 : fs_source_3_2),
+        GL_FRAGMENT_SHADER);
+
+    if (!fs) {
+        goto out2;
+    }
+
+    program = driver->CreateProgram();
+
+    if (!program) {
+        YAGL_LOG_ERROR("Unable to create program");
+        goto out3;
+    }
+
+    driver->AttachShader(program, vs);
+    driver->AttachShader(program, fs);
+    driver->LinkProgram(program);
+    driver->GetProgramiv(program, GL_LINK_STATUS, &tmp);
+
+    if (!tmp) {
+        char *buff;
+
+        tmp = 0;
+
+        driver->GetProgramiv(program, GL_INFO_LOG_LENGTH, &tmp);
+
+        buff = g_malloc0(tmp);
+
+        driver->GetProgramInfoLog(program, tmp, NULL, buff);
+
+        YAGL_LOG_ERROR("Unable to link program - %s", buff);
+
+        driver->DeleteProgram(program);
+
+        g_free(buff);
+
+        goto out4;
+    }
+
+    driver->GetUniformIndices(program, 1, &good_name, &index);
+
+    if (index == GL_INVALID_INDEX) {
+        driver->GetUniformIndices(program, 1, &bad_name, &index);
+
+        if (index == GL_INVALID_INDEX) {
+            YAGL_LOG_ERROR("UBO support is broken in unusual way, unable to workaround. Using UBO may cause undefined behavior");
+        } else {
+            YAGL_LOG_WARN("UBO support is broken, applying workaround");
+            res = true;
+        }
+    }
+
+out4:
+    driver->DetachShader(program, fs);
+    driver->DetachShader(program, vs);
+    driver->DeleteProgram(program);
+out3:
+    driver->DeleteShader(fs);
+out2:
+    driver->DeleteShader(vs);
+out1:
+    return res;
+}
 
 void yagl_gles_api_ts_init(struct yagl_gles_api_ts *gles_api_ts,
                            struct yagl_gles_driver *driver,
                            struct yagl_gles_api_ps *ps)
 {
+    YAGL_LOG_FUNC_SET(yagl_gles_api_ts_init);
+
     gles_api_ts->driver = driver;
     gles_api_ts->ps = ps;
-    gles_api_ts->use_map_buffer_range = -1;
+    gles_api_ts->api = (struct yagl_gles_api*)ps->base.api;
+
+    if (gles_api_ts->api->checked) {
+        return;
+    }
+
+    yagl_ensure_ctx(0);
+
+    if (driver->gl_version > yagl_gl_2) {
+        gles_api_ts->api->use_map_buffer_range = true;
+    } else {
+        const char *tmp = (const char*)driver->GetString(GL_EXTENSIONS);
+
+        gles_api_ts->api->use_map_buffer_range =
+            (tmp && (strstr(tmp, "GL_ARB_map_buffer_range ") != NULL));
+
+        if (!gles_api_ts->api->use_map_buffer_range) {
+            YAGL_LOG_WARN("glMapBufferRange not supported, using glBufferSubData");
+        }
+    }
+
+    if (driver->gl_version >= yagl_gl_3_1_es3) {
+        gles_api_ts->api->broken_ubo = yagl_gles_api_ts_broken_ubo_test(driver);
+    } else {
+        gles_api_ts->api->broken_ubo = false;
+    }
+
+    yagl_unensure_ctx(0);
+
+    gles_api_ts->api->checked = true;
 }
 
 void yagl_gles_api_ts_cleanup(struct yagl_gles_api_ts *gles_api_ts)
index f24ad7cbaccb49e4c48ba9973729701a38992758..0a881e7c3b05dbf7cd5a0925318d64592b574f28 100644 (file)
@@ -34,6 +34,7 @@
 
 struct yagl_gles_driver;
 struct yagl_gles_api_ps;
+struct yagl_gles_api;
 
 /*
  * OpenGL 3.1+ core profile doesn't allow one to
@@ -52,6 +53,11 @@ struct yagl_gles_api_ts
 
     struct yagl_gles_api_ps *ps;
 
+    /*
+     * From 'ps->base.api' for speed.
+     */
+    struct yagl_gles_api *api;
+
     struct yagl_gles_array *arrays;
     uint32_t num_arrays;
 
@@ -60,11 +66,6 @@ struct yagl_gles_api_ts
      */
     GLuint ebo;
     uint32_t ebo_size;
-
-    /*
-     * -1 when undecided, 0/1 when decided.
-     */
-    int use_map_buffer_range;
 };
 
 void yagl_gles_api_ts_init(struct yagl_gles_api_ts *gles_api_ts,
index 034f2d3108c9583dc8e3fd92b796f1d1a02bfab2..11bb2d4ffbca6e61dd789a56fc7397267b95ccbf 100644 (file)
@@ -61,28 +61,6 @@ typedef enum
     yagl_gles1_array_texcoord,
 } yagl_gles1_array_type;
 
-static bool yagl_gles_use_map_buffer_range(void)
-{
-    YAGL_LOG_FUNC_SET(yagl_gles_use_map_buffer_range);
-
-    if (gles_api_ts->use_map_buffer_range == -1) {
-        if (gles_api_ts->driver->gl_version > yagl_gl_2) {
-            gles_api_ts->use_map_buffer_range = 1;
-        } else {
-            const char *tmp = (const char*)gles_api_ts->driver->GetString(GL_EXTENSIONS);
-
-            gles_api_ts->use_map_buffer_range =
-                (tmp && (strstr(tmp, "GL_ARB_map_buffer_range ") != NULL));
-
-            if (!gles_api_ts->use_map_buffer_range) {
-                YAGL_LOG_WARN("glMapBufferRange not supported, using glBufferSubData");
-            }
-        }
-    }
-
-    return gles_api_ts->use_map_buffer_range;
-}
-
 static GLuint yagl_gles_bind_array(uint32_t indx,
                                    GLint first,
                                    GLsizei stride,
@@ -130,7 +108,7 @@ static GLuint yagl_gles_bind_array(uint32_t indx,
         gles_api_ts->arrays[indx].size = size;
     }
 
-    if (yagl_gles_use_map_buffer_range()) {
+    if (gles_api_ts->api->use_map_buffer_range) {
         ptr = gles_api_ts->driver->MapBufferRange(GL_ARRAY_BUFFER,
                                                   first * stride,
                                                   data_count,
@@ -176,7 +154,7 @@ static GLuint yagl_gles_bind_ebo(const GLvoid *data, int32_t size)
         gles_api_ts->ebo_size = size;
     }
 
-    if (yagl_gles_use_map_buffer_range()) {
+    if (gles_api_ts->api->use_map_buffer_range) {
         ptr = gles_api_ts->driver->MapBufferRange(GL_ELEMENT_ARRAY_BUFFER,
                                                   0,
                                                   size,
@@ -921,7 +899,7 @@ void yagl_host_glBufferSubData(GLenum target,
 
     YAGL_LOG_FUNC_SET(glBufferSubData);
 
-    if (yagl_gles_use_map_buffer_range()) {
+    if (gles_api_ts->api->use_map_buffer_range) {
         ptr = gles_api_ts->driver->MapBufferRange(target,
                                                   offset,
                                                   data_count,
@@ -2221,9 +2199,15 @@ void yagl_host_glGetUniformIndices(GLuint program,
     const GLchar *uniformNames, int32_t uniformNames_count,
     GLuint *uniformIndices, int32_t uniformIndices_maxcount, int32_t *uniformIndices_count)
 {
-    GLuint obj = yagl_gles_object_get(program);
-    int max_active_uniform_bufsize = 1, i;
+    GLuint obj;
+    int max_active_uniform_bufsize = 1, i, j;
     const GLchar **name_pointers;
+    int num_active_uniforms = 0;
+    GLchar *uniform_name;
+
+    YAGL_LOG_FUNC_SET(glGetUniformIndices);
+
+    obj = yagl_gles_object_get(program);
 
     gles_api_ts->driver->GetProgramiv(obj,
                                       GL_ACTIVE_UNIFORM_MAX_LENGTH,
@@ -2243,6 +2227,99 @@ void yagl_host_glGetUniformIndices(GLuint program,
     g_free(name_pointers);
 
     *uniformIndices_count = uniformIndices_maxcount;
+
+    if (!gles_api_ts->api->broken_ubo) {
+        return;
+    }
+
+    gles_api_ts->driver->GetProgramiv(obj,
+                                      GL_ACTIVE_UNIFORMS,
+                                      &num_active_uniforms);
+
+    uniform_name = g_malloc(max_active_uniform_bufsize + 1);
+
+    for (i = 0; i < num_active_uniforms; ++i) {
+        GLsizei length = 0;
+        GLint size = 0;
+        GLenum type = 0;
+        const GLchar *tmp;
+        size_t tmp_len;
+
+        gles_api_ts->driver->GetActiveUniform(obj,
+                                              i,
+                                              max_active_uniform_bufsize,
+                                              &length,
+                                              &size,
+                                              &type,
+                                              uniform_name);
+
+        if (length == 0) {
+            YAGL_LOG_ERROR("Cannot get active uniform %d for program %u", i, obj);
+            continue;
+        }
+
+        tmp = strchr(uniform_name, '.');
+
+        if (!tmp) {
+            continue;
+        }
+
+        tmp += 1;
+
+        tmp_len = strlen(tmp);
+
+        for (j = 0; j < uniformIndices_maxcount; ++j) {
+            const GLchar *test, *dot;
+            size_t test_len;
+
+            if (uniformIndices[j] != GL_INVALID_INDEX) {
+                continue;
+            }
+
+            /*
+             * This solution is not perfect, but it's better than nothing.
+             * It may yield incorrect index in cases like this:
+             *
+             * uniform myUB1
+             * {
+             *     mat4 myMatrix;
+             * } myName;
+             *
+             * uniform myUB2
+             * {
+             *     mat4 myMatrix;
+             * };
+             *
+             * A query for "myMatrix" may return index of
+             * "myUB1.myMatrix" instead of intended "myUB2.myMatrix".
+             * The problem is that we can't find out if UB is named or
+             * not from API, so if the uniform with same name is
+             * present in several UBs we might get the wrong one...
+             */
+
+            test = &uniformNames[max_active_uniform_bufsize * j];
+            test_len = strlen(test);
+
+            dot = strchr(test, '.');
+
+            if (dot &&
+                (strncmp(test,
+                         uniform_name,
+                         dot - test) == 0) &&
+                (uniform_name[dot - test] == '[')) {
+                test = dot + 1;
+                test_len = strlen(test);
+            }
+
+            if ((strncmp(test, tmp, test_len) == 0) &&
+                ((test_len == tmp_len) ||
+                 ((test_len < tmp_len) && (tmp[test_len] == '[')))) {
+                uniformIndices[j] = i;
+            }
+        }
+    }
+
+    g_free(uniform_name);
 }
 
 GLuint yagl_host_glGetUniformBlockIndex(GLuint program,