Open6

gl4esの描画異常を追う

okuokuokuoku

とりあえずある程度安定して動作するようになり、Vulkanレベルでのリプレイも取れるようになってきたので、そろそろ安定した描画が得られない原因を探ることにする。

okuokuokuoku

描画に穴が空く / 貼られるテクスチャが異常になる問題

謎の縦縞が表われている。Depthを確認する限りちゃんと透明で抜けているので、フラグメントシェーダか渡されているサンプラ等のUniformが異常なのではないだろうか。

で、ねっとりとリプレイを確認したところ(よくハングするのでめっちゃ時間が掛かった...)、いわゆるトゥーンシェーディングのための影パラメタをテクスチャで渡しているところがあり、それが正常に渡せていない = gl4esのマルチテクスチャ実装が不味そうということがわかった。

このタイトルは(記憶が正しければ)正方形でないテクスチャを使用することは無いはずだが、これらの異常な描画では 512x32 のテクスチャが使われているのでOpenGL側から渡されたテクスチャを正常に受けとっていないことになる。

okuokuokuoku

glBindBuffer が正常に行われない

... というかそれ以前に、Bufferの挙動がおかしい。

Using VBO 17 for indices
EVENT: glDrawElements(context = 1, mode = GL_TRIANGLES, count = 3528, type = GL_UNSIGNED_SHORT, indices = 0x0000000000000000)
INFO: glDrawElements: GL error: HIGH: No element array buffer and no pointer.
EVENT: glGetError(context = 1)
DUMMY: wglGetPixelFormat (did nothing)
glBindBuffer(GL_ARRAY_BUFFER, 25)
glBufferSubData(GL_ARRAY_BUFFER, 001D93D0, 2016, 1A5AC400)
EVENT: glBufferSubData(context = 1, target = GL_ARRAY_BUFFER, offset = 1938384, size = 2016, data = 0x000000001a5ac400)
EVENT: glGetError(context = 1)
DUMMY: wglGetPixelFormat (did nothing)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 76)
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 00001B90, 48, 22FEB6E0)
EVENT: glBufferSubData(context = 1, target = GL_ELEMENT_ARRAY_BUFFER, offset = 7056, size = 48, data = 0x0000000022feb6e0)
INFO: glBufferSubData: GL error: HIGH: A buffer must be bound.
EVENT: glGetError(context = 1)
EVENT: glGetError(context = 1)
EVENT: glGetError(context = 1)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 76)
EVENT: glCheckFramebufferStatus(context = 1, target = GL_FRAMEBUFFER)
glCheckFramebufferStatus(0x8D40)=0x8CD5
glDrawElementsBaseVertex(GL_TRIANGLES, 24, GL_UNSIGNED_SHORT, 00001B90, 0), vtx=20225040 map=2375B140, pending=0
glDrawElements(GL_TRIANGLES, 24, GL_UNSIGNED_SHORT, 00001B90), vtx=20225040 map=2375B140, pending=0
glDrawElementsCommon(GL_TRIANGLES, 0, 24, 0, 2375CCD0, 00000000, 1)
EVENT: glBindTexture(context = 1, target = GL_TEXTURE_2D, texture = 29)
fpe_glDrawElements(GL_TRIANGLES, 24, GL_UNSIGNED_SHORT, 2375CCD0), program=104, instanceID=0
GLSL program 104 need customization => 108
Uniform sync'd with 108 and father (264 uniforms)
Using VBO 17 for indices
EVENT: glDrawElements(context = 1, mode = GL_TRIANGLES, count = 24, type = GL_UNSIGNED_SHORT, indices = 0x0000000000001b90)

ANGLEがたまに

INFO: glBufferSubData: GL error: HIGH: A buffer must be bound.

とエラーを出力しているようだ。gl4esはバッファ関連の操作を一旦遅延して glDrawElements 等のバッファを使用する操作のときに纏めて行う実装になっているが、そこがバグっていることになる。

https://github.com/google/angle/blob/251ba5cb119ff2fed0e861cbc9b096c45004c1fa/src/libANGLE/validationES2.cpp#L3791

https://github.com/google/angle/blob/251ba5cb119ff2fed0e861cbc9b096c45004c1fa/src/libANGLE/validationES2.cpp#L3839

この辺にブレークを仕込んでデバッグしていくことにする。

okuokuokuoku

glDeleteBuffer がBufferを暗黙にunbindすることを考慮していない

これは https://github.com/ptitSeb/gl4es/issues/381#issuecomment-1237269621 で指摘されているが、今でも治っていないようだ。

この辺のコードはこのコミットで導入された:

https://github.com/ptitSeb/gl4es/commit/0b173f318dd8a0a4232300cb5264487fb7d17415

