I have a WebGL application that allows users to upload arbitrary images. I understand that there will have to be some limit to the file size they are allowed to upload, but currently a 4MB image is causing a stack overflow.
To get the image, I have some javascript on the page that uses a file picker html element, and onLoadEnd it sends a message to Unity containing the base64 encoded string of that image. Unity decodes that string to a byte[ ] and creates a Texture2D to display in the menu. Unfortunately, a 4MB image is allocating something like 30MB on the stack, and in WebGL that simply crashes it.
Is there a way to alloc less memory when creating the texture2d and sprite that I need?
If not, is there a way to tell Unity to give me a bigger stack size?
Relevant code snippets :
In my html:
function readFile()
{
// Get the list of files from the input tag
var files = document.getElementById('files').files;
if(!files.length)
{
return;
}
// Get the first file form the list
var file = files[0];
// Instantiate the FileReader
var reader = new FileReader();
reader.onloadstart = function(evt)
{
if(evt.target.readyState == FileReader.LOADING)
{
//ShowImageLoader();
}
};
// Wait until the reader is ready
reader.onloadend = function(evt)
{
// Check the state of the reader to understand when it si done
if (evt.target.readyState == FileReader.DONE)
{
var obj = {
"name": file.name,
"bytes": window.btoa(evt.target.result),
"index": 0,
"max":0
};
console.log("pushing image with " + obj.bytes.length + " bytes");
PushImage(obj.bytes); //this is simply a SendMessage helper method. Unity indeed receives the bytes
}
};
// Read the actual byte of the file
reader.readAsBinaryString(file);
}
In my C# receiver object:
public void UpdateImage(string obj)
{
if (!Application.isEditor)
{
byte[] image = System.Convert.FromBase64String(obj);//this is the line that crashes it
Texture2D tex = new Texture2D(1, 1);
tex.LoadImage(image);
fileCallback("No Name", tex, image);
Destroy(gameObject);
}
}
you could do it in multible steps, just cut the string into multible and do one after another, make sure the string length is always a multible of 4 , i assume you have read the official Microsoft doc about System.Convert.FromBase64String(obj) especially the part about “possible exceptions”
Thanks, I considered splitting it up but originally was thinking that it wouldn’t work. Good to know it needs to be a multiple of 4, and I’ll check the docs again before I begin this endeavor. I’ll post back with results.
Alright I got it working and it works really well actually.
function SendFileToUnity(file) {
// Instantiate the FileReader
var reader = new FileReader();
reader.onloadstart = function (evt) {
if (evt.target.readyState == FileReader.LOADING) {
//ShowImageLoader();
}
};
// Wait until the reader is ready
reader.onloadend = function (evt) {
// Check the state of the reader to understand when it si done
if (evt.target.readyState == FileReader.DONE) {
var bytes = window.btoa(evt.target.result);
var name = file.name;
console.log("pushing image with " + bytes.length + " bytes");
var maxChunkSize = 100000;
var numChunks = Math.ceil(bytes.length / maxChunkSize);
console.log("pushing " + numChunks + " chunks");
for (var i = 0; i < numChunks ; i++) {
var chunk = bytes.slice(i * maxChunkSize, (i + 1) * maxChunkSize);
SendMessage(receiver, "UploadChunk", chunk));
}
SendMessage(receiver, "FinishImage", name);
}
};
// Read the actual byte of the file
reader.readAsBinaryString(file);
}
Then the two functions in my c# reciever:
private List<byte> bytes = new List<byte>();
public void UploadChunk(string obj)
{
UIManager.instance.OpenProgressModal("Processing Image");
UnityEngine.Debug.Log("Update image");
byte[] chunk = System.Convert.FromBase64String(obj);
UnityEngine.Debug.Log("bytes concat");
for (int i = 0; i < chunk.Length; i++)
{
bytes.Add(chunk[i]);
}
//bytes = bytes.Concat(chunk).ToList();
//bytes = bytes.Concat(chunk).ToArray();
UnityEngine.Debug.Log("got chunk");
}
public void FinishImage(string name)
{
UnityEngine.Debug.Log("finish image");
if (!Application.isEditor)
{
//byte[] image = System.Convert.FromBase64String(bytes);
Texture2D tex = new Texture2D(1, 1);
byte[] byteArray = bytes.ToArray();
tex.LoadImage(byteArray);
fileCallback(name, tex, byteArray);
Destroy(gameObject);
}
UnityEngine.Debug.Log("completely done");
UIManager.instance.CloseProgressModal();
}
Basically I upload chunks of the image, and for each image, once all the chunks for that image are pushed to unity, I send a message saying all the chunks are done so unity builds the image from the chunks.
2 Likes
With less hassle, you can use a FileManager package from the Unity Asset store.