Following the initial intro post see http://peted.azurewebsites.net/gltf-directx/ I have written a few follow-up posts to highlight some of my learnings from writing this sample. This one is the final post and contains topics I thought were interesting but didn’t warrant a separate post. The full source code for the sample is here https://github.com/Microsoft/glTF-DXViewer
Having been using C# a fair bit and async/await becomes second nature for orchestrating any code of an asynchronous nature. Coroutines whilst not in the C++ standard as yet they are available in the Visual C++ compiler (and also, I believe in the Clang compiler) are hopefully headed for C++ 20. Here’s the current (as of the time of writing) ISO C++ draft proposal for coroutines http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4649.pdf and without going into the weeds of how this is implemented by the compiler I am going to briefly cover my usage as relates to this code sample.
So, this code will call the two methods in there whose names are postfixed with Async – which is a naming convention to identify async calls – and return from the outer method immediately scheduling the code after the co_await into a callback. This allows asynchronous code to be written in a sequential manner similar to non-asynchronous code. This results in much cleaner-looking code which, as a result is clearer and easier to understand.
In Visual Studio 2017 15.7.1 I noticed an Access Violation when running the code above. This is due to the compiler optimizing variables required by the coroutine body. The optimization can easily be disabled using the compiler switch /d2CoroOptsWorkaround.
Another C++ feature I hadn’t used before is variadic templates, introduced into the C++ 11 Standard. These allow template definitions to be parameterized with any number of arbitrary-typed arguments. To illustrate, I wanted to create a very simple notification system where I could associate an event with any method/function call. With this I could implement messages to communicate between ViewModels so when some data changes observers could be notified by hooking up their own functions. Then usage of this type looks like this:An opaque token is generated when registering an observer which can subsequently be used to deregister.
The PBR shaders are ported from the Khronos PBR shader sample here https://github.com/KhronosGroup/glTF-WebGL-PBR from GLSL -> HLSL. I ported these ‘by hand’ as it’s fairly straight-forward and I wanted to go through the process. There is a handy porting guide here https://docs.microsoft.com/en-us/windows/uwp/gaming/glsl-to-hlsl-reference. You might also get some leverage using ANGLE https://github.com/Microsoft/angle which with a bit of coercion you can access its translation of the shaders. I tried this but I found the result difficult to read so continued without using this method.
The shaders in the sample are compiled dynamically using the D3DCompileFromFile function and depending on the nature of the mesh that the material containing the shader is associated with a number of different compiler defines will get used. For example, if the mesh is associated with a material which in turn is associated with a metallic/roughness texture (as opposed to single values) then we can define the symbol HAS_METALROUGHNESSMAP and selectively compile shader code based on it. So the shader file itself might include code that looks like this:
So the texture lookups will only happen if there is a texture to lookup into!
Now, of course if we have already compiled the shader with a particular filename and the same set of compiler defines then we don’t want to compile again so there is a ShaderCache class to handle looking up the shaders based on a hash of the file name and defines used. Here’s the function used to load shaders for a mesh:
When programming against a low-level graphics API like OpenGL or DirectX it can sometimes be tricky to diagnose why things don’t work so hopefully if you are looking to incorporate glTF into your existing DirectX renderer this sample will be helpful. I extensively used the Visual Studio Graphics debugging tools which are not only brilliant for optimisation and performance profiling but when you have written some graphics code and are just faced with a blank screen they give you a detailed window onto the graphics pipeline.
Circled on the left is a call stack of events on the GPU and selecting one of these, in the screenshot I have selected the draw call for rendering the mesh, you can view the pipeline stages (circled at the bottom). This is great for diagnosing mistakes in input layout and the vertex shader. Also circled is a play button next to the pixel shader which enables shader debugging including placing breakpoints inside your shader code.
There are some interesting challenges when it comes to getting a stream from a UWP file picker to a native code environment. We can get an absolute file path from the picked file but we get an ‘Access Denied’ when using native file handling APIs. So, in order to pass a stream across from WinRT to native C++ it is necessary to make a transition from something like an IRandomAccessStream interface provided by a StorageFile to an istream which is an equivalent way of handling stream access in a native C++ environment. It is possible to achieve this by accessing the IStorageHandleAccess interface which is implemented by the StorageFile type (https://msdn.microsoft.com/en-us/library/windows/desktop/mt765063(v=vs.85).aspx). You can coerce this into a native C++ istream using code similar to the following:
From there I can pass the istream to the glTF deserializer. This avoids the need to copy the file or implement a C++ streambuf in terms of IRandomAccessStream. Another interesting issue is that when loading a glTF file which references textures, vertices, etc. in separate files it is not immediately obvious how we will be granted permission to access those loose files given the UWP sandbox. The glTF serializer/deserializer abstracts stream handling behind an IStreamReader interface so when a loose file is requested we are passed a uri string to our implementation of IStreamReader and we can pass back an istream. Now, the relative path we can reconstruct from the uri string we can pass to StorageFile::GetFileFromPathAsync to get a StorageFile and then we can again use code similar to the snippet above to convert that to an istream. But what if we don’t have permission to open the file at that path? Well, there is a relatively recent addition to restricted capabilities called broadFileSystemAccess (see https://docs.microsoft.com/en-us/windows/uwp/files/file-access-permissions#accessing-additional-locations). You can add this to your app manifest and then the user can decide whether or not your app can access the broader file system. Here are the user settings in Settings > File System
I am intending to extend the sample by updating the official repo with:
- More glTF features
- Supporting loading more of the glTF sample models
I will also fork the official sample to work on some features for my own research:
- VR Viewer
- Render to HoloLens
- Mesh decimation
- Port to DX12
- Edit and Save files
If there is an addition you’d like to add please submit a pull request to the repo https://github.com/Microsoft/glTF-DXViewer and/or ask for it in the Issues section on Github.
3 thoughts on “glTF & DirectX final part”
Hey pete, this is really cool to see! I’ll be going through this more closely in a bit in hopes of improving my own viewer 🙂
You can check out my DirectX 12 glTF lib/viewer here: https://github.com/jessey-git/fx-gltf
Yours is much nicer in the UI/Input dept. but there might be something there if DX 12 proves troublesome.
I’ve mainly stuck with DX11 until HoloLens supports DX12 and will be checking out your sample in more detail! It’s great to see the support for glTF – are you linked from the Khronos glTF site?
My fx-gltf library is listed on their site under the “C++” section. The viewer is not listed as it came a few weeks later and I wasn’t sure if I should list my entry twice or not (the viewer is just an example under the library)…