├── .gitignore
├── icons
├── ktx_favicon.ico
├── png
│ ├── ktx_app.png
│ └── ktx_document.png
└── README.md
├── CODE_OF_CONDUCT.md
├── images
├── logo-WD.svg
├── logo-spec.svg
├── ktx.svg
├── cubemap_coord_system.svg
└── khronos.svg
├── appendices
├── vendor-metadata.adoc
├── basislz-gdata.adoc
└── basislz-bitstream.adoc
├── ghpages-index.adoc
├── inline-images.rb
├── generate_format_switches.rb
├── docinfo.html
├── README.md
├── .github
└── workflows
│ └── build.yml
├── formats-include.rb
├── switch_test
├── Makefile
└── vk2gl.c
├── Makefile
├── license.adoc
├── ktx-media-registration.adoc
├── formats.schema.json
├── ktx-frag.adoc
└── khronos.css
/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /switch_test/build
3 | /out
4 |
--------------------------------------------------------------------------------
/icons/ktx_favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhronosGroup/KTX-Specification/HEAD/icons/ktx_favicon.ico
--------------------------------------------------------------------------------
/icons/png/ktx_app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhronosGroup/KTX-Specification/HEAD/icons/png/ktx_app.png
--------------------------------------------------------------------------------
/icons/png/ktx_document.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KhronosGroup/KTX-Specification/HEAD/icons/png/ktx_document.png
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | A reminder that this issue tracker is managed by the Khronos Group. Interactions here should follow the Khronos Code of Conduct (https://www.khronos.org/developers/code-of-conduct), which prohibits aggressive or derogatory language. Please keep the discussion friendly and civil.
2 |
--------------------------------------------------------------------------------
/images/logo-WD.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/logo-spec.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/icons/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Icon Notes
5 | ==========
6 |
7 | This directory houses PNG versions of the KTX icons. These were created from the master KTX logo in [../images/ktx.svg](../images/ktx.svg). It
8 | also houses `favicon.ico` used as the specification's `favicon`.
9 |
10 | The logo was designed by Dominic Agoro-Ombaka.
11 |
12 | Since actual icon files are application and platform specific, only PNG images are kept here.
--------------------------------------------------------------------------------
/appendices/vendor-metadata.adoc:
--------------------------------------------------------------------------------
1 | [appendix#vendorMetadata]
2 | == Registered Vendor Key/Value Pairs
3 |
4 | CAUTION: This appendix is non-normative.
5 |
6 | There are currently no registered key/value pairs.
7 | // The following key/value pairs have been registered. Readers and
8 | // writers may, but are not required, to support these keys.
9 |
10 | ////
11 | // Use this template when adding key/value pairs
12 | === Acme Corporation
13 |
14 | ==== ACMEfoo
15 | may be indicated by including a value with the key
16 |
17 | - `ACMEfoo`
18 |
19 | The value is , for example:
20 |
21 | [no-bullet]
22 | - `example value`
23 | ////
24 |
25 |
--------------------------------------------------------------------------------
/ghpages-index.adoc:
--------------------------------------------------------------------------------
1 | = KTX Specifications (Latest Committed Versions)
2 | :author: Mark Callow
3 | :author_org: Edgewise Consulting
4 | :description: Main page for KTX GitHub pages site
5 | //:docrev: 3
6 | //:ktxver: 2.0
7 | //:revnumber: {ktxver}.{docrev}
8 | //:revdate: {docdate}
9 | //:version-label: Version
10 | :lang: en
11 | :docinfo1:
12 | :doctype: article
13 | :encoding: utf-8
14 | :stylesheet: khronos.css
15 | :imagesdir: images
16 | :data-uri:
17 |
18 | :url-khr-reg: https://registry.khronos.org
19 | :url-khr-ktx: {url-khr-reg}/KTX
20 |
21 | These generated documents reflect the latest commits to the main branch
22 | of the specification source repo. For latest released (published) versions
23 | see {url-khr-ktx}[the KTX Registry].
24 |
25 | * link:ktxspec.v2.html[KTX v2.0 Specification]
26 | * link:ktx-frag.html[KTX Fragment URI Specification]
27 |
28 |
29 |
--------------------------------------------------------------------------------
/inline-images.rb:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2019 The Khronos Group Inc.
2 |
3 | require 'asciidoctor/extensions' unless RUBY_ENGINE == 'opal'
4 | require 'base64'
5 |
6 | Asciidoctor::Extensions.register do
7 | postprocessor do
8 | process do |document, output|
9 | # Work around a bug in Chrome where textit blocks are no shown in
10 | # italic with the default "HTML-CSS" Mathjax renderer.
11 | output.sub!('TeX-MML-AM_HTMLorMML', 'TeX-MML-AM_CHTML') if document.basebackend? 'html'
12 | _replace_urls(output) if document.basebackend? 'html'
13 | end
14 | end
15 | end
16 |
17 | MIME_TYPES = {
18 | '.png' => 'image/png',
19 | '.svg' => 'image/svg+xml',
20 | '.ico' => 'image/x-icon'
21 | }.freeze
22 |
23 | REGEX = /(?<=url\()[^.]+.(svg|png)(?=\))|(?<=href=")[^.]+.(ico|png|svg)(?=")/
24 |
25 | def _replace_urls(content)
26 | content.gsub(REGEX) do |filename|
27 | file = File.binread(filename)
28 | mime_type = MIME_TYPES[File.extname(filename)]
29 | "data:#{mime_type};base64,#{Base64.strict_encode64(file)}"
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/generate_format_switches.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Copyright (c) 2020 The Khronos Group Inc.
3 | # SPDX-License-Identifier: Apache-2.0
4 |
5 | require 'fileutils'
6 | require 'json'
7 | formats = JSON.parse(File.read('formats.json'), :symbolize_names => true).freeze
8 | targets = [:glFormat, :glType, :glInternalFormat, :dxgiFormat, :mtlFormat].freeze
9 | HEADER = %{// Copyright 2020 The Khronos Group Inc.
10 | // SPDX-License-Identifier: Apache-2.0
11 |
12 | /*************************************** Do not edit ***************************************
13 | Automatically generated by
14 | https://github.com/KhronosGroup/KTX-Specification/blob/master/generate_format_switches.rb
15 | *******************************************************************************************/
16 | }
17 |
18 | dir = FileUtils.mkdir_p(ARGV.fetch(0, 'out'))[0]
19 | files = targets.map { |t| [t, File.open("#{dir}/vkFormat2#{t}.inl", 'w')] }.to_h.freeze
20 | files.values.each { |file| file << HEADER }
21 | formats.each do |format|
22 | files.each do |target, file|
23 | file << "case #{format[:vkFormat]}: return #{format[target]};\n" if format[target]
24 | end
25 | end
26 | files.values.each(&:close)
27 |
--------------------------------------------------------------------------------
/docinfo.html:
--------------------------------------------------------------------------------
1 |
2 |
48 |
--------------------------------------------------------------------------------
/images/ktx.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Home of the KTX File Format Specification
2 |
3 | [](https://travis-ci.org/KhronosGroup/KTX-Specification)
4 |
5 | KTX is a file format that can be used for storing GPU-ready texture data (with cubemaps, mip levels, etc).
6 | Like DDS but with more features and more formal specification. It supports Basis Universal transcodable formats and supercompression which can yield JPEG-sized universal textures. glTF will use Basis Universal textures in KTX v2 containers.
7 |
8 | Click these links to see the latest published versions of the
9 | [KTX File Format Specification](https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html)
10 | or the [KTX Fragment URI Specification](https://registry.khronos.org/KTX/specs/2.0/ktx-frag.html)
11 | from the Khronos KTX Registry, visit the project's
12 | [GitHub Pages](https://github.khronos.org/KTX-Specification/) to see formatted
13 | versions of the latest commits to `main` (both options look much better than
14 | the ersatz views provided by GitHub) or run
15 |
16 | ```bash
17 | make
18 | ```
19 |
20 | in a Unix-like environment with [AsciiDoctor](https://asciidoctor.org/docs/install-toolchain/)
21 | installed to generate the publishable specs. They are the files `out/specs/ktxspec.v2.html` and `out/specs/ktx-frag.html`.
22 | Everything needed is inlined.
23 |
24 | The canonical KTX spec. text is in the file `ktxspec.adoc`. The canonical fragment URI spec. text is in the file `ktx-frag.html`.
25 |
26 | If you have questions or comments that don't merit creating an issue such as "why did you do
27 | so-and-so?" use GitHub [Discussions](https://github.com/KhronosGroup/KTX-Specification/discussions).
28 |
29 | ### GPU texture format mappings
30 |
31 | To ensure correct mappings from Vulkan's `VkFormat` to other GPU APIs, this repo additionally contains:
32 |
33 | - [JSON database](formats.json) ([schema](formats.schema.json)) with mappings to OpenGL, Direct3D, and Metal enums.
34 | - [Switch-case generator](generate_format_switches.rb) that produces 5 files with simple C-like case-return statements.
35 | > **Usage:** `./generate_format_switches.rb []`
36 | - [Compile test of the case statements](switch_test/vk2gl.c) that serves as an example of use. To try the compile test do
37 |
38 | ```bash
39 | cd switch_test
40 | make
41 | ```
42 | or
43 |
44 | ```bash
45 | make testswitches
46 | ```
47 |
48 |
49 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2015-2020 The Khronos Group Inc.
2 | # SPDX-License-Identifier: Apache-2.0
3 | name: KTX-Specification Build CI
4 |
5 | on:
6 | # Trigger the workflow on a pull request,
7 | pull_request:
8 |
9 | push:
10 | # And on pushes to main, which will occur when a PR is merged.
11 | branches:
12 | - main
13 | # Also trigger on push of release tags to any branch. Useful
14 | # for testing release builds before merging to main.
15 | tags:
16 | - 'v[0-9]+.[0-9]+.[0-9]+'
17 | - 'v[0-9]+.[0-9]+.[0-9]+-*'
18 | paths-ignore:
19 | - .appveyor.yml
20 | - .travis.yml
21 | - README.md
22 | - CODE_OF_CONDUCT.md
23 |
24 | # Allow manual trigger
25 | workflow_dispatch:
26 |
27 | jobs:
28 | build-specs:
29 | name: Build KTX File Format and KTX Fragment URI specifications
30 | runs-on: ubuntu-latest
31 |
32 | steps:
33 | - uses: actions/checkout@v4
34 |
35 | - name: Install Asciidoctor
36 | run: sudo apt-get install -y asciidoctor
37 |
38 | - name: Build specs
39 | run: make ghpages
40 |
41 | - name: Upload generated files for GitHub Pages
42 | id: deployment
43 | uses: actions/upload-pages-artifact@v3
44 | with:
45 | path: out/ghpages/
46 |
47 | generate-switches:
48 | name: Generate and test compile format conversion switches.
49 | runs-on: ubuntu-latest
50 |
51 | steps:
52 | - uses: actions/checkout@v4
53 |
54 | - name: Generate switches
55 | run: make switches
56 |
57 | - name: Test compile switches
58 | run: make testswitches
59 |
60 | deploy:
61 | name: Deploy to GitHub Pages
62 | # Add a dependency to the build job
63 | needs: build-specs
64 | # Only deploy when building `main`.
65 | if: github.ref == 'refs/heads/main'
66 |
67 | # Grant GITHUB_TOKEN the permissions required to make a Pages deployment
68 | permissions:
69 | pages: write # to deploy to Pages
70 | id-token: write # to verify the deployment originates from an appropriate source
71 |
72 | # Deploy to the github-pages environment
73 | environment:
74 | name: github-pages
75 | url: ${{ steps.deployment.outputs.page_url }}
76 |
77 | # Specify runner + deployment step
78 | runs-on: ubuntu-latest
79 | steps:
80 | - name: Deploy to GitHub Pages
81 | id: deployment
82 | uses: actions/deploy-pages@v4
83 |
--------------------------------------------------------------------------------
/formats-include.rb:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2019 The Khronos Group Inc.
2 |
3 | require 'asciidoctor/extensions' unless RUBY_ENGINE == 'opal'
4 | require 'json'
5 |
6 | Asciidoctor::Extensions.register do
7 | include_processor do
8 | def handles? target
9 | target == 'formats.json'
10 | end
11 |
12 | process do |doc, reader, target, attributes|
13 | METAL_URL_BASE = 'https://developer.apple.com/documentation/metal/mtlpixelformat/'
14 | DXGI_URL = 'https://docs.microsoft.com/en-us/windows/desktop/api/dxgiformat/ne-dxgiformat-dxgi_format'
15 | WEBGL_EX_URL_BASE = 'https://www.khronos.org/registry/webgl/extensions/'
16 |
17 | formats = JSON.parse(File.read(File.join(reader.dir, target)), :symbolize_names => true).freeze
18 |
19 | content = ''
20 | formats.each do |format|
21 | content << "#{format[:vkFormat]}::\n"
22 |
23 | content << "Layout:::\n"
24 | content << "* Type Size: #{format[:typeSize]}.\n"
25 | if format[:blockWidth] > 0
26 | content << "* Texel Block Dimensions: #{format[:blockWidth]}x#{format[:blockHeight]}x#{format[:blockDepth]}.\n"
27 | end
28 |
29 | content << "OpenGL:::\n"
30 | if format[:glInternalFormat]
31 | content << "* `glInternalFormat`: #{format[:glInternalFormat]}.\n"
32 |
33 | if format[:glFormat] && format[:glType]
34 | content << "* `glFormat`: #{format[:glFormat]}.\n"
35 | content << "* `glType`: #{format[:glType]}.\n"
36 | end
37 |
38 | write_gl_api_support('OpenGL', format[:glVersion], format[:glExtensions], content)
39 | write_gl_api_support('OpenGL ES', format[:glEsVersion], format[:glEsExtensions], content)
40 | write_gl_api_support('WebGL', format[:glWebVersion], format[:glWebExtensions], content)
41 |
42 | else
43 | content << "* No mapping available.\n"
44 | end
45 |
46 | content << "Direct3D:::\n"
47 | if format[:dxgiFormat]
48 | content << "* `DXGI_FORMAT`: #{DXGI_URL}[#{format[:dxgiFormat]}].\n"
49 | else
50 | content << "* No mapping available.\n"
51 | end
52 |
53 | content << "Metal:::\n"
54 | if format[:mtlFormat]
55 | content << "* `MTLPixelFormat`: #{METAL_URL_BASE}#{format[:mtlFormat]}[#{format[:mtlFormat]}].\n"
56 | else
57 | content << "* No mapping available.\n"
58 | end
59 | end
60 |
61 | reader.push_include content
62 | end
63 | end
64 | end
65 |
66 | def write_gl_api_support(name, version, extensions, content)
67 | content << "#{name} Support::::\n"
68 |
69 | if version
70 | content << "** Core #{version}+.\n"
71 | end
72 |
73 | if extensions
74 | extensions.each do |exts|
75 | content << "** "
76 | content << exts.map { |ext| name == 'WebGL' ? "#{WEBGL_EX_URL_BASE}#{ext}[`#{ext}`]" : "`#{ext}`" }.join(' + ')
77 | content << ".\n"
78 | end
79 | end
80 |
81 | unless version || extensions
82 | content << "** None.\n"
83 | end
84 | end
--------------------------------------------------------------------------------
/switch_test/Makefile:
--------------------------------------------------------------------------------
1 | # -*- tab-width: 4; -*-
2 |
3 | # Copyright 2024 Mark Callow.
4 | # SPDX-License-Identifier: Apache-2.0
5 |
6 | depth := ..
7 | dest := build
8 | switches_dir ?= $(dest)
9 | switches := vkFormat2glFormat.inl \
10 | vkFormat2glInternalFormat.inl \
11 | vkFormat2glType.inl
12 | switches := $(addprefix ${switches_dir}/,${switches})
13 |
14 | # Make a switches dir relative to the directory $(depth) because that is where
15 | # generate_format_switches must be run.
16 | ifeq ($(switches_dir), $(dest))
17 | # Produces a full path name. $(dir ...) results ends in a'/' so $(notdir ...)
18 | # would return an empty string.
19 | generated_switches_dir := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))$(dest)
20 | else
21 | generated_switches_dir := $(subst $(depth)/,,${switches_dir})
22 | endif
23 |
24 | khrregistry := https://registry.khronos.org
25 |
26 | # This is to use the specially generated vulkan_core.h with the ASTC 3D
27 | # format enumerators.
28 | vkheaderrepo := https://raw.githubusercontent.com/KhronosGroup/dfdutils/main/
29 | vkheaders := vulkan/vk_platform.h
30 | # The following are to use the standard Vulkan headers. Currently these
31 | # do not include the ASTC 3D enumerators so cannot be used to compile
32 | # the generated switches.
33 | #vkheaderrepo := https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include
34 | #vkheaders := vulkan/vk_platform.h \
35 | # vk_video/vulkan_video_codec_h264std.h \
36 | # vk_video/vulkan_video_codec_h264std_encode.h \
37 | # vk_video/vulkan_video_codecs_common.h \
38 | # vk_video/vulkan_video_codec_av1std.h \
39 | # vk_video/vulkan_video_codec_av1std_decode.h
40 | vkheaders := $(addprefix ${dest}/,${vkheaders})
41 |
42 | default: test_compile
43 | switches: $(switches)
44 | headers: $(dest) $(addprefix ${dest}/,GL/glcorearb.h KHR/khrplatform.h vulkan/vulkan_core.h)
45 | test_compile: $(dest)/vk2gl.o
46 |
47 | CFLAGS := -I $(switches_dir) -I $(dest) -Werror -c
48 |
49 | $(dest)/vk2gl.o: switches headers | $(dest)
50 | $(CC) $(CFLAGS) -o $@ vk2gl.c
51 |
52 | # &: is the "grouping separator" added in GNU make 4.3 to tell Make that
53 | # the command generates all listed targets. Earlier versionsa treat this
54 | # the same as the : separator and will issue the command for each target.
55 | $(switches) &: $(depth)/formats.json $(depth)/formats.schema.json $(depth)/generate_format_switches.rb |$(dest)
56 | cd $(depth); ./generate_format_switches.rb $(generated_switches_dir)
57 |
58 | $(dest)/GL/glcorearb.h:
59 | curl --create-dirs --output-dir $(dest)/GL -L -O $(khrregistry)/OpenGL/api/GL/glcorearb.h
60 |
61 | $(dest)/KHR/khrplatform.h:
62 | curl --create-dirs --output-dir $(dest)/KHR -L -O $(khrregistry)/EGL/api/KHR/khrplatform.h
63 |
64 | $(dest)/vulkan/vulkan_core.h: $(vkheaders)
65 |
66 | $(dest)/vulkan/vulkan_core.h $(vkheaders):
67 | curl --create-dirs --output-dir $(dir $@) -L -O $(vkheaderrepo)/$(subst ${dest}/,,$@)
68 |
69 | $(dest):
70 | mkdir -p $@
71 |
72 | # vim: ai noexpandtab ts=4 sw=4
73 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2014-2017 The Khronos Group Inc.
2 | # Copyright notice at https://www.khronos.org/registry/speccopyright.html
3 |
4 | out=out
5 | out.specs = $(out)/specs
6 | out.switches = $(out)/switches
7 | out.ghpages = ${out}/ghpages
8 |
9 | ktxspec = $(out.specs)/ktxspec.v2
10 | ktxfrag = $(out.specs)/ktx-frag
11 |
12 | all: $(ktxspec).html $(ktxfrag).html #$(ktxspec).pdf
13 |
14 | regdoc: $(out.specs)/ktx-media-registration.txt
15 |
16 | ktx_inlined_images := icons/ktx_favicon.ico \
17 | images/cubemap_coord_system.svg \
18 | images/khronos.svg \
19 | images/ktx.svg \
20 | images/logo-spec.svg
21 |
22 | appendices=appendices
23 |
24 | ktx_sources := ktxspec.adoc \
25 | $(appendices)/basislz-gdata.adoc \
26 | $(appendices)/basislz-bitstream.adoc \
27 | $(appendices)/vendor-metadata.adoc \
28 | ktx-media-registration.adoc \
29 | license.adoc \
30 | khronos.css \
31 | $(ktx_inlined_images) \
32 | docinfo.html
33 |
34 | frag_inlined_images := icons/ktx_favicon.ico
35 |
36 | frag_sources := ktx-frag.adoc \
37 | khronos.css \
38 | $(frag_inlined_images) \
39 | docinfo.html
40 |
41 | ghpages.index := $(out.ghpages)/index.html
42 | ghpages.ktxspec := $(out.ghpages)/ktxspec.v2.html
43 | ghpages.ktxfrag := $(out.ghpages)/ktx-frag.html
44 |
45 | # For GitHub CI to build GitHub Pages site.
46 | ghpages: $(ghpages.index) $(ghpages.ktxspec) $(ghpages.ktxfrag) images/khronos.svg images/ktx.svg
47 |
48 |
49 | switches := vkFormat2dxgiFormat.inl \
50 | vkFormat2glInternalFormat.inl \
51 | vkFormat2mtlFormat.inl \
52 | vkFormat2glFormat.inl \
53 | vkFormat2glType.inl
54 | switches := $(addprefix ${out.switches}/,${switches})
55 |
56 | switches: $(switches)
57 |
58 | $(ktxspec).html: $(ktx_sources) | $(out.specs)
59 | asciidoctor --trace -v --failure-level INFO -r ./inline-images.rb -r ./formats-include.rb -D $(dir $@) -o $(notdir $@) $<
60 |
61 | $(ktxfrag).html: $(frag_sources) | $(out.specs)
62 | asciidoctor --trace -v --failure-level INFO -r ./inline-images.rb -D $(dir $@) -o $(notdir $@) $<
63 |
64 | $(ktxspec).pdf:
65 |
66 | # Using a multiline define seemed like a good idea at the time.
67 | # However even with the quotes in the recipe, make only passes the first
68 | # line to the shell so we've made everything a single line with \
69 | # which means we also need ; to indicate statement ends to Ruby.
70 | # Note even with .ONESHELL only the frst line gets passed.
71 | define pure.rb
72 | ktxreg = "https://registry.khronos.org/KTX/specs/2.0"; \
73 | while gets ; \
74 | if $$_.match(/subs=normal/) then ; \
75 | puts "\n" ; \
76 | elsif $$_.match(/link:ktx-frag.html/) then ; \
77 | $$_[" link:ktx-frag.html\[KTX Fragments URI\]."] = "" ; \
78 | puts $$_ ; \
79 | puts "\n #{ktxreg}/ktx-frag.html\n" ; \
80 | else ; \
81 | puts $$_ unless $$_.match(/\.\.\.\./) ; \
82 | end ; \
83 | end
84 | endef
85 |
86 | # Creates pure-text version of media-registration for submission to IANA.
87 | $(out.specs)/ktx-media-registration.txt: ktx-media-registration.adoc | $(out.specs)
88 | ruby -e '$(pure.rb)' $< >$@
89 |
90 | $(ghpages.index): ghpages-index.adoc
91 | asciidoctor --trace -v --failure-level INFO -r ./inline-images.rb -D $(dir $@) -o $(notdir $@) $<
92 |
93 | ${out.ghpages}/%.html: ${out.specs}/%.html
94 | cp $< $@
95 |
96 | # &: is the "grouping separator" added in GNU make 4.3 to tell Make that
97 | # the command generates all listed targets. Earlier versionsa treat this
98 | # the same as the : separator and will issue the command for each target.
99 | $(switches) &: formats.json formats.schema.json generate_format_switches.rb | $(out.switches)
100 | ./generate_format_switches.rb $(out.switches)
101 |
102 | testswitches: $(switches)
103 | cd switch_test; $(MAKE) switches_dir=../$(out.switches)
104 |
105 | $(out):
106 | mkdir -p $@
107 |
108 | $(out.specs) $(out.switches): | $(out)
109 | mkdir -p $@
110 |
111 | clean:
112 |
113 | clobber: clean
114 | rm -rf $(out.specs) $(out.switches) $(out.ghpages) $(out)
115 |
116 | # vim: ai noexpandtab tw=72 ts=4 sw=4
117 |
--------------------------------------------------------------------------------
/license.adoc:
--------------------------------------------------------------------------------
1 | This Specification is protected by copyright laws and contains
2 | material proprietary to Khronos. Except as described by these terms,
3 | it or any components may not be reproduced, republished, distributed,
4 | transmitted, displayed, broadcast or otherwise exploited in any
5 | manner without the express prior written permission of Khronos.
6 |
7 | This Specification has been created under the Khronos Intellectual
8 | Property Rights Policy, which is Attachment A of the Khronos Group
9 | Membership Agreement available at
10 | https://www.khronos.org/files/member_agreement.pdf.
11 |
12 | Khronos grants a conditional copyright license to use and reproduce
13 | the unmodified Specification for any purpose, without fee or royalty,
14 | EXCEPT no licenses to any patent, trademark or other intellectual
15 | property rights are granted under these terms. Parties desiring to
16 | implement the Specification and make use of Khronos trademarks in
17 | relation to that implementation, and receive reciprocal patent
18 | license protection under the Khronos Intellectual Property Rights
19 | Policy must become Adopters and confirm the implementation as
20 | conformant under the process defined by Khronos for this
21 | Specification; see
22 | https://www.khronos.org/conformance/adopters/file-format-adopter-program.
23 |
24 | Khronos makes no, and expressly disclaims any, representations or
25 | warranties, express or implied, regarding this Specification,
26 | including, without limitation: merchantability, fitness for a
27 | particular purpose, non-infringement of any intellectual property,
28 | correctness, accuracy, completeness, timeliness, and reliability.
29 | Under no circumstances will Khronos, or any of its Promoters,
30 | Contributors or Members, or their respective partners, officers,
31 | directors, employees, agents or representatives be liable for any
32 | damages, whether direct, indirect, special or consequential damages
33 | for lost revenues, lost profits, or otherwise, arising from or in
34 | connection with these materials.
35 |
36 | The Khronos Intellectual Property Rights Policy defines the terms
37 | "`Scope`", "`Compliant Portion`", and "`Necessary Patent Claims`".
38 |
39 | Some parts of this Specification are purely informative and so are
40 | EXCLUDED from the Scope of this Specification. <>
41 | in <> defines how these parts of the Specification
42 | are identified.
43 |
44 | Where this Specification uses technical terminology, defined in the
45 | Glossary or otherwise, that refer to enabling technologies that are
46 | not expressly set forth in this Specification, those enabling
47 | technologies are EXCLUDED from the Scope of this Specification. For
48 | clarity, enabling technologies not disclosed with particularity in
49 | this Specification (e.g. semiconductor manufacturing technology,
50 | hardware architecture, processor architecture or microarchitecture,
51 | memory architecture, compiler technology, object oriented technology,
52 | basic operating system technology, compression technology, algorithms,
53 | and so on) are NOT to be considered expressly set forth; only those
54 | application program interfaces and data structures disclosed with
55 | particularity are included in the Scope of this Specification.
56 |
57 | For purposes of the Khronos Intellectual Property Rights Policy as
58 | it relates to the definition of Necessary Patent Claims, all
59 | recommended or optional features, behaviors and functionality set
60 | forth in this Specification, if implemented, are considered to be
61 | included as Compliant Portions.
62 |
63 | Where this Specification identifies specific sections of external
64 | references, only those specifically identified sections define
65 | normative functionality. The Khronos Intellectual Property Rights
66 | Policy excludes external references to materials and associated
67 | enabling technology not created by Khronos from the Scope of this
68 | Specification, and any licenses that may be required to implement
69 | such referenced materials and associated technologies must be
70 | obtained separately and may involve royalty payments.
71 |
72 | Khronos^®^ and Vulkan^®^ are registered trademarks, and KTX^™️^ and
73 | WebGL^™️^ are trademarks of The Khronos Group Inc. OpenGL^®^ is a
74 | registered trademark and the OpenGL ES^™️^ and OpenGL SC^™️^ logos
75 | are trademarks of Hewlett Packard Enterprise used under license by
76 | Khronos. ASTC is a trademark of ARM Holdings PLC. All other product
77 | names, trademarks, and/or company names are used solely for
78 | identification and belong to their respective owners.
79 |
--------------------------------------------------------------------------------
/images/cubemap_coord_system.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ktx-media-registration.adoc:
--------------------------------------------------------------------------------
1 | Type name: Image
2 |
3 | Subtype name: ktx2
4 |
5 | Required parameters: none
6 |
7 | Optional parameters: none
8 |
9 | Encoding considerations: binary
10 |
11 | Security considerations:
12 |
13 | The ktx2 type is a binary data stream which contains no executable
14 | code that could disrupt a client processor. There is no provision
15 | in the type specification that would allow authors to insert
16 | executable code that would present any security risk to a client
17 | machine. The only effect associated with this data is to cause an
18 | image to be rendered on the recipient's display.
19 |
20 | Because every item's length is available at its beginning, there
21 | is robust defense against corrupted or fraudulent data that might
22 | overflow a decoder's buffer. Also the signature bytes provide early
23 | detection of common file transmission errors.
24 |
25 | The image payload may be uncompressed or block-compressed. The
26 | payload may be further supercompressed with variable-rate compression.
27 |
28 | Block compression schemes are designed so small blocks of data
29 | (typically 64 to 128 bits) can be decompressed in real time into a
30 | small block of pixels (typically 4x4) during texel fetch. In such
31 | schemes it is not possible for a small amount of data to expand
32 | enormously because the level of compression is limited; the compressed
33 | size is related directly to the number of pixels in the uncompressed
34 | image and not to the content of the data.
35 |
36 | When variable-rate compression is used, the format includes information
37 | as to the expected size of the uncompressed data. However applications
38 | must not rely on this and must guard against buffer overflow and
39 | ultra large memory allocation.
40 |
41 | The ktx2 type does not provide encryption of the data payload. Users
42 | or applications wishing or needing to keep their images confidential
43 | must overlay their own encryption on the ktx data during transmission.
44 |
45 | Interoperability considerations:
46 |
47 | The ktx2 type is a container capable of holding a payload in one
48 | of a large number of formats, potentially supercompressed with one
49 | of a number of schemes. While the container format will not change,
50 | new payload formats and supercompression schemes may be added over
51 | time. Consumers of this media-type must fail gracefully in the
52 | face of unrecognized formats and schemes. Since formats and schemes
53 | are identified in the ktx2 header, applications can quickly reject
54 | those they do not support.
55 |
56 | Published specification:
57 |
58 | The KTX v2 file format specification can be found at
59 | [subs=normal]
60 | ....
61 | https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html
62 | ....
63 |
64 | Applications Usage:
65 |
66 | The tools in the KTX Software repo
67 | (https://github.com/KhronosGroup/KTX-Software) - ktx2check, ktx2ktx2,
68 | ktxinfo, ktxsc, toktx - and gltfpack. The KHR_texture_basisu glTF
69 | extension. Loaders are available in THREE.js and Babylon.js. It
70 | is anticipated that it will be widely used by applications using
71 | glTF content and by applications built on top of OpenGl, Vulkan,
72 | WebGL and other 3D API standards as the means of delivering texture
73 | data. Since image/ktx2 supports universal textures (textures that
74 | can be transcoded to any GPU block-compressed format) uptake is
75 | expected to be nearly, er, universal.
76 |
77 | Fragment Identifier Considerations:
78 |
79 | The syntax and semantics for identifying fragments in the payload of a KTX
80 | file, is as specified in link:ktx-frag.html[KTX Fragments URI].
81 |
82 | Restrictions on Usage: none
83 |
84 | Provisional registration? No
85 |
86 | Additional information:
87 |
88 | Magic number(s): 12 octets
89 | { 0xAB,0x4B,0x54,0x58,0x20,0x32,0x30,0xBB,0x0D,0x0A,0x1A,0x0A }
90 | File extension(s): .ktx2
91 | Macintosh file type code(s): n/a
92 | Macintosh Uniform Type Identifier: public.ktx2, conforms to public.image
93 |
94 | Intended usage: COMMON
95 |
96 | Restrictions on usage: none
97 |
98 | Other Information & Comments:
99 |
100 | Relationship to image/ktx: image/ktx shares the purpose of being a
101 | container for texture data for use with 3D APIs. image/ktx2 offers
102 | a significant increase in functionality, including universal textures
103 | and supercompression, that required incompatible changes to the
104 | container format hence this new registration request.
105 |
106 | Contact Person:
107 |
108 | Mark Callow (khronos at callow.im)
109 |
110 | Authors:
111 |
112 | Mark Callow, Alexey Knyazev
113 |
114 | Change controller:
115 |
116 | The Khronos Group (www.khronos.org), the industry consortium
117 | responsible for standards such as OpenGL, OpenGL ES, WebGL and
118 | Vulkan. For change requests contact .
119 |
120 |
--------------------------------------------------------------------------------
/images/khronos.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/formats.schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "title": "GPU Formats JSON Schema",
4 | "type": "array",
5 | "items": {
6 | "type": "object",
7 | "properties": {
8 | "type": {
9 | "type": "string",
10 | "enum": [
11 | "RAW",
12 | "PACKED",
13 | "BC",
14 | "ETC",
15 | "ASTC",
16 | "PVRTC",
17 | "MULTI-PLANE"
18 | ]
19 | },
20 | "blockWidth": {
21 | "type": "integer",
22 | "minimum": 0
23 | },
24 | "blockHeight": {
25 | "type": "integer",
26 | "minimum": 0
27 | },
28 | "blockDepth": {
29 | "type": "integer",
30 | "minimum": 0
31 | },
32 | "vkFormat": {
33 | "type": "string",
34 | "pattern": "^VK_[A-Z0-9_x]+$"
35 | },
36 | "typeSize": {
37 | "type": "integer",
38 | "enum": [1, 2, 4, 8]
39 | },
40 | "glFormat": {
41 | "type": ["string", "null"],
42 | "enum": [
43 | null,
44 | "GL_ALPHA",
45 | "GL_BGR",
46 | "GL_BGR_INTEGER",
47 | "GL_BGRA",
48 | "GL_BGRA_INTEGER",
49 | "GL_DEPTH_COMPONENT",
50 | "GL_DEPTH_STENCIL",
51 | "GL_RED",
52 | "GL_RED_INTEGER",
53 | "GL_RG",
54 | "GL_RG_INTEGER",
55 | "GL_RGB",
56 | "GL_RGB_INTEGER",
57 | "GL_RGBA",
58 | "GL_RGBA_INTEGER",
59 | "GL_STENCIL_INDEX"
60 | ]
61 | },
62 | "glType": {
63 | "type": ["string", "null"],
64 | "enum": [
65 | null,
66 | "GL_BYTE",
67 | "GL_FLOAT",
68 | "GL_FLOAT_32_UNSIGNED_INT_24_8_REV",
69 | "GL_HALF_FLOAT",
70 | "GL_INT",
71 | "GL_SHORT",
72 | "GL_UNSIGNED_BYTE",
73 | "GL_UNSIGNED_INT",
74 | "GL_UNSIGNED_INT_10F_11F_11F_REV",
75 | "GL_UNSIGNED_INT_2_10_10_10_REV",
76 | "GL_UNSIGNED_INT_24_8",
77 | "GL_UNSIGNED_INT_5_9_9_9_REV",
78 | "GL_UNSIGNED_SHORT",
79 | "GL_UNSIGNED_SHORT_4_4_4_4",
80 | "GL_UNSIGNED_SHORT_4_4_4_4_REV",
81 | "GL_UNSIGNED_SHORT_5_5_5_1",
82 | "GL_UNSIGNED_SHORT_1_5_5_5_REV",
83 | "GL_UNSIGNED_SHORT_5_6_5",
84 | "GL_UNSIGNED_SHORT_5_6_5_REV"
85 | ]
86 | },
87 | "glInternalFormat": {
88 | "type": ["string", "null"],
89 | "pattern": "^GL_[A-Z0-9_x]+$"
90 | },
91 | "glVersion": {
92 | "type": ["string", "null"],
93 | "pattern": "^[1-4]\\.[0-6]$"
94 | },
95 | "glExtensions": {
96 | "type": ["array", "null"],
97 | "minItems": 1,
98 | "items": {
99 | "type": "array",
100 | "items": {
101 | "type": "string",
102 | "minLength": 1
103 | },
104 | "uniqueItems": true,
105 | "minItems": 1
106 | }
107 | },
108 | "glEsVersion": {
109 | "type": ["string", "null"],
110 | "pattern": "^[1-3]\\.[0-2]$"
111 | },
112 | "glEsExtensions": {
113 | "type": ["array", "null"],
114 | "minItems": 1,
115 | "items": {
116 | "type": "array",
117 | "items": {
118 | "type": "string",
119 | "minLength": 1
120 | },
121 | "uniqueItems": true,
122 | "minItems": 1
123 | }
124 | },
125 | "glWebVersion": {
126 | "type": ["string", "null"],
127 | "pattern": "^[1-2]\\.0$"
128 | },
129 | "glWebExtensions": {
130 | "type": ["array", "null"],
131 | "minItems": 1,
132 | "items": {
133 | "type": "array",
134 | "items": {
135 | "type": "string",
136 | "minLength": 1
137 | },
138 | "uniqueItems": true,
139 | "minItems": 1
140 | }
141 | },
142 | "dxgiFormat": {
143 | "type": ["string", "null"],
144 | "pattern": "^DXGI_FORMAT_[A-Z0-9_]+$"
145 | },
146 | "mtlFormat": {
147 | "type": ["string", "null"],
148 | "pattern": "^MTLPixelFormat[A-Za-z0-9_]+$"
149 | }
150 | },
151 | "required": [
152 | "type",
153 | "blockWidth",
154 | "blockHeight",
155 | "blockDepth",
156 | "vkFormat",
157 | "typeSize",
158 | "glFormat",
159 | "glType",
160 | "glInternalFormat",
161 | "glVersion",
162 | "glExtensions",
163 | "glEsVersion",
164 | "glEsExtensions",
165 | "glWebVersion",
166 | "glWebExtensions",
167 | "dxgiFormat",
168 | "mtlFormat"
169 | ],
170 | "additionalProperties": false
171 | }
172 | }
--------------------------------------------------------------------------------
/appendices/basislz-gdata.adoc:
--------------------------------------------------------------------------------
1 | [appendix#basislz_gd]
2 | == BasisLZ Global Data
3 |
4 | BasisLZ combines encoding to a block-compressed format, that can be easily transcoded to various GPU-native block-compressed formats, with lossless supercompression. Supercompression is accomplished by conditioning the block-compressed representation for entropy encoding then Huffman encoding the result. BasisLZ creates a global codebook referenced by each supercompressed image that contains the processed endpoint and selector data from the block-compression and the Huffman tables. The global data block contains this codebook.
5 |
6 | It also contains an array of _image descriptors_ with flags for each image and the offset and length within the mip level of the data for that image.
7 |
8 | The global data structure is designed to accommodate various transcodable block-compressed formats. The format in use is indicated by the <<_data_format_descriptor, Data Format Descriptor>>. Currently BasisLZ only supports one, <> (a subset of ETC1, see {url-df-spec}#ETC1S[Section 21.1] of <>). ETC1S is a common subset of many GPU-native formats.
9 |
10 | The bitstreams for endpoints, selectors, Huffman tables and the image
11 | data are specific to the transcodable format in use. Those for ETC1S are
12 | defined in <>. These bitstreams have to be decoded to reconstruct the base images.
13 |
14 | The structure of the global data is shown below.
15 |
16 | [[basislz_global_data_structure]]
17 | .BasisLZ Global Data Structure
18 | [source,c,subs="+quotes,+attributes,+replacements"]
19 | ----
20 | UInt16 endpointCount;
21 | UInt16 selectorCount;
22 | UInt32 endpointsByteLength;
23 | UInt32 selectorsByteLength;
24 | UInt32 tablesByteLength;
25 | UInt32 extendedByteLength;
26 |
27 | ImageDesc[imageCount] imageDescs;
28 |
29 | Byte[endpointsByteLength] endpointsData
30 | Byte[selectorsByteLength] selectorsData
31 | Byte[tablesByteLength] tablesData
32 | Byte[extendedByteLength] extendedData
33 | ----
34 |
35 | `ImageDesc` is the following structure.
36 |
37 | .ImageDesc
38 | [source,c]
39 | ----
40 | UInt32 imageFlags
41 | UInt32 rgbSliceByteOffset
42 | UInt32 rgbSliceByteLength
43 | UInt32 alphaSliceByteOffset
44 | UInt32 alphaSliceByteLength
45 | ----
46 |
47 | Descriptions in the `imageDescs` array are in the order layer, face and z_slice as if arranged by the following pseudo code.
48 | [source,c]
49 | ----
50 | for each level in max(levelCount, 1)
51 | for each layer in max (layerCount, 1)
52 | for each face in faceCount // 1 or 6
53 | for each z_slice in max((pixelDepth of level), 1)
54 | ----
55 |
56 | `imageCount` is the total number of images in the Mip Level Array.
57 |
58 | [TIP]
59 | ====
60 | `imageCount` may be calculated as follows:
61 | [source,c]
62 | ----
63 | int imageCount = max(layerCount, 1) * faceCount * layerPixelDepth;
64 |
65 | // where layerPixelDepth can be derived as
66 | int layerPixelDepth = max(pixelDepth, 1);
67 | for(int i = 1; i < levelCount; i++)
68 | layerPixelDepth += max(pixelDepth >> i, 1);
69 | ----
70 | ====
71 |
72 | There must be no trailing bytes in the global data section after the `extendedData` field, i.e., the following condition must always be true:
73 | [source,c]
74 | ----
75 | sgdByteLength == 20 +
76 | 20 * imageCount +
77 | endpointsByteLength +
78 | selectorsByteLength +
79 | tablesByteLength +
80 | extendedByteLength
81 | ----
82 |
83 | === endpointCount
84 | The number of endpoints in <<_endpointsdata,endpointsData>>.
85 |
86 | === selectorCount
87 | The number of selectors in <<_selectorsdata,selectorsData>>.
88 |
89 | === endpointsByteLength
90 | The length of <<_endpointsdata,endpointsData>>.
91 |
92 | === selectorsByteLength
93 | The length of <<_selectorsdata,selectorsData>>.
94 |
95 | === tablesByteLength
96 | The length of <<_tablesdata,tablesData>>.
97 |
98 | === extendedByteLength
99 | The length of <<_extendeddata,extendedData>>. Must be 0 if the data format descriptor `colorModel` is `KHR_DF_MODEL_ETC1S` (= 163).
100 |
101 | === ImageDesc
102 | ==== imageFlags
103 | Flags giving information about an individual image. The following flag is valid:
104 | [source,c]
105 | ----
106 | isPFrame = 0x02
107 | ----
108 |
109 | BasisLZ/ETC1S supports inter-frame (video) encoding for 2D slices. If `isPFrame` is set the image (frame) is a P frame. That is, it refers to the previous image of an _animation sequence_. All other images are I frames. Only animation sequences can have P frames. See <> for details.
110 |
111 | ==== rgbSliceByteOffset, rgbSliceByteLength
112 | The offset of the start of the RGB slice within the <> of its mip level and its byte length. The offset of <> within the file is given in the <<_level_index,Level Index>>.
113 |
114 | `rgbSliceByteLength` must not be zero.
115 |
116 | `rgbSliceByteOffset + rgbSliceByteLength` must not be greater than the byte length of the corresponding mip level.
117 |
118 | ==== alphaSliceByteOffset, alphaSliceByteLength
119 | The offset of the start of the alpha slice within the <> of its mip level and its byte length.
120 |
121 | If there is only one slice these values must be 0. For ETC1S with the Data Format Descriptor color model of `KHR_DF_MODEL_ETC1S` this corresponds to the DFD having only one sample. (As the format is supercompressed `bytesPlane` fields can't be used to determine the number of planes.)
122 |
123 | If the second slice is present, `alphaSliceByteLength` must not be zero.
124 |
125 | `alphaSliceByteOffset + alphaSliceByteLength` must not be greater than the byte length of the corresponding mip level.
126 |
127 | === endpointsData
128 | Compressed endpoints data. The bitstream of this for ETC1S is
129 | described in <>.
130 |
131 | === selectorsData
132 | Compressed selectors data. The bitstream of this for ETC1S is
133 | described in <>.
134 |
135 | === tablesData
136 | Huffman tables data. The format of this data for ETC1S is described
137 | in <>.
138 |
139 | === extendedData
140 | Extended data. This is not used for ETC1S.
141 |
142 |
--------------------------------------------------------------------------------
/switch_test/vk2gl.c:
--------------------------------------------------------------------------------
1 | /* -*- tab-width: 4; -*- */
2 | /* vi: set sw=2 ts=4 expandtab textwidth=70: */
3 |
4 | /*
5 | * Copyright 2024 Mark Callow.
6 | * SPDX-License-Identifier: Apache-2.0
7 | */
8 |
9 | /**
10 | * @internal
11 | * @file vk2gl.h
12 | * @~English
13 | *
14 | * @brief Test compilation of GL format mappings.
15 | *
16 | * Note that this requires a @c vulkan_core.h with a VkFormat that includes
17 | * the ASTC 3D formats which are not currently in the standard
18 | * @c vulkan_core.h. A suitable file can be found at
19 | *
20 | * https://github.com/KhronosGroup/dfdutils/blob/main/vulkan/vulkan_core.h
21 | *
22 | * Once these formats are included in the standard @c vulkan_core.h
23 | * the above file will be removed.
24 | *
25 | * Tokens used in the case statements and results can be found in
26 | * @c vulkan_core.h version 267+ (except ASTC 3D tokens),
27 | * a @c glcorearb.h circa early 2024 (glcorearb.h has no version
28 | * number) or defined below.
29 | */
30 |
31 | #include "vulkan/vulkan_core.h"
32 | #include "GL/glcorearb.h"
33 |
34 | // These are only in glext.h.
35 |
36 | #if !defined( GL_COMPRESSED_SRGB_S3TC_DXT1_EXT )
37 | #define GL_COMPRESSED_SRGB_S3TC_DXT1_EXT 0x8C4C
38 | #endif
39 | #if !defined( GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT )
40 | #define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT 0x8C4D
41 | #endif
42 | #if !defined( GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT )
43 | #define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT 0x8C4E
44 | #endif
45 | #if !defined( GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT )
46 | #define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F
47 | #endif
48 |
49 | // These are only in GLES headers not in glcorearb.h or glext.h.
50 |
51 | //
52 | // ETC
53 | //
54 |
55 | #if !defined( GL_ETC1_RGB8_OES )
56 | #define GL_ETC1_RGB8_OES 0x8D64
57 | #endif
58 |
59 | //
60 | // PVRTC
61 | //
62 |
63 | #if !defined( GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG )
64 | #define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8C01
65 | #define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00
66 | #define GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG 0x8C03
67 | #define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02
68 | #endif
69 | #if !defined( GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG )
70 | #define GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG 0x9137
71 | #define GL_COMPRESSED_RGBA_PVRTC_4BPPV2_IMG 0x9138
72 | #endif
73 | #if !defined( GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT )
74 | #define GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT 0x8A54
75 | #define GL_COMPRESSED_SRGB_PVRTC_4BPPV1_EXT 0x8A55
76 | #define GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1_EXT 0x8A56
77 | #define GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1_EXT 0x8A57
78 | #endif
79 | #if !defined( GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV2_IMG )
80 | #define GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV2_IMG 0x93F0
81 | #define GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV2_IMG 0x93F1
82 | #endif
83 |
84 | //
85 | // ASTC
86 | //
87 |
88 | #if !defined( GL_COMPRESSED_RGBA_ASTC_3x3x3_OES )
89 | #define GL_COMPRESSED_RGBA_ASTC_3x3x3_OES 0x93C0
90 | #define GL_COMPRESSED_RGBA_ASTC_4x3x3_OES 0x93C1
91 | #define GL_COMPRESSED_RGBA_ASTC_4x4x3_OES 0x93C2
92 | #define GL_COMPRESSED_RGBA_ASTC_4x4x4_OES 0x93C3
93 | #define GL_COMPRESSED_RGBA_ASTC_5x4x4_OES 0x93C4
94 | #define GL_COMPRESSED_RGBA_ASTC_5x5x4_OES 0x93C5
95 | #define GL_COMPRESSED_RGBA_ASTC_5x5x5_OES 0x93C6
96 | #define GL_COMPRESSED_RGBA_ASTC_6x5x5_OES 0x93C7
97 | #define GL_COMPRESSED_RGBA_ASTC_6x6x5_OES 0x93C8
98 | #define GL_COMPRESSED_RGBA_ASTC_6x6x6_OES 0x93C9
99 | #endif
100 |
101 | #if !defined( GL_COMPRESSED_SRGB8_ALPHA8_ASTC_3x3x3_OES )
102 | #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_3x3x3_OES 0x93E0
103 | #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x3x3_OES 0x93E1
104 | #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4x3_OES 0x93E2
105 | #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4x4_OES 0x93E3
106 | #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4x4_OES 0x93E4
107 | #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5x4_OES 0x93E5
108 | #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5x5_OES 0x93E6
109 | #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5x5_OES 0x93E7
110 | #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6x5_OES 0x93E8
111 | #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6x6_OES 0x93E9
112 | #endif
113 |
114 |
115 | /**
116 | * @brief Return the glInternalFormat equivalent to the vkFormat.
117 | *
118 | * @param[in] vkFormat VkFormat enumerator value to look up.
119 | *
120 | * @return a GLenum with the equivalent internal format value.
121 | * GL_INVALID_VALUE if none found.
122 | */
123 | GLenum vkFormat2glInternalFormat( VkFormat vkFormat )
124 | {
125 | switch ( vkFormat )
126 | {
127 | #include "vkFormat2glInternalFormat.inl"
128 | default: return GL_INVALID_VALUE;
129 | }
130 | }
131 |
132 |
133 | /**
134 | * @brief Return the glFormat equivalent to the vkFormat.
135 | *
136 | * Only non-block compressed vkFormats have an equivalent glFormat as
137 | * only the glInternalformat is needed for loading compressed
138 | * textures. GL_INVALID_VALUE is returned for block-compressed formats.
139 | *
140 | * It is not possible to tell from the vkFormat token value when the
141 | * format is block compressed. If operating in the context of a KTX
142 | * file the embedded DFD's @c colorModel can be used to distinguish.
143 | *
144 | * @param[in] vkFormat VkFormat enumerator value to look up.
145 | *
146 | * @return a GLenum with the equivalent format value. GL_INVALID_VALUE
147 | * if none found.
148 | */
149 | GLenum vkFormat2glFormat( VkFormat vkFormat )
150 | {
151 | switch ( vkFormat )
152 | {
153 | #include "vkFormat2glFormat.inl"
154 | default: return GL_INVALID_VALUE;
155 | }
156 | }
157 |
158 |
159 | /**
160 | * @brief Return the glType equivalent to the vkFormat.
161 | *
162 | * Only non-block compressed vkFormats have an equivalent glType as
163 | * only the glInternalformat is needed for loading compressed
164 | * textures. @see vkFormat2glFormat for more details.
165 | *
166 | * @param[in] vkFormat VkFormat enumerator value to look up.
167 | *
168 | * @return a GLenum with the equivalent format value. GL_INVALID_VALUE
169 | * if none found.
170 | */
171 | GLenum vkFormat2glType( VkFormat vkFormat )
172 | {
173 | switch ( vkFormat )
174 | {
175 | #include "vkFormat2glType.inl"
176 | default: return GL_INVALID_VALUE;
177 | }
178 | }
179 |
180 |
--------------------------------------------------------------------------------
/appendices/basislz-bitstream.adoc:
--------------------------------------------------------------------------------
1 | [appendix#basisLZ/etc1s]
2 | == BasisLZ/ETC1S Bitstream Specification
3 |
4 | BasisLZ is a lossless compression scheme for ETC1S block data, which is based off https://www.sciencedirect.com/topics/engineering/vector-quantization[Vector Quantization]. VQ is applied to the ETC1S color endpoint/intensity values and texel selectors, each treated as two separate vectors. The two codebooks are global and shared across all mipmap levels, cubemap faces, animation frames, etc. There are two VQ codebook indices per block, which are compressed using a combination of canonical https://en.wikipedia.org/wiki/Huffman_coding[Huffman coding], https://en.wikipedia.org/wiki/Run-length_encoding[Run-Length Encoding], https://en.wikipedia.org/wiki/Differential_pulse-code_modulation[DPCM coding], and an approximate https://en.wikipedia.org/wiki/Move-to-front_transform[Move to Front] transform.
5 |
6 | It stores different ETC1S format fields in separately-compressed data streams thus reducing the transmission size.
7 |
8 | BasisLZ data comprises:
9 |
10 | * global endpoint array, where each element is a base color and an intensity table index pair;
11 | * global selectors array, where each element contains 16 values, called ETC1 "`pixel index bits`" in KDFS;
12 | * four global Huffman tables used for slice decoding;
13 | * per-slice block data encoded as references to the endpoint and selectors arrays.
14 |
15 | The following sections describe each section's payload independently. After decoding them and recovering per-block endpoint and selector values, the decoder can build ETC1S blocks. See the KDFS for ETC1S definition and bitstream.
16 |
17 | === Compressed Huffman Tables
18 |
19 | Many BasisLZ compressed sections use multiple Huffman tables. The process described in this section defines `read_huffman_table()` routine in the subsequent sections of this specification.
20 |
21 | Huffman codes are stored in each output byte in LSB to MSB order.
22 |
23 | Huffman coding in BasisLZ is compatible with the canonical Huffman methods used by Deflate encoders/decoders. Section 3.2.2 of https://tools.ietf.org/html/rfc1951[Deflate - RFC 1951], which describes how to compute the value of each Huffman code given an array of symbol codelengths.
24 |
25 | A BasisLZ Huffman table consists of 1 to 16383 symbols. Each compressed Huffman table is described by an array of symbol code lengths in bits. The table's symbol code lengths are themselves RLE+Huffman coded, just like Deflate.
26 |
27 | Each table begins with a small fixed header followed by the code lengths for the small Huffman table which is used to send the compressed codelengths.
28 |
29 | [source]
30 | ----
31 | total_used_syms b(14)
32 | num_codelength_codes b(5)
33 | for (int i = 0; i < num_codelength_codes; i++)
34 | {
35 | code_length_code_sizes[codes_order[i]] b(3)
36 | }
37 | ----
38 |
39 | There are a maximum of 21 symbols in a compressed Huffman code length table, so `num_codelength_codes` must be in [1..21] range.
40 |
41 | The code lengths are reordered to potentially reduce the number of used codes:
42 |
43 | [source]
44 | ----
45 | codes_order = {
46 | 17, 18, 19, 20, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15, 16
47 | };
48 | ----
49 |
50 | A canonical Huffman decoding table (of up to 21 symbols) is built from these code lengths.
51 |
52 | Immediately following this data are the Huffman symbols (sometimes intermixed with raw bits) which describe how to unpack the codelengths of each symbol in the Huffman table. This is a slightly modified version of dynamic Huffman codes described in RFC 1951, Section 3.2.7.
53 |
54 | - Symbols [0..16] indicate a specific symbol code length in bits, so maximum supported Huffman code size is 16 bits.
55 |
56 | - Symbol 17 indicates a short run of symbols with 0 bit code lengths. 3 bits are sent after this symbol, which indicates the run's size after adding the minimum size of 3, so the run has 3..10 symbols.
57 |
58 | - Symbol 18 indicates a long run of symbols with 0 bit code lengths. 7 bits are sent after this symbol, which indicates the run's size after adding the minimum size of 11, so the run has 11..138 symbols.
59 |
60 | - Symbol 19 indicates a short run of symbols that repeat the previous symbol's code length. 2 bits are sent after this symbol, which indicates the number of times to repeat the previous symbol's code length, after adding the minimum size of 3, so the run has 3..6 symbols. Cannot be the first symbol, and the previous symbol cannot have a code length of 0.
61 |
62 | - Symbol 20 indicates a short run of symbols that repeat the previous symbol's code length. 7 bits are sent after this symbol, which indicates the number of times to repeat the previous symbol's code length, after adding the minimum size of 7, so the run has 7..134 symbols. Cannot be the first symbol, and the previous symbol cannot have a code length of 0.
63 |
64 | There should be exactly `total_used_syms` code lengths stored in the compressed Huffman table. If not, the stream is either corrupted or invalid.
65 |
66 | After all the symbol codelengths are uncompressed, the symbol codes can be computed and the canonical Huffman decoding tables can be built.
67 |
68 | `decode_huffman(model)` in the sections below denotes reading the Huffman-encoded value from a current bitstream position using the table `model`.
69 |
70 | === ETC1S Endpoint Codebooks
71 |
72 | The byte offset and the byte length (`endpointsByteLength`) of the
73 | endpoint codebook section (`endpointsData`) as well as the number
74 | of endpoint entries (called `endpointCount`) are defined by the
75 | <>.
76 |
77 | ==== Header
78 | At the beginning of the compressed endpoint codebook section are four compressed Huffman tables, stored using the procedure outlined in <> and a 1-bit flag signalling whether endpoints have three color channles or one.
79 |
80 | [source]
81 | ----
82 | colorDeltaModel0 read_huffman_table()
83 | colorDeltaModel1 read_huffman_table()
84 | colorDeltaModel2 read_huffman_table()
85 | intenDeltaModel read_huffman_table()
86 | isGrayscale b(1)
87 | ----
88 |
89 | ==== Endpoints
90 |
91 | Immediately after the header is the compressed color endpoint codebook data (`inten[endpointCount]`, `red[endpointCount]`, `green[endpointCount]` and `blue[endpointCount]` arrays). A simple form of DPCM (Delta Pulse Code Modulation) coding is used to send the ETC1S intensity table indices and color values. Here is the procedure to decode the endpoint codebook:
92 |
93 | . Set initial values:
94 | .. Let the previous intensity index `prevInten` be `0`.
95 | .. If `isGrayscale` is `1`, let `prevLuma` be `16`.
96 | .. Else, let `prevRed`, `prevGreen` and `prevBlue` be `16`.
97 | . For `i` from `0` to `endpointCount`:
98 | .. Decode table intensity index:
99 | ... Read an `intenDelta` value using the `intenDeltaModel` Huffman table.
100 | ... Set `inten[i]` to `((prevInten + intenDelta) & 7)`.
101 | ... Set `prevInten` to `inten[i]`.
102 | .. Decode endpoint color value:
103 | ... Depending on the `isGrayscale` value, read `lumaDelta` or {`redDelta`, `greenDelta` and `blueDelta`} using the Huffman tables depending on the `prevLuma` or {`prevRed`, `prevGreen` and `prevBlue`} ranges:
104 | - `colorDeltaModel0` for values in [0..9];
105 | - `colorDeltaModel1` for values in [10..21];
106 | - `colorDeltaModel2` for values in [22..31].
107 | ... Sum deltas with the previous values as `value = (prevValue + valueDelta) & 31`.
108 | ... Set `red[i]`, `green[i]` and `blue[i]` from the decoded values. For grayscale endpoints, set all channels to the luma value.
109 | ... Update `prevLuma` or {`prevRed`, `prevGreen` and `prevBlue`}.
110 | . The rest of the section's data (if any) can be ignored.
111 |
112 | === ETC1S Selector Codebooks
113 |
114 | Selector entries contain 16 2-bit values that map to the ETC1 pixel index bits as:
115 |
116 | [options="header"]
117 | |====
118 | | Selector Value | Pixel Index MSB | Pixel Index LSB | Modifier
119 | | 0 | 1 | 1 | -b
120 | | 1 | 1 | 0 | -a
121 | | 2 | 0 | 0 | +a
122 | | 3 | 0 | 1 | +b
123 | |====
124 |
125 | The byte offset and the byte length (`selectorsByteLength`) of the
126 | selector codebook section (`selectorsData`) as well as the number
127 | of selector entries (called `selectorCount`) are defined by the
128 | <>.
129 |
130 | ==== Header
131 |
132 | The first two bits are reserved and must always be set to `0`. The input is invalid otherwise.
133 |
134 | The third bit indicates if the selector codebook is stored in raw form (uncompressed). If it's unset, the `deltaSelectorModel` Huffman table will immediately follow the third bit.
135 |
136 | ==== Selectors
137 |
138 | Each selector entry is a 4x4 grid, ordered left-to-right, top-to-bottom. Each row is packed to 8 bits, thus each selector entry could be expressed as four 8-bit bytes. Each packed row corresponds to four 2-bit values. The first (left) value of each row starts at the LSB (least significant bit) of each 8-bit group.
139 |
140 | [options="header"]
141 | [cols="h,1,1,1,1"]
142 | |====
143 | | | X0 | X1 | X2 | X3
144 | | Y0 | s0[1:0] | s0[3:2] | s0[5:4] | s0[7:6]
145 | | Y1 | s1[1:0] | s1[3:2] | s1[5:4] | s1[7:6]
146 | | Y2 | s2[1:0] | s2[3:2] | s2[5:4] | s2[7:6]
147 | | Y3 | s3[1:0] | s3[3:2] | s3[5:4] | s3[7:6]
148 | |====
149 |
150 | When `isUncompressed` bit is set, all selectors are stored uncompressed. When that bit is unset, only the first selector entry is stored uncompressed while all subsequent entries are DPCM coded (by using four XOR-deltas for each subsequent selector entry) with Huffman coding.
151 |
152 | .Sample implementation
153 | [%collapsible]
154 | =====
155 | [source]
156 | ----
157 | zeros b(2)
158 | isUncompressed b(1)
159 | if (isUncompressed)
160 | {
161 | for (int i = 0; i < selectorCount; i++)
162 | {
163 | for (int j = 0; j < 4; j++)
164 | {
165 | selector[i][j] b(8)
166 | }
167 | }
168 | }
169 | else
170 | {
171 | deltaSelectorModel read_huffman_table()
172 |
173 | for (int j = 0; j < 4; j++)
174 | {
175 | selector[0][j] b(8)
176 | }
177 |
178 | for (int i = 1; i < selectorCount; i++)
179 | {
180 | for (int j = 0; j < 4; j++)
181 | {
182 | selector[i][j] =
183 | decode_huffman(deltaSelectorModel) ^
184 | selector[i - 1][j]
185 | }
186 | }
187 | }
188 | ----
189 | =====
190 |
191 | Any bytes in this section following the selector codebook bits can be safely ignored.
192 |
193 | === ETC1S Slice Huffman Tables
194 |
195 | Each ETC1S slice is compressed with four Huffman tables (`tablesData`)
196 | stored using the procedure outlined in <>.
197 | Their byte offset and byte length (`tablesByteLength`) are defined
198 | by the <>.
199 |
200 | Following the last Huffman table are 13-bits indicating the size of the selector history buffer.
201 |
202 | [source]
203 | ----
204 | endpointPredModel read_huffman_table()
205 | endpointDeltaModel read_huffman_table()
206 | selectorModel read_huffman_table()
207 | selectorHistoryBufRleModel read_huffman_table()
208 | selectorHistoryBufSize b(13)
209 | ----
210 |
211 | Any remaining bits may be safely ignored.
212 |
213 | [#etc1s_slice]
214 | === ETC1S Slice Decoding
215 |
216 | The data for each mip level is a set of ETC1S slices. The corresponding element of the `imageDescs` array in the <> provides slice locations within the mip level data.
217 |
218 | ETC1S slices consist of a compressed 2D array of ETC1S blocks, compressed in the order indicated by <> metadata (defaults to top-down/left-right raster order). For an animation sequence, the previous slice's already decoded contents may be referred to when blocks are encoded using Conditional Replenishment (also known as "`skip blocks`").
219 |
220 | Each ETC1S block is encoded by using references to the color endpoint codebook and the selector codebook. The following sections describe the helper procedures used by the decoder, and how the array of ETC1S blocks is actually decoded.
221 |
222 | ==== ETC1S Approximate Move to Front Routines
223 |
224 | An approximate Move to Front (MTF) approach is used to efficiently encode the selector codebook references. Here is the C++ example class for approximate MTF decoding:
225 |
226 | .Example implementation
227 | [%collapsible]
228 | =====
229 | [source,cpp]
230 | ----
231 | class approx_move_to_front
232 | {
233 | public:
234 | approx_move_to_front(uint32_t n)
235 | {
236 | init(n);
237 | }
238 |
239 | void init(uint32_t n)
240 | {
241 | m_values.resize(n);
242 | m_rover = n / 2;
243 | }
244 |
245 | size_t size() const { return m_values.size(); }
246 |
247 | const int& operator[] (uint32_t index) const { return m_values[index]; }
248 | int operator[] (uint32_t index) { return m_values[index]; }
249 |
250 | void add(int new_value)
251 | {
252 | m_values[m_rover++] = new_value;
253 | if (m_rover == m_values.size())
254 | {
255 | m_rover = (uint32_t)m_values.size() / 2;
256 | }
257 | }
258 |
259 | void use(uint32_t index)
260 | {
261 | if (index)
262 | {
263 | int x = m_values[index / 2];
264 | int y = m_values[index];
265 | m_values[index / 2] = y;
266 | m_values[index] = x;
267 | }
268 | }
269 |
270 | private:
271 | std::vector m_values;
272 | uint32_t m_rover;
273 | };
274 | ----
275 | =====
276 |
277 | ==== ETC1S VLC Decoding Procedure
278 |
279 | ETC1S slice decoding utilizes a simple Variable Length Coding (VLC) scheme that sends raw bits using chunks of 5 or 8 bits. The MSB of each chunk signals whether there's another chunk for the current encoded value.
280 |
281 | Here is the VLC decoding procedures, `get_bits(n)` extracts next `n` bits from the bitstream:
282 |
283 | .Example implementation
284 | [%collapsible]
285 | =====
286 | [source,cpp]
287 | ----
288 | uint32_t decode_vlc4()
289 | {
290 | uint32_t v = 0;
291 | uint32_t ofs = 0;
292 |
293 | for ( ; ; )
294 | {
295 | uint32_t s = get_bits(5);
296 | v |= ((s & 0xF) << ofs);
297 | ofs += 4;
298 |
299 | if ((s & 0x10) == 0)
300 | {
301 | break;
302 | }
303 |
304 | if (ofs >= 32)
305 | {
306 | // Invalid encoding
307 | break;
308 | }
309 | }
310 |
311 | return v;
312 | }
313 |
314 | uint32_t decode_vlc7()
315 | {
316 | uint32_t v = 0;
317 | uint32_t ofs = 0;
318 |
319 | for ( ; ; )
320 | {
321 | uint32_t s = get_bits(8);
322 | v |= ((s & 0x7F) << ofs);
323 | ofs += 7;
324 |
325 | if ((s & 0x80) == 0)
326 | {
327 | break;
328 | }
329 |
330 | if (ofs >= 32)
331 | {
332 | // Invalid encoding
333 | break;
334 | }
335 | }
336 |
337 | return v;
338 | }
339 | ----
340 | =====
341 |
342 | ==== ETC1S Slice Block Decoding
343 |
344 | The decoder has no knowledge of the orientation of the image it is decoding. It iterates through all the slice blocks in order of increasing memory. The blocks form a `num_blocks_x` by `num_blocks_y` grid. The block at `num_blocks_x * y` starts row `y` where `0 \<= y < num_blocks_y`. To simplify the following description, a top-down, left-right raster order is assumed. _Left_ refers to the previous block in memory and _above_ to the block `num_blocks_x * 8` bytes earlier in memory. Each block is represented by an index into the color endpoint codebook and another index into the selector endpoint codebook. The endpoint codebook contains each ETC1S block's base RGB color and intensity table information, and the selector codebook contains the 4x4 texel selector entry (which are 2-bits each) information. This is all the information needed to fully represent the texels within each block.
345 |
346 | The decoding procedure loops over all the blocks in memory order, and decodes the endpoint and selector indices used to represent each block. The decoding procedure is complex enough that commented code is best used to describe it.
347 |
348 | The compressed format allows the encoder to reuse the endpoint index used by the block to the left, the block immediately above the current block, or the block to the upper left (if the slice is not a P-frame). Alternately, the encoder can send a Huffman-coded DPCM encoded index relative to the previously used endpoint index.
349 |
350 | Which type of prediction was used by the encoder is controlled by the endpoint prediction indices, which are sent with Huffman coding (using the `endpointPredModel` table) once every 2x2 blocks.
351 |
352 | For P-frames (that have `isPFrame` flag in `imageFlags` set, matches `is_p_frame` flag in the code below) used in animation sequences (matches `is_video` flag in the code below), the endpoint prediction symbol normally used to refer to the upper left block (endpoint prediction index 2) instead indicates that both the endpoint and selector indices from the previous frame's block should be reused on the current frame's block. The endpoint prediction indices are RLE coded, so this allows the encoder to efficiently skip over a large number of unchanged blocks in a video sequence.
353 |
354 | The first frame of an animation sequence must be an I-frame.
355 |
356 | A KTX file that is not an animation sequence cannot contain P-frames.
357 |
358 | .Reference implementation
359 | [%collapsible]
360 | =====
361 | [source,cpp]
362 | ----
363 | // Constants used by the decoder
364 | const uint32_t ENDPOINT_PRED_TOTAL_SYMBOLS = (4 * 4 * 4 * 4) + 1;
365 | const uint32_t ENDPOINT_PRED_REPEAT_LAST_SYMBOL = ENDPOINT_PRED_TOTAL_SYMBOLS - 1;
366 | const uint32_t ENDPOINT_PRED_MIN_REPEAT_COUNT = 3;
367 |
368 | const uint32_t NUM_ENDPOINT_PREDS = 3;
369 | const uint32_t CR_ENDPOINT_PRED_INDEX = NUM_ENDPOINT_PREDS - 1;
370 |
371 | // Endpoint/selector codebooks - decoded previously.
372 | endpoint endpoints[endpointCount];
373 | selector selectors[selectorCount];
374 |
375 | // Array of per-block values used for endpoint index prediction (enough for 2 rows).
376 | uint16_t [2][num_block_x] block_endpoint_preds;
377 |
378 | // Odd rows prediction information for two blocks packed into 4-bit values
379 | uint8_t block_pred_bits[(num_blocks_x + 1) >> 1]
380 |
381 | // Some constants and state used during block decoding
382 | const uint32_t SELECTOR_HISTORY_BUF_FIRST_SYMBOL_INDEX = selectorCount;
383 | const uint32_t SELECTOR_HISTORY_BUF_RLE_SYMBOL_INDEX = selectorHistoryBufSize + SELECTOR_HISTORY_BUF_FIRST_SYMBOL_INDEX;
384 | uint32_t cur_selector_rle_count = 0;
385 |
386 | uint32_t cur_pred_bits = 0;
387 | uint32_t prev_endpoint_pred_sym = 0;
388 | uint32_t endpoint_pred_repeat_count = 0;
389 | uint32_t prev_endpoint_index = 0;
390 |
391 | // These arrays are only used for texture video. They hold the previous frame's endpoint and selector indices.
392 | uint16_t prev_frame_endpoints[num_blocks_x][num_blocks_y];
393 | uint16_t prev_frame_selectors[num_blocks_x][num_blocks_y];
394 |
395 | // Selector history buffer - See Approximate Move to Front Routines
396 | approx_move_to_front selector_history_buf(selectorHistoryBufSize);
397 |
398 | // Loop over all slice blocks in raster order
399 | for (uint32_t block_y = 0; block_y < num_blocks_y; block_y++)
400 | {
401 | // The index into the block_endpoint_preds array
402 | const uint32_t cur_block_endpoint_pred_array = block_y & 1;
403 |
404 | for (uint32_t block_x = 0; block_x < num_blocks_x; block_x++)
405 | {
406 | // Check if we're at the start of a 2x2 block group.
407 | if ((block_x & 1) == 0)
408 | {
409 | // Are we on an even or odd row of blocks?
410 | if ((block_y & 1) == 0)
411 | {
412 | // We're on an even row and column of blocks. Decode the combined endpoint index predictor
413 | // symbols for 2x2 blocks. This symbol tells the decoder how the endpoints are decoded for
414 | // each block in a 2x2 group of blocks.
415 |
416 | // Are we in an RLE run?
417 | if (endpoint_pred_repeat_count)
418 | {
419 | // Inside a run of endpoint predictor symbols.
420 | endpoint_pred_repeat_count--;
421 | cur_pred_bits = prev_endpoint_pred_sym;
422 | }
423 | else
424 | {
425 | // Decode the endpoint prediction symbol, using the "endpoint pred" Huffman table.
426 | cur_pred_bits = decode_huffman(endpointPredModel);
427 | if (cur_pred_bits == ENDPOINT_PRED_REPEAT_LAST_SYMBOL)
428 | {
429 | // It's a run of symbols, so decode the count using VLC decoding
430 | endpoint_pred_repeat_count = decode_vlc4() + ENDPOINT_PRED_MIN_REPEAT_COUNT - 1;
431 |
432 | cur_pred_bits = prev_endpoint_pred_sym;
433 | }
434 | else
435 | {
436 | // It's not a run of symbols
437 | prev_endpoint_pred_sym = cur_pred_bits;
438 | }
439 | }
440 |
441 | // The symbol has enough endpoint prediction information for 4 blocks (2 bits per block),
442 | // so 8 bits total. Remember the prediction information we should use for the next row of
443 | // 2 blocks beneath the current block.
444 | block_pred_bits[block_x >> 1] = (uint8_t)(cur_pred_bits >> 4);
445 | }
446 | else
447 | {
448 | // We're on an odd row of blocks, so use the endpoint prediction information we previously
449 | // stored on the previous even row.
450 | cur_pred_bits = block_pred_bits[block_x >> 1];
451 | }
452 | }
453 |
454 | // Decode the current block's endpoint and selector indices.
455 | uint32_t endpoint_index, selector_index = 0;
456 |
457 | // Get the 2-bit endpoint prediction index for this block.
458 | const uint32_t pred = cur_pred_bits & 3;
459 |
460 | // Get the next block's endpoint prediction bits ready.
461 | cur_pred_bits >>= 2;
462 |
463 | // Now check to see if we should reuse a previously encoded block's endpoints.
464 | if (pred == 0)
465 | {
466 | // Reuse the left block's endpoint index
467 | assert(block_x > 0);
468 | endpoint_index = prev_endpoint_index;
469 | }
470 | else if (pred == 1)
471 | {
472 | // Reuse the upper block's endpoint index
473 | assert(block_y > 0)
474 | endpoint_index = block_endpoint_preds[cur_block_endpoint_pred_array ^ 1][block_x];
475 | }
476 | else if (pred == 2)
477 | {
478 | if (is_p_frame)
479 | {
480 | // If it's a P-frame, reuse the previous frame's endpoint index, at this block.
481 | assert(pred == CR_ENDPOINT_PRED_INDEX);
482 | endpoint_index = prev_frame_endpoints[block_x][block_y];
483 | selector_index = prev_frame_selectors[block_x][block_y];
484 | }
485 | else
486 | {
487 | // Reuse the upper left block's endpoint index.
488 | assert((block_x > 0) && (block_y > 0));
489 | endpoint_index = block_endpoint_preds[cur_block_endpoint_pred_array ^ 1][block_x - 1];
490 | }
491 | }
492 | else
493 | {
494 | // We need to decode and apply a DPCM encoded delta to the previously used endpoint index.
495 | // This uses the delta endpoint Huffman table.
496 | const uint32_t delta_sym = decode_huffman(endpointDeltaModel);
497 |
498 | endpoint_index = delta_sym + prev_endpoint_index;
499 |
500 | // Wrap around if the index goes beyond the end of the endpoint codebook
501 | if (endpoint_index >= endpointCount)
502 | endpoint_index -= endpointCount;
503 | }
504 |
505 | // Remember the endpoint index we used on this block, so the next row can potentially reuse the index.
506 | block_endpoint_preds[cur_block_endpoint_pred_array][block_x] = (uint16_t)endpoint_index;
507 |
508 | // Remember the endpoint index used
509 | prev_endpoint_index = endpoint_index;
510 |
511 | // Now we have fully decoded the ETC1S endpoint codebook index, in endpoint_index.
512 |
513 | // Now decode the selector index.
514 | const uint32_t MAX_SELECTOR_HISTORY_BUF_SIZE = 64;
515 | const uint32_t SELECTOR_HISTORY_BUF_RLE_COUNT_THRESH = 3;
516 | const uint32_t SELECTOR_HISTORY_BUF_RLE_COUNT_BITS = 6;
517 | const uint32_t SELECTOR_HISTORY_BUF_RLE_COUNT_TOTAL = (1 << SELECTOR_HISTORY_BUF_RLE_COUNT_BITS);
518 |
519 | // Decode selector index, unless it's a P-frame and the endpoint predictor indicated that the
520 | // block's endpoints and selectors were reused from the previous frame.
521 | if ((!is_p_frame) || (pred != CR_ENDPOINT_PRED_INDEX))
522 | {
523 | int selector_sym;
524 |
525 | // Are we in a selector RLE run?
526 | if (cur_selector_rle_count > 0)
527 | {
528 | // Handle selector RLE run.
529 | cur_selector_rle_count--;
530 |
531 | selector_sym = selectorCount;
532 | }
533 | else
534 | {
535 | // Decode the selector symbol, using the selector Huffman table.
536 | selector_sym = decode_huffman(selectorModel);
537 |
538 | // Is it a run?
539 | if (selector_sym == static_cast(SELECTOR_HISTORY_BUF_RLE_SYMBOL_INDEX))
540 | {
541 | // Decode the selector run's size, using the selector history buf RLE Huffman table.
542 | int run_sym = decode_huffman(selectorHistoryBufRleModel);
543 |
544 | // Is it a very long run?
545 | if (run_sym == (SELECTOR_HISTORY_BUF_RLE_COUNT_TOTAL - 1))
546 | cur_selector_rle_count = decode_vlc7() + SELECTOR_HISTORY_BUF_RLE_COUNT_THRESH;
547 | else
548 | cur_selector_rle_count = run_sym + SELECTOR_HISTORY_BUF_RLE_COUNT_THRESH;
549 |
550 | selector_sym = selectorCount;
551 |
552 | cur_selector_rle_count--;
553 | }
554 | }
555 |
556 | // Is it a reference into the selector history buffer?
557 | if (selector_sym >= selectorCount)
558 | {
559 | assert(selectorHistoryBufSize > 0);
560 |
561 | // Compute the history buffer index
562 | int history_buf_index = selector_sym - selectorCount;
563 |
564 | assert(history_buf_index < selector_history_buf.size());
565 |
566 | // Access the history buffer
567 | selector_index = selector_history_buf[history_buf_index];
568 |
569 | // Update the history buffer
570 | if (history_buf_index != 0)
571 | selector_history_buf.use(history_buf_index);
572 | }
573 | else
574 | {
575 | // It's an index into the selector codebook
576 | selector_index = selector_sym;
577 |
578 | // Add it to the selector history buffer
579 | if (m_selector_history_buf_size)
580 | selector_history_buf.add(selector_index);
581 | }
582 | }
583 |
584 | // For texture video, remember the endpoint and selector indices used by the block on this frame,
585 | // for later reuse on the next frame.
586 | if (is_video)
587 | {
588 | prev_frame_endpoints[block_x][block_y] = endpoint_index;
589 | prev_frame_selectors[block_x][block_y] = selector_index;
590 | }
591 |
592 | // The block is fully decoded here. The codebook indices are endpoint_index and selector_index.
593 | // Make sure they are valid
594 | assert((endpoint_index < endpointCount) && (selector_index < selectorCount));
595 |
596 | } // block_x
597 | } // block_y
598 | ----
599 | =====
600 |
601 | At this point, the decoder has decoded each block's endpoint and selector codebook indices. It can now fetch the actual ETC1S endpoints/selectors from the codebooks and write out ETC1S texture data, or it can immediately transcode the ETC1S data to another GPU texture format.
602 |
603 | // vim: filetype=asciidoc ai expandtab tw=0 ts=4 sts=2 sw=2
604 |
605 |
--------------------------------------------------------------------------------
/ktx-frag.adoc:
--------------------------------------------------------------------------------
1 | = KTX Fragment URI
2 | :author: Mark Callow
3 | :author_org: Edgewise Consulting
4 | :description: URI syntax for accessing fragments of a KTX v2 file.
5 | :docrev: 1
6 | :ktxfragver: 1.0
7 | :revnumber: {ktxfragver}.{docrev}
8 | :revdate: {docdate}
9 | :version-label: Version
10 | :lang: en
11 | :docinfo1:
12 | :doctype: article
13 | :encoding: utf-8
14 | // Disabling toc and numbered attributes doesn't work with a2x.
15 | // Use the xsltproc options instead.
16 | :toc!:
17 | // a2x: --xsltproc-opts "--stringparam generate.toc nop"
18 | :numbered:
19 | // a2x: --xsltproc-opts "--stringparam chapter.autolabel 0"
20 | // a2x: --xsltproc-opts "--stringparam section.autolabel 0"
21 | :data-uri:
22 | :icons: font
23 | :stylesheet: khronos.css
24 | :xrefstyle: full
25 |
26 | [abstract]
27 | == Abstract
28 | This document describes the KTX Fragments 1.0 specification. It
29 | specifies the syntax for constructing KTX fragment URIs and explains
30 | how to handle them when used over the HTTP protocol. The syntax is
31 | based on the specification of particular name-value pairs that can
32 | be used in URI fragment and URI query requests to restrict a media
33 | resource to a certain fragment. It is compatible with the W3C Media
34 | Fragments syntax <>.
35 |
36 | [discrete]
37 | === Status of this document
38 | Approved by the 3D Formats WG Dec 7th, 2022.
39 |
40 | == Introduction
41 |
42 | This document provides a standard means of addressing fragments of
43 | KTX texture files on the Web using Uniform Resource Identifiers
44 | (URI). In the context of this document KTX fragments are regarded
45 | along several different dimensions such as mip level, stratal (array
46 | layer) and spatial.
47 |
48 | The aim of this specification is to enhance the Web infrastructure
49 | for supporting the addressing and retrieval of subparts of the
50 | texture payload of KTX version 2 Web resources, as well as the
51 | automated processing of such subparts for reuse.
52 |
53 | For discussion of standardization issues including terminology, and the
54 | difference between URI fragments and URI queries see § 2 and §
55 | 3 of _Media Fragments URI 1.0 (basic)_ <>.
56 |
57 | == KTX Fragments Syntax
58 |
59 | The guiding principles for the definition of the KTX fragment syntax
60 | are:
61 |
62 | a. The KF syntax for queries and fragments should be identical.
63 | b. The KF syntax should be unambiguous.
64 | c. The KF syntax should allow any UTF-8 character for dimensions that need it.
65 | d. The KF syntax should adhere to applicable formal standards.
66 | e. The KF syntax should adhere to de-facto usage of queries and fragments.
67 | f. The KF syntax should be as concise as possible.
68 |
69 | === General Structure
70 |
71 | The general structure is as in <>, a list of name value pairs
72 | encoded in the query or fragment component of a URI. Name and value
73 | components are separated by an equal sign (=), while multiple
74 | name-value pairs are separated by an ampersand (&).
75 |
76 | [[namevalue]]
77 | ----
78 | name = fragment - "&" - "="
79 | value = fragment - "&"
80 | namevalue = name [ "=" value ]
81 | namevalues = namevalue *( "&" namevalue )
82 | ----
83 |
84 | The names and values can be arbitrary Unicode strings, encoded in
85 | UTF-8 and percent-encoded as per <>.
86 |
87 | .Examples
88 | --
89 | http://www.example.com/example.ktx2#m=0,2
90 | http://www.example.com/example.ktx2#a=1,18
91 | http://www.example.com/example.ktx2#a=1,18&xyzwhd=percent:25,25,25,50,50,50
92 | http://www.example.com/example.ktx2#f=4,5
93 | http://www.example.com/example.ktx2#t=10,20
94 | --
95 |
96 | While arbitrary name-value pairs can be encoded in this manner,
97 | this specification defines a fixed set of dimensions. The dimension
98 | keyword name is encoded in the name component, while dimension-specific
99 | syntax is encoded in the value component.
100 |
101 | <> defines in more
102 | detail how to process the name-value pair syntax, arriving at a
103 | list of name-value Unicode string pairs. The syntax definitions in
104 | <> apply to these Unicode strings.
105 |
106 | === Fragment Dimensions
107 | KTX fragments support addressing the KTX file's payload along 5
108 | dimensions
109 |
110 | [qanda]
111 | relating to mip level::
112 | This dimension denotes a range of mip levels in the KTX file.
113 |
114 | stratal::
115 | This dimension denotes a range of array layers when the KTX file
116 | contains an array texture.
117 |
118 | temporal::
119 | This dimension denotes a specific time range in a KTX file containing
120 | `KTXanimData` metadata. Since a frame is an array layer, this is an
121 | alternate way of selecting in the stratal dimension.
122 |
123 | facial::
124 | This dimension denotes a range of faces when the KTX file contains a
125 | cube map.
126 |
127 | spatial:: xyzwhd
128 | This dimension denotes a range of pixels in the KTX file such as "a
129 | volume with size (100,100,1) with its origin at (10,10,0).
130 |
131 | ==== Mip Level Dimension
132 |
133 | Mip level selection is denoted by the name _m_ and specified as a
134 | range with a first level and a last level. Either one or both
135 | parameters may be omitted with the first level defaulting to level
136 | 0, the largest level and the last level defaulting to level~n~, the
137 | texture's smallest mip level.
138 |
139 | levelprefix = %x6C ; "m"
140 |
141 | Examples:
142 |
143 | m=2,5 # => selects mip level[2] to level[5].
144 | m=,5 # => selects mip level[0] to level[5].
145 | m=2 # => selects mip level[2] to level[n].
146 | m=0,0 # => selects layer[0].
147 |
148 | ==== Stratal Dimension
149 |
150 | Stratal or array layer selection is denoted by the name _a_ and
151 | specified as a range with a first layer and a last layer. Either
152 | one or both parameters may be omitted with the first layer defaulting
153 | to layer 0 and the last layer defaulting to layer~n~, the array
154 | texture's last layer.
155 |
156 | layerprefix = %x61 ; "a"
157 |
158 | Examples:
159 |
160 | a=3,6 # => selects layer[3] to layer[6].
161 | a=,6 # => selects layer[0] to layer[6].
162 | a=3 # => selects layer[3] to layer[n].
163 | a=3,3 # => selects layer[3].
164 |
165 | ==== Temporal Dimension
166 |
167 | Temporal clipping is denoted by the name _t_. Since a frame is a
168 | single array layer, it is an alternate way of selecting array layers
169 | and only valid for files with KTXanimData metadata. It is specified
170 | as an interval with a begin time and an end time (or an in-point
171 | and an out-point in video editing terms). Either one or both
172 | parameters may be omitted, with the begin time defaulting to 0
173 | seconds and the end time defaulting to the duration of the source
174 | media. The interval is half-open: the begin time is considered part
175 | of the interval whereas the end time is considered to be the first
176 | time point that is not part of the interval. If a single number
177 | only is given, this corresponds to the begin time except if it is
178 | preceded by a comma in which case it corresponds to end time.
179 |
180 | The duration of the source media in seconds is calculated from the
181 | KTXanimData by
182 |
183 | // This is the only way to get an indented paragraph.
184 | [none]
185 | * _duration~source~_ = _duration~frame~_ / _timescale_ x _layerCount_
186 |
187 | where _duration~frame~_ and _timescale_ are the values given in the
188 | KTXanimData metadata and _layerCount_ is the value given in the KTX
189 | header.
190 |
191 | timeprefix = %x74 ; "t"
192 |
193 | Examples:
194 |
195 | t=10,20 # => results in the time interval [10,20)
196 | t=,20 # => results in the time interval [0,20)
197 | t=10 # => results in the time interval [10,end)
198 |
199 | Temporal clipping is specified as Normal Play Time (npt) <>.
200 |
201 | Normal Play Time can either be specified as seconds, with an optional
202 | fractional part to indicate milliseconds, or as colon-separated
203 | hours, minutes and seconds (again with an optional fraction). Minutes
204 | and seconds must be specified as exactly two digits, hours and
205 | fractional seconds can be any number of digits. The hours, minutes
206 | and seconds specification for NPT is a convenience only, it does
207 | not signal frame accuracy. This specification builds on the RTSP
208 | specification of NPT in <>.
209 |
210 | [source,bn,subs=+macros]
211 | ----
212 | npt-sec = 1*DIGIT [ "." *DIGIT ] ; definitions
213 | npt-hhmmss = npt-hh ":" npt-mm ":" npt-ss [ "." *DIGIT] ; from <>.
214 | npt-mmss = npt-mm ":" npt-ss [ "." *DIGIT]
215 | npt-hh = 1*DIGIT ; any positive number
216 | npt-mm = 2DIGIT ; 0-59
217 | npt-ss = 2DIGIT ; 0-59
218 |
219 | npttimedef = ( npttime [ "," npttime ] ) / ( "," npttime )
220 |
221 | npttime = npt-sec / npt-mmss / npt-hhmmss
222 | ----
223 |
224 | Examples:
225 |
226 | t=10,20 # => results in the time interval [10,20)
227 | t=,121.5 # => results in the time interval [0,121.5)
228 | t=0:02:00,121.5 # => results in the time interval [120,121.5)
229 | t=120,0:02:01.5 # => also results in the time interval [120,121.5)
230 |
231 | ==== Facial Dimension
232 |
233 | Face selection is denoted by the name _f_ and specified as a range with
234 | a first face and a last face. Either one or both parameters may be
235 | omitted with the first face defaulting to to face 0 and the last face to
236 | face 5.
237 |
238 | faceprefix = %x66 ; "f"
239 |
240 | Examples:
241 |
242 | f=1,2 # selects face[1] and face[2].
243 | f=,3 # selects face[0] to face[3].
244 | f=3 # selects face[3] to face[5].
245 | f=3,3 # selects face[3].
246 | f=5 # selects face[5].
247 |
248 | ==== Spatial Dimension
249 |
250 | Spatial clipping selects a volume of pixels from a KTX texture.
251 | Only cubic selections are supported though, of course, width, height
252 | or depth can be 1. The cube can be specified as pixel coordinates
253 | or percentages.
254 |
255 | Pixels coordinates are interpreted after taking into account the
256 | texture's base level dimensions and the mip levels being accessed.
257 |
258 | Cube selection is denoted by the name _xyzwhd_. The value is an
259 | optional format, _pixel:_ or _percent:_ (defaulting to _pixel_) and
260 | 6 comma-separated integers. The integers denote x, y, z, width
261 | height and depth, respectively, with x=0, y=0, z=0 being the origin
262 | indicated by the texture's `KTXorientation` metadata. If there is no
263 | metadata, the origin is the top-left-front corner of the cube.
264 |
265 | If pixel is used, coordinates are in the space of the texture's base
266 | level. When selecting from other than the base level, the user agent
267 | must adjust the coordinates according to the level being accessed.
268 | Level~n+1~ offsets and sizes are max(1, level~n~/2) offsets and sizes.
269 |
270 | If percent is used, x and width are interpreted as a percentage of the width
271 | of the level being accessed, y and height as a percentage of the level's height
272 | and z and depth as a percentage of the level's depth.
273 |
274 |
275 | xyzwhdprefix = %x78.79.7F.77.68.64 ; "xyzwhd"
276 | xyzwhdparam = [ xywhunit ":" ] 1*DIGIT "," 1*DIGIT "," 1*DIGIT "," 1*DIGIT," 1*DIGIT "," 1*DIGIT"
277 | xyzwhdunit = %x70.69.78.65.6C ; "pixel"
278 | / %x70.65.72.63.65.6E.74 ; "percent"
279 |
280 | Examples:
281 |
282 | xyzwhd=160,120,0,320,240,1 # => selects a 320x240x1 cube at x=160, y=120
283 | # and z=0
284 | xyzwhd=pixel:160,120,0,320,240,1 # => selects a 320x240x1 cube at x=160, y=120
285 | # and z=0
286 | xyzwhd=percent:25,25,25,50,50,50 # => selects a 50%x50%x50% cube at x=25%,
287 | # y=25% and z = 25%
288 |
289 | == Media Fragments Processing
290 |
291 | This section defines the different exchange scenarios for the situations
292 | explained in § 3 _URI fragment and URI query over the HTTP
293 | protocol_ in <>.
294 |
295 | The formal grammar defined in <> describes
296 | what producers of a KTX fragment URI should output. It is not taking
297 | into account possible percent-encodings that are valid according to
298 | <> and the grammar is not a specification of how a media
299 | fragment should be parsed. Therefore, <>
300 | defines how to parse media fragment URIs.
301 |
302 | === Processing Media Fragment URI
303 |
304 | This section defines how to parse media fragment URIs defined in
305 | <>, along with notes on some of the caveats
306 | to be aware of. Implementors are free to use any equivalent
307 | technique(s).
308 |
309 | ==== Processing name-value components
310 |
311 | This section defines how to convert an octet string (from the query
312 | or fragment component of a URI) into a list of name-value Unicode
313 | string pairs.
314 |
315 | 1. Parse the octet string according to the <> syntax,
316 | yielding a list of name-value pairs, where name and value are both
317 | octet string. In accordance with <>, the name and value
318 | components must be parsed and separated before percent-encoded
319 | octets are decoded.
320 |
321 | 2. For each name-value pair:
322 |
323 | a. Decode percent-encoded octets in name and value as defined
324 | by <>. If either name or value are not valid
325 | percent-encoded strings, then remove the name-value pair
326 | from the list.
327 |
328 | b. Convert name and value to Unicode strings by interpreting
329 | them as UTF-8. If either name or value are not valid UTF-8
330 | strings, then remove the name-value pair from the list.
331 |
332 | Note that the output is well defined for any input.
333 |
334 | Examples:
335 | |===
336 | | Input | Output | Notes
337 |
338 | | "t=1" | [("t", "1")] | simple case
339 | | "t=1&t=2" | [("t", "1"), ("t", "2")] | repeated name
340 | | "a=b=c" | [("a", "b=c")] | "=" in value
341 | | "a&b=c" | [("a", ""), ("b", "c")] | missing value
342 | | "%74=%6ept%3A%310" | [("t", "npt:10")] | unnecssary percent-encoding
343 | | "id=%xy&t=1" | [("t", "1")] | invalid percent-encoding
344 | | "id=%E4r&t=1" | [("t", "1")] | invalid UTF-8
345 | |===
346 |
347 | While the processing defined in this section is designed to be
348 | largely compatible with the parsing of the URI query component in
349 | many HTTP server environments, there are incompatible differences
350 | that implementors should be aware of:
351 |
352 | * "&" is the only primary separator for name-value pairs, but some server-side languages also treat ";" as a separator.
353 |
354 | * name-value pairs with invalid percent-encoding should be ignored, but some server-side languages silently mask such errors.
355 |
356 | * The "+" character should not be treated specially, but some server-side languages replace it with a space (" ") character.
357 |
358 | * Multiple occurrences of the same name must be preserved, but some server-side languages only preserve the last occurrence.
359 |
360 | === Processing name-value lists
361 |
362 | This section defines how to convert a list of name-value Unicode
363 | string pairs into the KTX fragment dimensions.
364 |
365 | Given the dimensions defined in section <>,
366 | each has a pair of production rules that corresponds to the name
367 | and value component respectively:
368 |
369 | |===
370 | |Keyword | Dimension
371 |
372 | |m | <>
373 | |a | <>
374 | |f | <>
375 | |xyzwhd | <>
376 | |t | <>
377 | |===
378 |
379 | 1. Initially, all dimensions are undefined.
380 |
381 | 2. For each name-value pair:
382 |
383 | a. If name matches a keyword in the above table, interpret value
384 | as per the corresponding section.
385 |
386 | b. Otherwise, the name-value pair does not represent a KTX
387 | fragment dimension. Validators should emit a warning. User
388 | agents must ignore the name-value pair.
389 |
390 | NOTE: Because the name-value pairs are processed in order, the last
391 | valid occurence of any dimension is the one that is used.
392 |
393 | == Media Fragments Semantics
394 |
395 | In this section, we discuss how media fragment URIs should be
396 | interpreted by user agents. Valid and error cases are presented.
397 | In case of errors, we distinguish between errors that can be detected
398 | solely based on the media fragment URI and errors that can only be
399 | detected when the user agent has information of the KTX resource
400 | (such as the number of mip levels).
401 |
402 | === Valid KTX Fragment URIs
403 |
404 | For each dimension, a number of valid KTX fragments and their
405 | semantics are presented.
406 |
407 | ==== Valid mip level dimension
408 |
409 | To describe the different cases for valid mip levels, we make the
410 | following definitions:
411 |
412 | [%hardbreaks]
413 | b: the base (largest) mip level which is always 0;
414 | x: the maximum (smallest) mip level within the KTX file;
415 | p: a positive integer, p >= 0;
416 | q: a positive integer, q >= 0.
417 |
418 | For m=p,q with p \<= q the following level selections are valid:
419 |
420 | * m=p with p < x: the user agent selects levels p to x.
421 | * m=,q with q \<= x: the user agent selects levels b to q.
422 | * m=,q with x < q: the user agent selects levels b to x.
423 | * m=p,q with p = b and q = x: the user agent selects all levels.
424 | * m=p,q with p < q, p < x and q \<= x: the user agent selects levels p to q.
425 | * m=p,q with p < q, p < x and x < q: the user agent selects levels p to x.
426 | * %6D=5,12: resolve percent encoding to m=5,12.
427 | * m=%31%30: resolve percent encoding to m=10.
428 | * m=5%2C12: resolve percent encoding to t=5,12.
429 |
430 | When clipping levels from a KTX file with multiple layers, faces
431 | or depth-slices the selection include all layers, faces and
432 | depth-slices of the selected levels or all those selected by clipping
433 | in additional dimensions.
434 |
435 | ==== Valid stratal dimension
436 |
437 | To describe the different cases for valid array layers, we make the
438 | following definitions:
439 |
440 | [%hardbreaks]
441 | f: the first array layer which is always 0;
442 | l: the last array layer
443 | i: a positive integer, i >= 0;
444 | j: a positive integer, j >= 0.
445 |
446 | For a=i,j with i \<= j the following layer selections are valid:
447 |
448 | * a=i with i < l: the user agent selects layers i to l.
449 | * a=,j with j \<= l: the user agent selects layers f to j.
450 | * a=,j with l < j: the user agent selects layers f to l.
451 | * a=i,j with i = f and j = l, the user agent selects all layers.
452 | * a=i,j with i < j, i < l and j <= l: the user agent selects layers i to j.
453 | * %61=3,14 resolve percent encoding to a=3,14.
454 | * a=%31%30 resolve percent encoding to a=10.
455 | * a=3%2C14 resolve percent encoding to t=3,14.
456 |
457 | When clipping layers from a KTX file with multiple levels or faces
458 | the selection includes all the levels and faces of the selected
459 | layers or all those selected by clipping in additional dimensions.
460 |
461 | ==== Valid temporal dimension
462 |
463 | To describe the different cases for temporal media fragments, we
464 | make the following definitions:
465 |
466 | [%hardbreaks]
467 | s: the start point of the animation sequence, which is always zero (in NPT);
468 | e: the end point of the animation sequence (i.e. duration) and e > 0;
469 | a: a positive integer, a >= 0;
470 | b: a positive integer, b >= 0.
471 |
472 | Further, as stated in <>, temporal intervals
473 | are half-open. Thus, if we state below that "the media is played
474 | from x to y", this means that the frame corresponding to y will not
475 | be played.
476 |
477 | For t=a,b with a <= b, the following temporal fragments are valid:
478 |
479 | * t=a with a < e: sequence is played from a to e.
480 | * t=,b with b \<= e: sequence is played from s to b.
481 | * t=,b with e < b: sequence is played from s to e.
482 | * t=a,b with a = 0, b = e: whole sequence resource is played.
483 | * t=a,b with a < b, a < e and b \<= e: sequence is played from a to b (the normal case).
484 | * t=a,b with a < b, a < e and e < b: sequence is played from a to e.
485 | * %74=10,20 resolve percent encoding to t=10,20.
486 | * t=%31%30 resolve percent encoding to t=10.
487 | * t=10%2C20 resolve percent encoding to t=10,20.
488 | * t=%6ept:10 resolve percent encoding to t=npt:10.
489 | * t=npt%3a10 resolve percent encoding to t=npt:10.
490 |
491 | ==== Valid facial dimension
492 |
493 | To describe the different cases for valid faces, we make the
494 | following definitions:
495 |
496 | [%hardbreaks]
497 | i: a positive integer, i >= 0 and i < 6.
498 | j: a positive integer, j >= 0 and j < 6.
499 |
500 | For f=i,j with i < j the following face selections are
501 | valid.
502 |
503 | * f=i, the user agent selects face[i] to face[5].
504 | * f=i,j the user agent selects face[i] to face[j].
505 | * f=,j the user agent selects face[0] to face[j].
506 |
507 | Note that when a subset of faces is selected, the texture is lowered from a
508 | cube map to an array or a 2D texture.
509 |
510 | ==== Valid spatial dimension
511 |
512 | To describe the different cases for spatial media fragments, we
513 | make the following definitions:
514 |
515 | [%hardbreaks]
516 | a: the x coordinate of the spatial region (a >= 0).
517 | b: the y coordinate of the spatial region (b >= 0).
518 | c: the z coordinate of the spatial region (c >= 0).
519 | e: the width the spatial region (e > 0).
520 | f: the height of the spatial region (f > 0).
521 | g: the depth of the spatial region (g > 0).
522 | w: the width of the texture base level (w > 0).
523 | h: the height of the texture base level (h > 0).
524 | d: the depth of the texture base level (h > 0).
525 |
526 | The coordinate system has an upper-left origin.
527 |
528 | The following spatial fragments are valid:
529 |
530 | * xyzwhd=a,b,c,e,f,g with a+e \<= w, b+f \<= h and c+g \<= d: the
531 | user agent displays a spatial fragment with coordinates (in pixel
532 | xyzefg format) a,b,c,e,f,g (the normal pixel case).
533 | * xyzwhd=a,b,c,e,f,g with a+e > w, a < w, b+f < h and c+g < d: the
534 | user agent displays a spatial fragment with coordinates (in pixel
535 | xyzwhd format) a,b,c,w-a,f,g.
536 | * xyzwhd=a,b,c,e,f,g with a+e < w, b+f > h, b < h and c+g < d: the
537 | user agent displays a spatial fragment with coordinates (in pixel
538 | xyzwwhd format) a,b,c,e,h-b,g.
539 | * xyzwhd=a,b,c,e,f,g with a+e < w, b+f < h, c+g > d and c < d: the
540 | user agent displays a spatial fragment with coordinates (in pixel
541 | xyzwwhd format) a,b,c,e,f,d-c.
542 | * xyzwhd=a,b,c,e,f,g with a+e > w, a < w, b+f > h, b < h, c+g < d:
543 | the user agent displays a spatial fragment with coordinates (in
544 | pixel xyzwhd format) a,b,c,w-a,h-f,g.
545 | * xyzwhd=a,b,c,e,f,g with a+e > w, a < w, b+f > h, b < h, c+g > d
546 | and c < d: the user agent displays a spatial fragment with
547 | coordinates (in pixel xyzwhd format) a,b,c,w-a,h-f,d-g.
548 | * xyzwhd=pixel:a,b,c,e,f,g with a+e \<= w, b+f \<= h and c+g \<= d:
549 | the user agent displays a spatial fragment with coordinates (in
550 | pixel xyzwhd format) a,b,c,e,f,g (the normal pixel case).
551 | * xyzwhd=percent:a,b,c,e,f,g with a+e \<= 100, b+f \<= 100 and c+g
552 | \<= 100: the user agent displays a spatial fragment with coordinates
553 | (in pixel xyzwhd format) floor(a/w*100), floor(b/h*100),
554 | floor(c/d*100), ceil(e/w*100), ceil(f/h*100) and ceil(g/d*100) (the
555 | normal percent case).
556 |
557 | The result of doing spatial clipping on a KTX file that has multiple
558 | layers, faces or depth-slices is that the spatial clipping is done
559 | across all layers and faces.
560 |
561 | When doing spatial clipping on multiple mip levels the user agent
562 | must scale the coordinates to each mip level being clipped.
563 |
564 | === Errors detectable based on the URI syntax
565 |
566 | Both syntactical and semantical errors are treated similarly. More
567 | specifically, the user agent SHOULD ignore name-value pairs causing
568 | errors detectable based on the URI syntax. We provide below more
569 | details for each dimension. We look at errors in the different
570 | dimensions and their values in the subsequent sub-sections. We start
571 | with errors on the more general levels.
572 |
573 | ==== Errors on the general URI level
574 |
575 | The following list provides the different kind of errors that can
576 | occur on the general URI level and how they should be treated:
577 |
578 | * Unknown dimension: only dimensions described in this specification
579 | (i.e. m, a, t, f and xyzwhd ) are considered as known dimensions.
580 | All other dimensions are considered as unknown. Unknown dimensions
581 | SHOULD be ignored by the user agent.
582 | * Multiple occurrences of the same dimension: only the last valid
583 | occurrence of a dimension (e.g. t=10 in `#t=2&t=10`) is interpreted
584 | and all previous occurrences (valid or invalid) SHOULD be ignored
585 | by the user agent.
586 |
587 | ==== Errors on the mip level dimension
588 |
589 | The value cannot be parsed for the mip level dimension or the parsed
590 | value is invalid according to the specification. Invalid mip level
591 | fragments SHOULD be ignored by the user agent.
592 |
593 | Examples:
594 |
595 | m=b
596 | m=1,
597 | m=qwer
598 | m=asdf,9
599 | m='4'
600 | m=3:20
601 | m=25,50,75
602 |
603 | ==== Errors on the array layer dimension
604 |
605 | The value cannot be parsed for the stratal dimension or the parsed
606 | value is invalid according to the specification. Invalid stratal
607 | fragments SHOULD be ignored by the user agent.
608 |
609 | Examples:
610 |
611 | a=b
612 | a=1,
613 | a=qwer
614 | a=asdf,9
615 | a='4'
616 | a=3:20
617 | a=25,50,75
618 |
619 | ==== Errors on the temporal dimension
620 |
621 | The value cannot be parsed for the temporal dimension or the parsed
622 | value is invalid according to the specification. Invalid temporal
623 | fragments SHOULD be ignored by the user agent.
624 |
625 | Examples:
626 |
627 | t=a,b with a >= b (the case of an empty temporal fragment (a=b) is also considered as an error)
628 | t=a,
629 | t=asdf
630 | t=5,ekj
631 | t=agk,9
632 | t='0'
633 | t=10-20
634 | t=10:20
635 | t=10,20,40
636 | t%3D10 where %3D is equivalent to =; percent encoding does not resolve
637 |
638 | ==== Errors on the face dimension
639 |
640 | The value cannot be parsed for the facial dimension or the parsed
641 | value is invalid according to the specification. Invalid facial
642 | fragments SHOULD be ignored by the user agent.
643 |
644 | Examples:
645 |
646 | f=6
647 | f=1,
648 | f=a,b
649 | f=posx
650 | f="negy"
651 |
652 | ==== Errors on the spatial dimension
653 |
654 | The value cannot be parsed for the spatial dimension or the parsed
655 | value is invalid according to the specification. Invalid spatial
656 | fragments SHOULD be ignored by the user agent.
657 |
658 | Examples:
659 |
660 | xyzwhd=4,5,abc,8,9,a
661 | xyzwhd=4,5
662 | xyzwhd=foo:4,5,6,8,9,10
663 | xyzwhd=percent:400,5,6,7,8,9
664 | xyzwhd=4,5,6,0,3,2
665 |
666 | === Errors detectable based on information of the source KTX file.
667 |
668 | Errors that can only be detected when the user agent has information
669 | of the source KTX file are treated differently. Examples of such
670 | information are the number of mip levels, the number of array layers,
671 | the duration of an animation sequence and the size of an image (i.e. all
672 | information that is not detectable solely based on the URI).
673 | We provide below more details for each of the dimensions.
674 |
675 | ==== Errors on the general level
676 |
677 | The following errors can occur on the general level:
678 |
679 | Not a KTX Version 2 file. If the user agent knows the media type,
680 | it is able to detect that the source is not a KTX file so it SHOULD
681 | ignore KTX specific dimensions. The temporal dimension is the only
682 | non KTX specific dimension.
683 |
684 | Non-existent dimension: a dimension that does not exist in the
685 | source KTX (e.g. level clipping on a file with only a single mip level,
686 | layer clipping on a file with only 1 array layer or temporal clipping
687 | on a file without KTXanimData) is considered as a non-existent
688 | dimension. The user agent SHOULD ignore these.
689 |
690 | ==== Errors on the mip level dimension
691 |
692 | To describe the different cases for mip level fragments, we
693 | use the definitions from <>. The invalidity of the
694 | following mip level fragments can only be detected by the user agent if
695 | it knows the number of mip levels in the KTX source file.
696 |
697 | * m=p,q with p > 0, p < q, p > x: a non-existent mip level fragment,
698 | the user agent selects mip level x.
699 | * m=p with p > x: a non-existent mip level, the user agent selects mip
700 | level x.
701 |
702 | ==== Errors on the stratal dimension
703 |
704 | To describe the different cases for stratal fragments, we
705 | use the definitions from <>. The invalidity of the
706 | following stratal fragments can only be detected by the user agent if
707 | it knows the number of array layers in the KTX source file.
708 |
709 | * a=i,j with i > 0, i < j, i > l: a non-existent mip level fragment,
710 | the user agent selects array level l.
711 | * a=i with i > l: a non-existent array layer, the user agent selects mip
712 | level x.
713 |
714 | ==== Errors on the temporal dimension
715 |
716 | To describe the different cases for temporal media fragments, we
717 | use the definitions from <>. The invalidity
718 | of the following temporal fragments can only be detected by the
719 | user agent if it knows the duration (for non-existent temporal
720 | fragments) and the frame rate of the source sequence.
721 |
722 | * t=a,b with a > 0, a < b, a >= e and b > e: a non-existent temporal
723 | fragment, the user agent seeks to the end of the sequence e.
724 | * t=a with a >= e: a non-existent temporal fragment, the user agent
725 | seeks to the end of the media e.
726 |
727 | ==== Errors on the facial dimension
728 |
729 | To describe this case we use the definitions from <>.
730 | The invalidity of the following facial fragments can only be detected
731 | if the user agent knows the KTX file does not contain a cubemap.
732 | In that case the user agent SHOULD ignore these facial fragments.
733 |
734 |
735 | * f=i,j with i >= 0, i < j, i < 6
736 | * f=i with i >= 0, i < 6
737 | * f=,j with j >= 0, j < 6
738 |
739 | ==== Errors on the spatial dimension
740 |
741 | To describe the different cases for spatial media fragments, we use
742 | the definitions from <>. The invalidity
743 | of the following spatial fragments can only be detected by the user
744 | agent if it knows the size and depth of the source KTX file.
745 |
746 | * xyzwhd=a,b,c,e,f,g with a >= w or b >= h or c >= d: the origin
747 | (a,b,c) of the cube lies outside the source image and is therefore
748 | invalid. The user agent SHOULD ignore this spatial fragment.
749 |
750 | [bibliography]
751 | == References
752 |
753 | - [[[W3CMF]]] https://www.w3.org/TR/media-frags/[Media Fragments URI 1.0 (basic)].
754 | Raphaël Troncy et al. World Wide Web Consortium, September 2012.
755 |
756 | ////
757 | // The initial initial, "T." is placed after the doc title to prevent
758 | // Asciidoctor thinking I am trying to make a list.
759 | ////
760 | - [[[RFC3986]]] https://tools.ietf.org/html/rfc3986[Uniform Resource
761 | Identifier (URI): Generic Syntax]. T.
762 | Berners-Lee, R. Fielding and L. Masinter. IETF, January 2005.
763 |
764 | - [[[RFC7826]]] https://tools.ietf.org/html/rfc7826#page-29[Real Time Streaming
765 | Protocol Version 2.0]. H.
766 | Schulzrinne, A. Rao, R. Lanphier, M. Westerlund, M Stiemerling. IETF,
767 | December 2016.
768 |
769 | [.revhistory,cols="^25,^20,<55a",options="header"]
770 | |===
771 | | Document Revision | Date ^| Remark
772 | | 0 | 2021-04-18 | - Initial release.
773 | | {docrev} | {revdate} | - Fix typos.
774 | - Embed images.
775 | |===
776 |
--------------------------------------------------------------------------------
/khronos.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v2.1.2 | MIT License | git.io/normalize */
2 | /* ========================================================================== HTML5 display definitions ========================================================================== */
3 | /** Correct `block` display not defined in IE 8/9. */
4 | article, aside, details, figcaption, figure, footer, header, hgroup, main, nav, section { display: block; }
5 |
6 | /** Correct `inline-block` display not defined in IE 8/9. */
7 | audio, canvas, video { display: inline-block; }
8 |
9 | /** Prevent modern browsers from displaying `audio` without controls. Remove excess height in iOS 5 devices. */
10 | audio:not([controls]) { display: none; height: 0; }
11 |
12 | /** Address `[hidden]` styling not present in IE 8/9. Hide the `template` element in IE, Safari, and Firefox < 22. */
13 | [hidden], template { display: none; }
14 |
15 | script { display: none !important; }
16 |
17 | /* ========================================================================== Base ========================================================================== */
18 | /** 1. Set default font family to sans-serif. 2. Prevent iOS text size adjust after orientation change, without disabling user zoom. */
19 | html { font-family: sans-serif; /* 1 */ -ms-text-size-adjust: 100%; /* 2 */ -webkit-text-size-adjust: 100%; /* 2 */ }
20 |
21 | /** Remove default margin. */
22 | body { margin: 0; }
23 |
24 | /* ========================================================================== Links ========================================================================== */
25 | /** Remove the gray background color from active links in IE 10. */
26 | a { background: transparent; }
27 |
28 | /** Address `outline` inconsistency between Chrome and other browsers. */
29 | a:focus { outline: thin dotted; }
30 |
31 | /** Improve readability when focused and also mouse hovered in all browsers. */
32 | a:active, a:hover { outline: 0; }
33 |
34 | /* ========================================================================== Typography ========================================================================== */
35 | /** Address variable `h1` font-size and margin within `section` and `article` contexts in Firefox 4+, Safari 5, and Chrome. */
36 | h1 { font-size: 2em; margin: 0.67em 0; }
37 |
38 | /** Address styling not present in IE 8/9, Safari 5, and Chrome. */
39 | abbr[title] { border-bottom: 1px dotted; }
40 |
41 | /** Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. */
42 | b, strong { font-weight: bold; }
43 |
44 | /** Address styling not present in Safari 5 and Chrome. */
45 | dfn { font-style: italic; }
46 |
47 | /** Address differences between Firefox and other browsers. */
48 | hr { -moz-box-sizing: content-box; box-sizing: content-box; height: 0; }
49 |
50 | /** Address styling not present in IE 8/9. */
51 | mark { background: #ff0; color: #000; }
52 |
53 | /** Correct font family set oddly in Safari 5 and Chrome. */
54 | code, kbd, pre, samp { font-family: monospace, serif; font-size: 1em; }
55 |
56 | /** Improve readability of pre-formatted text in all browsers. */
57 | pre { white-space: pre-wrap; }
58 |
59 | /** Set consistent quote types. */
60 | q { quotes: "\201C" "\201D" "\2018" "\2019"; }
61 |
62 | /** Address inconsistent and variable font size in all browsers. */
63 | small { font-size: 80%; }
64 |
65 | /** Prevent `sub` and `sup` affecting `line-height` in all browsers. */
66 | sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
67 |
68 | sup { top: -0.5em; }
69 |
70 | sub { bottom: -0.25em; }
71 |
72 | /* ========================================================================== Embedded content ========================================================================== */
73 | /** Remove border when inside `a` element in IE 8/9. */
74 | img { border: 0; }
75 |
76 | /** Correct overflow displayed oddly in IE 9. */
77 | svg:not(:root) { overflow: hidden; }
78 |
79 | /* ========================================================================== Figures ========================================================================== */
80 | /** Address margin not present in IE 8/9 and Safari 5. */
81 | figure { margin: 0; }
82 |
83 | /* ========================================================================== Forms ========================================================================== */
84 | /** Define consistent border, margin, and padding. */
85 | fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; }
86 |
87 | /** 1. Correct `color` not being inherited in IE 8/9. 2. Remove padding so people aren't caught out if they zero out fieldsets. */
88 | legend { border: 0; /* 1 */ padding: 0; /* 2 */ }
89 |
90 | /** 1. Correct font family not being inherited in all browsers. 2. Correct font size not being inherited in all browsers. 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. */
91 | button, input, select, textarea { font-family: inherit; /* 1 */ font-size: 100%; /* 2 */ margin: 0; /* 3 */ }
92 |
93 | /** Address Firefox 4+ setting `line-height` on `input` using `!important` in the UA stylesheet. */
94 | button, input { line-height: normal; }
95 |
96 | /** Address inconsistent `text-transform` inheritance for `button` and `select`. All other form control elements do not inherit `text-transform` values. Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. Correct `select` style inheritance in Firefox 4+ and Opera. */
97 | button, select { text-transform: none; }
98 |
99 | /** 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` and `video` controls. 2. Correct inability to style clickable `input` types in iOS. 3. Improve usability and consistency of cursor style between image-type `input` and others. */
100 | button, html input[type="button"], input[type="reset"], input[type="submit"] { -webkit-appearance: button; /* 2 */ cursor: pointer; /* 3 */ }
101 |
102 | /** Re-set default cursor for disabled elements. */
103 | button[disabled], html input[disabled] { cursor: default; }
104 |
105 | /** 1. Address box sizing set to `content-box` in IE 8/9. 2. Remove excess padding in IE 8/9. */
106 | input[type="checkbox"], input[type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ }
107 |
108 | /** 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome (include `-moz` to future-proof). */
109 | input[type="search"] { -webkit-appearance: textfield; /* 1 */ -moz-box-sizing: content-box; -webkit-box-sizing: content-box; /* 2 */ box-sizing: content-box; }
110 |
111 | /** Remove inner padding and search cancel button in Safari 5 and Chrome on OS X. */
112 | input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; }
113 |
114 | /** Remove inner padding and border in Firefox 4+. */
115 | button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; }
116 |
117 | /** 1. Remove default vertical scrollbar in IE 8/9. 2. Improve readability and alignment in all browsers. */
118 | textarea { overflow: auto; /* 1 */ vertical-align: top; /* 2 */ }
119 |
120 | /* ========================================================================== Tables ========================================================================== */
121 | /** Remove most spacing between table cells. */
122 | table { border-collapse: collapse; border-spacing: 0; }
123 |
124 | meta.foundation-mq-small { font-family: "only screen and (min-width: 768px)"; width: 768px; }
125 |
126 | meta.foundation-mq-medium { font-family: "only screen and (min-width:1280px)"; width: 1280px; }
127 |
128 | meta.foundation-mq-large { font-family: "only screen and (min-width:1440px)"; width: 1440px; }
129 |
130 | *, *:before, *:after { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; }
131 |
132 | html, body { font-size: 100%; }
133 |
134 | body { background: #fff; color: #222; padding: 0; margin: 0; font-family: sans-serif; font-weight: normal; font-style: normal; line-height: 1; position: relative; cursor: auto; }
135 |
136 | a:hover { cursor: pointer; }
137 |
138 | img, object, embed { max-width: 100%; height: auto; }
139 |
140 | object, embed { height: 100%; }
141 |
142 | img { -ms-interpolation-mode: bicubic; }
143 |
144 | #map_canvas img, #map_canvas embed, #map_canvas object, .map_canvas img, .map_canvas embed, .map_canvas object { max-width: none !important; }
145 |
146 | .left { float: left !important; }
147 |
148 | .right { float: right !important; }
149 |
150 | .text-left { text-align: left !important; }
151 |
152 | .text-right { text-align: right !important; }
153 |
154 | .text-center { text-align: center !important; }
155 |
156 | .text-justify { text-align: justify !important; }
157 |
158 | .hide { display: none; }
159 |
160 | .antialiased { -webkit-font-smoothing: antialiased; }
161 |
162 | img { display: inline-block; vertical-align: middle; }
163 |
164 | textarea { height: auto; min-height: 50px; }
165 |
166 | select { width: 100%; }
167 |
168 | object, svg { display: inline-block; vertical-align: middle; }
169 |
170 | .center { margin-left: auto; margin-right: auto; }
171 |
172 | .spread { width: 100%; }
173 |
174 | p.lead, .paragraph.lead > p, #preamble > .sectionbody > .paragraph:first-of-type p { font-size: 1.21875em; line-height: 1.6; }
175 |
176 | .subheader, .admonitionblock td.content > .title, .audioblock > .title, .exampleblock > .title, .imageblock > .title, .listingblock > .title, .literalblock > .title, .stemblock > .title, .openblock > .title, .paragraph > .title, .quoteblock > .title, table.tableblock > .title, .verseblock > .title, .videoblock > .title, .dlist > .title, .olist > .title, .ulist > .title, .qlist > .title, .hdlist > .title { line-height: 1.4; color: black; font-weight: 300; margin-top: 0.2em; margin-bottom: 0.5em; }
177 |
178 | /* Typography resets */
179 | div, dl, dt, dd, ul, ol, li, h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6, pre, form, p, blockquote, th, td { margin: 0; padding: 0; direction: ltr; }
180 |
181 | /* Default Link Styles */
182 | a { color: #0068b0; text-decoration: none; line-height: inherit; }
183 | a:hover, a:focus { color: #333; }
184 | a img { border: none; }
185 |
186 | /* Default paragraph styles */
187 | p { font-weight: normal; font-size: 1em; line-height: 1.6; margin-bottom: 0.75em; text-rendering: optimizeLegibility; }
188 | p aside { font-size: 0.875em; line-height: 1.35; font-style: italic; }
189 |
190 | /* Default header styles */
191 | h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 { font-weight: normal; font-style: normal; color: black; text-rendering: optimizeLegibility; margin-top: 0.5em; margin-bottom: 0.5em; line-height: 1.2125em; }
192 | h1 small, h2 small, h3 small, #toctitle small, .sidebarblock > .content > .title small, h4 small, h5 small, h6 small { font-size: 60%; color: #4d4d4d; line-height: 0; }
193 |
194 | h1 { font-size: 2.125em; }
195 |
196 | h2 { font-size: 1.6875em; }
197 |
198 | h3, #toctitle, .sidebarblock > .content > .title { font-size: 1.375em; }
199 |
200 | h4 { font-size: 1.125em; }
201 |
202 | h5 { font-size: 1.125em; }
203 |
204 | h6 { font-size: 1em; }
205 |
206 | hr { border: solid #ddd; border-width: 1px 0 0; clear: both; margin: 1.25em 0 1.1875em; height: 0; }
207 |
208 | /* Helpful Typography Defaults */
209 | em, i { font-style: italic; line-height: inherit; }
210 |
211 | strong, b { font-weight: bold; line-height: inherit; }
212 |
213 | small { font-size: 60%; line-height: inherit; }
214 |
215 | code { font-family: monospace; font-weight: normal; color: #264357; }
216 |
217 | /* Lists */
218 | ul, ol, dl { font-size: 1em; line-height: 1.6; margin-bottom: 0.75em; list-style-position: outside; }
219 |
220 | ul, ol { margin-left: 1.5em; }
221 | ul.no-bullet, ol.no-bullet { margin-left: 1.5em; }
222 |
223 | /* Unordered Lists */
224 | ul li ul, ul li ol { margin-left: 1.25em; margin-bottom: 0; font-size: 1em; /* Override nested font-size change */ }
225 | ul.square li ul, ul.circle li ul, ul.disc li ul { list-style: inherit; }
226 | ul.square { list-style-type: square; }
227 | ul.circle { list-style-type: circle; }
228 | ul.disc { list-style-type: disc; }
229 | ul.no-bullet { list-style: none; }
230 |
231 | /* Ordered Lists */
232 | ol li ul, ol li ol { margin-left: 1.25em; margin-bottom: 0; }
233 |
234 | /* Definition Lists */
235 | dl dt { margin-bottom: 0.3em; font-weight: bold; }
236 | dl dd { margin-bottom: 0.75em; }
237 |
238 | /* Abbreviations */
239 | abbr, acronym { text-transform: uppercase; font-size: 90%; color: black; border-bottom: 1px dotted #ddd; cursor: help; }
240 |
241 | abbr { text-transform: none; }
242 |
243 | /* Blockquotes */
244 | blockquote { margin: 0 0 0.75em; padding: 0.5625em 1.25em 0 1.1875em; border-left: 1px solid #ddd; }
245 | blockquote cite { display: block; font-size: 0.8125em; color: #5e93b8; }
246 | blockquote cite:before { content: "\2014 \0020"; }
247 | blockquote cite a, blockquote cite a:visited { color: #5e93b8; }
248 |
249 | blockquote, blockquote p { line-height: 1.6; color: #333; }
250 |
251 | /* Microformats */
252 | .vcard { display: inline-block; margin: 0 0 1.25em 0; border: 1px solid #ddd; padding: 0.625em 0.75em; }
253 | .vcard li { margin: 0; display: block; }
254 | .vcard .fn { font-weight: bold; font-size: 0.9375em; }
255 |
256 | .vevent .summary { font-weight: bold; }
257 | .vevent abbr { cursor: auto; text-decoration: none; font-weight: bold; border: none; padding: 0 0.0625em; }
258 |
259 | @media only screen and (min-width: 768px) { h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 { line-height: 1.4; }
260 | h1 { font-size: 2.75em; }
261 | h2 { font-size: 2.3125em; }
262 | h3, #toctitle, .sidebarblock > .content > .title { font-size: 1.6875em; }
263 | h4 { font-size: 1.4375em; } }
264 | /* Tables */
265 | table { background: #fff; margin-bottom: 1.25em; border: solid 1px #d8d8ce; }
266 | table thead, table tfoot { background: #eee; font-weight: bold; }
267 | table thead tr th, table thead tr td, table tfoot tr th, table tfoot tr td { padding: 0.5em 0.625em 0.625em; font-size: inherit; color: #222; text-align: left; }
268 | table tr th, table tr td { padding: 0.5625em 0.625em; font-size: inherit; color: #6d6e71; }
269 | table tr.even, table tr.alt, table tr:nth-of-type(even) { background: #f8f8f8; }
270 | table thead tr th, table tfoot tr th, table tbody tr td, table tr td, table tfoot tr td { display: table-cell; line-height: 1.4; }
271 |
272 | body { -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; tab-size: 4; }
273 |
274 | h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 { line-height: 1.4; }
275 |
276 | a:hover, a:focus { text-decoration: underline; }
277 |
278 | .clearfix:before, .clearfix:after, .float-group:before, .float-group:after { content: " "; display: table; }
279 | .clearfix:after, .float-group:after { clear: both; }
280 |
281 | *:not(pre) > code { font-size: inherit; font-style: normal !important; letter-spacing: 0; padding: 0; background-color: transparent; -webkit-border-radius: 0; border-radius: 0; line-height: inherit; word-wrap: break-word; }
282 | *:not(pre) > code.nobreak { word-wrap: normal; }
283 | *:not(pre) > code.nowrap { white-space: nowrap; }
284 |
285 | pre, pre > code { line-height: 1.6; color: #264357; font-family: monospace; font-weight: normal; }
286 |
287 | em em { font-style: normal; }
288 |
289 | strong strong { font-weight: normal; }
290 |
291 | .keyseq { color: #333333; }
292 |
293 | kbd { font-family: monospace; display: inline-block; color: black; font-size: 0.65em; line-height: 1.45; background-color: #f7f7f7; border: 1px solid #ccc; -webkit-border-radius: 3px; border-radius: 3px; -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 0.1em white inset; -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 0.1em white inset; box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 0.1em white inset; margin: 0 0.15em; padding: 0.2em 0.5em; vertical-align: middle; position: relative; top: -0.1em; white-space: nowrap; }
294 |
295 | .keyseq kbd:first-child { margin-left: 0; }
296 |
297 | .keyseq kbd:last-child { margin-right: 0; }
298 |
299 | .menuseq, .menuref { color: #000; }
300 |
301 | .menuseq b:not(.caret), .menuref { font-weight: inherit; }
302 |
303 | .menuseq { word-spacing: -0.02em; }
304 | .menuseq b.caret { font-size: 1.25em; line-height: 0.8; }
305 | .menuseq i.caret { font-weight: bold; text-align: center; width: 0.45em; }
306 |
307 | b.button:before, b.button:after { position: relative; top: -1px; font-weight: normal; }
308 |
309 | b.button:before { content: "["; padding: 0 3px 0 2px; }
310 |
311 | b.button:after { content: "]"; padding: 0 2px 0 3px; }
312 |
313 | #header, #content, #footnotes, #footer { width: 100%; margin-left: auto; margin-right: auto; margin-top: 0; margin-bottom: 0; max-width: 62.5em; *zoom: 1; position: relative; padding-left: 1.5em; padding-right: 1.5em; }
314 | #header:before, #header:after, #content:before, #content:after, #footnotes:before, #footnotes:after, #footer:before, #footer:after { content: " "; display: table; }
315 | #header:after, #content:after, #footnotes:after, #footer:after { clear: both; }
316 |
317 | #content { margin-top: 1.25em; }
318 |
319 | #content:before { content: none; }
320 |
321 | #header > h1:first-child { color: black; margin-top: 2.25rem; margin-bottom: 0; }
322 | #header > h1:first-child + #toc { margin-top: 8px; border-top: 1px solid #ddd; }
323 | #header > h1:only-child, body.toc2 #header > h1:nth-last-child(2) { border-bottom: 1px solid #ddd; padding-bottom: 8px; }
324 | #header .details { border-bottom: 1px solid #ddd; line-height: 1.45; padding-top: 0.25em; padding-bottom: 0.25em; padding-left: 0.25em; color: #5e93b8; display: -ms-flexbox; display: -webkit-flex; display: flex; -ms-flex-flow: row wrap; -webkit-flex-flow: row wrap; flex-flow: row wrap; }
325 | #header .details span:first-child { margin-left: -0.125em; }
326 | #header .details span.email a { color: #333; }
327 | #header .details br { display: none; }
328 | #header .details br + span:before { content: "\00a0\2013\00a0"; }
329 | #header .details br + span.author:before { content: "\00a0\22c5\00a0"; color: #333; }
330 | #header .details br + span#revremark:before { content: "\00a0|\00a0"; }
331 | #header #revnumber { text-transform: capitalize; }
332 | #header #revnumber:after { content: "\00a0"; }
333 |
334 | #content > h1:first-child:not([class]) { color: black; border-bottom: 1px solid #ddd; padding-bottom: 8px; margin-top: 0; padding-top: 1rem; margin-bottom: 1.25rem; }
335 |
336 | #toc { border-bottom: 0 solid #ddd; padding-bottom: 0.5em; }
337 | #toc > ul { margin-left: 0.125em; }
338 | #toc ul.sectlevel0 > li > a { font-style: italic; }
339 | #toc ul.sectlevel0 ul.sectlevel1 { margin: 0.5em 0; }
340 | #toc ul { list-style-type: none; }
341 | #toc li { line-height: 1.3334; margin-top: 0.3334em; }
342 | #toc a { text-decoration: none; }
343 | #toc a:active { text-decoration: underline; }
344 |
345 | #toctitle { color: black; font-size: 1.2em; }
346 |
347 | @media only screen and (min-width: 768px) { #toctitle { font-size: 1.375em; }
348 | body.toc2 { padding-left: 15em; padding-right: 0; }
349 | #toc.toc2 { margin-top: 0 !important; background-color: #fff; position: fixed; width: 15em; left: 0; top: 0; border-right: 1px solid #ddd; border-top-width: 0 !important; border-bottom-width: 0 !important; z-index: 1000; padding: 1.25em 1em; height: 100%; overflow: auto; }
350 | #toc.toc2 #toctitle { margin-top: 0; margin-bottom: 0.8rem; font-size: 1.2em; }
351 | #toc.toc2 > ul { font-size: 0.9em; margin-bottom: 0; }
352 | #toc.toc2 ul ul { margin-left: 0; padding-left: 1em; }
353 | #toc.toc2 ul.sectlevel0 ul.sectlevel1 { padding-left: 0; margin-top: 0.5em; margin-bottom: 0.5em; }
354 | body.toc2.toc-right { padding-left: 0; padding-right: 15em; }
355 | body.toc2.toc-right #toc.toc2 { border-right-width: 0; border-left: 1px solid #ddd; left: auto; right: 0; } }
356 | @media only screen and (min-width: 1280px) { body.toc2 { padding-left: 20em; padding-right: 0; }
357 | #toc.toc2 { width: 20em; }
358 | #toc.toc2 #toctitle { font-size: 1.375em; }
359 | #toc.toc2 > ul { font-size: 0.95em; }
360 | #toc.toc2 ul ul { padding-left: 1.25em; }
361 | body.toc2.toc-right { padding-left: 0; padding-right: 20em; } }
362 | #content #toc { border-style: solid; border-width: 1px; border-color: #e6e6e6; margin-bottom: 1.25em; padding: 1.25em; background: #fff; -webkit-border-radius: 0; border-radius: 0; }
363 | #content #toc > :first-child { margin-top: 0; }
364 | #content #toc > :last-child { margin-bottom: 0; }
365 |
366 | #footer { max-width: 100%; background-color: none; padding: 1.25em; }
367 |
368 | #footer-text { color: black; line-height: 1.44; }
369 |
370 | #content { margin-bottom: 0.625em; }
371 |
372 | .sect1 { padding-bottom: 0.625em; }
373 |
374 | @media only screen and (min-width: 768px) { #content { margin-bottom: 1.25em; }
375 | .sect1 { padding-bottom: 1.25em; } }
376 | .sect1:last-child { padding-bottom: 0; }
377 |
378 | .sect1 + .sect1 { border-top: 0 solid #ddd; }
379 |
380 | #content h1 > a.anchor, h2 > a.anchor, h3 > a.anchor, #toctitle > a.anchor, .sidebarblock > .content > .title > a.anchor, h4 > a.anchor, h5 > a.anchor, h6 > a.anchor { position: absolute; z-index: 1001; width: 1.5ex; margin-left: -1.5ex; display: block; text-decoration: none !important; visibility: hidden; text-align: center; font-weight: normal; }
381 | #content h1 > a.anchor:before, h2 > a.anchor:before, h3 > a.anchor:before, #toctitle > a.anchor:before, .sidebarblock > .content > .title > a.anchor:before, h4 > a.anchor:before, h5 > a.anchor:before, h6 > a.anchor:before { content: "\00A7"; font-size: 0.85em; display: block; padding-top: 0.1em; }
382 | #content h1:hover > a.anchor, #content h1 > a.anchor:hover, h2:hover > a.anchor, h2 > a.anchor:hover, h3:hover > a.anchor, #toctitle:hover > a.anchor, .sidebarblock > .content > .title:hover > a.anchor, h3 > a.anchor:hover, #toctitle > a.anchor:hover, .sidebarblock > .content > .title > a.anchor:hover, h4:hover > a.anchor, h4 > a.anchor:hover, h5:hover > a.anchor, h5 > a.anchor:hover, h6:hover > a.anchor, h6 > a.anchor:hover { visibility: visible; }
383 | #content h1 > a.link, h2 > a.link, h3 > a.link, #toctitle > a.link, .sidebarblock > .content > .title > a.link, h4 > a.link, h5 > a.link, h6 > a.link { color: black; text-decoration: none; }
384 | #content h1 > a.link:hover, h2 > a.link:hover, h3 > a.link:hover, #toctitle > a.link:hover, .sidebarblock > .content > .title > a.link:hover, h4 > a.link:hover, h5 > a.link:hover, h6 > a.link:hover { color: black; }
385 |
386 | details, .audioblock, .imageblock, .literalblock, .listingblock, .stemblock, .videoblock { margin-bottom: 1.25em; }
387 |
388 | details > summary:first-of-type { display:list-item; cursor:pointer; margin-bottom:.75em; }
389 |
390 | .admonitionblock td.content > .title, .audioblock > .title, .exampleblock > .title, .imageblock > .title, .listingblock > .title, .literalblock > .title, .stemblock > .title, .openblock > .title, .paragraph > .title, .quoteblock > .title, table.tableblock > .title, .verseblock > .title, .videoblock > .title, .dlist > .title, .olist > .title, .ulist > .title, .qlist > .title, .hdlist > .title { text-rendering: optimizeLegibility; text-align: left; }
391 |
392 | table.tableblock > caption.title { white-space: nowrap; overflow: visible; max-width: 0; }
393 |
394 | .paragraph.lead > p, #preamble > .sectionbody > .paragraph:first-of-type p { color: black; }
395 |
396 | table.tableblock #preamble > .sectionbody > .paragraph:first-of-type p { font-size: inherit; }
397 |
398 | .admonitionblock > table { border-collapse: separate; border: 0; background: none; width: 100%; }
399 | .admonitionblock > table td.icon { text-align: center; width: 80px; }
400 | .admonitionblock > table td.icon img { max-width: initial; }
401 | .admonitionblock > table td.icon .title { font-weight: bold; text-transform: uppercase; }
402 | .admonitionblock > table td.content { padding-left: 1.125em; padding-right: 1.25em; border-left: 1px solid #ddd; color: #5e93b8; }
403 | .admonitionblock > table td.content > :last-child > :last-child { margin-bottom: 0; }
404 |
405 | .exampleblock > .content { border-style: solid; border-width: 1px; border-color: #e6e6e6; margin-bottom: 1.25em; padding: 1.25em; background: #fff; -webkit-border-radius: 0; border-radius: 0; }
406 | .exampleblock > .content > :first-child { margin-top: 0; }
407 | .exampleblock > .content > :last-child { margin-bottom: 0; }
408 |
409 | .sidebarblock { border-style: solid; border-width: 1px; border-color: #e6e6e6; margin-bottom: 1.25em; padding: 1.25em; background: #fff; -webkit-border-radius: 0; border-radius: 0; }
410 | .sidebarblock > :first-child { margin-top: 0; }
411 | .sidebarblock > :last-child { margin-bottom: 0; }
412 | .sidebarblock > .content > .title { color: black; margin-top: 0; }
413 |
414 | .exampleblock > .content > :last-child > :last-child, .exampleblock > .content .olist > ol > li:last-child > :last-child, .exampleblock > .content .ulist > ul > li:last-child > :last-child, .exampleblock > .content .qlist > ol > li:last-child > :last-child, .sidebarblock > .content > :last-child > :last-child, .sidebarblock > .content .olist > ol > li:last-child > :last-child, .sidebarblock > .content .ulist > ul > li:last-child > :last-child, .sidebarblock > .content .qlist > ol > li:last-child > :last-child { margin-bottom: 0; }
415 |
416 | .literalblock pre, .listingblock pre:not(.highlight), .listingblock pre[class="highlight"], .listingblock pre[class^="highlight "], .listingblock pre.CodeRay, .listingblock pre.prettyprint { background: #eee; }
417 | .sidebarblock .literalblock pre, .sidebarblock .listingblock pre:not(.highlight), .sidebarblock .listingblock pre[class="highlight"], .sidebarblock .listingblock pre[class^="highlight "], .sidebarblock .listingblock pre.CodeRay, .sidebarblock .listingblock pre.prettyprint { background: #f2f1f1; }
418 |
419 | .literalblock pre, .literalblock pre[class], .listingblock pre, .listingblock pre[class] { border: 1px hidden #666; -webkit-border-radius: 0; border-radius: 0; word-wrap: break-word; padding: 1.25em 1.5625em 1.125em 1.5625em; font-size: 0.8125em; }
420 | .literalblock pre.nowrap, .literalblock pre[class].nowrap, .listingblock pre.nowrap, .listingblock pre[class].nowrap { overflow-x: auto; white-space: pre; word-wrap: normal; }
421 | @media only screen and (min-width: 768px) { .literalblock pre, .literalblock pre[class], .listingblock pre, .listingblock pre[class] { font-size: 0.90625em; } }
422 | @media only screen and (min-width: 1280px) { .literalblock pre, .literalblock pre[class], .listingblock pre, .listingblock pre[class] { font-size: 1em; } }
423 |
424 | .literalblock.output pre { color: #eee; background-color: #264357; }
425 |
426 | .listingblock pre.highlightjs { padding: 0; }
427 | .listingblock pre.highlightjs > code { padding: 1.25em 1.5625em 1.125em 1.5625em; -webkit-border-radius: 0; border-radius: 0; }
428 |
429 | .listingblock > .content { position: relative; }
430 |
431 | .listingblock code[data-lang]:before { display: none; content: attr(data-lang); position: absolute; font-size: 0.75em; top: 0.425rem; right: 0.5rem; line-height: 1; text-transform: uppercase; color: #999; }
432 |
433 | .listingblock:hover code[data-lang]:before { display: block; }
434 |
435 | .listingblock.terminal pre .command:before { content: attr(data-prompt); padding-right: 0.5em; color: #999; }
436 |
437 | .listingblock.terminal pre .command:not([data-prompt]):before { content: "$"; }
438 |
439 | table.pyhltable { border-collapse: separate; border: 0; margin-bottom: 0; background: none; }
440 |
441 | table.pyhltable td { vertical-align: top; padding-top: 0; padding-bottom: 0; line-height: 1.6; }
442 |
443 | table.pyhltable td.code { padding-left: .75em; padding-right: 0; }
444 |
445 | pre.pygments .lineno, table.pyhltable td:not(.code) { color: #999; padding-left: 0; padding-right: .5em; border-right: 1px solid #ddd; }
446 |
447 | pre.pygments .lineno { display: inline-block; margin-right: .25em; }
448 |
449 | table.pyhltable .linenodiv { background: none !important; padding-right: 0 !important; }
450 |
451 | .quoteblock { margin: 0 1em 0.75em 1.5em; display: table; }
452 | .quoteblock > .title { margin-left: -1.5em; margin-bottom: 0.75em; }
453 | .quoteblock blockquote, .quoteblock blockquote p { color: #333; font-size: 1.15rem; line-height: 1.75; word-spacing: 0.1em; letter-spacing: 0; font-style: italic; text-align: justify; }
454 | .quoteblock blockquote { margin: 0; padding: 0; border: 0; }
455 | .quoteblock blockquote:before { content: "\201c"; float: left; font-size: 2.75em; font-weight: bold; line-height: 0.6em; margin-left: -0.6em; color: black; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); }
456 | .quoteblock blockquote > .paragraph:last-child p { margin-bottom: 0; }
457 | .quoteblock .attribution { margin-top: 0.5em; margin-right: 0.5ex; text-align: right; }
458 | .quoteblock .quoteblock { margin-left: 0; margin-right: 0; padding: 0.5em 0; border-left: 3px solid #5e93b8; }
459 | .quoteblock .quoteblock blockquote { padding: 0 0 0 0.75em; }
460 | .quoteblock .quoteblock blockquote:before { display: none; }
461 |
462 | .verseblock { margin: 0 1em 0.75em 1em; }
463 | .verseblock pre { font-family: sans-serif; font-size: 1.15rem; color: #333; font-weight: 300; text-rendering: optimizeLegibility; }
464 | .verseblock pre strong { font-weight: 400; }
465 | .verseblock .attribution { margin-top: 1.25rem; margin-left: 0.5ex; }
466 |
467 | .quoteblock .attribution, .verseblock .attribution { font-size: 0.8125em; line-height: 1.45; font-style: italic; }
468 | .quoteblock .attribution br, .verseblock .attribution br { display: none; }
469 | .quoteblock .attribution cite, .verseblock .attribution cite { display: block; letter-spacing: -0.025em; color: #5e93b8; }
470 |
471 | .quoteblock.abstract { margin: 0 0 0.75em 0; display: block; }
472 | .quoteblock.abstract blockquote, .quoteblock.abstract blockquote p { text-align: left; word-spacing: 0; }
473 | .quoteblock.abstract blockquote:before, .quoteblock.abstract blockquote p:first-of-type:before { display: none; }
474 |
475 | table.tableblock { max-width: 100%; border-collapse: separate; }
476 | table.tableblock td > .paragraph:last-child p > p:last-child, table.tableblock th > p:last-child, table.tableblock td > p:last-child { margin-bottom: 0; }
477 |
478 | table.tableblock, th.tableblock, td.tableblock { border: 0 solid #d8d8ce; }
479 |
480 | table.grid-all > thead > tr > .tableblock, table.grid-all > tbody > tr > .tableblock { border-width: 0 1px 1px 0; }
481 |
482 | table.grid-all > tfoot > tr > .tableblock { border-width: 1px 1px 0 0; }
483 |
484 | table.grid-cols > * > tr > .tableblock { border-width: 0 1px 0 0; }
485 |
486 | table.grid-rows > thead > tr > .tableblock, table.grid-rows > tbody > tr > .tableblock { border-width: 0 0 1px 0; }
487 |
488 | table.grid-rows > tfoot > tr > .tableblock { border-width: 1px 0 0 0; }
489 |
490 | table.grid-all > * > tr > .tableblock:last-child, table.grid-cols > * > tr > .tableblock:last-child { border-right-width: 0; }
491 |
492 | table.grid-all > tbody > tr:last-child > .tableblock, table.grid-all > thead:last-child > tr > .tableblock, table.grid-rows > tbody > tr:last-child > .tableblock, table.grid-rows > thead:last-child > tr > .tableblock { border-bottom-width: 0; }
493 |
494 | table.frame-all { border-width: 1px; }
495 |
496 | table.frame-sides { border-width: 0 1px; }
497 |
498 | table.frame-topbot { border-width: 1px 0; }
499 |
500 | th.halign-left, td.halign-left { text-align: left; }
501 |
502 | th.halign-right, td.halign-right { text-align: right; }
503 |
504 | th.halign-center, td.halign-center { text-align: center; }
505 |
506 | th.valign-top, td.valign-top { vertical-align: top; }
507 |
508 | th.valign-bottom, td.valign-bottom { vertical-align: bottom; }
509 |
510 | th.valign-middle, td.valign-middle { vertical-align: middle; }
511 |
512 | table thead th, table tfoot th { font-weight: bold; }
513 |
514 | tbody tr th { display: table-cell; line-height: 1.4; background: #eee; }
515 |
516 | tbody tr th, tbody tr th p, tfoot tr th, tfoot tr th p { color: #222; font-weight: bold; }
517 |
518 | p.tableblock > code:only-child { background: none; padding: 0; }
519 |
520 | p.tableblock { font-size: 1em; }
521 |
522 | td > div.verse { white-space: pre; }
523 |
524 | ol { margin-left: 1.75em; }
525 |
526 | ul li ol { margin-left: 1.5em; }
527 |
528 | dl dd { margin-left: 1.125em; }
529 |
530 | dl dd:last-child, dl dd:last-child > :last-child { margin-bottom: 0; }
531 |
532 | ol > li p, ul > li p, ul dd, ol dd, .olist .olist, .ulist .ulist, .ulist .olist, .olist .ulist { margin-bottom: 0.375em; }
533 |
534 | ul.checklist, ul.none, ol.none, ul.no-bullet, ol.no-bullet, ol.unnumbered, ul.unstyled, ol.unstyled { list-style-type: none; }
535 |
536 | ul.no-bullet, ol.no-bullet, ol.unnumbered { margin-left: 0.625em; }
537 |
538 | ul.unstyled, ol.unstyled { margin-left: 0; }
539 |
540 | ul.checklist { margin-left: 0.625em; }
541 |
542 | ul.checklist li > p:first-child > .fa-square-o:first-child, ul.checklist li > p:first-child > .fa-check-square-o:first-child { width: 1.25em; font-size: 0.8em; position: relative; bottom: 0.125em; }
543 |
544 | ul.checklist li > p:first-child > input[type="checkbox"]:first-child { margin-right: 0.25em; }
545 |
546 | ul.inline { display: -ms-flexbox; display: -webkit-box; display: flex; -ms-flex-flow: row wrap; -webkit-flex-flow: row wrap; flex-flow: row wrap; list-style: none; margin: 0 0 0.375em -0.75em; }
547 |
548 | ul.inline > li { margin-left: 0.75em; }
549 |
550 | .unstyled dl dt { font-weight: normal; font-style: normal; }
551 |
552 | ol.arabic { list-style-type: decimal; }
553 |
554 | ol.decimal { list-style-type: decimal-leading-zero; }
555 |
556 | ol.loweralpha { list-style-type: lower-alpha; }
557 |
558 | ol.upperalpha { list-style-type: upper-alpha; }
559 |
560 | ol.lowerroman { list-style-type: lower-roman; }
561 |
562 | ol.upperroman { list-style-type: upper-roman; }
563 |
564 | ol.lowergreek { list-style-type: lower-greek; }
565 |
566 | .hdlist > table, .colist > table { border: 0; background: none; }
567 | .hdlist > table > tbody > tr, .colist > table > tbody > tr { background: none; }
568 |
569 | td.hdlist1, td.hdlist2 { vertical-align: top; padding: 0 0.625em; }
570 |
571 | td.hdlist1 { font-weight: bold; padding-bottom: 0.75em; }
572 |
573 | .literalblock + .colist, .listingblock + .colist { margin-top: -0.5em; }
574 |
575 | .colist > table tr > td:first-of-type { padding: 0.4em 0.75em 0 0.75em; line-height: 1; vertical-align: top; }
576 | .colist > table tr > td:first-of-type img { max-width: initial; }
577 | .colist > table tr > td:last-of-type { padding: 0.25em 0; }
578 |
579 | .thumb, .th { line-height: 0; display: inline-block; border: solid 4px #fff; -webkit-box-shadow: 0 0 0 1px #ddd; box-shadow: 0 0 0 1px #ddd; }
580 |
581 | .imageblock.left, .imageblock[style*="float: left"] { margin: 0.25em 0.625em 1.25em 0; }
582 | .imageblock.right, .imageblock[style*="float: right"] { margin: 0.25em 0 1.25em 0.625em; }
583 | .imageblock > .title { margin-bottom: 0; }
584 | .imageblock.thumb, .imageblock.th { border-width: 6px; }
585 | .imageblock.thumb > .title, .imageblock.th > .title { padding: 0 0.125em; }
586 |
587 | .image.left, .image.right { margin-top: 0.25em; margin-bottom: 0.25em; display: inline-block; line-height: 0; }
588 | .image.left { margin-right: 0.625em; }
589 | .image.right { margin-left: 0.625em; }
590 |
591 | a.image { text-decoration: none; display: inline-block; }
592 | a.image object { pointer-events: none; }
593 |
594 | sup.footnote, sup.footnoteref { font-size: 0.875em; position: static; vertical-align: super; }
595 | sup.footnote a, sup.footnoteref a { text-decoration: none; }
596 | sup.footnote a:active, sup.footnoteref a:active { text-decoration: underline; }
597 |
598 | #footnotes { padding-top: 0.75em; padding-bottom: 0.75em; margin-bottom: 0.625em; }
599 | #footnotes hr { width: 20%; min-width: 6.25em; margin: -0.25em 0 0.75em 0; border-width: 1px 0 0 0; }
600 | #footnotes .footnote { padding: 0 0.375em 0 0.225em; line-height: 1.3334; font-size: 0.875em; margin-left: 1.2em; margin-bottom: 0.2em; }
601 | #footnotes .footnote a:first-of-type { font-weight: bold; text-decoration: none; margin-left: -1.05em; }
602 | #footnotes .footnote:last-of-type { margin-bottom: 0; }
603 | #content #footnotes { margin-top: -0.625em; margin-bottom: 0; padding: 0.75em 0; }
604 |
605 | .gist .file-data > table { border: 0; background: #fff; width: 100%; margin-bottom: 0; }
606 | .gist .file-data > table td.line-data { width: 99%; }
607 |
608 | div.unbreakable { page-break-inside: avoid; }
609 |
610 | .big { font-size: larger; }
611 |
612 | .small { font-size: smaller; }
613 |
614 | .underline { text-decoration: underline; }
615 |
616 | .overline { text-decoration: overline; }
617 |
618 | .line-through { text-decoration: line-through; }
619 |
620 | .aqua { color: #00bfbf; }
621 |
622 | .aqua-background { background-color: #00fafa; }
623 |
624 | .black { color: black; }
625 |
626 | .black-background { background-color: black; }
627 |
628 | .blue { color: #0000bf; }
629 |
630 | .blue-background { background-color: #0000fa; }
631 |
632 | .fuchsia { color: #bf00bf; }
633 |
634 | .fuchsia-background { background-color: #fa00fa; }
635 |
636 | .gray { color: #606060; }
637 |
638 | .gray-background { background-color: #7d7d7d; }
639 |
640 | .green { color: #006000; }
641 |
642 | .green-background { background-color: #007d00; }
643 |
644 | .lime { color: #00bf00; }
645 |
646 | .lime-background { background-color: #00fa00; }
647 |
648 | .maroon { color: #600000; }
649 |
650 | .maroon-background { background-color: #7d0000; }
651 |
652 | .navy { color: #000060; }
653 |
654 | .navy-background { background-color: #00007d; }
655 |
656 | .olive { color: #606000; }
657 |
658 | .olive-background { background-color: #7d7d00; }
659 |
660 | .purple { color: #600060; }
661 |
662 | .purple-background { background-color: #7d007d; }
663 |
664 | .red { color: #bf0000; }
665 |
666 | .red-background { background-color: #fa0000; }
667 |
668 | .silver { color: #909090; }
669 |
670 | .silver-background { background-color: #bcbcbc; }
671 |
672 | .teal { color: #006060; }
673 |
674 | .teal-background { background-color: #007d7d; }
675 |
676 | .white { color: #bfbfbf; }
677 |
678 | .white-background { background-color: #fafafa; }
679 |
680 | .yellow { color: #bfbf00; }
681 |
682 | .yellow-background { background-color: #fafa00; }
683 |
684 | span.icon > .fa { cursor: default; }
685 | a span.icon > .fa { cursor: inherit; }
686 |
687 | .admonitionblock td.icon [class^="fa icon-"] { font-size: 2.5em; text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); cursor: default; }
688 | .admonitionblock td.icon .icon-note:before { content: "\f05a"; color: #29475c; }
689 | .admonitionblock td.icon .icon-tip:before { content: "\f0eb"; text-shadow: 1px 1px 2px rgba(155, 155, 0, 0.8); color: #111; }
690 | .admonitionblock td.icon .icon-warning:before { content: "\f071"; color: #bf6900; }
691 | .admonitionblock td.icon .icon-caution:before { content: "\f06d"; color: #bf3400; }
692 | .admonitionblock td.icon .icon-important:before { content: "\f06a"; color: #bf0000; }
693 |
694 | .conum[data-value] { display: inline-block; color: #fff !important; background-color: black; -webkit-border-radius: 100px; border-radius: 100px; text-align: center; font-size: 0.75em; width: 1.67em; height: 1.67em; line-height: 1.67em; font-family: "Open Sans", "DejaVu Sans", sans-serif; font-style: normal; font-weight: bold; }
695 | .conum[data-value] * { color: #fff !important; }
696 | .conum[data-value] + b { display: none; }
697 | .conum[data-value]:after { content: attr(data-value); }
698 | pre .conum[data-value] { position: relative; top: -0.125em; }
699 |
700 | b.conum * { color: inherit !important; }
701 |
702 | .conum:not([data-value]):empty { display: none; }
703 |
704 | h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 { border-bottom: 1px solid #ddd; }
705 |
706 | .sect1 { padding-bottom: 0; }
707 |
708 | #toctitle { color: #00406F; font-weight: normal; margin-top: 1.5em; }
709 |
710 | .sidebarblock { border-color: #aaa; }
711 |
712 | code { -webkit-border-radius: 4px; border-radius: 4px; }
713 |
714 | p.tableblock.header { color: #6d6e71; }
715 |
716 | .literalblock pre, .listingblock pre { background: #eee; }
717 |
--------------------------------------------------------------------------------