しかし、DeleteBufferだけ対策してもまったく状況が改善しない。

diff --git a/src/gl/buffers.c b/src/gl/buffers.c
index 6770f023..eaf0bc8d 100644
--- a/src/gl/buffers.c
+++ b/src/gl/buffers.c
@@ -162,6 +162,22 @@ void APIENTRY_GL4ES gl4es_glBindBuffer(GLenum target, GLuint buffer) {
     noerrorShim();
 }

+static void fixup_binding(GLuint buffer){
+    if(! buffer) return; // Debug
+    if(glstate->bind_buffer.array == buffer){
+        printf("I guess array buffer %d was bound and unbound implicitly\n", buffer);
+        glstate->bind_buffer.array = 0;
+        //FIXME: Update used
+
+    }
+    if(glstate->bind_buffer.index == buffer){
+        printf("I guess index buffer %d was bound and unbound implicitly\n", buffer);
+        glstate->bind_buffer.index = 0;
+        //FIXME: Update used, want
+    }
+}
+
+
 void APIENTRY_GL4ES gl4es_glBufferData(GLenum target, GLsizeiptr size, const GLvoid * data, GLenum usage) {
     DBG(printf("glBufferData(%s, %zi, %p, %s)\n", PrintEnum(target), size, data, PrintEnum(usage));)
        if (!buffer_target(target)) {
@@ -186,6 +202,7 @@ void APIENTRY_GL4ES gl4es_glBufferData(GLenum target, GLsizeiptr size, const GLv
         rebind_real_buff_arrays(buff->real_buffer, 0);
         LOAD_GLES(glDeleteBuffers);
         gles_glDeleteBuffers(1, &buff->real_buffer);
+        fixup_binding(buff->real_buffer);
         // what about VA already pointing there?
         buff->real_buffer = 0;
     }
@@ -244,6 +261,7 @@ void APIENTRY_GL4ES gl4es_glNamedBufferData(GLuint buffer, GLsizeiptr size, cons
     if(buff->real_buffer && !go_real) {
         LOAD_GLES(glDeleteBuffers);
         gles_glDeleteBuffers(1, &buff->real_buffer);
+        fixup_binding(buff->real_buffer);
         // what about VA already pointing there?
         buff->real_buffer = 0;
     }
@@ -350,6 +368,7 @@ void APIENTRY_GL4ES gl4es_glDeleteBuffers(GLsizei n, const GLuint * buffers) {
                         rebind_real_buff_arrays(buff->real_buffer, 0);  // unbind
                         LOAD_GLES(glDeleteBuffers);
                         gles_glDeleteBuffers(1, &buff->real_buffer);
+                        fixup_binding(buff->real_buffer);
                     }
                     if (glstate->vao->vertex == buff)
                         glstate->vao->vertex = NULL;

仕様 https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glBindBuffer.xhtml では

When a buffer object is bound to a target, the previous binding for that target is automatically broken.

なので、array bufferだったのが index bufferに代わるようなケースも対策しないとダメなんだろうか。。ただ基本的にbufferの種別を途中で変えるのは非効率なので避けるべきと言える。 → 対策してもダメだった

okuokuokuoku

Attributeの設定がたまに抜ける

ログ https://gist.github.com/okuoku/fefff02b3d76201f2c91572d02df6699#file-crashlog-txt-L9647-L9988 によると、この2つの glDrawArrays の間に再度Bufferを作り直しているが、その間に行われた glVertexAttribPointer をやりなおしている形跡がない。

(ANGLEのログには EVENT: がプレフィックスされているので、それが無いログは gl4es がエミュレートしている呼出しということになる。)

Bufferの同一性チェックを番号比較で行うのはダメで、タイムスタンプなり何なりを振らなければならない。

... まぁとりあえずこのキャッシュ自体を無効化するか。。

diff --git a/src/gl/fpe.c b/src/gl/fpe.c
index e9a8cad4..36bed57c 100644
--- a/src/gl/fpe.c
+++ b/src/gl/fpe.c
@@ -1437,6 +1437,7 @@ void realize_glenv(int ispoint, int first, int count, GLenum type, const void* i
             else
                 gles_glDisableVertexAttribArray(i);
         }
+        dirty = 1; // DEBUG
         // check if new value has to be sent to hardware
         if(v->enabled) {
             // array case
okuokuokuoku

文字列のテクスチャだった

512x32 のテクスチャを再度確認してみると、これは文字列のテクスチャだった。このタイトルはいわゆるテクスチャアトラスをGPU側に持たせるのではなく、都度CPUで描画してアップロードする形を取っている。

背景を描画するタイミングではこれは不要なので、明かに間違っている。... ということは、Buffer同様にテクスチャも取り違える何かがあるのか。。?