Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved binary read and write support #7

Merged
merged 9 commits into from
Sep 12, 2017
Merged

Improved binary read and write support #7

merged 9 commits into from
Sep 12, 2017

Conversation

vpenades
Copy link
Contributor

@vpenades vpenades commented Sep 11, 2017

Something that was missing from the current interface was the ability to read the binary buffer chunk from a glb file, and the ability to write glb files.

Notice that with the current API, we need to open the file two times, because the json chunk and the binary chunk are read separately (as if they were separate files)

I added these functions, along with a test that reads and writes binary files.

Also, I added an additional check for the byte padding for the json chunk.

I noticed some glb files don't have any padding on the binary buffer chunk. I understand it's because it's the last chunk... but, if in the future more chunk types are added, this might be an issue.

Finally, I added a unit test for the new methods.


var buffer = jsonChunk.Buffers[0];
Assert.IsTrue(buffer.ByteLength <= binChunk.Length);
// should we check padding as with jsonChunk? some reference files fail!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See KhronosGroup/glTF#1026. We should clarify spec on this issue.

Added a test that demonstrates how to load the buffers and the images for any gltf.
@vpenades
Copy link
Contributor Author

I took the liberty to add two additional helper methods to help load buffers and images, regardless of how they're stored. These methods greatly simplify loading all the data of a gltf as shown in the unit test.

public static Stream OpenImageFile(this Gltf model, string gltfFilePath, int imageIndex)

public static Byte[] LoadBinaryBuffer(this Gltf model, string gltfFilePath, int bufferIndex)

Both methods work the same way, you pass the model, the file path from where the model was originally loaded, and the index of the buffer or image. Internally, they will handle embedded or buffered content, so how the content has been stored is transparent to the end user.


if (buffer.Uri == null) return LoadBinaryBuffer(gltfFilePath);

if (buffer.Uri.StartsWith("data:application/octet-stream;base64"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to replace this with EMBEDDEDOCTETSTREAM?

{
string content = null;

if (image.Uri.StartsWith(EMBEDDEDPNG)) content = image.Uri.Substring(EMBEDDEDPNG.Length + 1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

glTF only support PNG and JPG. None of the other formats are supported, so you don't need the other mime types.

https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#file-extensions-and-mime-types

@@ -17,6 +17,13 @@ public static class Interface
const uint JSON = 0x4E4F534A;
const uint BIN = 0x004E4942;

const string EMBEDDEDOCTETSTREAM = "data:application/octet-stream;base64";
Copy link
Contributor

@bghgary bghgary Sep 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would recommend adding the trailing comma to this constant so that you are actually checking for the comma instead of ignoring the character using + 1.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okey, I'll do that change.

Btw, detecting embedded images is quite tricky and I'm aware there's room for improvement. For example, I'm sure some people will want to use hardware specific images, like DDS or ASTC , I don't know if there's specific mime types for these formats... in which case, is it possible to find images stored as data:application/octet-stream;base64 ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There have been lots of discussions around this already, but the short answer, at least for core glTF 2.0, is that hardware specific textures are not supported. Thus, you don't have to worry about them for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okey then.

About the Interface API, there's a few more methods I would like to add, but I don't know if you think it's too much already...

Usually, when I work with file formats, I try to make everything to work with Streams, certainly, it gets complicated when you have multiple files around. The use case is when you try to load a file from within a ZIP or any other package container (which is quite common to use in videogames)

The idea I had in mind was to replicate the previous methods used to load buffers and images, but using lambdas, so the end user could intercept/inject file accesses to whatever package container is using.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do that in a separate change.

throw new NotImplementedException($"The first chunk must be format 'JSON': {chunkFormat}");
if ((chunkLength & 3) != 0)
{
throw new NotImplementedException($"The second chunk must be padded to 4 bytes: {chunkLength}");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NotImplementedException seems weird for this. Perhaps use the InvalidDataException you are using elsewhere?

uint chunkFormat = binaryReader.ReadUInt32();
if (chunkFormat != BIN)
{
throw new NotImplementedException($"The second chunk must be format 'BIN': {chunkFormat}");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

fullLength += 8 + buffer.Length + binPadding;

binaryWriter.Write(GLTF);
binaryWriter.Write((UInt32)2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This might be good in a constant also

}

return Encoding.UTF8.GetString(binaryReader.ReadBytes((int)chunkLength));
uint chunkFormat = binaryReader.ReadUInt32();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This technically is incorrect. The spec does not explicitly say that the second chunk must be the BIN chunk. It says this:

https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#chunks

Client implementations must ignore chunks with unknown types.

This is to support extensions that add additional chunks to the file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okey, going to change all that 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You also need to consider when the BIN chunk is not present.

}
}

public static void SaveBinaryModel(this Gltf model, byte[] buffer, BinaryWriter binaryWriter)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider allowing buffer to be null which will then not write out the BIN chunk. BIN chunk is not always required for all glTF files.

Copy link
Contributor

@bghgary bghgary left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, looks good.

Added support for writing glb files without binary chunk
Added image and buffer loader funcions that use lambdas, to support loading gltf/glb from package files.
@vpenades
Copy link
Contributor Author

Okey, I think all the changes are in place, I might think to add a few more unit tests, but so far, in terms of loading/saving, I've covered most offline scenarios.


if (buffer.Uri == null) return externalReferenceSolver(null);

if (buffer.Uri.StartsWith(EMBEDDEDOCTETSTREAM))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While spec says that binary buffers use application/octet-stream, some tools may start using application/gltf-buffer after IANA approval. See KhronosGroup/glTF#944.

Copy link
Contributor

@bghgary bghgary Sep 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can fix this once the spec is updated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's something easy to add

{
string content = null;

if (image.Uri.StartsWith(EMBEDDEDPNG)) content = image.Uri.Substring(EMBEDDEDPNG.Length);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You still have more types than just PNG and JPG. Remove BMP, GIF, and TIFF here and the constants.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean, only PNG and JPG are supported?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well... that surprised me!, I guess it's a matter of time they include more formats

@bghgary bghgary merged commit cb1721e into KhronosGroup:master Sep 12, 2017
@bghgary bghgary mentioned this pull request Sep 12, 2017
@vpenades
Copy link
Contributor Author

I'll have a look at documenting some of my methods in a few days, time to bed!

@realvictorprm
Copy link

Good, if I have enough time I might do some tuff here too. Looks funny here 😃

@vpenades
Copy link
Contributor Author

I've just noticed there's a bug I introduced in the unit tests... I've just created a new fork... I'll fix that one and also add some documentation... pull request soon

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants