-
Notifications
You must be signed in to change notification settings - Fork 297
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
feat: option to use Arraybuffer for volume loader instead of sharedArrayBuffer #358
feat: option to use Arraybuffer for volume loader instead of sharedArrayBuffer #358
Conversation
@sedghi what are your thoughts on this? |
c527ec8
to
90471bf
Compare
✅ Deploy Preview for cornerstone-3d-docs ready!
To edit notification comments on pull requests, go to your Netlify site settings. |
const buffer = useSharedArrayBuffer | ||
? new SharedArrayBuffer(length * 4) | ||
: new ArrayBuffer(length * 4); | ||
|
||
return new Float32Array(sharedArrayBuffer); | ||
return new Float32Array(buffer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you should do it here. This file should do what it says (creating sharedArray). Maybe in the cornerstoneStreamingImageVolume you branch to use non-shared
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good I can make that change
offset: imageIdIndex * lengthInBytes, | ||
length, | ||
type, | ||
}, | ||
skipCreateImage: true, | ||
transferPixelData: arrayBuffer instanceof ArrayBuffer, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was about to fix this in another PR. Do you need it here? since I prefer it to be a separate fix: broken transferPixelData
for both volume and stack viewport.
Does it hurt to set transferPixelData
to true for SharedArrayBuffer too? although I guess it is shared by construct ..?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can remove this for your fix. It shouldn’t cause any issue to copy rather than transfer the encoded pixel array data
@@ -415,12 +414,14 @@ export default class StreamingImageVolume extends ImageVolume { | |||
const options = { | |||
// WADO Image Loader | |||
targetBuffer: { | |||
arrayBuffer, | |||
arrayBuffer: | |||
arrayBuffer instanceof ArrayBuffer ? undefined : arrayBuffer, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please add a comment to explain why we need it for sharedArrayBuffer (since the .set is happening inside the worker in cswil), but not for the arrayBuffer since it happens down below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I’ll add something like: keeping this in the options means a large empty volume array buffer will be transferred to the worker.
This is undesirable for streaming volume without shared array buffer because the target is now an empty 300-500MB volume array buffer. Instead the volume should be progressively set in the main thread.
const scalarData = this.scalarData; | ||
if (scalarData.buffer instanceof ArrayBuffer) { | ||
const offset = options.targetBuffer.offset; // in bytes | ||
const length = options.targetBuffer.length; // in frames | ||
try { | ||
if (scalarData instanceof Float32Array) { | ||
const floatView = new Float32Array(image.pixelData); | ||
if (floatView.length !== length) { | ||
throw 'Error pixelData length does not match frame length'; | ||
} | ||
scalarData.set(floatView, offset / 4); | ||
} | ||
if (scalarData instanceof Uint8Array) { | ||
const intView = new Uint8Array(image.pixelData); | ||
if (intView.length !== length) { | ||
throw 'Error pixelData length does not match frame length'; | ||
} | ||
scalarData.set(intView, offset); | ||
} | ||
} catch (e) { | ||
console.error(e); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These all better be refactor into its own function e.g., handleArrayBufferLoad
or somthing similar
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (scalarData.buffer instanceof ArrayBuffer) {
handleArrayBufferLoad()
}
successCallback...
@@ -179,7 +179,7 @@ if (configuration.examples) { | |||
// console.log('conf', conf); | |||
shell.ShellString(conf).to(webpackConfigPath); | |||
shell.cd(exBasePath); | |||
shell.exec(`webpack serve --progress --config ${webpackConfigPath}`); | |||
shell.exec(`webpack serve --host 0.0.0.0 --progress --config ${webpackConfigPath}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this make it run on 0.0.0.0 instead of localhost:3000?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will run on localhost and bind ip address so mobile devices on the same network can access.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great as first iteration.
The only part I'm not certain right now is the global setting for sharedArrayBuffer
usage in the init
. Hmmmm, Do we want it there? I'm not really sure I like the idea of people fallbacking to ArrayBuffer (instead of more performant sharedArrayBuffer) if they have not set the headers correctly. They can just debug the error for cross origin and fix their headers and enjoy the performant path.
Do you think we put it at the volume loader options? or maybe just a boolean at the init instead of checking new SharedArrayBuffer(0)
for those who know what they are doing
@sedghi one interesting find. I've been testing this across different devices desktop chrome, safari, android chrome, and iOS safari. They all work well except iOS safari which occasionally crashes the worker. On a single worker, either all slices will load properly without a crash. Or, after successful initialization, the worker throws a "Range Error: Out of Memory". Refreshing the page after waiting about 10 seconds usually fixes the problem and all slices load in. Have you seen this behavior before? |
90471bf
to
e0e3e00
Compare
After some more investigation. It seems like the iOS safari behavior is due to something with web worker garbage collection. Hard refreshing iOS will have 1 web worker properly spawn; however, follow on web workers will fail with a generic "Range Error: Out of Memory". With just one web worker, we can refresh the page within safari and have the volume properly load. However, the web worker will occasionally run into some error and crash (not loading any slices). A hard refresh (hard close safari and refresh) causes the page to function again. The consistent finding is that the first load after a hard refresh will always work. |
@Ouwen Will the memory occupied by the browser increase after the Arraybuffer replaces the sharedArrayBuffer? |
The browser memory creates an empty volume array. Let's say it is 100MB or so. The main difference is that without sharedArrayBuffer the workers have to transfer the 1MB decoded slice back into the main thread. The main thread then performs the set. With sharedArrayBuffer, the workers can set directly into the 100MB sharedArrayBuffer |
@Ouwen I talked to Bill, and we think |
Yeah I think that makes sense. |
This is unrelated to the non shared array buffer implementation and is an issue with loading codecs on iOS safari. |
0cbaefb
to
d58ab7c
Compare
d58ab7c
to
85098d8
Compare
@sedghi, ready for review. |
@Ouwen One of the tests are failing. You can force run it via changing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great !
Thanks @sedghi |
This feature allows for volumes to be loaded even if the page is not cross origin isolated.
For some use cases such as running cornerstone3D in cordova or mobile webview this is required.