google / filament

Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WebGL2

Home Page:https://google.github.io/filament/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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()
          }
        }
      }
    }
  }