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

opengl support for memory64 #18744

Closed

Conversation

PhilippMuigg
Copy link

@PhilippMuigg PhilippMuigg commented Feb 14, 2023

@sbc100, this is a second try at getting the opengl layer of emscripten to work when the MEMORY64 flag is used.
Since i worked on this as an employee of Siemens i was advised to add some copyright comments into file headers that were modified.

@juj i have incorporated some of the changes from #15433 into this pull request and i will wait for you finishing that up before considering merging.

emcc.py Outdated Show resolved Hide resolved
@PhilippMuigg PhilippMuigg force-pushed the memory64-opengl branch 6 times, most recently from 4111e3f to a0f65ae Compare March 17, 2023 14:00
src/library_egl.js Outdated Show resolved Hide resolved
@PhilippMuigg PhilippMuigg force-pushed the memory64-opengl branch 2 times, most recently from 6084252 to 6d06901 Compare March 21, 2023 11:58
@PhilippMuigg PhilippMuigg force-pushed the memory64-opengl branch 2 times, most recently from 68d6c7b to bc1a4fd Compare May 11, 2023 11:16
src/library_html5.js Outdated Show resolved Hide resolved
src/library_html5.js Outdated Show resolved Hide resolved
@@ -651,7 +657,7 @@ var LibraryHTML5 = {
{{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaY, 'wheelDeltaY', 'double') }}};
{{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaZ, '0 /* Not available */', 'double') }}};
{{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaMode, '0 /* DOM_DELTA_PIXEL */', 'i32') }}};
var shouldCancel = {{{ makeDynCall('iiii', 'callbackfunc') }}}( eventTypeId, JSEvents.wheelEvent, userData);
var shouldCancel = {{{ makeDynCall('iipp', 'callbackfunc') }}}( eventTypeId, JSEvents.wheelEvent, userData);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes look good. I wonder is we could figure out a test that uses these in test_browser.py and add also_with_wasm64 the that test? Then we could that this change in isolation?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like the existing tests in test-windows test-mac and test-other are failing due to some of these changes. And right, i can take a quick look and see if this can be added to a separate commit/pull request.

