Filament: Unable to parse glb file from remote source (via http)
kelvinwatson opened this issue · comments
Describe the bug
I am getting "Filament: Unable to parse glb file"
when I try to load a remote glb file.
The remote glb file I used was the DamagedHelmet.glb
used in the sample-gltf-viewer
app. I uploaded it to my GitHub repository (see: https://github.com/kelvinwatson/glb-files/blob/main/DamagedHelmet.glb) and am trying to load it via this url: https://github.com/kelvinwatson/glb-files/raw/main/DamagedHelmet.glb or this url: https://raw.githubusercontent.com/kelvinwatson/glb-files/main/DamagedHelmet.glb
The call to nCreateAssetFromBinary
seems to be failing.
NOTE: This URL(https://github.com/kelvinwatson/glb-files/raw/main/DamagedHelmet.glb) to the asset works when I plug it into ModelRenderable
in SceneForm
, so the URL to the asset is not the issue. When I download the asset, then place it in the assets folder in my Filament app, the asset also loads fine from the local assets folder, so the file itself doesn't appear to be the issue. Perhaps it's how I'm calling the URL or writing the byte buffer?
Would you be able to post a working example for loading glb
or gltf
file from a remote source in Filament?
To Reproduce
Steps to reproduce the behavior:
In the sample-gltf-viewer app, I replace this:
loadGlb("DamagedHelmet")
with this:
val glbUrl = "https://raw.githubusercontent.com/kelvinwatson/glb-files/main/DamagedHelmet.glb"
private fun loadGlbRemote() {
lifecycleScope.launch {
withContext(Dispatchers.IO) {
val url = URL("https://github.com/kelvinwatson/glb-files/raw/main/DamagedHelmet.glb")
val urlConnection: HttpURLConnection = url.openConnection() as HttpURLConnection
urlConnection.connect()
val inputStream = BufferedInputStream(urlConnection.getInputStream())
val buffer = ByteArray(37929216)
var bufferLength = 0
while (inputStream.read(buffer).also { bufferLength = it } > 0) {
}
val byteBuffer = ByteBuffer.wrap(buffer)
modelViewer.loadModelGlb(byteBuffer)
modelViewer.transformToUnitCube()
}
}
}
I've also tried this but also get connection.inputStream.buffer.size = 0
.
val glbUrl = "https://github.com/kelvinwatson/glb-files/raw/main/DamagedHelmet.glb"
val url = URL(glbUrl)
val connection = url.openConnection() as HttpURLConnection
connection.connect()
if (connection.responseCode == 200) {
inputStream = BufferedInputStream(connection.inputStream)
}
Expected behavior
GLB file should load in modelViewer
.
Screenshots
Logs
2022-02-23 17:31:15.003 11908-11962/com.example.myfilamentapp E/Filament: Unable to parse glb file.
Desktop (please complete the following information):
- OS: MacOS Catalina
- GPU: [e.g. NVIDIA GTX 1080]
- Backend: [OpenGL/Vulkan]
Smartphone (please complete the following information):
- Device: Google Pixel 4a
- OS: Android 12
Additional context
Add any other context about the problem here.
Thanks, this is a very clear bug report! I suspect that your download logic isn't quite right, but it's not obvious to me just by eyeballing the code. Maybe try adding byteBuffer.rewind()
on the line before the loadModelGlb call?
If you're still stuck I can try it out on my end.
( Our gltf_viewer sample can "download" models but it uses WebSockets not http. )
@prideout Adding the rewind
call didn't fix it.
As far as I can tell, the rewind only sets byteBuffer.position
to 0
and and byteBuffer.mark
to -1
but those values were already 0
and -1
respectively before the rewind
.
Looks like I'm getting closer. Now I'm getting this error, with this code:
private fun loadGlbRemote() {
lifecycleScope.launch {
withContext(Dispatchers.IO) {
val url = URL("https://github.com/kelvinwatson/glb-files/raw/main/DamagedHelmet.glb")
val urlConnection: HttpURLConnection = url.openConnection() as HttpURLConnection
urlConnection.connect()
val inputStream = BufferedInputStream(urlConnection.getInputStream())
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
val byteArrayOutputStream = ByteArrayOutputStream()
var bytesRead:Int
while ((inputStream.read(buffer).also { bytesRead = it }) != -1) {
byteArrayOutputStream.write(buffer, 0, bytesRead)
}
val byteArr = byteArrayOutputStream.toByteArray()
val byteBuffer = ByteBuffer.wrap(byteArr)
// val rewound = byteBuffer.rewind()
modelViewer.loadModelGlb(byteBuffer.rewind())
modelViewer.transformToUnitCube()
2022-02-24 11:25:19.128 23624-23656/com.example.myfilamentapp E/Filament: Panic
in JobSystem::ThreadState &utils::JobSystem::getState():270
reason: This thread has not been adopted.
2022-02-24 11:25:19.128 23624-23656/com.example.myfilamentapp E/Filament:
2022-02-24 11:25:19.128 23624-23656/com.example.myfilamentapp A/libc: Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 23656 (DefaultDispatch), pid 23624 (e.myfilamentapp)
I think the last two lines need to be moved out of the IO context and into the Main context:
withContext(Dispatchers.Main) {
modelViewer.destroyModel()
modelViewer.loadModelGlb(message.buffer)
modelViewer.transformToUnitCube(message.buffer)
}
Thanks @prideout. That was it! Thank you for your help.
Pasting a copy of the working code:
private fun loadGlbRemote() {
lifecycleScope.launch {
withContext(Dispatchers.IO) {
val url =
URL("https://github.com/kelvinwatson/glb-files/raw/main/DamagedHelmet.glb")
val urlConnection: HttpURLConnection = url.openConnection() as HttpURLConnection
urlConnection.connect()
val inputStream = BufferedInputStream(urlConnection.getInputStream())
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
val byteArrayOutputStream = ByteArrayOutputStream()
var bytesRead: Int
while ((inputStream.read(buffer).also { bytesRead = it }) != -1) {
byteArrayOutputStream.write(buffer, 0, bytesRead)
}
val byteArr = byteArrayOutputStream.toByteArray()
val byteBuffer = ByteBuffer.wrap(byteArr)
withContext(Dispatchers.Main) {
modelViewer.destroyModel()
modelViewer.loadModelGlb(byteBuffer.rewind())
modelViewer.transformToUnitCube()
}
Or the more condensed Kotlin version:
URL(glbUrl).openStream().use { inputStream: InputStream ->
val inputStream = BufferedInputStream(inputStream)
ByteArrayOutputStream().use { output->
inputStream.copyTo(output)
val byteArr = output.toByteArray()
val byteBuffer = ByteBuffer.wrap(byteArr)
val rewound = byteBuffer.rewind()
withContext(Dispatchers.Main) {
modelViewer.destroyModel()
modelViewer.loadModelGlb(rewound)
modelViewer.transformToUnitCube()
@prideout Do you want me to contribute this as a sample in the app?
Thanks Kelvin, glad it worked! GitHub will keep this ticket around even after it is closed so maybe that's good enough. The problem with adding more samples is that we need to maintain them... :)
var url = "https://github.com/kelvinwatson/glb-files/raw/main/DamagedHelmet.glb"
runBlocking {
async {
customViewer!!.loadRemoteGlb(url)
}
}
suspend fun loadRemoteGlb(url: String) {
GlobalScope.launch(Dispatchers.IO) {
URL(url).openStream().use { inputStream: InputStream ->
val inputStream = BufferedInputStream(inputStream)
ByteArrayOutputStream().use { output ->
inputStream.copyTo(output)
val byteArr = output.toByteArray()
val byteBuffer = ByteBuffer.wrap(byteArr)
val rewound = byteBuffer.rewind()
withContext(Dispatchers.Main) {
modelViewer.destroyModel()
modelViewer.loadModelGlb(rewound)
modelViewer.transformToUnitCube()
output.close()
inputStream.close()
}
}
}
}
}