-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Evaluating texture storage for different APIs #739
Comments
I would vote for only supporting KTX period. I wouldn't even support plain textures or other formats like JPEG and PNG. In most cases you don't want to render with uncompressed textures, and you don't want to transcode textures at load time. Imho transcoding is only useful when streaming textures. Otherwise you want the texture data in a GPU accessible format from the start. However, the commonly supported texture formats are different between mobile and desktop. So to make textures work on a variety of platforms you may need at least two versions of each texture: DXT/BC, ETC2. When using a container like KTX there is no need for the redundant info per image that is only relevant for other formats like JPEG and PNG. It just simplifies things as opposed to basically replicating the info stored in a KTX header for formats like JPEG and PNG. |
While I generally agree with this, I don't think we could remove them from spec. In WebGL environment a browser does all loading/decoding stuff, so those formats are more popular than GPU formats. PNG compression is lossless, so it could be better to use it for UI elements instead of uncompressed GPU format. Also with full support for npot textures in WebGL 2.0, an asset could reference some arbitrary user-generated images (such as photos from social media).
Since we can't embed JPEG into KTX, duplicating fields seems to be the only option. |
I would argue that formats like JPEG and PNG go against the design goals of glTF. These formats fit the convenience goals of glTF but they don't fit the fast loading goals. Especially PNG is not a good format for fast load times. In most cases these formats also won't get transcoded to a GPU compressed format. This can waste a lot of performance, bandwidth, power draw and heat. This may not be a concern on desktop hardware plugged into the wall with active cooling. However, the waste of bandwidth, power draw and heat is a crying shame on mobile. On mobile there is no such thing as "it runs fast enough". You can effectively optimize forever to minimize power draw and heat. Formats like BC7 and ASTC are pretty adequate for textures for UI elements that need to be high quality. |
Do you want to remove JPEG/PNG support from the glTF core (and possible move it to an extension), or just recommend to not use them on mobile? |
I'd move it to an extension and make loading KTX files the standard. That makes the standard plain and simple. That's just my opinion though. Happy to hear counter arguments or alternate solutions :) |
My first concern here is that even with WebGL we don't have one commonly supported format:
With WebGL 2.0, we'll have ETC2 everywhere (with software decode on almost all desktops). On the other hand, JPEG/PNGs are supported everywhere (while being slow and inefficient). The second reason is ease of UGC exchange. Let's say someone wants to share an asset to social network website. What format should textures be? |
Not anymore: KhronosGroup/WebGL#2030 |
I think the most interoperable approach could be like:
Btw, YouTube does something similar: user uploads anything, backend prepares MP4/AVC + WebM/VPx, clients fetch what's most compatible. |
Exactly. Client should be able to ask what it needs and receive what it needs and only what it needs. I guess I'm repeating myself in several threads :-) Regards, -- Rémi
|
I don't necessarily disagree with any of this but I do want to point out one of my concerns. glTF already depends on various other standards:
By allowing glTF to directly reference JPEG and PNG images we add two more dependencies. I understand that these standards are natively supported on various platforms. However, there are also many platforms where that is not the case. In other words, to support glTF on these platforms you have to find appropriate libraries for all these other standards (or you can write your own if that's your forte). Picking the right libraries is actually not trivial. JSON is a good example. There are hundreds of open source JSON implementations out there but many of them are poorly designed, poorly tested or just really slow. So which one do you pick? To support glTF I ended up evaluating various different JSON parsers.
So if you're on a platform that does not (properly) support exceptions then you have to disable exception handling in rapidjson. Unfortunately that makes rapidjson unstable. If glTF depends on JPEG and PNG I'll have to pick libraries for these formats as well (for C/C++: stb, IJG, etc.). Aside from picking a library, some of these libraries are not small by any means and cause significant code bloat. Maybe this is not considered that big of a deal but I would still urge to minimize the dependencies on other standards. Officially supporting KTX obviously also adds a dependency on another standard. However, the KTX format is so simply that it hardly needs a library. Besides there is an obvious Khronos KTX library. |
After looking through formats' specs and implementations, I agree. True interoperability across all platforms is almost unachievable unless we define a restricted subset for each image codec. So yes, let's move them to an extension. |
Updated schema could look like this (example with 4 images: external KTX, external PNGs, glb-embedded PNGs, glb-embdded DDS): {
"textures": {
"texture01": {
"target": 3553,
"sampler": "sampler01", // optional
"source": ["image00", "image01", "image02", "image03"], // list of images of different formats with texture data
"internalformat": 6408 // optional
}
},
"images": {
"image00": {
"format": 6408,
"mimeType": "image/ktx", // optional; "image/ktx" is default
"uri": [ // uri.length equals to 1 when no image extension is used
"file.ktx"
]
},
"image01": {
"format": 6408,
"mimeType": "image/png", // to avoid runtime guessing, valid only with "KHR_image_web" enabled
"uri": [
"file01.png",
"file02.png"
"file11.png"
"file12.png"
],
"extensions": {
"KHR_image_web": { // extension object could be omitted when all fields have default values
"depth": 2, // optional; depth of the mip level 0, used for 3D textures; must be zero for 2D and cube textures
"surfaces": 0, // optional; used for 2D texture arrays and cube map arrays
"faces": 1, // optional; 1 or 6 (cube map)
"levels": 2, // optional; 0 means runtime should call generateMipmap()
"type": 5121 // optional;
}
}
},
"image02": {
"format": 6408,
"mimeType": "image/png", // valid only when "KHR_image_web" enabled
"bufferView": [ // mutually exclusive with "image.uri"
"bufferView01",
"bufferView02",
"bufferView11",
"bufferView12"
],
"extensions": {
"KHR_image_web": {
"depth": 2, // optional; depth of the mip level 0, used for 3D textures; must be zero for 2D and cube textures
"surfaces": 0, // optional; used for 2D texture arrays and cube map arrays
"faces": 1, // optional; 1 or 6 (cube map)
"levels": 2, // optional; 0 means runtime should call generateMipmap()
"type": 5121 // optional;
}
}
},
"image03": {
"format": 6408,
"mimeType": "image/dds", // valid when "EXT_image_dds" is enabled
"bufferView": [
"bufferView_dds"
]
}
}
} KTX-like header fields are moved to extension object. "image01" and "image02" could be used only when We may need to refine the definition of "glTF extension" to allow extensions extending valid enums for existing properties ( We could also promote |
I would also argue to not support BMP in |
Agreed. Unless we have a solid use case now, I would generally be conservative with adding features, functionality and extensions even, in anticipation of their use. It's easy to add functionality and extensions later. It is much harder to remove functionality and glTF already suffers from that. For instance, do we really need an EXT_image_dds extension? What use cases do we have right now that makes this a critical extension? |
Fat fingered the close button ;) |
Both with core schema changes and BMP removal? |
BMP removal yes. The schema changes look fine. If the standard only supports KTX then textures don't need to store the "format", "internalFormat", "target" and "type". Those fields are uniquely defined by the KTX header and are only relevant for the extensions that support other image formats. This raises the question why these properties are stored on textures anyway as opposed to images. For instance, can you load the same image and interpret it as different formats or internalFormats? |
It's not critical at all, so it's not in the core. Use cases could be: simplifying re-usage of existing content and native DDS format support by D3D engines. Anyway, there's no urge there.
|
Wouldn't picking a format from an glTF image that stores multiple formats be an implementation issue? An glTF image should report which formats are available and the implementation can pick the one that is best suited for the platform. |
Here's what I'm talking about: {
"textures": {
"texture01": {
"target": 3553,
"sampler": "sampler01",
"source": ["image_bc", "image_etc", "image_pvr"]
}
},
"images": {
"image_bc": {
"format": 0x83F3, // COMPRESSED_RGBA_S3TC_DXT5_EXT
"uri": [
"file_bc.ktx"
]
},
"image_etc": {
"format": 0x9278, // COMPRESSED_RGBA8_ETC2_EAC
"uri": [
"file_etc.ktx"
]
},
"image_pvr": {
"format": 0x8C02, // COMPRESSED_RGBA_PVRTC_4BPPV1_IMG
"uri": [
"file_pvr.ktx"
]
}
}
} |
Right, as opposed to the "texture" specifying a "format" (like in glTF 1.0), the "image" specifies a "format". So the question is whether we should have the texture specify multiple "images" in different formats, or if an "image" specifies multiple formats. It feels more natural to me to have a texture reference a single image and have the image specify all the formats that are available. This also allows an implementation to select which image format to load without the need to know about textures. This would look like the following:
Here "format" is equivalent to the OpenGL internalFormat. I'm not a fan of the fact that KTX files can store the data in a different format than the actual internalFormat but that's the reality we live in. Arguably, in the case of KTX files, the "format" is already specified in the KTX header. However, the implementation needs to be able to select a format that is supported by the platform and we wouldn't want to force an implementation to read each KTX header to figure out if it stores the data in a format that is supported. At the end of the day the "texture" only cares about the flat image data. It doesn't care about the internalFormat. Only the implementation cares about selecting an image in an internalFormat that is suitable for the platform. The KHR_image_web extension could look like this:
Some subtle changes are "layers" instead of "surfaces" and defaulting to a depth of 1 and layer count of 1 for 2D textures and cube maps. I don't like a value of 0 in the latter cases because a value of 0 has no special meaning. Also note that the texture does not specify a "target" because the target can be trivially derived from the "depth", "layers", "faces" and "height" (KTX files don't store a target either). The target also doesn't directly translate to other graphics APIs like Vulkan. So overall an implementation can iterate over all the image formats and select a combination of image format (DXT, ETC, ASTC etc.) and file format (KTX, PNG, JPEG etc.) that are supported by the platform/implementation. |
As opposed to KHR_image_web should we be more granular and have extensions like KHR_image_png and KHR_image_jpeg? |
In that case, don't we need Why do we have If {
"images": {
"image0": {
"formats": [
{
"format": 33779, // GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
"mimeType": "image/ktx",
"uri": undefined,
"bufferView": "bufferView_dxt0"
},
{
"format": 32856, // GL_RGBA8
"mimeType": "image/png", // valid only when "KHR_image_web" enabled
"uri": undefined,
"bufferView": undefined,
"extensions": {
"KHR_image_web": {
"width": 256,
"height": 256, // specify 0 for a 1D texture
"depth": 1, // optional; depth of mip level 0 of a 3D texture; must be one for 2D and cube textures
"layers": 1, // optional; used for 2D texture arrays and cube map arrays
"faces": 1, // optional; 1 or 6 (cube map)
"levels": 2, // optional; 0 means run-time should call generateMipmap()
"uri": undefined,
"bufferViews": [ // list images in the order: depth-layers-faces-levels
"bufferView_image_level0",
"bufferView_image_level1"
]
}
}
}
]
}
}
} To tighten up schema, we could keep
We have also GIF. All web browsers and desktop image libraries support all three formats, so I don't see any reason to increase granularity there. Afaiu, we don't expect any of them on mobile platforms anyway. |
A texture with depth=1 and layers=1 is still not a 3D or array texture. I added the "width" and "height" for clarity last minute. You're right that they can just be read from the image file. I'm fine with keeping a list of URIs and buffer views on the image instead of the extension. Having one KHR_image_web extension works for me. Just wanted to make sure there's no good reason to be more granular. |
Sorry, I'm not following. If there's nor |
This is what you typically do to load a KTX file. Setting the depth and layers to anything less-equal to 1 results in the same target. In Vulkan you also specify a 2D texture with depth=1 and layers=1. |
I understand it, but such approach isn't 100% aligned with KTX spec:
Looks like KTX allows texture arrays of 1 element. Also see libktx. |
I guess that could be useful if you want to forcibly make a texture work with a particular shader. I'm not in love with the KTX format even though I think it's our best choice. I'm mostly going off of how Vulkan works, but even there I guess it could be useful to force an image view to be an 1 element array or 3D texture with a depth of 1. |
Is this now duplicate with #835? Or should we leave this open for notes post 2.0? |
Yes. And post-2.0 stuff should go there. |
Related previous discussions:
#620
#640
#674
#728
Considering ongoing efforts (#733) to go beyond WebGL 1.0 and to make glTF suitable for a wide range of APIs and runtimes, we can evaluate texture storage besides common web formats (such as JPEG or PNG).
Usage of raw texture data from
bufferView
s (as was proposed in #620) could be error-prone and doesn't allow to leverage existing tools.Currently, there're three widely used texture containers: KTX, PVR, and DDS. They already have lots of pipeline/tooling support (PVRTexTool, MetalKit, D3DX, etc).
So, here are several comments on them and related implementation thoughts.
KTX, PVR, and DDS
0
) for at-runtime generationOES_texture_(half_)float
is required.KTXorientation
fieldMetadata
fieldsformat
/internalformat
enumsColour Space
flagDXGI_FORMAT
enumEXT_sRGB
.format
enumPVRTC
,ETC1
,ETC2
,BCn
,ASTC
BCn
As seen from table above, all three formats provide almost the same functionality. For glTF to remain vendor-neutral, we can require only KTX support in the core spec and call community (or vendors) for PVR and DDS extensions.
For more advanced usages, an extension for Khronos Data Format support could be proposed.
glTF
texture
andimage
objectsSince almost all pixels-related properties are located in the container's header, there is no need to duplicate them in
texture
.However, runtime may need to know in advance what "codec" image uses (e.g., to acquire only supported compression format).
Also,
sampler
property could be made optional to enable non-sampled texture units support (ES 3.2, D3D10).Web image codecs
Working with currently supported image codecs (BMP, GIF, JPEG, PNG) could be a bit more tricky, because JavaScript runtime has no information about pixels type or color channels count.
JSON changes
As @ParticlePeter mentioned in #640, it makes sense to move couple
texture
properties toimage
object:texture.type
->image.type
. This field should be optional and an asset should provide it only for web image codecs.texture.format
->image.format
. This field should be mandatory and in case of KTX image, it must match actual data format.Valid enum values for
image.format
depend on enabled glTF and/or API extensions.To assist non-web runtimes and to explicitly declare image container,
image.mimeType
is added with enum values:["image/bmp", "image/gif", "image/jpeg", "image/ktx", "image/png"]
.Unlike KTX/PVR/DDS, web image codecs don't support volume textures, cube maps, texture arrays, or MIP levels. So we can emulate such functionality in JSON to provide a fail-safe alternative.
image
andtexture
objects could be redefined like this (example):For web codecs, the order of
uri
elements corresponds to the image data order in KTX format:Other possible image format extensions (HDR use cases, CPU decompression needed)
References
CC @pjcozzi, @mre4ce
The text was updated successfully, but these errors were encountered: