Texturing overhaul: GPU compression, sRGB sampling, swizzles, etc. (#240)
* WIP compressed textures, swizzles, sRGB sampling, ... * refactor texture type info & fix random bugs * fix preprocessing of sRGB textures * handle y-flipped basis textures * glcommon: better WebGL compat for compressed format detection * missed WEBGL_compressed_texture_pvrtc * implement compressed texture xcoding and uploading * Add basis_universal submodule * Reorganize texture loader code Clean up some code Isolate Basis Universal loader into a separate module * Add wrapper script for encoding .basis textures * basisu: honor custom metadata written by the mkbasis.py script * mkbasis.py: add --incredibly-slow and --dry-run * Move pixmap code from util/ to pixmap/ * Add an on-disk transcode cache for basis textures to speed up loads * Compress texture cache with zlib * Use readable format names for basisu cache filenames * basisu: mip bias test code * basisu: small caching cleanup * add TAISEI_BASISU_MIP_BIAS env variable * Improve OpenGL format matching heuristics * Document considerations for compressed format priority * Remove dead code * Enable two forgotten formats, BC3_RGBA and ATC_RGBA Also prefer BC7 over BC1/BC3 * Recognize GL_ANGLE_compressed_texture_etc for ETC2 textures * Default depth buffers to 24-bit; remove ANGLE hack * Fix glcommon_check_extension for GLES2/legacy gl * Add renderer feature bit for texture swizzle masks * glcommon: Fixup internal formats for GLES2 Sized internal formats are not allowed in GLES2 * Fix emscripten compile errors * Update basis_universal * remove more dead code * revert irrelevant stage4 change * shut up UBSan * basisu: shut up some debug spam * Add normalmap sampling helper to util.glslh * basisu: add a gray-alpha mode * mkbasis.py: Abort if image dimansions aren't multiples of 4 * Add basic Basis Universal encoding documentation (WIP) * doc/basisu: Add paragraph about modes; minor tweaks * basisu: workarounds for GL texture size requirements * gles20: fix uncompressed sRGB formats * Partial workaround for missing swizzles in gles2 and webgl * remove invalid assertion * New renderer API to expose glDrawBuffers-like functionality * stagedraw: disable all color outputs for copy_depth pass required for WebGL compatibility * support GL_ANGLE_request_extension * emscripten: include *.basis in gfx package Also fix a potential problem when more than one .pkgdir is used to construct emscripten packages * Don't rely on emscripten runtime to enable webgl extensions
This commit is contained in:
parent
324dccb79a
commit
a5fd6fe5d9
83 changed files with 5330 additions and 1561 deletions
6
.gitmodules
vendored
6
.gitmodules
vendored
|
@ -8,4 +8,8 @@
|
|||
branch = master
|
||||
[submodule "external/koishi"]
|
||||
path = external/koishi
|
||||
url = https://github.com/taisei-project/koishi
|
||||
url = https://github.com/taisei-project/koishi.git
|
||||
[submodule "external/basis_universal"]
|
||||
path = external/basis_universal
|
||||
url = https://github.com/taisei-project/basis_universal.git
|
||||
branch = taisei
|
||||
|
|
142
doc/BASISU.rst
Normal file
142
doc/BASISU.rst
Normal file
|
@ -0,0 +1,142 @@
|
|||
|
||||
Basis Universal
|
||||
===============
|
||||
|
||||
.. contents::
|
||||
|
||||
Intro
|
||||
-----
|
||||
|
||||
`Basis Universal <https://github.com/taisei-project/basis_universal>`__ is a GPU texture compression system with ability
|
||||
to transcode images into a wide variety of formats at runtime from a single source. This document explains how to author
|
||||
Basis Universal (``.basis``) textures for Taisei, and highlights limitations and caveats of our Basis support.
|
||||
|
||||
First-time setup
|
||||
----------------
|
||||
|
||||
Step 1: The encoder
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Compile the encoder and put it somewhere in your ``PATH``. Assuming you have a working and up to date clone of the
|
||||
Taisei repository, including submodules, this can be done like so on Linux:
|
||||
|
||||
.. code:: sh
|
||||
|
||||
cd subprojects/basis_universal
|
||||
meson setup --buildtype=release -Db_lto=true -Dcpp_args=-march=native build
|
||||
meson compile -C build basisu
|
||||
ln -s $PWD/build/basisu ~/.local/bin
|
||||
|
||||
Verify that the encoder is working by running ``basisu``. It should print a long list of options. If the command is not
|
||||
found, make sure ``~/.local/bin`` is in your ``PATH``, or choose another directory that is.
|
||||
|
||||
The optimization options in ``meson setup`` are optional but highly recommended, as the encoding process can be quite
|
||||
slow.
|
||||
|
||||
It's also possible to use `the upstream encoder <https://github.com/BinomialLLC/basis_universal>`__, which may be
|
||||
packaged by your distribution. However, this is not recommended. As of 2020-08-06, the upstream encoder is missing some
|
||||
important performance optimizations; see
|
||||
`BinomialLLC/basis_universal#105 <https://github.com/BinomialLLC/basis_universal/pull/105>`__
|
||||
`BinomialLLC/basis_universal#112 <https://github.com/BinomialLLC/basis_universal/pull/112>`__
|
||||
`BinomialLLC/basis_universal#113 <https://github.com/BinomialLLC/basis_universal/pull/113>`__.
|
||||
|
||||
Step 2: The wrapper
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``mkbasis`` wrapper script is what you'll actually use to create ``.basis`` files. Simply symlink it into your
|
||||
``PATH``:
|
||||
|
||||
.. code:: sh
|
||||
|
||||
ln -s $PWD/scripts/mkbasis.py ~/.local/bin/mkbasis
|
||||
|
||||
Verify that it works by running ``mkbasis``.
|
||||
|
||||
Encoding TL;DR
|
||||
--------------
|
||||
|
||||
Encode a **diffuse or ambient map** (sRGB data, decoded to linear when sampled in a shader):
|
||||
|
||||
.. code:: sh
|
||||
|
||||
# Outputs to foo.basis
|
||||
mkbasis foo.png
|
||||
|
||||
# Outputs to /path/to/bar.basis
|
||||
mkbasis foo.png -o /path/to/bar.basis
|
||||
|
||||
Encode a **tangent-space normal map** (special case):
|
||||
|
||||
.. code:: sh
|
||||
|
||||
mkbasis foo.png --normal
|
||||
|
||||
Encode a **roughness map** (single-channel linear data):
|
||||
|
||||
.. code:: sh
|
||||
|
||||
mkbasis foo.png --channels=r --linear
|
||||
# Equivalent to:
|
||||
mkbasis foo.png --r --linear
|
||||
|
||||
Encode **RGBA** color data and **pre-multiply alpha**:
|
||||
|
||||
.. code:: sh
|
||||
|
||||
mkbasis foo.png --channels=rgba
|
||||
# Equivalent to:
|
||||
mkbasis foo.png --rgba
|
||||
|
||||
Encode **Gray+Alpha** data and **pre-multiply alpha**:
|
||||
|
||||
.. code:: sh
|
||||
|
||||
mkbasis foo.png --channels=gray-alpha
|
||||
# Equivalent to:
|
||||
mkbasis foo.png --gray-alpha
|
||||
|
||||
Do **not** pre-multiply alpha:
|
||||
|
||||
.. code:: sh
|
||||
|
||||
mkbasis foo.png --no-multiply-alpha
|
||||
|
||||
Sacrifice quality to speed up the encoding process:
|
||||
|
||||
.. code:: sh
|
||||
|
||||
mkbasis foo.png --fast
|
||||
|
||||
For a complete list of options and their default values, see
|
||||
|
||||
.. code:: sh
|
||||
|
||||
mkbasis --help
|
||||
|
||||
Encoding details
|
||||
----------------
|
||||
|
||||
Encoding modes
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Basis Universal supports two very different encoding modes: ETC1S and UASTC. The primary difference between the two is
|
||||
the size/quality trade-off.
|
||||
|
||||
ETC1S is the default mode. It offers medium/low quality and excellent compression.
|
||||
|
||||
UASTC has significantly higher quality, but much larger file sizes. UASTC-encoded Basis files must also be additionally
|
||||
compressed with an LZ-based scheme, such as deflate (zlib). Zopfli-compressed UASTC files are roughly 4 times as large
|
||||
as their ETC1S equivalents (including mipmaps), comparable to the source file stored with lossless PNG or WebP
|
||||
compression.
|
||||
|
||||
Although UASTC should theoretically work, it has not been well tested with Taisei yet. The ``mkbasis`` wrapper also does
|
||||
not apply LZ compression to UASTC files automatically yet, and Taisei wouldn't pick them up either (unless they are
|
||||
stored compressed inside of a ``.zip`` package). If you want to use UASTC nonetheless, pass ``--uastc`` to ``mkbasis``.
|
||||
|
||||
*TODO*
|
||||
|
||||
|
||||
Caveats and limitations
|
||||
-----------------------
|
||||
|
||||
*TODO*
|
|
@ -28,11 +28,20 @@ var glContext = canvasElement.getContext('webgl2', {
|
|||
'stencil' : false,
|
||||
});
|
||||
|
||||
// This actually enables the extension. Galaxy-brain API right here.
|
||||
glContext.getExtension('WEBGL_debug_renderer_info');
|
||||
if(!glContext) {
|
||||
throw "Could not create a WebGL 2 context";
|
||||
}
|
||||
|
||||
// glContext = WebGLDebugUtils.makeDebugContext(glContext);
|
||||
|
||||
// WebGL extensions have to be explicitly enabled to make the functionality available.
|
||||
// Note that Emscripten will always report all *supported* extensions in GL_EXTENSIONS,
|
||||
// regardless of whether they are enabled or not. This is non-conformant from the GLES
|
||||
// perspective. The easiest way to fix that is to enable all of them here.
|
||||
glContext.getSupportedExtensions().forEach(function(ext) {
|
||||
glContext.getExtension(ext);
|
||||
});
|
||||
|
||||
canvasElement.addEventListener("webglcontextlost", function(e) {
|
||||
alert('WebGL context lost. You will need to reload the page.');
|
||||
e.preventDefault();
|
||||
|
|
1
external/basis_universal
vendored
Submodule
1
external/basis_universal
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 8abe8b36d5c2fae27a876a2238e0fa07b6c3ec38
|
|
@ -148,11 +148,13 @@ dep_crypto = dependency('libcrypto', required : f
|
|||
|
||||
dep_m = cc.find_library('m', required : false)
|
||||
|
||||
dep_basisu_transcoder = subproject('basis_universal').get_variable('basisu_transcoder_dep')
|
||||
dep_cglm = subproject('cglm').get_variable('cglm_dep')
|
||||
dep_glad = subproject('glad').get_variable('glad_dep')
|
||||
dep_koishi = subproject('koishi').get_variable('koishi_dep')
|
||||
|
||||
taisei_deps = [
|
||||
dep_basisu_transcoder,
|
||||
dep_cglm,
|
||||
dep_freetype,
|
||||
dep_koishi,
|
||||
|
@ -252,6 +254,8 @@ else
|
|||
endif
|
||||
|
||||
config.set('TAISEI_BUILDCONF_USE_GNU_EXTENSIONS', get_option('use_gnu_ext'))
|
||||
config.set('TAISEI_BUILDCONF_HAVE_BUILTIN_POPCOUNTLL', cc.has_function('__builtin_popcountll'))
|
||||
config.set('TAISEI_BUILDCONF_HAVE_BUILTIN_POPCOUNT', cc.has_function('__builtin_popcount'))
|
||||
|
||||
prefer_relpath_systems = [
|
||||
'windows',
|
||||
|
|
|
@ -84,6 +84,12 @@ vec3 hueShift(vec3 c, float s) {
|
|||
return hsv2rgb(hsv);
|
||||
}
|
||||
|
||||
vec3 sample_normalmap(sampler2D src, vec2 uv) {
|
||||
vec2 xy = texture(src, uv).rg * 2 - 1;
|
||||
float z = sqrt(1 - xy.x * xy.x - xy.y * xy.y);
|
||||
return vec3(xy, z);
|
||||
}
|
||||
|
||||
float flip_topleft_to_native(float x) {
|
||||
#ifdef NATIVE_ORIGIN_BOTTOMLEFT
|
||||
return 1 - x;
|
||||
|
|
|
@ -7,6 +7,7 @@ UNIFORM(1) sampler2D alphamap;
|
|||
UNIFORM(2) int linearize;
|
||||
UNIFORM(3) int multiply_alpha;
|
||||
UNIFORM(4) int apply_alphamap;
|
||||
UNIFORM(5) int write_srgb;
|
||||
|
||||
/*
|
||||
* Adapted from https://github.com/tobspr/GLSL-Color-Spaces/blob/master/ColorSpaces.inc.glsl
|
||||
|
@ -22,6 +23,14 @@ float srgb_to_linear(float channel) {
|
|||
}
|
||||
}
|
||||
|
||||
float linear_to_srgb(float channel) {
|
||||
if(channel <= 0.0031308) {
|
||||
return 12.92 * channel;
|
||||
} else {
|
||||
return (1.0 + SRGB_ALPHA) * pow(channel, 1.0/2.4) - SRGB_ALPHA;
|
||||
}
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
fragColor = textureLod(tex, texCoordRaw, 0);
|
||||
|
||||
|
@ -40,4 +49,12 @@ void main(void) {
|
|||
if(apply_alphamap != 0) {
|
||||
fragColor.a *= textureLod(alphamap, texCoordRaw, 0).r;
|
||||
}
|
||||
|
||||
if(write_srgb != 0) {
|
||||
fragColor.rgb = vec3(
|
||||
linear_to_srgb(fragColor.r),
|
||||
linear_to_srgb(fragColor.g),
|
||||
linear_to_srgb(fragColor.b)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ if host_machine.system() == 'emscripten'
|
|||
em_bundle_gfx_patterns = [
|
||||
'gfx/*.png',
|
||||
'gfx/*.webp',
|
||||
'gfx/*.basis',
|
||||
'fonts/*.ttf',
|
||||
'fonts/*.otf',
|
||||
]
|
||||
|
@ -66,13 +67,15 @@ foreach pkg : packages
|
|||
assert(glob_result.returncode() == 0, 'Glob script failed')
|
||||
|
||||
foreach file : glob_result.stdout().strip().split('\n')
|
||||
fpath = join_paths(meson.current_source_dir(), pkg_pkgdir, file)
|
||||
if file != ''
|
||||
fpath = join_paths(meson.current_source_dir(), pkg_pkgdir, file)
|
||||
|
||||
set_variable(var_files, get_variable(var_files) + [fpath])
|
||||
set_variable(var_files, get_variable(var_files) + [fpath])
|
||||
|
||||
set_variable(var_packer_args, get_variable(var_packer_args) + [
|
||||
'--preload', '@0@@@1@/@2@'.format(fpath, em_data_prefix, file)
|
||||
])
|
||||
set_variable(var_packer_args, get_variable(var_packer_args) + [
|
||||
'--preload', '@0@@@1@/@2@'.format(fpath, em_data_prefix, file)
|
||||
])
|
||||
endif
|
||||
endforeach
|
||||
endforeach
|
||||
elif package_data
|
||||
|
|
429
scripts/mkbasis.py
Executable file
429
scripts/mkbasis.py
Executable file
|
@ -0,0 +1,429 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from taiseilib.common import (
|
||||
add_common_args,
|
||||
run_main,
|
||||
)
|
||||
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
BASISU_TAISEI_ID = 0x52656900
|
||||
BASISU_TAISEI_CHANNELS = ('r', 'rg', 'rgb', 'rgba', 'gray-alpha')
|
||||
BASISU_TAISEI_CHANNELS_MASK = 0x3
|
||||
BASISU_TAISEI_SRGB = (BASISU_TAISEI_CHANNELS_MASK + 1) << 0
|
||||
BASISU_TAISEI_NORMALMAP = (BASISU_TAISEI_CHANNELS_MASK + 1) << 1
|
||||
BASISU_TAISEI_GRAYALPHA = (BASISU_TAISEI_CHANNELS_MASK + 1) << 2
|
||||
|
||||
|
||||
def channels_have_alpha(chans):
|
||||
return chans in ('rgba', 'gray-alpha')
|
||||
|
||||
|
||||
def format_cmd(cmd):
|
||||
s = []
|
||||
|
||||
for a in [str(x) for x in cmd]:
|
||||
if repr(a) != f"'{a}'" or ' ' in a:
|
||||
a = repr(a)
|
||||
s.append(a)
|
||||
|
||||
return ' '.join(s)
|
||||
|
||||
|
||||
def run(args, cmd):
|
||||
print('RUN: ' + format_cmd(cmd))
|
||||
if not args.dry_run:
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
|
||||
def image_size(img_path):
|
||||
o = subprocess.check_output(['convert', img_path, '-print', '%wx%h', 'null:'])
|
||||
return tuple(int(d) for d in o.strip().decode('utf8').split('x'))
|
||||
|
||||
|
||||
def preprocess(args, tempdir):
|
||||
cmd = [
|
||||
'convert',
|
||||
'-verbose',
|
||||
args.input
|
||||
]
|
||||
|
||||
if args.channels == 'gray-alpha':
|
||||
cmd += [
|
||||
'-colorspace', 'gray'
|
||||
]
|
||||
|
||||
if channels_have_alpha(args.channels):
|
||||
if args.multiply_alpha:
|
||||
cmd += [
|
||||
'(',
|
||||
'+clone',
|
||||
'-background', 'black',
|
||||
'-alpha', 'remove',
|
||||
')',
|
||||
'+swap',
|
||||
'-compose', 'copy_opacity',
|
||||
'-composite',
|
||||
]
|
||||
|
||||
if args.alphamap:
|
||||
cmd += [
|
||||
'(',
|
||||
'+clone',
|
||||
'-channel', 'a',
|
||||
'-separate',
|
||||
args.alphamap_path,
|
||||
'-colorspace', 'gray',
|
||||
'-compose', 'multiply',
|
||||
'-composite',
|
||||
')',
|
||||
'-compose', 'copy_opacity',
|
||||
'-composite',
|
||||
]
|
||||
elif args.channels == 'r':
|
||||
cmd += [
|
||||
'-alpha', 'off',
|
||||
'-channel', 'r',
|
||||
'-separate',
|
||||
'-colorspace', 'gray'
|
||||
]
|
||||
|
||||
if cmd[-1] != args.input or args.input.suffix not in ('.png', '.jpg'):
|
||||
dst = tempdir / 'preprocessed.png'
|
||||
cmd += [dst]
|
||||
run(args, cmd)
|
||||
return dst
|
||||
|
||||
return args.input
|
||||
|
||||
|
||||
def process(args):
|
||||
width, height = image_size(args.input)
|
||||
|
||||
if width % 4 != 0 or height % 4 != 0:
|
||||
print(f'{args.input}: image dimensions are not multiples of 4 ({width}x{height})', file=sys.stderr)
|
||||
exit(1)
|
||||
|
||||
with TemporaryDirectory() as tempdir:
|
||||
tempdir = Path(tempdir)
|
||||
img = preprocess(args, tempdir)
|
||||
|
||||
cmd = [
|
||||
args.basisu,
|
||||
'-file', img,
|
||||
'-output_file', args.output,
|
||||
]
|
||||
|
||||
if args.channels == 'gray-alpha':
|
||||
taisei_flags = BASISU_TAISEI_CHANNELS.index('rg') | BASISU_TAISEI_GRAYALPHA
|
||||
else:
|
||||
taisei_flags = BASISU_TAISEI_CHANNELS.index(args.channels)
|
||||
assert taisei_flags == taisei_flags & BASISU_TAISEI_CHANNELS_MASK
|
||||
|
||||
if args.y_flip:
|
||||
cmd += ['-y_flip']
|
||||
|
||||
if args.normal:
|
||||
taisei_flags |= BASISU_TAISEI_NORMALMAP
|
||||
cmd += ['-normal_map']
|
||||
|
||||
if args.srgb_sampling:
|
||||
taisei_flags |= BASISU_TAISEI_SRGB
|
||||
|
||||
if not args.srgb:
|
||||
cmd += ['-linear']
|
||||
|
||||
if args.mipmaps:
|
||||
cmd += ['-mipmap']
|
||||
|
||||
if args.srgb:
|
||||
cmd += ['-mip_srgb']
|
||||
else:
|
||||
cmd += ['-mip_linear']
|
||||
|
||||
if args.normal:
|
||||
cmd += ['-mip_scale', '0.5']
|
||||
|
||||
if args.channels == 'rg':
|
||||
cmd += ['-separate_rg_to_color_alpha']
|
||||
|
||||
if not channels_have_alpha(args.channels):
|
||||
cmd += ['-no_alpha']
|
||||
|
||||
cmd += [
|
||||
'-userdata0', str(taisei_flags),
|
||||
'-userdata1', str(BASISU_TAISEI_ID)
|
||||
]
|
||||
|
||||
if args.uastc:
|
||||
cmd += ['-uastc']
|
||||
|
||||
profiles = {
|
||||
'slow': [
|
||||
'-uastc_level', '3',
|
||||
'-uastc_rdo_q', '4',
|
||||
],
|
||||
'fast': [
|
||||
'-uastc_level', '1',
|
||||
],
|
||||
'incredibly_slow': [
|
||||
'-uastc_level', '4',
|
||||
'-uastc_rdo_q', '4',
|
||||
],
|
||||
}
|
||||
else:
|
||||
profiles = {
|
||||
'slow': [
|
||||
'-comp_level', '4',
|
||||
'-q', '255'
|
||||
],
|
||||
'fast': [],
|
||||
'incredibly_slow': [
|
||||
'-comp_level', '5',
|
||||
'-max_endpoints', '16128',
|
||||
'-max_selectors', '16128',
|
||||
'-no_selector_rdo',
|
||||
'-no_endpoint_rdo',
|
||||
],
|
||||
}
|
||||
|
||||
cmd += profiles[args.profile]
|
||||
|
||||
"""
|
||||
if args.basisu_args is not None:
|
||||
cmd += args.basisu_args
|
||||
"""
|
||||
|
||||
run(args, cmd)
|
||||
|
||||
if args.uastc and not args.dry_run:
|
||||
print('\nNOTE: UASTC textures must be additionally compressed with a general-purpose lossless algorithm!')
|
||||
|
||||
|
||||
def main(args):
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Generate a Basis Universal compressed texture from image.',
|
||||
prog=args[0]
|
||||
)
|
||||
|
||||
def make_action(func):
|
||||
class ActionWrapper(argparse.Action):
|
||||
def __call__(self, parser, namespace, values, option_string):
|
||||
return func(parser, namespace, values, option_string)
|
||||
return ActionWrapper
|
||||
|
||||
def normal(parser, namespace, *a):
|
||||
namespace.channels = 'rg'
|
||||
namespace.normal = True
|
||||
namespace.srgb = False
|
||||
|
||||
image_suffixes = ('.webp', '.png')
|
||||
|
||||
parser.add_argument('input',
|
||||
help='the input image file',
|
||||
type=Path,
|
||||
)
|
||||
|
||||
parser.add_argument('-o', '--output',
|
||||
help='the output .basis file',
|
||||
type=Path,
|
||||
)
|
||||
|
||||
parser.add_argument('--basisu',
|
||||
help='Basis Universal encoder command (default: basisu)',
|
||||
metavar='COMMAND',
|
||||
default='basisu',
|
||||
)
|
||||
|
||||
parser.add_argument('--mipmaps',
|
||||
dest='mipmaps',
|
||||
help='generate mipmaps (default)',
|
||||
action='store_true',
|
||||
default=True,
|
||||
)
|
||||
|
||||
parser.add_argument('--no-mipmaps',
|
||||
dest='mipmaps',
|
||||
help="don't generate mipmaps",
|
||||
action='store_false',
|
||||
default=True,
|
||||
)
|
||||
|
||||
parser.add_argument('--srgb',
|
||||
dest='srgb',
|
||||
help='treat input as sRGB encoded (default)',
|
||||
action='store_true',
|
||||
default=True,
|
||||
)
|
||||
|
||||
parser.add_argument('--linear',
|
||||
dest='srgb',
|
||||
help='treat input as linear data, disable sRGB sampling',
|
||||
action='store_false',
|
||||
)
|
||||
|
||||
parser.add_argument('--srgb-sampling',
|
||||
dest='srgb_sampling',
|
||||
help='enable sRGB decoding when sampling texture; ignored unless --srgb is active (default)',
|
||||
action='store_true',
|
||||
default=True,
|
||||
)
|
||||
|
||||
parser.add_argument('--no-srgb-sampling',
|
||||
dest='srgb_sampling',
|
||||
help='disable sRGB decoding when sampling texture; ignored unless --srgb is active',
|
||||
action='store_false',
|
||||
)
|
||||
|
||||
parser.add_argument('--force-srgb-sampling',
|
||||
help="force sRGB decoding of linear data when sampling; ignored unless --linear is active (you probably don't want this!)",
|
||||
action='store_true',
|
||||
)
|
||||
|
||||
channels_default = 'rgb'
|
||||
|
||||
parser.add_argument('-c', '--channels',
|
||||
help=f'which input channels must be preserved (default: {channels_default})',
|
||||
default=channels_default,
|
||||
choices=BASISU_TAISEI_CHANNELS,
|
||||
)
|
||||
|
||||
for ch in BASISU_TAISEI_CHANNELS:
|
||||
parser.add_argument(f'--{ch}',
|
||||
dest='channels',
|
||||
help=f'alias for --channels={ch}{" (default)" if ch == channels_default else ""}',
|
||||
action='store_const',
|
||||
const=ch,
|
||||
)
|
||||
|
||||
parser.add_argument('--normal',
|
||||
help='treat texture as a tangent space normalmap; implies --linear --channels=rg',
|
||||
action=make_action(normal),
|
||||
nargs=0,
|
||||
)
|
||||
|
||||
parser.add_argument('--multiply-alpha',
|
||||
dest='multiply_alpha',
|
||||
help='premultiply color channels with alpha; ignored unless --channels=rgba (default)',
|
||||
action='store_true',
|
||||
default=True,
|
||||
)
|
||||
|
||||
parser.add_argument('--no-multiply-alpha',
|
||||
dest='multiply_alpha',
|
||||
help="don't premultiply color channels with alpha; ignored unless --channels=rgba",
|
||||
action='store_false',
|
||||
)
|
||||
|
||||
parser.add_argument('--alphamap',
|
||||
dest='alphamap',
|
||||
help='multiply alpha channel with a custom grayscale image; done after alpha-premultiplication; ignored unless --channels=rgba (default)',
|
||||
action='store_true',
|
||||
default=True,
|
||||
)
|
||||
|
||||
parser.add_argument('--no-alphamap',
|
||||
dest='alphamap',
|
||||
help="don't multiply alpha channel with a custom grayscale image; ignored unless --channels=rgba",
|
||||
action='store_false',
|
||||
)
|
||||
|
||||
parser.add_argument('--alphamap-path',
|
||||
help=f'path to alphamap image; defaults to {{input_basename}}.alphamap.{{{",".join(x[1:] for x in image_suffixes)}}} if exists',
|
||||
default=None,
|
||||
type=Path,
|
||||
)
|
||||
|
||||
parser.add_argument('--y-flip',
|
||||
dest='y_flip',
|
||||
help='flip the texture vertically (default)',
|
||||
action='store_true',
|
||||
default=True,
|
||||
)
|
||||
|
||||
parser.add_argument('--no-y-flip',
|
||||
dest='y_flip',
|
||||
help="don't flip the texture vertically",
|
||||
action='store_false',
|
||||
default=True,
|
||||
)
|
||||
|
||||
parser.add_argument('--slow',
|
||||
dest='profile',
|
||||
help='compress slowly, emphasize quality (default)',
|
||||
action='store_const',
|
||||
const='slow',
|
||||
default='slow',
|
||||
)
|
||||
|
||||
parser.add_argument('--fast',
|
||||
dest='profile',
|
||||
help='compress much faster, significantly lower quality',
|
||||
action='store_const',
|
||||
const='fast',
|
||||
)
|
||||
|
||||
parser.add_argument('--incredibly-slow',
|
||||
dest='profile',
|
||||
help='takes forever, maximum quality',
|
||||
action='store_const',
|
||||
const='incredibly_slow',
|
||||
)
|
||||
|
||||
parser.add_argument('--etc1s',
|
||||
dest='uastc',
|
||||
help='encode to ETC1S: small size, low/mediocre quality (default)',
|
||||
action='store_false',
|
||||
default=False,
|
||||
)
|
||||
|
||||
parser.add_argument('--uastc',
|
||||
dest='uastc',
|
||||
help='encode to UASTC: large size, high quality',
|
||||
action='store_true',
|
||||
)
|
||||
|
||||
parser.add_argument('--dry-run',
|
||||
help='do nothing, print commands that would have been run',
|
||||
action='store_true',
|
||||
)
|
||||
|
||||
# FIXME this doesn't work properly (goddamn argparse)
|
||||
"""
|
||||
parser.add_argument('--basisu-args',
|
||||
help='pass remaining args through to the encoder',
|
||||
nargs='*',
|
||||
)
|
||||
"""
|
||||
|
||||
args = parser.parse_args(args[1:])
|
||||
|
||||
if channels_have_alpha(args.channels) and args.alphamap and args.alphamap_path is None:
|
||||
try:
|
||||
for s in image_suffixes:
|
||||
p = args.input.with_suffix(f'.alphamap{s}')
|
||||
if p.is_file():
|
||||
args.alphamap_path = p
|
||||
break
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
args.alphamap = args.alphamap_path is not None
|
||||
|
||||
if args.output is None:
|
||||
args.output = args.input.with_suffix('.basis')
|
||||
|
||||
if not args.srgb:
|
||||
args.srgb_sampling = args.force_srgb_sampling
|
||||
|
||||
# print(args)
|
||||
process(args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run_main(main)
|
|
@ -121,7 +121,7 @@ void log_set_gui_error_appendix(const char *message);
|
|||
#undef UNREACHABLE
|
||||
#define UNREACHABLE log_fatal("This code should never be reached (%s:%i)", __FILE__, __LINE__)
|
||||
#else
|
||||
#define log_debug(...)
|
||||
#define log_debug(...) ((void)0)
|
||||
#define LOG_NO_FILENAMES
|
||||
#endif
|
||||
|
||||
|
|
|
@ -123,6 +123,7 @@ subdir('audio')
|
|||
subdir('dialog')
|
||||
subdir('eventloop')
|
||||
subdir('menu')
|
||||
subdir('pixmap')
|
||||
subdir('plrmodes')
|
||||
subdir('renderer')
|
||||
subdir('resource')
|
||||
|
@ -138,6 +139,7 @@ taisei_src += [
|
|||
dialog_src,
|
||||
eventloop_src,
|
||||
menu_src,
|
||||
pixmap_src,
|
||||
plrmodes_src,
|
||||
renderer_src,
|
||||
resource_src,
|
||||
|
@ -167,21 +169,22 @@ if host_machine.system() == 'emscripten'
|
|||
em_link_args = [
|
||||
'-O@0@'.format(get_option('optimization')),
|
||||
'-s', 'ALLOW_MEMORY_GROWTH=1',
|
||||
'-s', 'DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=["$autoResumeAudioContext"]',
|
||||
'-s', 'ENVIRONMENT=web',
|
||||
'-s', 'EXIT_RUNTIME=0',
|
||||
'-s', 'EXPORTED_FUNCTIONS=["_main", "_vfs_sync_callback"]',
|
||||
'-s', 'EXTRA_EXPORTED_RUNTIME_METHODS=["ccall"]',
|
||||
'-s', 'DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=["$autoResumeAudioContext"]',
|
||||
'-s', 'FILESYSTEM=1',
|
||||
'-s', 'FORCE_FILESYSTEM=1',
|
||||
'-s', 'GL_POOL_TEMP_BUFFERS=0',
|
||||
'-s', 'GL_PREINITIALIZED_CONTEXT=1',
|
||||
'-s', 'GL_SUPPORT_AUTOMATIC_ENABLE_EXTENSIONS=0',
|
||||
'-s', 'INITIAL_MEMORY=268435456',
|
||||
'-s', 'LZ4=1',
|
||||
'-s', 'MAX_WEBGL_VERSION=2',
|
||||
'-s', 'MIN_WEBGL_VERSION=2',
|
||||
'-s', 'MODULARIZE=0',
|
||||
'-s', 'STRICT=1',
|
||||
'-s', 'INITIAL_MEMORY=268435456',
|
||||
'-s', 'WASM=1',
|
||||
'-lGL',
|
||||
'-legl.js',
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
|
||||
#include "taisei.h"
|
||||
|
||||
#include "util.h"
|
||||
#include "loaders.h"
|
||||
#include "../pngcruft.h"
|
||||
#include "util.h"
|
||||
#include "util/pngcruft.h"
|
||||
|
||||
static uchar png_magic[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
|
||||
|
||||
|
@ -112,12 +112,12 @@ static bool px_png_load(SDL_RWops *stream, Pixmap *pixmap, PixmapFormat preferre
|
|||
|
||||
size_t pixel_size = PIXMAP_FORMAT_PIXEL_SIZE(pixmap->format);
|
||||
|
||||
if(pixmap->height > PNG_SIZE_MAX/(pixmap->width * pixel_size)) {
|
||||
if(pixmap->height > PIXMAP_BUFFER_MAX_SIZE / (pixmap->width * pixel_size)) {
|
||||
error = "The image is too large";
|
||||
goto done;
|
||||
}
|
||||
|
||||
png_bytep buffer = pixmap->data.untyped = pixmap_alloc_buffer_for_copy(pixmap);
|
||||
png_bytep buffer = pixmap->data.untyped = pixmap_alloc_buffer_for_copy(pixmap, &pixmap->data_size);
|
||||
|
||||
for(int pass = 0; pass < num_passes; ++pass) {
|
||||
for(int row = pixmap->height - 1; row >= 0; --row) {
|
|
@ -98,18 +98,18 @@ static bool px_webp_load(SDL_RWops *stream, Pixmap *pixmap, PixmapFormat preferr
|
|||
|
||||
size_t pixel_size = PIXMAP_FORMAT_PIXEL_SIZE(pixmap->format);
|
||||
|
||||
if(pixmap->height > ((size_t)-1)/(pixmap->width * pixel_size)) {
|
||||
if(pixmap->height > PIXMAP_BUFFER_MAX_SIZE / (pixmap->width * pixel_size)) {
|
||||
WebPIDelete(idec);
|
||||
log_error("The image is too large");
|
||||
return false;
|
||||
}
|
||||
|
||||
pixmap->data.untyped = pixmap_alloc_buffer_for_copy(pixmap);
|
||||
pixmap->data.untyped = pixmap_alloc_buffer_for_copy(pixmap, &pixmap->data_size);
|
||||
|
||||
config.output.is_external_memory = true;
|
||||
config.output.colorspace = features.has_alpha ? MODE_RGBA : MODE_RGB;
|
||||
config.output.u.RGBA.rgba = pixmap->data.untyped;
|
||||
config.output.u.RGBA.size = pixmap_data_size(pixmap);
|
||||
config.output.u.RGBA.size = pixmap->data_size;
|
||||
config.output.u.RGBA.stride = pixel_size * pixmap->width;
|
||||
|
||||
do {
|
|
@ -6,8 +6,8 @@
|
|||
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
|
||||
*/
|
||||
|
||||
#ifndef IGUARD_util_pixmap_loaders_loaders_h
|
||||
#define IGUARD_util_pixmap_loaders_loaders_h
|
||||
#ifndef IGUARD_pixmap_loaders_loaders_h
|
||||
#define IGUARD_pixmap_loaders_loaders_h
|
||||
|
||||
#include "taisei.h"
|
||||
|
||||
|
@ -24,4 +24,4 @@ typedef struct PixmapLoader {
|
|||
extern PixmapLoader pixmap_loader_png;
|
||||
extern PixmapLoader pixmap_loader_webp;
|
||||
|
||||
#endif // IGUARD_util_pixmap_loaders_loaders_h
|
||||
#endif // IGUARD_pixmap_loaders_loaders_h
|
8
src/pixmap/meson.build
Normal file
8
src/pixmap/meson.build
Normal file
|
@ -0,0 +1,8 @@
|
|||
|
||||
pixmap_src = files(
|
||||
'pixmap.c',
|
||||
'serialize.c',
|
||||
)
|
||||
|
||||
subdir('loaders')
|
||||
pixmap_src += pixmap_loaders_src
|
|
@ -9,8 +9,8 @@
|
|||
#include "taisei.h"
|
||||
|
||||
#include "pixmap.h"
|
||||
#include "loaders/loaders.h"
|
||||
#include "util.h"
|
||||
#include "pixmap_loaders/loaders.h"
|
||||
|
||||
// NOTE: this is pretty stupid and not at all optimized, patches welcome
|
||||
|
||||
|
@ -134,7 +134,7 @@ typedef void (*convfunc_t)(
|
|||
size_t num_pixels,
|
||||
void *vbuf_in,
|
||||
void *vbuf_out,
|
||||
void *default_pixel
|
||||
int swizzle[4]
|
||||
);
|
||||
|
||||
#define CONV(in, in_depth, out, out_depth) { convert_##in##_to_##out, in_depth, out_depth }
|
||||
|
@ -179,35 +179,32 @@ static struct conversion_def* find_conversion(uint depth_in, uint depth_out) {
|
|||
log_fatal("Pixmap conversion for %upbc -> %upbc undefined, please add", depth_in, depth_out);
|
||||
}
|
||||
|
||||
static void *default_pixel(uint depth) {
|
||||
static uint8_t default_u8[] = { 0, 0, 0, UINT8_MAX };
|
||||
static uint16_t default_u16[] = { 0, 0, 0, UINT16_MAX };
|
||||
static uint32_t default_u32[] = { 0, 0, 0, UINT32_MAX };
|
||||
static float default_f32[] = { 0, 0, 0, 1.0f };
|
||||
|
||||
switch(depth) {
|
||||
case 8: return default_u8;
|
||||
case 16: return default_u16;
|
||||
case 32: return default_u32;
|
||||
case 32 | DEPTH_FLOAT_BIT: return default_f32;
|
||||
default: UNREACHABLE;
|
||||
}
|
||||
}
|
||||
|
||||
void *pixmap_alloc_buffer(PixmapFormat format, size_t width, size_t height) {
|
||||
static uint32_t pixmap_data_size(PixmapFormat format, uint32_t width, uint32_t height) {
|
||||
assert(width >= 1);
|
||||
assert(height >= 1);
|
||||
size_t pixel_size = PIXMAP_FORMAT_PIXEL_SIZE(format);
|
||||
uint64_t pixel_size = PIXMAP_FORMAT_PIXEL_SIZE(format);
|
||||
assert(pixel_size >= 1);
|
||||
return calloc(width * height, pixel_size);
|
||||
uint64_t s = (uint64_t)width * (uint64_t)height * pixel_size;
|
||||
assert(s <= PIXMAP_BUFFER_MAX_SIZE);
|
||||
return s;
|
||||
}
|
||||
|
||||
void *pixmap_alloc_buffer_for_copy(const Pixmap *src) {
|
||||
return pixmap_alloc_buffer(src->format, src->width, src->height);
|
||||
void *pixmap_alloc_buffer(PixmapFormat format, uint32_t width, uint32_t height, uint32_t *out_bufsize) {
|
||||
uint32_t s = pixmap_data_size(format, width, height);
|
||||
|
||||
if(out_bufsize) {
|
||||
*out_bufsize = s;
|
||||
}
|
||||
|
||||
return calloc(1, s);
|
||||
}
|
||||
|
||||
void *pixmap_alloc_buffer_for_conversion(const Pixmap *src, PixmapFormat format) {
|
||||
return pixmap_alloc_buffer(format, src->width, src->height);
|
||||
void *pixmap_alloc_buffer_for_copy(const Pixmap *src, uint32_t *out_bufsize) {
|
||||
return pixmap_alloc_buffer(src->format, src->width, src->height, out_bufsize);
|
||||
}
|
||||
|
||||
void *pixmap_alloc_buffer_for_conversion(const Pixmap *src, PixmapFormat format, uint32_t *out_bufsize) {
|
||||
return pixmap_alloc_buffer(format, src->width, src->height, out_bufsize);
|
||||
}
|
||||
|
||||
static void pixmap_copy_meta(const Pixmap *src, Pixmap *dst) {
|
||||
|
@ -242,12 +239,50 @@ void pixmap_convert(const Pixmap *src, Pixmap *dst, PixmapFormat format) {
|
|||
num_pixels,
|
||||
src->data.untyped,
|
||||
dst->data.untyped,
|
||||
default_pixel(PIXMAP_FORMAT_DEPTH(dst->format) | (PIXMAP_FORMAT_IS_FLOAT(dst->format) * DEPTH_FLOAT_BIT))
|
||||
NULL
|
||||
);
|
||||
}
|
||||
|
||||
static int swizzle_idx(char s) {
|
||||
switch(s) {
|
||||
case 'r': return 0;
|
||||
case 'g': return 1;
|
||||
case 'b': return 2;
|
||||
case 'a': return 3;
|
||||
case '0': return 4;
|
||||
case '1': return 5;
|
||||
default: UNREACHABLE;
|
||||
}
|
||||
}
|
||||
|
||||
void pixmap_swizzle_inplace(Pixmap *px, SwizzleMask swizzle) {
|
||||
uint channels = pixmap_format_layout(px->format);
|
||||
swizzle = swizzle_canonize(swizzle);
|
||||
|
||||
if(!swizzle_is_significant(swizzle, channels)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint cvt_id = pixmap_format_depth(px->format) | (pixmap_format_is_float(px->format) * DEPTH_FLOAT_BIT);
|
||||
struct conversion_def *cv = find_conversion(cvt_id, cvt_id);
|
||||
|
||||
cv->func(
|
||||
channels,
|
||||
channels,
|
||||
px->width * px->height,
|
||||
px->data.untyped,
|
||||
px->data.untyped,
|
||||
(int[]) {
|
||||
swizzle_idx(swizzle.r),
|
||||
swizzle_idx(swizzle.g),
|
||||
swizzle_idx(swizzle.b),
|
||||
swizzle_idx(swizzle.a),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void pixmap_convert_alloc(const Pixmap *src, Pixmap *dst, PixmapFormat format) {
|
||||
dst->data.untyped = pixmap_alloc_buffer_for_conversion(src, format);
|
||||
dst->data.untyped = pixmap_alloc_buffer_for_conversion(src, format, &dst->data_size);
|
||||
pixmap_convert(src, dst, format);
|
||||
}
|
||||
|
||||
|
@ -268,19 +303,22 @@ void pixmap_convert_inplace_realloc(Pixmap *src, PixmapFormat format) {
|
|||
|
||||
void pixmap_copy(const Pixmap *src, Pixmap *dst) {
|
||||
assert(dst->data.untyped != NULL);
|
||||
assert(src->data_size > 0);
|
||||
assert(dst->data_size >= src->data_size);
|
||||
|
||||
if(!pixmap_format_is_compressed(src->format)) {
|
||||
assert(src->data_size == pixmap_data_size(src->format, src->width, src->height));
|
||||
}
|
||||
|
||||
pixmap_copy_meta(src, dst);
|
||||
memcpy(dst->data.untyped, src->data.untyped, pixmap_data_size(src));
|
||||
memcpy(dst->data.untyped, src->data.untyped, src->data_size);
|
||||
}
|
||||
|
||||
void pixmap_copy_alloc(const Pixmap *src, Pixmap *dst) {
|
||||
dst->data.untyped = pixmap_alloc_buffer_for_copy(src);
|
||||
dst->data.untyped = pixmap_alloc_buffer_for_copy(src, &dst->data_size);
|
||||
pixmap_copy(src, dst);
|
||||
}
|
||||
|
||||
size_t pixmap_data_size(const Pixmap *px) {
|
||||
return px->width * px->height * PIXMAP_FORMAT_PIXEL_SIZE(px->format);
|
||||
}
|
||||
|
||||
void pixmap_flip_y(const Pixmap *src, Pixmap *dst) {
|
||||
assert(dst->data.untyped != NULL);
|
||||
pixmap_copy_meta(src, dst);
|
||||
|
@ -297,7 +335,7 @@ void pixmap_flip_y(const Pixmap *src, Pixmap *dst) {
|
|||
}
|
||||
|
||||
void pixmap_flip_y_alloc(const Pixmap *src, Pixmap *dst) {
|
||||
dst->data.untyped = pixmap_alloc_buffer_for_copy(src);
|
||||
dst->data.untyped = pixmap_alloc_buffer_for_copy(src, &dst->data_size);
|
||||
pixmap_flip_y(src, dst);
|
||||
}
|
||||
|
||||
|
@ -326,7 +364,7 @@ void pixmap_flip_to_origin(const Pixmap *src, Pixmap *dst, PixmapOrigin origin)
|
|||
}
|
||||
|
||||
void pixmap_flip_to_origin_alloc(const Pixmap *src, Pixmap *dst, PixmapOrigin origin) {
|
||||
dst->data.untyped = pixmap_alloc_buffer_for_copy(src);
|
||||
dst->data.untyped = pixmap_alloc_buffer_for_copy(src, &dst->data_size);
|
||||
pixmap_flip_to_origin(src, dst, origin);
|
||||
}
|
||||
|
||||
|
@ -428,3 +466,75 @@ char *pixmap_source_path(const char *prefix, const char *path) {
|
|||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *pixmap_format_name(PixmapFormat fmt) {
|
||||
switch(fmt) {
|
||||
#define HANDLE(f) case f: return #f
|
||||
HANDLE(PIXMAP_FORMAT_R8);
|
||||
HANDLE(PIXMAP_FORMAT_R16);
|
||||
HANDLE(PIXMAP_FORMAT_R16F);
|
||||
HANDLE(PIXMAP_FORMAT_R32F);
|
||||
HANDLE(PIXMAP_FORMAT_RG8);
|
||||
HANDLE(PIXMAP_FORMAT_RG16);
|
||||
HANDLE(PIXMAP_FORMAT_RG16F);
|
||||
HANDLE(PIXMAP_FORMAT_RG32F);
|
||||
HANDLE(PIXMAP_FORMAT_RGB8);
|
||||
HANDLE(PIXMAP_FORMAT_RGB16);
|
||||
HANDLE(PIXMAP_FORMAT_RGB16F);
|
||||
HANDLE(PIXMAP_FORMAT_RGB32F);
|
||||
HANDLE(PIXMAP_FORMAT_RGBA8);
|
||||
HANDLE(PIXMAP_FORMAT_RGBA16);
|
||||
HANDLE(PIXMAP_FORMAT_RGBA16F);
|
||||
HANDLE(PIXMAP_FORMAT_RGBA32F);
|
||||
|
||||
#define HANDLE_COMPRESSED(comp, layout, ...) HANDLE(PIXMAP_FORMAT_##comp);
|
||||
PIXMAP_COMPRESSION_FORMATS(HANDLE_COMPRESSED,)
|
||||
#undef HANDLE_COMPRESSED
|
||||
#undef HANDLE
|
||||
|
||||
default: return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
SwizzleMask swizzle_canonize(SwizzleMask sw_in) {
|
||||
SwizzleMask sw_out;
|
||||
sw_out.r = sw_in.r ? sw_in.r : 'r';
|
||||
sw_out.g = sw_in.g ? sw_in.g : 'g';
|
||||
sw_out.b = sw_in.b ? sw_in.b : 'b';
|
||||
sw_out.a = sw_in.a ? sw_in.a : 'a';
|
||||
assert(swizzle_is_valid(sw_out));
|
||||
return sw_out;
|
||||
}
|
||||
|
||||
static bool swizzle_component_is_valid(char c) {
|
||||
switch(c) {
|
||||
case 'r': case 'g': case 'b': case 'a': case '0': case '1':
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool swizzle_is_valid(SwizzleMask sw) {
|
||||
for(int i = 0; i < 4; ++i) {
|
||||
if(!swizzle_component_is_valid(sw.rgba[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool swizzle_is_significant(SwizzleMask sw, uint num_significant_channels) {
|
||||
assert(num_significant_channels <= 4);
|
||||
|
||||
for(uint i = 0; i < num_significant_channels; ++i) {
|
||||
assert(swizzle_component_is_valid(sw.rgba[i]));
|
||||
if(sw.rgba[i] != "rgba"[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
288
src/pixmap/pixmap.h
Normal file
288
src/pixmap/pixmap.h
Normal file
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
* This software is licensed under the terms of the MIT License.
|
||||
* See COPYING for further information.
|
||||
* ---
|
||||
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
|
||||
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
|
||||
*/
|
||||
|
||||
#ifndef IGUARD_pixmap_pixmap_h
|
||||
#define IGUARD_pixmap_pixmap_h
|
||||
|
||||
#include "taisei.h"
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#define PIXMAP_BUFFER_MAX_SIZE INT32_MAX
|
||||
|
||||
typedef enum PixmapLayout {
|
||||
PIXMAP_LAYOUT_R = 1,
|
||||
PIXMAP_LAYOUT_RG,
|
||||
PIXMAP_LAYOUT_RGB,
|
||||
PIXMAP_LAYOUT_RGBA,
|
||||
} PixmapLayout;
|
||||
|
||||
typedef enum PixmapOrigin {
|
||||
PIXMAP_ORIGIN_TOPLEFT,
|
||||
PIXMAP_ORIGIN_BOTTOMLEFT,
|
||||
} PixmapOrigin;
|
||||
|
||||
#define PIXMAP_COMPRESSION_FORMATS(X, ...) \
|
||||
X(ETC1_RGB, RGB, __VA_ARGS__) \
|
||||
X(ETC2_RGBA, RGBA, __VA_ARGS__) \
|
||||
X(BC1_RGB, RGB, __VA_ARGS__) \
|
||||
X(BC3_RGBA, RGBA, __VA_ARGS__) \
|
||||
X(BC4_R, R, __VA_ARGS__) \
|
||||
X(BC5_RG, RG, __VA_ARGS__) \
|
||||
X(BC7_RGBA, RGBA, __VA_ARGS__) \
|
||||
X(PVRTC1_4_RGB, RGB, __VA_ARGS__) \
|
||||
X(PVRTC1_4_RGBA, RGBA, __VA_ARGS__) \
|
||||
X(ASTC_4x4_RGBA, RGBA, __VA_ARGS__) \
|
||||
X(ATC_RGB, RGB, __VA_ARGS__) \
|
||||
X(ATC_RGBA, RGBA, __VA_ARGS__) \
|
||||
X(PVRTC2_4_RGB, RGB, __VA_ARGS__) \
|
||||
X(PVRTC2_4_RGBA, RGBA, __VA_ARGS__) \
|
||||
X(ETC2_EAC_R11, R, __VA_ARGS__) \
|
||||
X(ETC2_EAC_RG11, RG, __VA_ARGS__) \
|
||||
X(FXT1_RGB, RGB, __VA_ARGS__) \
|
||||
|
||||
typedef enum PixmapCompression {
|
||||
#define DEFINE_PIXMAP_COMPRESSION(cformat, ...) \
|
||||
PIXMAP_COMPRESSION_##cformat,
|
||||
PIXMAP_COMPRESSION_FORMATS(DEFINE_PIXMAP_COMPRESSION,)
|
||||
#undef DEFINE_PIXMAP_COMPRESSION
|
||||
} PixmapCompression;
|
||||
|
||||
enum {
|
||||
PIXMAP_FLOAT_BIT = 0x80,
|
||||
PIXMAP_COMPRESSED_BIT = 0x8000,
|
||||
|
||||
PIXMAP_FLAG_BITS = PIXMAP_FLOAT_BIT | PIXMAP_COMPRESSED_BIT,
|
||||
};
|
||||
|
||||
#define PIXMAP_MAKE_FORMAT(layout, depth) \
|
||||
(((layout) << 8) | (depth))
|
||||
|
||||
#define PIXMAP_MAKE_FLOAT_FORMAT(layout, depth) \
|
||||
(PIXMAP_MAKE_FORMAT(layout, depth) | PIXMAP_FLOAT_BIT)
|
||||
|
||||
#define PIXMAP_MAKE_COMPRESSED_FORMAT(layout, compression) \
|
||||
(PIXMAP_MAKE_FORMAT(layout, compression) | PIXMAP_COMPRESSED_BIT)
|
||||
|
||||
#define _impl_pixmap_format_is_compressed(fmt) \
|
||||
((fmt) & PIXMAP_COMPRESSED_BIT)
|
||||
#define _impl_pixmap_format_is_float(fmt) \
|
||||
((fmt) & PIXMAP_FLOAT_BIT)
|
||||
#define _impl_pixmap_format_layout(fmt) \
|
||||
((fmt) & ~PIXMAP_FLAG_BITS) >> 8
|
||||
#define _impl_pixmap_format_depth(fmt) \
|
||||
((fmt) & ~PIXMAP_FLAG_BITS & 0xff)
|
||||
#define _impl_pixmap_format_compression(fmt) \
|
||||
((fmt) & ~PIXMAP_FLAG_BITS & 0xff)
|
||||
#define _impl_pixmap_format_pixel_size(fmt) \
|
||||
((_impl_pixmap_format_depth(fmt) >> 3) * _impl_pixmap_format_layout(fmt))
|
||||
|
||||
typedef enum PixmapFormat {
|
||||
PIXMAP_FORMAT_R8 = PIXMAP_MAKE_FORMAT(PIXMAP_LAYOUT_R, 8),
|
||||
PIXMAP_FORMAT_R16 = PIXMAP_MAKE_FORMAT(PIXMAP_LAYOUT_R, 16),
|
||||
PIXMAP_FORMAT_R32 = PIXMAP_MAKE_FORMAT(PIXMAP_LAYOUT_R, 32),
|
||||
|
||||
PIXMAP_FORMAT_R16F = PIXMAP_MAKE_FLOAT_FORMAT(PIXMAP_LAYOUT_R, 16),
|
||||
PIXMAP_FORMAT_R32F = PIXMAP_MAKE_FLOAT_FORMAT(PIXMAP_LAYOUT_R, 32),
|
||||
|
||||
PIXMAP_FORMAT_RG8 = PIXMAP_MAKE_FORMAT(PIXMAP_LAYOUT_RG, 8),
|
||||
PIXMAP_FORMAT_RG16 = PIXMAP_MAKE_FORMAT(PIXMAP_LAYOUT_RG, 16),
|
||||
PIXMAP_FORMAT_RG32 = PIXMAP_MAKE_FORMAT(PIXMAP_LAYOUT_RG, 32),
|
||||
|
||||
PIXMAP_FORMAT_RG16F = PIXMAP_MAKE_FLOAT_FORMAT(PIXMAP_LAYOUT_RG, 16),
|
||||
PIXMAP_FORMAT_RG32F = PIXMAP_MAKE_FLOAT_FORMAT(PIXMAP_LAYOUT_RG, 32),
|
||||
|
||||
PIXMAP_FORMAT_RGB8 = PIXMAP_MAKE_FORMAT(PIXMAP_LAYOUT_RGB, 8),
|
||||
PIXMAP_FORMAT_RGB16 = PIXMAP_MAKE_FORMAT(PIXMAP_LAYOUT_RGB, 16),
|
||||
PIXMAP_FORMAT_RGB32 = PIXMAP_MAKE_FORMAT(PIXMAP_LAYOUT_RGB, 32),
|
||||
|
||||
PIXMAP_FORMAT_RGB16F = PIXMAP_MAKE_FLOAT_FORMAT(PIXMAP_LAYOUT_RGB, 16),
|
||||
PIXMAP_FORMAT_RGB32F = PIXMAP_MAKE_FLOAT_FORMAT(PIXMAP_LAYOUT_RGB, 32),
|
||||
|
||||
PIXMAP_FORMAT_RGBA8 = PIXMAP_MAKE_FORMAT(PIXMAP_LAYOUT_RGBA, 8),
|
||||
PIXMAP_FORMAT_RGBA16 = PIXMAP_MAKE_FORMAT(PIXMAP_LAYOUT_RGBA, 16),
|
||||
PIXMAP_FORMAT_RGBA32 = PIXMAP_MAKE_FORMAT(PIXMAP_LAYOUT_RGBA, 32),
|
||||
|
||||
PIXMAP_FORMAT_RGBA16F = PIXMAP_MAKE_FLOAT_FORMAT(PIXMAP_LAYOUT_RGBA, 16),
|
||||
PIXMAP_FORMAT_RGBA32F = PIXMAP_MAKE_FLOAT_FORMAT(PIXMAP_LAYOUT_RGBA, 32),
|
||||
|
||||
#define DEFINE_PIXMAP_COMPRESSED_FORMAT(cformat, layout, ...) \
|
||||
PIXMAP_FORMAT_##cformat = PIXMAP_MAKE_COMPRESSED_FORMAT(PIXMAP_LAYOUT_##layout, PIXMAP_COMPRESSION_##cformat),
|
||||
PIXMAP_COMPRESSION_FORMATS(DEFINE_PIXMAP_COMPRESSED_FORMAT,)
|
||||
#undef DEFINE_PIXMAP_COMPRESSION
|
||||
} PixmapFormat;
|
||||
|
||||
// sanity check
|
||||
static_assert_nomsg(_impl_pixmap_format_layout(PIXMAP_FORMAT_ETC2_EAC_RG11) == PIXMAP_LAYOUT_RG);
|
||||
static_assert_nomsg(_impl_pixmap_format_compression(PIXMAP_FORMAT_ASTC_4x4_RGBA) == PIXMAP_COMPRESSION_ASTC_4x4_RGBA);
|
||||
static_assert_nomsg(_impl_pixmap_format_depth(PIXMAP_FORMAT_RGB16F) == 16);
|
||||
static_assert_nomsg(_impl_pixmap_format_depth(PIXMAP_FORMAT_RGB8) == 8);
|
||||
static_assert_nomsg(_impl_pixmap_format_layout(PIXMAP_FORMAT_RGB8) == PIXMAP_LAYOUT_RGB);
|
||||
|
||||
INLINE bool pixmap_format_is_compressed(PixmapFormat fmt) {
|
||||
return _impl_pixmap_format_is_compressed(fmt);
|
||||
}
|
||||
|
||||
INLINE bool pixmap_format_is_float(PixmapFormat fmt) {
|
||||
assert(!pixmap_format_is_compressed(fmt));
|
||||
return _impl_pixmap_format_is_float(fmt);
|
||||
}
|
||||
|
||||
INLINE PixmapLayout pixmap_format_layout(PixmapFormat fmt) {
|
||||
return _impl_pixmap_format_layout(fmt);
|
||||
}
|
||||
|
||||
INLINE uint pixmap_format_depth(PixmapFormat fmt) {
|
||||
assert(!pixmap_format_is_compressed(fmt));
|
||||
return _impl_pixmap_format_depth(fmt);
|
||||
}
|
||||
|
||||
INLINE PixmapCompression pixmap_format_compression(PixmapFormat fmt) {
|
||||
assert(pixmap_format_is_compressed(fmt));
|
||||
return _impl_pixmap_format_compression(fmt);
|
||||
}
|
||||
|
||||
INLINE uint pixmap_format_pixel_size(PixmapFormat fmt) {
|
||||
assert(!pixmap_format_is_compressed(fmt));
|
||||
return _impl_pixmap_format_pixel_size(fmt);
|
||||
}
|
||||
|
||||
// TODO deprecate?
|
||||
#define PIXMAP_FORMAT_LAYOUT(format) (pixmap_format_layout(format))
|
||||
#define PIXMAP_FORMAT_DEPTH(format) (pixmap_format_depth(format))
|
||||
#define PIXMAP_FORMAT_PIXEL_SIZE(format) (pixmap_format_pixel_size(format))
|
||||
#define PIXMAP_FORMAT_IS_FLOAT(format) (pixmap_format_is_float(format))
|
||||
#define PIXMAP_FORMAT_IS_COMPRESSED(format) (pixmap_format_is_compressed(format))
|
||||
|
||||
#define PIXMAP_PIXEL_STRUCT_R(type) union { \
|
||||
type values[1]; \
|
||||
struct { \
|
||||
type r; \
|
||||
}; \
|
||||
}
|
||||
|
||||
#define PIXMAP_PIXEL_STRUCT_RG(type) union { \
|
||||
type values[2]; \
|
||||
struct { \
|
||||
type r; \
|
||||
type g; \
|
||||
}; \
|
||||
}
|
||||
|
||||
#define PIXMAP_PIXEL_STRUCT_RGB(type) union { \
|
||||
type values[3]; \
|
||||
struct { \
|
||||
type r; \
|
||||
type g; \
|
||||
type b; \
|
||||
}; \
|
||||
}
|
||||
|
||||
#define PIXMAP_PIXEL_STRUCT_RGBA(type) union { \
|
||||
type values[4]; \
|
||||
struct { \
|
||||
type r; \
|
||||
type g; \
|
||||
type b; \
|
||||
type a; \
|
||||
}; \
|
||||
}
|
||||
|
||||
typedef PIXMAP_PIXEL_STRUCT_R(uint8_t) PixelR8;
|
||||
typedef PIXMAP_PIXEL_STRUCT_R(uint16_t) PixelR16;
|
||||
typedef PIXMAP_PIXEL_STRUCT_R(uint32_t) PixelR32;
|
||||
typedef PIXMAP_PIXEL_STRUCT_R(float) PixelR32F;
|
||||
|
||||
typedef PIXMAP_PIXEL_STRUCT_RG(uint8_t) PixelRG8;
|
||||
typedef PIXMAP_PIXEL_STRUCT_RG(uint16_t) PixelRG16;
|
||||
typedef PIXMAP_PIXEL_STRUCT_RG(uint32_t) PixelRG32;
|
||||
typedef PIXMAP_PIXEL_STRUCT_RG(float) PixelRG32F;
|
||||
|
||||
typedef PIXMAP_PIXEL_STRUCT_RGB(uint8_t) PixelRGB8;
|
||||
typedef PIXMAP_PIXEL_STRUCT_RGB(uint16_t) PixelRGB16;
|
||||
typedef PIXMAP_PIXEL_STRUCT_RGB(uint32_t) PixelRGB32;
|
||||
typedef PIXMAP_PIXEL_STRUCT_RGB(float) PixelRGB32F;
|
||||
|
||||
typedef PIXMAP_PIXEL_STRUCT_RGBA(uint8_t) PixelRGBA8;
|
||||
typedef PIXMAP_PIXEL_STRUCT_RGBA(uint16_t) PixelRGBA16;
|
||||
typedef PIXMAP_PIXEL_STRUCT_RGBA(uint32_t) PixelRGBA32;
|
||||
typedef PIXMAP_PIXEL_STRUCT_RGBA(float) PixelRGBA32F;
|
||||
|
||||
typedef struct Pixmap {
|
||||
union {
|
||||
void *untyped;
|
||||
|
||||
PixelR8 *r8;
|
||||
PixelR16 *r16;
|
||||
PixelR32 *r32;
|
||||
PixelR32F *r32f;
|
||||
|
||||
PixelRG8 *rg8;
|
||||
PixelRG16 *rg16;
|
||||
PixelRG32 *rg32;
|
||||
PixelRG32F *rg32f;
|
||||
|
||||
PixelRGB8 *rgb8;
|
||||
PixelRGB16 *rgb16;
|
||||
PixelRGB32 *rgb32;
|
||||
PixelRGB32F *rgb32f;
|
||||
|
||||
PixelRGBA8 *rgba8;
|
||||
PixelRGBA16 *rgba16;
|
||||
PixelRGBA32 *rgba32;
|
||||
PixelRGBA32F *rgba32f;
|
||||
} data;
|
||||
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t data_size;
|
||||
PixmapFormat format;
|
||||
PixmapOrigin origin;
|
||||
} Pixmap;
|
||||
|
||||
typedef union SwizzleMask {
|
||||
char rgba[4];
|
||||
struct {
|
||||
char r, g, b, a;
|
||||
};
|
||||
} SwizzleMask;
|
||||
|
||||
void *pixmap_alloc_buffer(PixmapFormat format, uint32_t width, uint32_t height, uint32_t *out_bufsize) attr_returns_allocated;
|
||||
void *pixmap_alloc_buffer_for_copy(const Pixmap *src, uint32_t *out_bufsize) attr_nonnull(1) attr_returns_allocated;
|
||||
void *pixmap_alloc_buffer_for_conversion(const Pixmap *src, PixmapFormat format, uint32_t *out_bufsize) attr_nonnull(1) attr_returns_allocated;
|
||||
|
||||
void pixmap_copy(const Pixmap *src, Pixmap *dst) attr_nonnull(1, 2);
|
||||
void pixmap_copy_alloc(const Pixmap *src, Pixmap *dst) attr_nonnull(1, 2);
|
||||
|
||||
void pixmap_convert(const Pixmap *src, Pixmap *dst, PixmapFormat format) attr_nonnull(1, 2);
|
||||
void pixmap_convert_alloc(const Pixmap *src, Pixmap *dst, PixmapFormat format) attr_nonnull(1, 2);
|
||||
void pixmap_convert_inplace_realloc(Pixmap *src, PixmapFormat format);
|
||||
|
||||
void pixmap_swizzle_inplace(Pixmap *px, SwizzleMask swizzle);
|
||||
|
||||
void pixmap_flip_y(const Pixmap *src, Pixmap *dst) attr_nonnull(1, 2);
|
||||
void pixmap_flip_y_alloc(const Pixmap *src, Pixmap *dst) attr_nonnull(1, 2);
|
||||
void pixmap_flip_y_inplace(Pixmap *src) attr_nonnull(1);
|
||||
|
||||
void pixmap_flip_to_origin(const Pixmap *src, Pixmap *dst, PixmapOrigin origin) attr_nonnull(1, 2);
|
||||
void pixmap_flip_to_origin_alloc(const Pixmap *src, Pixmap *dst, PixmapOrigin origin) attr_nonnull(1, 2);
|
||||
void pixmap_flip_to_origin_inplace(Pixmap *src, PixmapOrigin origin) attr_nonnull(1);
|
||||
|
||||
bool pixmap_load_file(const char *path, Pixmap *dst, PixmapFormat preferred_format) attr_nonnull(1, 2) attr_nodiscard;
|
||||
bool pixmap_load_stream(SDL_RWops *stream, Pixmap *dst, PixmapFormat preferred_format) attr_nonnull(1, 2) attr_nodiscard;
|
||||
|
||||
bool pixmap_check_filename(const char *path);
|
||||
char *pixmap_source_path(const char *prefix, const char *path) attr_nodiscard;
|
||||
|
||||
const char *pixmap_format_name(PixmapFormat fmt);
|
||||
|
||||
SwizzleMask swizzle_canonize(SwizzleMask sw_in);
|
||||
bool swizzle_is_valid(SwizzleMask sw);
|
||||
bool swizzle_is_significant(SwizzleMask sw, uint num_significant_channels);
|
||||