ADD: libktx
This commit is contained in:
@@ -112,3 +112,27 @@ add_custom_command(TARGET vulkan_engine POST_BUILD
|
|||||||
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_RUNTIME_DLLS:vulkan_engine> $<TARGET_FILE_DIR:vulkan_engine>
|
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_RUNTIME_DLLS:vulkan_engine> $<TARGET_FILE_DIR:vulkan_engine>
|
||||||
COMMAND_EXPAND_LISTS
|
COMMAND_EXPAND_LISTS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
option(ENABLE_LIBKTX "Enable KTX2 loading via libktx" ON)
|
||||||
|
if (ENABLE_LIBKTX)
|
||||||
|
find_package(ktx CONFIG QUIET)
|
||||||
|
if (NOT ktx_FOUND)
|
||||||
|
find_package(ktx QUIET)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(_KTX_TARGET "")
|
||||||
|
if (TARGET ktx::ktx)
|
||||||
|
set(_KTX_TARGET ktx::ktx)
|
||||||
|
elseif (TARGET KTX::ktx)
|
||||||
|
set(_KTX_TARGET KTX::ktx)
|
||||||
|
elseif (TARGET ktx)
|
||||||
|
set(_KTX_TARGET ktx)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (_KTX_TARGET STREQUAL "")
|
||||||
|
message(STATUS "libktx not found via find_package; looking for in-tree build...")
|
||||||
|
else()
|
||||||
|
target_link_libraries(vulkan_engine PUBLIC ${_KTX_TARGET})
|
||||||
|
target_compile_definitions(vulkan_engine PUBLIC VULKAN_ENGINE_HAS_KTX=1)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ except Exception:
|
|||||||
PIL_OK = False
|
PIL_OK = False
|
||||||
|
|
||||||
DEFAULT_SUFFIX = {
|
DEFAULT_SUFFIX = {
|
||||||
"albedo": ["_albedo", "_basecolor", "_base_colour", "_base_color", "_base"],
|
"albedo": ["_albedo", "_basecolor", "_base_colour", "_base_color", "_base", "baseColor"],
|
||||||
"mr": ["_mr", "_orm", "_metalrough", "_metallicroughness"],
|
"mr": ["_mr", "_orm", "_metalrough", "_metallicroughness", "metallicRoughness"],
|
||||||
"normal": ["_normal", "_norm", "_nrm", "_normalgl"]
|
"normal": ["_normal", "_norm", "_nrm", "_normalgl"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SUPPORTED_IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".tga", ".tif", ".tiff"}
|
||||||
|
|
||||||
def which_or_die(cmd):
|
def which_or_die(cmd):
|
||||||
from shutil import which
|
from shutil import which
|
||||||
if which(cmd) is None:
|
if which(cmd) is None:
|
||||||
@@ -78,24 +80,22 @@ def parse_gltf_roles(gltf_path: Path):
|
|||||||
|
|
||||||
return roles
|
return roles
|
||||||
|
|
||||||
def has_meaningful_alpha(png_path: Path) -> bool:
|
def has_meaningful_alpha(img_path: Path) -> bool:
|
||||||
if not PIL_OK:
|
if not PIL_OK:
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
with Image.open(png_path) as im:
|
with Image.open(img_path) as im:
|
||||||
if im.mode in ("RGBA", "LA") or ("transparency" in im.info):
|
if ("A" in im.getbands()) or ("transparency" in im.info):
|
||||||
im = im.convert("RGBA")
|
im = im.convert("RGBA")
|
||||||
alpha = im.getchannel("A")
|
alpha = im.getchannel("A")
|
||||||
extrema = alpha.getextrema()
|
extrema = alpha.getextrema()
|
||||||
if extrema and extrema != (255, 255):
|
return bool(extrema and extrema != (255, 255))
|
||||||
return True
|
return False
|
||||||
|
|
||||||
return False
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def decide_targets(role, albedo_target, png_path):
|
def decide_targets(role, albedo_target, img_path):
|
||||||
""" return transcode target(BCn), OETF(srgb/linear)"""
|
""" return transcode target(BCn), OETF(srgb/linear)"""
|
||||||
if role == "normal":
|
if role == "normal":
|
||||||
return "bc5", "linear"
|
return "bc5", "linear"
|
||||||
@@ -103,7 +103,7 @@ def decide_targets(role, albedo_target, png_path):
|
|||||||
return "bc7", "linear"
|
return "bc7", "linear"
|
||||||
# albedo
|
# albedo
|
||||||
if albedo_target == "auto":
|
if albedo_target == "auto":
|
||||||
if has_meaningful_alpha(png_path):
|
if has_meaningful_alpha(img_path):
|
||||||
return "bc3", "srgb"
|
return "bc3", "srgb"
|
||||||
else:
|
else:
|
||||||
return "bc1", "srgb"
|
return "bc1", "srgb"
|
||||||
@@ -124,15 +124,15 @@ def run_cmd(args_list, dry_run=False):
|
|||||||
print(f"[ERR] {cmd}\n -> exit {e.returncode}", file=sys.stderr)
|
print(f"[ERR] {cmd}\n -> exit {e.returncode}", file=sys.stderr)
|
||||||
return e.returncode
|
return e.returncode
|
||||||
|
|
||||||
def process_one(png_path: Path, out_dir: Path, role, opts):
|
def process_one(img_path: Path, out_dir: Path, role, opts):
|
||||||
stem = png_path.stem
|
stem = img_path.stem
|
||||||
out_dir.mkdir(parents=True, exist_ok=True)
|
out_dir.mkdir(parents=True, exist_ok=True)
|
||||||
tmp_dir = out_dir / ".intermediate"
|
tmp_dir = out_dir / ".intermediate"
|
||||||
tmp_dir.mkdir(parents=True, exist_ok=True)
|
tmp_dir.mkdir(parents=True, exist_ok=True)
|
||||||
tmp_ktx2 = tmp_dir / f"{stem}.uastc.ktx2"
|
tmp_ktx2 = tmp_dir / f"{stem}.uastc.ktx2"
|
||||||
|
|
||||||
# 1) PNG -> KTX2(UASTC)
|
# 1) PNG -> KTX2(UASTC)
|
||||||
target_bc, oetf = decide_targets(role, opts.albedo_target, png_path)
|
target_bc, oetf = decide_targets(role, opts.albedo_target, img_path)
|
||||||
toktx = [
|
toktx = [
|
||||||
"toktx",
|
"toktx",
|
||||||
"--t2",
|
"--t2",
|
||||||
@@ -147,7 +147,7 @@ def process_one(png_path: Path, out_dir: Path, role, opts):
|
|||||||
toktx += ["--lower_left_maps_to_s0t0"]
|
toktx += ["--lower_left_maps_to_s0t0"]
|
||||||
# albedo: srgb, else linear
|
# albedo: srgb, else linear
|
||||||
toktx += ["--assign_oetf", oetf]
|
toktx += ["--assign_oetf", oetf]
|
||||||
toktx += [str(tmp_ktx2), str(png_path)]
|
toktx += [str(tmp_ktx2), str(img_path)]
|
||||||
rc = run_cmd(toktx, dry_run=opts.dry_run)
|
rc = run_cmd(toktx, dry_run=opts.dry_run)
|
||||||
if rc != 0: return rc
|
if rc != 0: return rc
|
||||||
|
|
||||||
@@ -170,8 +170,8 @@ def process_one(png_path: Path, out_dir: Path, role, opts):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
p = argparse.ArgumentParser(description="PNG → KTX2(BCn) encoder (toktx + ktx transcode)")
|
p = argparse.ArgumentParser(description="Image → KTX2(BCn) encoder (toktx + ktx transcode)")
|
||||||
p.add_argument("-i", "--input", required=True, help="Input folder(recursive) or PNG file")
|
p.add_argument("-i", "--input", required=True, help="Input folder(recursive) or image file")
|
||||||
p.add_argument("-o", "--output", required=True, help="Output folder")
|
p.add_argument("-o", "--output", required=True, help="Output folder")
|
||||||
p.add_argument("--gltf", help=".gltf File path (optional). glTF first, suffix last")
|
p.add_argument("--gltf", help=".gltf File path (optional). glTF first, suffix last")
|
||||||
p.add_argument("--suffix-albedo", default=",".join(DEFAULT_SUFFIX["albedo"]),
|
p.add_argument("--suffix-albedo", default=",".join(DEFAULT_SUFFIX["albedo"]),
|
||||||
@@ -202,13 +202,14 @@ def main():
|
|||||||
gltf_roles = parse_gltf_roles(Path(opts.gltf))
|
gltf_roles = parse_gltf_roles(Path(opts.gltf))
|
||||||
|
|
||||||
in_path = Path(opts.input)
|
in_path = Path(opts.input)
|
||||||
png_files = []
|
img_files = []
|
||||||
if in_path.is_file() and in_path.suffix.lower() == ".png":
|
if in_path.is_file() and in_path.suffix.lower() in SUPPORTED_IMAGE_EXTS:
|
||||||
png_files = [in_path]
|
img_files = [in_path]
|
||||||
else:
|
else:
|
||||||
png_files = list(in_path.rglob("*.png"))
|
for ext in SUPPORTED_IMAGE_EXTS:
|
||||||
if not png_files:
|
img_files.extend(in_path.rglob(f"*{ext}"))
|
||||||
sys.exit("No input PNGs.")
|
if not img_files:
|
||||||
|
sys.exit("No input images (supported: .png .jpg .jpeg .tga .tif .tiff).")
|
||||||
|
|
||||||
out_dir = Path(opts.output)
|
out_dir = Path(opts.output)
|
||||||
|
|
||||||
@@ -224,16 +225,16 @@ def main():
|
|||||||
tasks = []
|
tasks = []
|
||||||
with ThreadPoolExecutor(max_workers=opts.jobs) as ex:
|
with ThreadPoolExecutor(max_workers=opts.jobs) as ex:
|
||||||
futs = {}
|
futs = {}
|
||||||
for png in png_files:
|
for img in img_files:
|
||||||
role = decide_role_for_path(png)
|
role = decide_role_for_path(img)
|
||||||
fut = ex.submit(process_one, png, out_dir, role, opts)
|
fut = ex.submit(process_one, img, out_dir, role, opts)
|
||||||
futs[fut] = (png, role)
|
futs[fut] = (img, role)
|
||||||
any_err = False
|
any_err = False
|
||||||
for fut in as_completed(futs):
|
for fut in as_completed(futs):
|
||||||
png, role = futs[fut]
|
img, role = futs[fut]
|
||||||
rc = fut.result()
|
rc = fut.result()
|
||||||
status = "OK" if rc == 0 else f"ERR({rc})"
|
status = "OK" if rc == 0 else f"ERR({rc})"
|
||||||
print(f"[{status}] {png.name} -> role={role}")
|
print(f"[{status}] {img.name} -> role={role}")
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
any_err = True
|
any_err = True
|
||||||
if any_err:
|
if any_err:
|
||||||
|
|||||||
Reference in New Issue
Block a user