MTKTextureLoader by URL fails

I’m new to Metal and I’m in Chapter 8 (Textures) and I wanted to load a simple .jpg as a texture. I added it as a texture asset and it worked, but I wanted to load it from outside the app. Below is a code-snippet, and the ‘url’ is ok, but the ‘texture’ is always nil. What am I doing wrong?

static func loadTexture(fileName: String) -> MTLTexture? {
    let loader = MTKTextureLoader(device: Renderer.Device)
    let texture: MTLTexture?
    var url:URL
    url = URL(fileURLWithPath: fileName)
    let loaderOptions : [MTKTextureLoader.Option: Any] =
                        [.origin:MTKTextureLoader.Origin.bottomLeft,
                         .generateMipmaps: true]
    var ok:Bool
    ok = ((try? url.checkResourceIsReachable()) != nil)
    texture = try? loader.newTexture(URL:url,options:loaderOptions)
}

Additionally, when I step past ‘loader.newTexture(URL:options:)’ statement, the following “stuff” appears in the debug window (not really sure if it’s relevant or just Apple log cruft):

BOOL _NSPersistentUIDeleteItemAtFileURL(NSURL *const __strong) Failed to stat item: file:///Users/<u.id>/Library/Containers/<app.id>/Data/Library/Saved%20Application%20State/<app.id>.savedState/restorecount.plist

Nah, that “Failed to stat item” is a red herring. I get this even when I load a texture from the Bundle.

2 Likes

I’m not sure. It works for me when I place a jpg in the Documents folder of the challenge sample app.

To find the Documents folder, I print out the URL as a modifier on ContentView() in TexturesApp.swift:

.onAppear {
  print(URL.documentsDirectory)
}

I then created this method in TextureController.swift:

static func loadTexture(fileName: String) -> MTLTexture? {
  let loader = MTKTextureLoader(device: Renderer.device)
  let texture: MTLTexture?
  let url = URL(fileURLWithPath: fileName)
  let loaderOptions: [MTKTextureLoader.Option: Any] =
  [
    .origin: MTKTextureLoader.Origin.bottomLeft,
    .SRGB: false,
    .generateMipmaps: true]
  print(
    "Is resource reachable?",
    (try? url.checkResourceIsReachable()) != nil)
  texture = try? loader.newTexture(
    URL: url,
    options: loaderOptions)
  return texture
}

Which is almost the same as yours.

I then hijacked Model.setTexture(name:type:) to test it, and placed this code at the top of the method:

if name == "barn-ground",
   type == BaseColor {
  let path = "Documents/roadDiffuse.jpg"
  if let texture = TextureController.loadTexture(fileName: path) {
    meshes[0].submeshes[0].textures.baseColor = texture
    return
  }
}

And the grass loaded as my new image in the Documents folder.

Have you printed out your URLs and checked that the image is actually where you think it is? Is the image included in the bundle? In which case you’d have to have Bundle.main as part of the URL.

1 Like

Caroline,
Thanks for the response. My original code was correct. My error was that I forgot to remove “App Sandbox” from my project in order to use “real” paths (and not sandbox’ed paths). Doh!

1 Like