Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

glPixelStorei does not support PACK_ALIGNMENT #23151

Open
seven332 opened this issue Dec 13, 2024 · 4 comments
Open

glPixelStorei does not support PACK_ALIGNMENT #23151

seven332 opened this issue Dec 13, 2024 · 4 comments

Comments

@seven332
Copy link

This issue originates from #21968

Calling glPixelStorei affects the memory layout of pixel data in glTexImage2D, glTexSubImage2D, and glReadPixels, thereby affecting the valid length of the pixel data. The OpenGL documentation provides a more detailed explanation:
https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glPixelStorei.xhtml

Emscripten restricts the length of pixel data through emscriptenWebGLGetTexPixelData, but this restriction may be shorter than expected after calling glPixelStorei. This results in browser errors.
Chrome and Safari:

WebGL: INVALID_OPERATION: texSubImage2D: ArrayBufferView not big enough for request

FireFox:

WebGL warning: texSubImage: Desired upload requires more bytes (20004) than are available (1440). 

The problem with emscriptenWebGLGetTexPixelData is that it does not distinguish between pack and unpack, nor does it handle all possible parameters in glPixelStorei.

My simple patch is:

sed -i'.original' -e 's/return heap.subarray(toTypedArrayIndex(pixels, heap), toTypedArrayIndex(pixels + bytes, heap));/return heap.subarray(toTypedArrayIndex(pixels, heap));/g' emsdk/src/library_webgl.js

This removes the length restriction on the pixel data, since the C API only passes a pointer without data length.

The issue with this patch is that it makes it easier for the pixel data length to exceed 2GB, which in FireFox leads to a new problem:

WebGL2RenderingContext.readPixels: Argument 7 can't be an ArrayBuffer or an ArrayBufferView larger than 2 GB
@sbc100
Copy link
Collaborator

sbc100 commented Dec 13, 2024

Can you share an example of a call to glPixelStorei where emscriptenWebGLGetTexPixelData doesn't do the right thing?

Could you perhaps give this bug with something more descriptive? Something like glPixelStorei does not support xxx

@seven332 seven332 changed the title More accurate pixel data length glPixelStorei does not support PACK_ALIGNMENT Dec 16, 2024
@seven332
Copy link
Author

seven332 commented Dec 16, 2024

Here is an example

index.html

<body>
<script>
    const canvas = document.createElement('canvas');
    const gl = canvas.getContext('webgl');

    const texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([
        1, 2, 3, 4,
        5, 6, 7, 8,
    ]));
    const framebuffer = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);

    gl.pixelStorei(gl.PACK_ALIGNMENT, 8);

    const buffer = new ArrayBuffer(3 * 4);
    gl.readPixels(0, 0, 1, 2, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(buffer));
    console.log(new Uint8Array(buffer));
</script>
<canvas id="canvas"></canvas>
<script src="./a.out.js"></script>
</body>

main.cpp

#include <emscripten/html5.h>
#include <iostream>
#include <GLES3/gl3.h>

int main() {
    EmscriptenWebGLContextAttributes attrs;
    emscripten_webgl_init_context_attributes(&attrs);
    auto handle = emscripten_webgl_create_context("canvas", &attrs);
    emscripten_webgl_make_context_current(handle);

    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    std::array<std::uint8_t, 8> data = {
        1, 2, 3, 4,
        5, 6, 7, 8,
    };
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.data());

    GLuint framebuffer;
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);

    glPixelStorei(GL_PACK_ALIGNMENT, 8);

    std::array<std::uint8_t, 3 * 4> buffer{};
    glReadPixels(0, 0, 1, 2, GL_RGBA, GL_UNSIGNED_BYTE, buffer.data());
    for (auto x : buffer) {
        std::cout << static_cast<int>(x) << ", ";
    }
    std::cout << std::endl;

    return 0;
}

Put index.html main.cpp in the same directory. Run emcc main.cpp. You will get:

.
├── a.out.js
├── a.out.wasm
├── index.html
└── main.cpp

Use a http server to host these files, for example python -m http.server.

Open the url of the index.html in any browser, and open the console, you will get an error about buffer length.

I ran this example in Emscripten 3.1.74. The js version works fine, but the c++ version doesn't.

Chrome:

WebGL: INVALID_OPERATION: readPixels: buffer is not large enough for dimensions

FireFox:

WebGL warning: readPixels: buffer too small

@seven332
Copy link
Author

If WebGL2 and 4G memory is enable. glPixelStorei does not support GL_PACK_ROW_LENGTH either.

Here is the code.
index.html

<body>
<script>
    const canvas = document.createElement('canvas');
    const gl = canvas.getContext('webgl2');

    const texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([
        1, 2, 3, 4,
        5, 6, 7, 8,
    ]));
    const framebuffer = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);

    gl.pixelStorei(gl.PACK_ROW_LENGTH, 2);

    const buffer = new ArrayBuffer(3 * 4);
    gl.readPixels(0, 0, 1, 2, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(buffer));
    console.log(new Uint8Array(buffer));
</script>
<canvas id="canvas"></canvas>
<script src="./a.out.js"></script>
</body>

main.cpp

#include <emscripten/html5.h>
#include <iostream>
#include <GLES3/gl3.h>

int main() {
    EmscriptenWebGLContextAttributes attrs;
    emscripten_webgl_init_context_attributes(&attrs);
    attrs.majorVersion = 2;
    auto handle = emscripten_webgl_create_context("canvas", &attrs);
    emscripten_webgl_make_context_current(handle);

    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    std::array<uint8_t, 16> data = {
        1, 2, 3, 4,
        5, 6, 7, 8,
    };
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.data());

    GLuint framebuffer;
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);

    glPixelStorei(GL_PACK_ROW_LENGTH, 2);

    std::array<std::uint8_t, 3 * 4> buffer{};
    glReadPixels(0, 0, 1, 2, GL_RGBA, GL_UNSIGNED_BYTE, buffer.data());
    for (auto x : buffer) {
        std::cout << static_cast<int>(x) << ", ";
    }
    std::cout << std::endl;

    return 0;
}

The operation process is the same as above, except for the C++ compilation parameters.
emcc main.cpp -sUSE_WEBGL2=1 -sINITIAL_MEMORY=3gb

@sbc100
Copy link
Collaborator

sbc100 commented Dec 18, 2024

Thanks for the repro cases. As far as you are aware have these features always been missing from emscripten or is this a regression? Any ideas for fixes? Would you like to send a PR?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants