EffekseerForWebGL icon indicating copy to clipboard operation
EffekseerForWebGL copied to clipboard

Offscreen Canvas Support

Open Ctrlmonster opened this issue 7 months ago • 2 comments

Hello,

I tried to use Effekseer in a three.js app with an Offscreen Canvas on a web worker today, but failed to make it happen.

First there are multiple usages of new Image, e.g. here and here which is not available on web workers. I replaced those with createImageBitMap(), but there is still an error thrown somewhere in a promise, followed by [.WebGL-0x11f423912900] GL_INVALID_OPERATION: Must have element array buffer bound..

Would it be possible to make Effekseer for WebGL compatible with offscreen canvas rendering? 🙏 Kind Regards

Ctrlmonster avatar Apr 11 '25 14:04 Ctrlmonster

Update:

I did some more digging and the createImageBitMap replacements work in principal, but this line

this.nativeptr = Core.LoadEffect(this.context.nativeptr, memptr, buffer.byteLength, this.scale);

fails for some effects. For example I tried some of the demo effects from here:

Simple_Ring_Shape1.efk - works ✅ Laser01.efk- fails ❌ Laser02.efk- fails ❌ Simple_Ribbon_Parent.efk- fails ❌ Simple_Ribbon_Sword.efk- fails ❌ Simple_Sprite_FixedYAxis.efk- fails ❌ Simple_Track1.efk- works ✅ block.efk- fails ❌

I'm not sure what exactly is different on these effects, but maybe someone with Effekseer internals knowledge will know what's wrong.

Ctrlmonster avatar Apr 11 '25 15:04 Ctrlmonster

These are the changes that I made to effekseer.src.js to get initial worker support:

Replace this:

// original code with new Image()
const arrayBufferView = new Uint8Array( arrayBuffer );
      Promise.resolve(new Blob([arrayBufferView], { type: 'image/png' }))
      .then(blob => {
        return Promise.resolve(URL.createObjectURL(blob))
      })
      .then(url => {
        const img = new Image();
        img.onload = () => {
          res.image = img;
          res.isLoaded = true;
          effect._update();
        };
        img.src = url;
      });

With:

// use createImageBitmap() 
var arrayBufferView = new Uint8Array(arrayBuffer);
  Promise.resolve(new Blob([arrayBufferView], { type: 'image/png' }))
    .then(blob => createImageBitmap(blob))
    .then(imageBitmap => {
      res.image = imageBitmap;
      res.isLoaded = true;
      effect._update();
    }).catch(err => {
      console.error('Error loading image:', err);
      if (typeof onerror !== "undefined") onerror(err.message || 'error', path);
    });

and replace this:

// original code with new Image()
const image = new Image();
image.onload = () => {
  let converted_image = _convertPowerOfTwoImage(image);
  onload(converted_image);
};
image.onerror = () => {
  if (!(typeof onerror === "undefined")) onerror('not found', path);
};

image.crossOrigin = _imageCrossOrigin;
image.src = path;

with:

// use createImageBitmap() 
fetch(path, { mode: 'cors' })
    .then(response => {
      if (!response.ok) throw new Error('not found');
      return response.blob();
    })
    .then(blob => createImageBitmap(blob))
    .then(imageBitmap => {
      var converted_image = _convertPowerOfTwoImage(imageBitmap);
      onload(converted_image);
    })
    .catch(err => {
      if (typeof onerror !== "undefined") onerror(err.message || 'error', path);
    });


Ctrlmonster avatar Apr 11 '25 15:04 Ctrlmonster