@@ -157,6 +157,9 @@ mergeInto(LibraryManager.library, {
#if CAN_ADDRESS_2GB
outIdx >>>= 0;
#endif
#if MEMORY64
outIdx = Number(outIdx);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not bee needed outIdx should already be a number by the time it gets here. Can you find the backtrace that leads here without outIdx being converted to an number already?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this is now also working without the explicit conversion. Will remove it.

} else {
const mod = size == 1 ? '' : ('>>' + Math.log2(size));
return `HEAP${which}.subarray((${start})${mod}, (${end})${mod})`;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last line of each branch here looks the same, right? Can we simplify here to avoid duplication? e.g.:

if (MEMORY64) {
  mod = ..
} else {
  mod = ..
}
return `HEAP${which}.subarray((${start})${mod}, (${end})${mod})`;  

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, will make this change.

@pavshen
Copy link

pavshen commented Aug 2, 2023

I checked this commit over tag 3.1.38 hoping it fixes WebGL problems in wasm32 with 4GB memory (as it incorporates changes from #15433). Vertex/index buffers and uncompressed textures work with pointers over 2GB, but compressed textures (DXT1/BC1) work only in WebGL 1 but don't work in WebGL 2, the following run-time error is shown:

Uncaught RangeError: Failed to execute 'compressedTexImage2D' on 'WebGL2RenderingContext': The ArrayBuffer/ArrayBufferView size exceeds the supported range.
    at _glCompressedTexImage2D

@PhilippMuigg
Copy link
Author

I checked this commit over tag 3.1.38 hoping it fixes WebGL problems in wasm32 with 4GB memory (as it incorporates changes from #15433). Vertex/index buffers and uncompressed textures work with pointers over 2GB, but compressed textures (DXT1/BC1) work only in WebGL 1 but don't work in WebGL 2, the following run-time error is shown:

Uncaught RangeError: Failed to execute 'compressedTexImage2D' on 'WebGL2RenderingContext': The ArrayBuffer/ArrayBufferView size exceeds the supported range.
    at _glCompressedTexImage2D

The primary issue with these _glXTexImageX methods in WebGL 2 is that they directly access an ArrayBuffer. It looks like most browsers nowadays have limitations wrt the size of these buffers when passed into the corresponding webgl 2 functions. Thus my changes in this commit create a copy of the data that is being sent to WebGL 2. This is similar to how the WebGL 1 code path is working. Though please note, that most of the changes proposed here are only active when compiling to wasm64.

@pavshen
Copy link

pavshen commented Aug 2, 2023

Anyway, I found the same problem with _glCompressedTexSubImage2D. Both problems are solved by changing the ifinside the functions' implementations so that the code for wasm64 works also for wasm32 with > 2GB.

emcc.py Outdated Show resolved Hide resolved
@@ -630,23 +646,23 @@ var LibraryWebGL2 = {
assert((value & 3) == 0, 'Pointer to integer data passed to glClearBufferiv must be aligned to four bytes!');
#endif

GLctx.clearBufferiv(buffer, drawbuffer, HEAP32, value>>2);
GLctx.clearBufferiv(buffer, drawbuffer, HEAP32, {{{ ptrToIdx('value', 2) }}});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about using the existing getHeapOffset('value', 'u32') here instead of this new macro?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In particular I don't love the 2 in the second argument to ptrToIdx. It seems very non-obvious compared to getHeapOffset('value', 'u32')

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, since there is now a more standardized way to make this conversion i will use it (these index conversion macros are part of an old commit from @juj that i pulled in to get some of this to work which i did not want to change too much).

}

// Browsers might not support passing BigInts to different API entry points that take in addresses, so convert those to Number()s before calling into the browser APIs.
function idxToMemory53(heapIndex) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would hope this is not needed since all pointers should be converted to int53 numbers when they arrive from native code.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, i will give removing this a try

@PhilippMuigg PhilippMuigg force-pushed the memory64-opengl branch 10 times, most recently from 80329b9 to 4801d2c Compare October 13, 2023 17:29
@@ -376,7 +376,11 @@ var LibraryGL = {
var source = '';
for (var i = 0; i < count; ++i) {
var len = length ? {{{ makeGetValue('length', 'i*4', 'i32') }}} : -1;
#if MEMORY64
source += UTF8ToString({{{ makeGetValue('string', 'i*8', '*') }}}, len < 0 ? undefined : len);
#else
source += UTF8ToString({{{ makeGetValue('string', 'i*4', 'i32') }}}, len < 0 ? undefined : len);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you just use i*{POINTERSIZE} and * here, which should work in both cases I think?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, will make that change.

@@ -1424,7 +1428,7 @@ var LibraryGL = {
},

glCompressedTexImage2D: (target, level, internalFormat, width, height, border, imageSize, data) => {
#if MAX_WEBGL_VERSION >= 2
#if MAX_WEBGL_VERSION >= 2 && !MEMORY64
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this?

Copy link
Author

@PhilippMuigg PhilippMuigg Oct 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not checked the newest versions of chrome/firefox, but up until recently calling webgl functions that directly accessed an array view via an offset did not work, when the array view was larger than 4GB (this is why i am using the fallback versions of these functions for which data has to be copied into a separate array). I will check, if this still holds true. If newer browser versions got rid of that limitation i can remove a lot of these checks.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I am not mistaken, in wasm32 when the array view is larger than 2GB, there is the same problem.


#if MEMORY64
{{{ makeSetValue('pointer', '0', 'GLctx.getVertexAttribOffset(index, pname)', 'i64') }}};
#else
{{{ makeSetValue('pointer', '0', 'GLctx.getVertexAttribOffset(index, pname)', 'i32') }}};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you just use '* here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that should work.

@@ -2320,17 +2338,17 @@ var LibraryGL = {
glUniform1iv: (location, count, value) => {
#if GL_ASSERTIONS
GL.validateGLObjectID(GLctx.currentProgram.uniformLocsById, location, 'glUniform1iv', 'location');
assert((value & 3) == 0, 'Pointer to integer data passed to glUniform1iv must be aligned to four bytes!');
assert({{{ isPtrAligned('value', 4) }}}, 'Pointer to integer data passed to glUniform1iv must be aligned to four bytes!');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{POINTER_SIZE} bytes?

@@ -920,6 +925,36 @@ function to64(x) {
return `BigInt(${x})`;
}

// Generates an expression that tests whether a pointer (either a BigInt or a signed int32 JS Number) is aligned to the given byte multiple.
function isPtrAligned(ptr, alignment) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need to second argument here? Shouldn't pointer always be aligned to pointer size? i.e. should alignment here not always be POINTER_SIZE?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is used in all the places where we previously did something like this

assert((value & 3) == 0, 'Pointer to integer data passed to glUniform2iv must be aligned to four bytes!');

So for example, if we want to pass int32 values to webGL (e.g. via glUniform2iv) then this data has to be aligned to 4 bytes because it is being passed via the HEAP32 array buffer view. To be honest, when i first saw this code i was a bit weary, since on the C/C++ side such guarantees are never required and i am pretty sure, that i could write some simple code which would break this assumption. But as far as i can tell, up until now, our production code has never caused any issues here.

As for whether the second parameter is required. I think currently we are always either using 4 byte alignment or 1 byte alignment (i.e. unaligned). So there is definitely space for improvement. Will take a closer look at this.

@pavshen
Copy link

pavshen commented Oct 16, 2023

I tested it above 3.1.47 on wasm32 with 4GB: WebGL2 still doesn't work.

@PhilippMuigg
Copy link
Author

I tested it above 3.1.47 on wasm32 with 4GB: WebGL2 still doesn't work.

Right, so i am trying to keep the changes in this commit limited to the wasm64 compile target. The whole 2GB (and sometimes 4GB) limit on array views that are passed off to webgl2 functions seems to be something that probably has to be addressed by browser developers? In the long run i would also like to get rid of the workarounds used in my commit to actually use the faster webgl2 functions that do not involve copying all the data that is being passed in.

@pavshen
Copy link

pavshen commented Oct 19, 2023

By the way, there are some failures with WebGL1, too. Cannot you take into consideration the case of wasm32 >2GB as well? I suppose it requires similar fixes. I can help to test many different uses of WebGL 1/2 with my graphical engine. Thanks in advance.

@sbc100
Copy link
Collaborator

sbc100 commented Mar 28, 2024

I believe that all of these fixes have now landed as part of the sequence of changes that made all our tests pass in wasm64 and 2gb+ mode and 4gb+ mode. e.g. #21573, #21445, #21306, #21264, #21220.

My apologies for not landing this change and instead incrementally implementing it. Hopefully the end result is that all your code should now work. If you think there are any remaining wasm64 or 2gb+ issue please let me know.

@PhilippMuigg
Copy link
Author

I believe that all of these fixes have now landed as part of the sequence of changes that made all our tests pass in wasm64 and 2gb+ mode and 4gb+ mode. e.g. #21573, #21445, #21306, #21264, #21220.

My apologies for not landing this change and instead incrementally implementing it. Hopefully the end result is that all your code should now work. If you think there are any remaining wasm64 or 2gb+ issue please let me know.

No worries, i am glad that i was able to point out issues that we ran into with this PR and i am excited to test this again.

@pavshen
Copy link

pavshen commented Apr 11, 2024

Next week, I am going to check 4GB WebGL2 with the newly released 3.1.57 (which should include all these fixes, am I right?) and I let you know if there are problems.

@sbc100
Copy link
Collaborator

sbc100 commented Apr 11, 2024

Thanks! I guess we can close this issue, and please open any new ones if you fine them. I'm pretty sure we have good test coverage at this point.

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

Successfully merging this pull request may close these issues.

3 participants