├── .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 | 2 | 3 | Khronos Working Draft 4 | -------------------------------------------------------------------------------- /images/logo-spec.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Khronos Specification 4 | -------------------------------------------------------------------------------- /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 | 2 | 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Home of the KTX File Format Specification 2 | 3 | [![Build Status](https://travis-ci.org/KhronosGroup/KTX-Specification.svg?branch=master)](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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | A 9 | B 10 | C 11 | D 12 | E 13 | F 14 | H 15 | G 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | X 27 | Y 28 | Z 29 | 30 | 31 | 32 | 33 | +X 34 | C 35 | D 36 | G 37 | H 38 | 39 | 40 | 41 | +Y 42 | A 43 | D 44 | B 45 | C 46 | 47 | 48 | 49 | +Z 50 | B 51 | C 52 | F 53 | G 54 | 55 | 56 | 57 | -X 58 | A 59 | B 60 | E 61 | F 62 | 63 | 64 | 65 | -Y 66 | F 67 | G 68 | E 69 | H 70 | 71 | 72 | 73 | -Z 74 | D 75 | A 76 | H 77 | E 78 | 79 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------