basisu: add bindings for basis-universal (supercompressed textures) (#477)

This commit is contained in:
Ali Chraghi 2022-08-25 15:26:06 +04:30 committed by GitHub
parent 88558b7d52
commit 4a0802639c
Failed to generate hash of commit
13 changed files with 1075 additions and 0 deletions

3
.gitmodules vendored
View file

@ -28,6 +28,9 @@
[submodule "sysaudio/upstream"]
path = sysaudio/upstream
url = https://github.com/hexops/soundio
[submodule "basisu/upstream"]
path = basisu/upstream
url = https://github.com/hexops/basisu
[submodule "examples/image-blur/assets"]
path = examples/image-blur/assets
url = https://github.com/hexops/mach-example-assets

3
basisu/.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "upstream"]
path = upstream
url = https://github.com/hexops/basisu

124
basisu/build.zig Normal file
View file

@ -0,0 +1,124 @@
const std = @import("std");
const Builder = std.build.Builder;
const basisu_root = thisDir() ++ "/upstream/basisu";
pub const pkg = std.build.Pkg{
.name = "basisu",
.source = .{
.path = "src/main.zig",
},
};
pub fn build(b: *Builder) void {
const mode = b.standardReleaseOptions();
const target = b.standardTargetOptions(.{});
const test_step = b.step("test", "Run library tests");
test_step.dependOn(&testStep(b, mode, target).step);
}
pub fn testStep(b: *Builder, mode: std.builtin.Mode, target: std.zig.CrossTarget) *std.build.RunStep {
const main_tests = b.addTestExe("basisu-tests", comptime thisDir() ++ "/src/main.zig");
main_tests.setBuildMode(mode);
main_tests.setTarget(target);
main_tests.main_pkg_path = thisDir();
link(b, main_tests, .{
.encoder = true,
.transcoder = true,
});
main_tests.install();
return main_tests.run();
}
pub fn link(b: *Builder, step: *std.build.LibExeObjStep, options: Options) void {
if (options.encoder) {
step.linkLibrary(buildEncoder(b));
step.addCSourceFile(comptime thisDir() ++ "/src/encoder/wrapper.cpp", &.{});
step.addIncludeDir(basisu_root ++ "/encoder");
}
if (options.transcoder) {
step.linkLibrary(buildTranscoder(b));
step.addCSourceFile(comptime thisDir() ++ "/src/transcoder/wrapper.cpp", &.{});
step.addIncludeDir(basisu_root ++ "/transcoder");
}
}
pub fn buildEncoder(b: *Builder) *std.build.LibExeObjStep {
// TODO(build-system): https://github.com/hexops/mach/issues/229#issuecomment-1100958939
ensureDependencySubmodule(b.allocator, "upstream") catch unreachable;
const encoder = b.addStaticLibrary("basisu-encoder", null);
encoder.linkLibCpp();
encoder.addCSourceFiles(
encoder_sources,
&.{},
);
encoder.defineCMacro("BASISU_FORCE_DEVEL_MESSAGES", "0");
encoder.defineCMacro("BASISD_SUPPORT_KTX2_ZSTD", "0");
encoder.install();
return encoder;
}
pub fn buildTranscoder(b: *Builder) *std.build.LibExeObjStep {
// TODO(build-system): https://github.com/hexops/mach/issues/229#issuecomment-1100958939
ensureDependencySubmodule(b.allocator, "upstream") catch unreachable;
const transcoder = b.addStaticLibrary("basisu-transcoder", null);
transcoder.linkLibCpp();
transcoder.addCSourceFiles(
transcoder_sources,
&.{},
);
transcoder.defineCMacro("BASISU_FORCE_DEVEL_MESSAGES", "0");
transcoder.defineCMacro("BASISD_SUPPORT_KTX2_ZSTD", "0");
transcoder.install();
return transcoder;
}
pub const Options = struct {
encoder: bool,
transcoder: bool,
};
fn thisDir() []const u8 {
return std.fs.path.dirname(@src().file) orelse ".";
}
fn ensureDependencySubmodule(allocator: std.mem.Allocator, path: []const u8) !void {
if (std.process.getEnvVarOwned(allocator, "NO_ENSURE_SUBMODULES")) |no_ensure_submodules| {
if (std.mem.eql(u8, no_ensure_submodules, "true")) return;
} else |_| {}
var child = std.ChildProcess.init(&.{ "git", "submodule", "update", "--init", path }, allocator);
child.cwd = (comptime thisDir());
child.stderr = std.io.getStdErr();
child.stdout = std.io.getStdOut();
_ = try child.spawnAndWait();
}
const transcoder_sources = &[_][]const u8{
basisu_root ++ "/transcoder/basisu_transcoder.cpp",
};
const encoder_sources = &[_][]const u8{
basisu_root ++ "/encoder/basisu_backend.cpp",
basisu_root ++ "/encoder/basisu_basis_file.cpp",
basisu_root ++ "/encoder/basisu_bc7enc.cpp",
basisu_root ++ "/encoder/basisu_comp.cpp",
basisu_root ++ "/encoder/basisu_enc.cpp",
basisu_root ++ "/encoder/basisu_etc.cpp",
basisu_root ++ "/encoder/basisu_frontend.cpp",
basisu_root ++ "/encoder/basisu_gpu_texture.cpp",
basisu_root ++ "/encoder/basisu_kernels_sse.cpp",
basisu_root ++ "/encoder/basisu_opencl.cpp",
basisu_root ++ "/encoder/basisu_pvrtc1_4.cpp",
basisu_root ++ "/encoder/basisu_resample_filters.cpp",
basisu_root ++ "/encoder/basisu_resampler.cpp",
basisu_root ++ "/encoder/basisu_ssim.cpp",
basisu_root ++ "/encoder/basisu_uastc_enc.cpp",
basisu_root ++ "/encoder/jpgd.cpp",
basisu_root ++ "/encoder/pvpngreader.cpp",
};

259
basisu/src/encoder.zig Normal file
View file

@ -0,0 +1,259 @@
const std = @import("std");
const b = @import("encoder/binding.zig");
const BasisTextureFormat = @import("main.zig").BasisTextureFormat;
const testing = std.testing;
/// Must be called before encoding anything
pub fn init_encoder() void {
b.basisu_encoder_init();
}
pub const Compressor = struct {
pub const Error = error{
InitializationFailed,
ValidationFailed,
EncodingUASTCFailed,
CannotReadSourceImages,
FrontendFault,
FrontendExtractionFailed,
BackendFault,
CannotCreateBasisFile,
CannotWriteOutput,
UASTCRDOPostProcessFailed,
CannotCreateKTX2File,
};
handle: *b.Compressor,
pub fn init(params: CompressorParams) error{Unknown}!Compressor {
return Compressor{
.handle = if (b.compressor_init(params.handle)) |v| v else return error.Unknown,
};
}
pub fn deinit(self: Compressor) void {
b.compressor_deinit(self.handle);
}
pub fn process(self: Compressor) Error!void {
return switch (b.compressor_process(self.handle)) {
0 => {},
1 => error.InitializationFailed,
2 => error.CannotReadSourceImages,
3 => error.ValidationFailed,
4 => error.EncodingUASTCFailed,
5 => error.FrontendFault,
6 => error.FrontendExtractionFailed,
7 => error.BackendFault,
8 => error.CannotCreateBasisFile,
9 => error.UASTCRDOPostProcessFailed,
10 => error.CannotCreateKTX2File,
else => unreachable,
};
}
/// output will be freed with `Compressor.deinit`
pub fn output(self: Compressor) []const u8 {
return b.compressor_get_output(self.handle)[0..b.compressor_get_output_size(self.handle)];
}
pub fn outputBitsPerTexel(self: Compressor) f64 {
return b.compressor_get_output_bits_per_texel(self.handle);
}
pub fn anyImageHasAlpha(self: Compressor) bool {
return b.compressor_get_any_source_image_has_alpha(self.handle);
}
};
pub const CompressorParams = struct {
handle: *b.CompressorParams,
pub fn init(threads_count: u32) CompressorParams {
const h = CompressorParams{ .handle = b.compressor_params_init() };
h.setStatusOutput(false);
b.compressor_params_set_thread_count(h.handle, threads_count);
return h;
}
pub fn deinit(self: CompressorParams) void {
b.compressor_params_deinit(self.handle);
}
pub fn clear(self: CompressorParams) void {
b.compressor_params_clear(self.handle);
}
pub fn setStatusOutput(self: CompressorParams, enable_output: bool) void {
b.compressor_params_set_status_output(self.handle, enable_output);
}
/// `level` ranges from [1, 255]
pub fn setQualityLevel(self: CompressorParams, level: u8) void {
b.compressor_params_set_quality_level(self.handle, level);
}
pub fn getPackUASTCFlags(self: CompressorParams) PackUASTCFlags {
return PackUASTCFlags.from(b.compressor_params_get_pack_uastc_flags(self.handle));
}
pub fn setPackUASTCFlags(self: CompressorParams, flags: PackUASTCFlags) void {
b.compressor_params_set_pack_uastc_flags(self.handle, flags.cast());
}
pub fn setBasisFormat(self: CompressorParams, format: BasisTextureFormat) void {
b.compressor_params_set_uastc(self.handle, switch (format) {
.etc1s => false,
.uastc4x4 => true,
});
}
pub fn setColorSpace(self: CompressorParams, color_space: ColorSpace) void {
b.compressor_params_set_perceptual(self.handle, switch (color_space) {
.linear => false,
.srgb => true,
});
}
pub fn setMipColorSpace(self: CompressorParams, color_space: ColorSpace) void {
b.compressor_params_set_mip_srgb(self.handle, switch (color_space) {
.linear => false,
.srgb => true,
});
}
/// Disable selector RDO, for faster compression but larger files.
/// Enabled by default
pub fn setSelectorRDO(self: CompressorParams, enable: bool) void {
b.compressor_params_set_no_selector_rdo(self.handle, !enable);
}
/// Enabled by default
pub fn setEndpointRDO(self: CompressorParams, enable: bool) void {
b.compressor_params_set_no_endpoint_rdo(self.handle, !enable);
}
pub fn setRDO_UASTC(self: CompressorParams, enable: bool) void {
b.compressor_params_set_rdo_uastc(self.handle, enable);
}
pub fn setRDO_UASTCQualityScalar(self: CompressorParams, quality: f32) void {
b.compressor_params_set_rdo_uastc_quality_scalar(self.handle, quality);
}
pub fn setGenerateMipMaps(self: CompressorParams, enable: bool) void {
b.compressor_params_set_generate_mipmaps(self.handle, enable);
}
pub fn setMipSmallestDimension(self: CompressorParams, smallest_dimension: i32) void {
b.compressor_params_set_mip_smallest_dimension(self.handle, smallest_dimension);
}
/// Resizes sources list and creates a new Image in case index is out of bounds
pub fn getImageSource(self: CompressorParams, index: u32) Image {
return .{ .handle = b.compressor_params_get_or_create_source_image(self.handle, index) };
}
pub fn resizeImageSource(self: CompressorParams, new_size: u32) void {
b.compressor_params_resize_source_image_list(self.handle, new_size);
}
pub fn clearImageSource(self: CompressorParams) void {
b.compressor_params_clear_source_image_list(self.handle);
}
};
pub const Image = struct {
handle: *b.Image,
pub fn fill(self: Image, data: []const u8, w: u32, h: u32, channels: u3) void {
b.compressor_image_fill(self.handle, data.ptr, w, h, channels);
}
pub fn resize(self: Image, w: u32, h: u32, p: ?u32) void {
b.compressor_image_resize(self.handle, w, h, p orelse std.math.maxInt(u32));
}
pub fn width(self: Image) u32 {
return b.compressor_image_get_width(self.handle);
}
pub fn height(self: Image) u32 {
return b.compressor_image_get_height(self.handle);
}
pub fn pitch(self: Image) u32 {
return b.compressor_image_get_pitch(self.handle);
}
pub fn totalPixels(self: Image) u32 {
return b.compressor_image_get_total_pixels(self.handle);
}
pub fn blockWidth(self: Image, w: u32) u32 {
return (self.width() + (w - 1)) / w;
}
pub fn blockHeight(self: Image, h: u32) u32 {
return (self.height() + (h - 1)) / h;
}
pub fn totalBlocks(self: Image, w: u32, h: u32) u32 {
return self.blockWidth(w) * self.blockHeight(h);
}
};
pub const PackUASTCFlags = packed struct {
fastest: bool = false,
faster: bool = false,
default: bool = false,
slower: bool = false,
verySlow: bool = false,
mask: bool = false,
favor_uastc_error: bool = false,
favor_bc7_error: bool = false,
etc1_faster_hints: bool = false,
etc1_fastest_hints: bool = false,
etc1_disable_flip_and_individual: bool = false,
favor_simpler_modes: bool = false,
pub const Flag = enum(u32) {
fastest = 0,
faster = 1,
default = 2,
slower = 3,
verySlow = 4,
mask = 0xF,
favor_uastc_error = 8,
favor_bc7_error = 16,
etc1_faster_hints = 64,
etc1_fastest_hints = 128,
etc1_disable_flip_and_individual = 256,
favor_simpler_modes = 512,
};
pub fn from(bits: u32) PackUASTCFlags {
var value = PackUASTCFlags{};
inline for (comptime std.meta.fieldNames(Flag)) |field_name| {
if (bits & (@enumToInt(@field(Flag, field_name))) != 0) {
@field(value, field_name) = true;
}
}
return value;
}
pub fn cast(self: PackUASTCFlags) u32 {
var value: u32 = 0;
inline for (comptime std.meta.fieldNames(Flag)) |field_name| {
if (@field(self, field_name)) {
value |= @enumToInt(@field(Flag, field_name));
}
}
return value;
}
};
pub const ColorSpace = enum {
linear,
srgb,
};

View file

@ -0,0 +1,42 @@
pub const Compressor = opaque {};
pub const CompressorParams = opaque {};
pub const Image = opaque {};
pub extern fn basisu_encoder_init() void;
pub extern fn compressor_params_init() *CompressorParams;
pub extern fn compressor_params_deinit(*CompressorParams) void;
pub extern fn compressor_params_clear(*CompressorParams) void;
pub extern fn compressor_params_set_status_output(*CompressorParams, bool) void;
pub extern fn compressor_params_set_thread_count(*CompressorParams, u32) void;
pub extern fn compressor_params_set_quality_level(*CompressorParams, c_int) void;
pub extern fn compressor_params_get_pack_uastc_flags(*CompressorParams) u32;
pub extern fn compressor_params_set_pack_uastc_flags(*CompressorParams, u32) void;
pub extern fn compressor_params_set_uastc(*CompressorParams, bool) void;
pub extern fn compressor_params_set_perceptual(*CompressorParams, bool) void;
pub extern fn compressor_params_set_mip_srgb(*CompressorParams, bool) void;
pub extern fn compressor_params_set_no_selector_rdo(*CompressorParams, bool) void;
pub extern fn compressor_params_set_no_endpoint_rdo(*CompressorParams, bool) void;
pub extern fn compressor_params_set_rdo_uastc(*CompressorParams, bool) void;
pub extern fn compressor_params_set_rdo_uastc_quality_scalar(*CompressorParams, f32) void;
pub extern fn compressor_params_set_generate_mipmaps(*CompressorParams, bool) void;
pub extern fn compressor_params_set_mip_smallest_dimension(*CompressorParams, c_int) void;
pub extern fn compressor_params_get_or_create_source_image(*CompressorParams, u32) *Image;
pub extern fn compressor_params_resize_source_image_list(*CompressorParams, usize) void;
pub extern fn compressor_params_clear_source_image_list(*CompressorParams) void;
pub extern fn compressor_image_fill(*Image, [*]const u8, u32, u32, u32) void;
pub extern fn compressor_image_resize(*Image, u32, u32, u32) void;
pub extern fn compressor_image_get_width(*Image) u32;
pub extern fn compressor_image_get_height(*Image) u32;
pub extern fn compressor_image_get_pitch(*Image) u32;
pub extern fn compressor_image_get_total_pixels(*Image) u32;
pub extern fn compressor_init(*CompressorParams) ?*Compressor;
pub extern fn compressor_deinit(*Compressor) void;
pub extern fn compressor_process(*Compressor) u32;
pub extern fn compressor_get_output(*Compressor) [*]const u8;
pub extern fn compressor_get_output_size(*Compressor) u32;
pub extern fn compressor_get_output_bits_per_texel(*Compressor) f64;
pub extern fn compressor_get_any_source_image_has_alpha(*Compressor) bool;

View file

@ -0,0 +1,176 @@
#include <basisu_comp.h>
#include <basisu_enc.h>
#include <cstdbool>
#include <cstdint>
extern "C" {
void basisu_encoder_init() { basisu::basisu_encoder_init(); }
basisu::basis_compressor_params *compressor_params_init() {
return new basisu::basis_compressor_params();
};
void compressor_params_deinit(basisu::basis_compressor_params *params) {
delete params;
}
void compressor_params_clear(basisu::basis_compressor_params *params) {
params->clear();
}
void compressor_params_set_status_output(
basisu::basis_compressor_params *params, bool status_output) {
params->m_status_output = status_output;
}
void compressor_params_set_thread_count(basisu::basis_compressor_params *params,
uint32_t thread_count) {
params->m_pJob_pool = new basisu::job_pool(thread_count);
}
void compressor_params_set_quality_level(
basisu::basis_compressor_params *params, uint8_t quality_level) {
params->m_quality_level = quality_level;
}
uint32_t compressor_params_get_pack_uastc_flags(
basisu::basis_compressor_params *params) {
return params->m_pack_uastc_flags;
}
void compressor_params_set_pack_uastc_flags(
basisu::basis_compressor_params *params, uint32_t pack_uastc_flags) {
params->m_pack_uastc_flags = pack_uastc_flags;
}
void compressor_params_set_uastc(basisu::basis_compressor_params *params,
bool is_uastc) {
params->m_uastc = is_uastc;
}
void compressor_params_set_perceptual(basisu::basis_compressor_params *params,
bool perceptual) {
params->m_perceptual = perceptual;
}
void compressor_params_set_mip_srgb(basisu::basis_compressor_params *params,
bool mip_srgb) {
params->m_mip_srgb = mip_srgb;
}
void compressor_params_set_no_selector_rdo(
basisu::basis_compressor_params *params, bool no_selector_rdo) {
params->m_no_selector_rdo = no_selector_rdo;
}
void compressor_params_set_no_endpoint_rdo(
basisu::basis_compressor_params *params, bool no_endpoint_rdo) {
params->m_no_endpoint_rdo = no_endpoint_rdo;
}
void compressor_params_set_rdo_uastc(basisu::basis_compressor_params *params,
bool rdo_uastc) {
params->m_rdo_uastc = rdo_uastc;
}
void compressor_params_set_generate_mipmaps(
basisu::basis_compressor_params *params, bool generate_mipmaps) {
params->m_mip_gen = generate_mipmaps;
}
void compressor_params_set_rdo_uastc_quality_scalar(
basisu::basis_compressor_params *params, float rdo_uastc_quality_scalar) {
params->m_rdo_uastc_quality_scalar = rdo_uastc_quality_scalar;
}
void compressor_params_set_mip_smallest_dimension(
basisu::basis_compressor_params *params, int mip_smallest_dimension) {
params->m_mip_smallest_dimension = mip_smallest_dimension;
}
basisu::image *compressor_params_get_or_create_source_image(
basisu::basis_compressor_params *params, uint32_t index) {
if (params->m_source_images.size() < index + 1) {
params->m_source_images.resize(index + 1);
}
return &params->m_source_images[index];
}
void compressor_params_resize_source_image_list(
basisu::basis_compressor_params *params, size_t size) {
params->m_source_images.resize(size);
}
void compressor_params_clear_source_image_list(
basisu::basis_compressor_params *params) {
params->clear();
}
///
void compressor_image_fill(basisu::image *image, const uint8_t *pData,
uint32_t width, uint32_t height, uint32_t comps) {
image->init(pData, width, height, comps);
}
void compressor_image_resize(basisu::image *image, uint32_t w, uint32_t h,
uint32_t p) {
image->resize(w, h, p);
}
uint32_t compressor_image_get_width(basisu::image *image) {
return image->get_width();
}
uint32_t compressor_image_get_height(basisu::image *image) {
return image->get_height();
}
uint32_t compressor_image_get_pitch(basisu::image *image) {
return image->get_pitch();
}
uint32_t compressor_image_get_total_pixels(basisu::image *image) {
return image->get_total_pixels();
}
///
basisu::basis_compressor *
compressor_init(basisu::basis_compressor_params *params) {
auto comp = new basisu::basis_compressor();
if (comp->init(*params))
return comp;
else
return nullptr;
}
void compressor_deinit(basisu::basis_compressor *compressor) {
delete compressor;
}
basisu::basis_compressor::error_code
compressor_process(basisu::basis_compressor *compressor) {
return compressor->process();
}
const uint8_t *compressor_get_output(basisu::basis_compressor *compressor) {
return compressor->get_output_basis_file().data();
}
uint32_t
compressor_get_output_size(const basisu::basis_compressor *compressor) {
return compressor->get_basis_file_size();
}
double compressor_get_output_bits_per_texel(
const basisu::basis_compressor *compressor) {
return compressor->get_basis_bits_per_texel();
}
bool compressor_get_any_source_image_has_alpha(
const basisu::basis_compressor *compressor) {
return compressor->get_any_source_image_has_alpha();
}
}

45
basisu/src/main.zig Normal file
View file

@ -0,0 +1,45 @@
pub usingnamespace @import("encoder.zig");
pub usingnamespace @import("transcoder.zig");
pub const BasisTextureFormat = enum(u1) {
etc1s = 0,
uastc4x4 = 1,
};
// Test
const t = @import("transcoder.zig");
const e = @import("encoder.zig");
const testing = @import("std").testing;
test "reference decls" {
testing.refAllDeclsRecursive(t);
testing.refAllDeclsRecursive(e);
}
test "encode/transcode" {
// Encode
e.init_encoder();
const params = e.CompressorParams.init(1);
params.setGenerateMipMaps(true);
params.setBasisFormat(.uastc4x4);
params.setPackUASTCFlags(.{ .fastest = true });
defer params.deinit();
const image = params.getImageSource(0);
image.fill(@embedFile("../test/ziggy.png"), 379, 316, 4);
const comp = try e.Compressor.init(params);
try comp.process();
// Transcode
t.init_transcoder();
const trans = try t.Transcoder.init(comp.output());
defer trans.deinit();
var out_buf = try testing.allocator.alloc(u8, try trans.calcTranscodedSize(0, 0, .astc_4x4_rgba));
defer testing.allocator.free(out_buf);
try trans.transcode(out_buf, 0, 0, .astc_4x4_rgba, .{});
}

229
basisu/src/transcoder.zig Normal file
View file

@ -0,0 +1,229 @@
const std = @import("std");
const b = @import("transcoder/binding.zig");
const BasisTextureFormat = @import("main.zig").BasisTextureFormat;
const testing = std.testing;
/// Must be called before a `.basis` file can be transcoded.
/// NOTE: this function *isn't* thread safe.
pub fn init_transcoder() void {
b.basisu_transcoder_init();
}
/// Returns true if the specified format was enabled at compile time.
pub fn isFormatEnabled(self: BasisTextureFormat, transcoder_format: Transcoder.TextureFormat) bool {
return b.transcoder_is_format_supported(@enumToInt(self), @enumToInt(transcoder_format));
}
pub const Transcoder = struct {
handle: *b.BasisFile,
pub fn init(src: []const u8) error{Unknown}!Transcoder {
const h = b.transcoder_init(src.ptr, @intCast(u32, src.len));
return if (!b.transcoder_start_transcoding(h))
error.Unknown
else .{ .handle = h };
}
pub fn deinit(self: Transcoder) void {
if (!b.transcoder_stop_transcoding(self.handle))
unreachable;
b.transcoder_deinit(self.handle);
}
/// Returns the total number of images in the basis file (always 1 or more).
/// Note that the number of mipmap levels for each image may differ, and that images may have different resolutions.
pub fn getImageCount(self: Transcoder) u32 {
return b.transcoder_get_images_count(self.handle);
}
/// Returns the number of mipmap levels in an image.
pub fn getImageLevelCount(self: Transcoder, image_index: u32) u32 {
return b.transcoder_get_levels_count(self.handle, image_index);
}
/// Returns basic information about an image.
/// Note that orig_width/orig_height may not be a multiple of 4.
pub fn getImageLevelDescriptor(self: Transcoder, image_index: u32, level_index: u32) error{OutOfBoundsLevelIndex}!ImageLevelDescriptor {
var desc: ImageLevelDescriptor = undefined;
return if (b.transcoder_get_image_level_desc(
self.handle,
image_index,
level_index,
&desc.original_width,
&desc.original_height,
&desc.block_count,
))
desc
else
error.OutOfBoundsLevelIndex;
}
/// Returns the bytes neeeded to store output.
pub fn calcTranscodedSize(self: Transcoder, image_index: u32, level_index: u32, format: TextureFormat) error{OutOfBoundsLevelIndex}!u32 {
var size: u32 = undefined;
return if (b.transcoder_get_image_transcoded_size(self.handle, image_index, level_index, @enumToInt(format), &size))
size
else
error.OutOfBoundsLevelIndex;
}
pub const TranscodeParams = struct {
decode_flags: ?DecodeFlags = null,
/// Output row pitch in blocks or pixels.
/// Should be at least the image level's total_blocks (num_blocks_x * num_blocks_y),
/// or the total number of output pixels if fmt==cTFRGBA32.
output_row_pitch: ?u32 = null,
/// Output rows in pixels
/// Ignored unless fmt is uncompressed (cRGBA32, etc.).
/// The total number of output rows in the output buffer. If 0,
/// the transcoder assumes the slice's orig_height (NOT num_blocks_y * 4).
output_rows: ?u32 = null,
};
/// Decodes a single mipmap level from the .basis file to any of the supported output texture formats.
/// Currently, to decode to PVRTC1 the basis texture's dimensions in pixels must be a power of 2,
/// due to PVRTC1 format requirements.
/// NOTE:
/// - `transcoder_init()` must have been called first to initialize
/// the transcoder lookup tables before calling this function.
/// - This method assumes the output texture buffer is readable.
/// In some cases to handle alpha, the transcoder will write temporary data
/// to the output texture in a first pass, which will be read in a second pass.
pub fn transcode(
self: Transcoder,
out_buf: []u8,
image_index: u32,
level_index: u32,
format: TextureFormat,
params: TranscodeParams,
) error{Unknown}!void {
if (!b.transcoder_transcode(
self.handle,
out_buf.ptr,
@intCast(u32, out_buf.len),
image_index,
level_index,
@enumToInt(format),
if (params.decode_flags) |f| f.cast() else 0,
params.output_row_pitch orelse 0,
params.output_rows orelse 0,
)) return error.Unknown;
}
pub const ImageLevelDescriptor = struct {
original_width: u32,
original_height: u32,
block_count: u32,
};
pub const DecodeFlags = packed struct {
pvrtc_decode_to_next_pow_2: bool = false,
transcode_alpha_data_to_opaque_formats: bool = false,
bc1_forbid_three_color_blocks: bool = false,
output_has_alpha_indices: bool = false,
high_quality: bool = false,
pub const Flag = enum(u32) {
pvrtc_decode_to_next_pow_2 = 2,
transcode_alpha_data_to_opaque_formats = 4,
bc1_forbid_three_color_blocks = 8,
output_has_alpha_indices = 16,
high_quality = 32,
};
pub fn from(bits: u32) DecodeFlags {
var value = DecodeFlags{};
inline for (comptime std.meta.fieldNames(Flag)) |field_name| {
if (bits & (@enumToInt(@field(Flag, field_name))) != 0) {
@field(value, field_name) = true;
}
}
return value;
}
pub fn cast(self: DecodeFlags) u32 {
var value: u32 = 0;
inline for (comptime std.meta.fieldNames(Flag)) |field_name| {
if (@field(self, field_name)) {
value |= @enumToInt(@field(Flag, field_name));
}
}
return value;
}
};
pub const TextureFormat = enum(u5) {
etc1_rgb = 0,
etc2_rgba = 1,
bc1_rgb = 2,
bc3_rgba = 3,
bc4_r = 4,
bc5_rg = 5,
bc7_rgba = 6,
bc7_alt = 7,
pvrtc1_4_rgb = 8,
pvrtc1_4_rgba = 9,
astc_4x4_rgba = 10,
atc_rgb = 11,
atc_rgba = 12,
rgba32 = 13,
rgb565 = 14,
bgr565 = 15,
rgba4444 = 16,
fxt1_rgb = 17,
pvrtc2_4_rgb = 18,
pvrtc2_4_rgba = 19,
etc2_eac_r11 = 20,
etc2_eac_rg11 = 21,
pub fn isEnabled(
self: TextureFormat,
basis_texture_format: BasisTextureFormat,
) bool {
return isFormatEnabled(basis_texture_format, self);
}
pub fn bytesPerBlockOrPixel(self: TextureFormat) u5 {
return switch (self) {
.rgb565, .bgr565, .rgba4444 => return 2,
.rgba32 => return 4,
.etc1_rgb, .bc1_rgb, .bc4_r, .pvrtc1_4_rgb, .pvrtc1_4_rgba, .atc_rgb, .pvrtc2_4_rgb, .pvrtc2_4_rgba, .etc2_eac_r11 => 8,
.bc7_rgba, .bc7_alt, .etc2_rgba, .bc3_rgba, .bc5_rg, .astc_4x4_rgba, .atc_rgba, .fxt1_rgb, .etc2_eac_rg11 => return 16,
};
}
};
pub const BlockFormat = enum(u5) {
etc1 = 0,
etc2_rgba = 1,
bc1 = 2,
bc3 = 3,
bc4 = 4,
bc5 = 5,
pvrtc1_4_rgb = 6,
pvrtc1_4_rgba = 7,
bc7 = 8,
bc7_m5_color = 9,
bc7_m5_alpha = 10,
etc2_eac_a8 = 11,
astc_4x4 = 12,
atc_rgb = 13,
atc_rgba_interpolated_alpha = 14,
fxt1_rgb = 15,
pvrtc2_4_rgb = 16,
pvrtc2_4_rgba = 17,
etc2_eac_r11 = 18,
etc2_eac_rg11 = 19,
indices = 20,
rgb32 = 21,
rgba32 = 23,
a32 = 24,
rgb565 = 25,
bgr565 = 26,
rgba4444_color = 27,
rgba4444_alpha = 28,
rgba4444_color_opaque = 29,
rgba4444 = 30,
uastc_4x4 = 31,
};
};

View file

@ -0,0 +1,38 @@
pub const BasisFile = opaque {};
pub extern fn basisu_transcoder_init() void;
pub extern fn transcoder_is_format_supported(tex_type: u32, fmt: u32) bool;
pub extern fn transcoder_init([*]const u8, u32) *BasisFile;
pub extern fn transcoder_deinit(*BasisFile) void;
pub extern fn transcoder_get_images_count(*BasisFile) u32;
pub extern fn transcoder_get_levels_count(*BasisFile, image_index: u32) u32;
pub extern fn transcoder_get_image_level_desc(
*BasisFile,
image_index: u32,
level_index: u32,
orig_width: *u32,
orig_height: *u32,
total_block: *u32,
) bool;
pub extern fn transcoder_get_image_transcoded_size(
*BasisFile,
image_index: u32,
level_index: u32,
format: u32,
size: *u32,
) bool;
pub extern fn transcoder_start_transcoding(*BasisFile) bool;
pub extern fn transcoder_stop_transcoding(*BasisFile) bool;
pub extern fn transcoder_transcode(
*BasisFile,
out: [*]const u8,
out_size: u32,
image_index: u32,
level_index: u32,
format: u32,
decode_flags: u32,
output_row_pitch_in_blocks_or_pixels: u32,
output_rows_in_pixels: u32,
) bool;

View file

@ -0,0 +1,153 @@
#include <basisu_transcoder.h>
#include <cstdbool>
#include <cstdint>
extern "C" {
void basisu_transcoder_init();
bool transcoder_is_format_supported(uint32_t tex_type, uint32_t fmt);
void *transcoder_init(void *src, uint32_t src_size);
void transcoder_deinit(void *h);
uint32_t transcoder_get_images_count(void *h);
uint32_t transcoder_get_levels_count(void *h, uint32_t image_index);
bool transcoder_get_image_level_desc(void *h, uint32_t image_index,
uint32_t level_index, uint32_t &orig_width,
uint32_t &orig_height,
uint32_t &block_count);
bool transcoder_get_image_transcoded_size(void *h, uint32_t image_index,
uint32_t level_index, uint32_t format,
uint32_t &size);
bool transcoder_start_transcoding(void *h);
bool transcoder_stop_transcoding(void *h);
bool transcoder_transcode(void *h, void *out, uint32_t out_size,
uint32_t image_index, uint32_t level_index,
uint32_t format, uint32_t decode_flags,
uint32_t output_row_pitch_in_blocks_or_pixels,
uint32_t output_rows_in_pixels);
}
void basisu_transcoder_init() { basist::basisu_transcoder_init(); }
#define MAGIC 0xDEADBEE1
struct basis_file {
int m_magic;
basist::basisu_transcoder m_transcoder;
void *m_pFile;
uint32_t m_file_size;
basis_file() : m_transcoder() {}
};
// transcoder_texture_format, basis_tex_format
bool transcoder_is_format_supported(uint32_t tex_type, uint32_t fmt) {
return basist::basis_is_format_supported(
(basist::transcoder_texture_format)tex_type,
(basist::basis_tex_format)fmt);
}
// !null - success
// null - validation failure
void *transcoder_init(void *src, uint32_t src_size) {
auto f = new basis_file;
f->m_pFile = src;
f->m_file_size = src_size;
if (!f->m_transcoder.validate_header(f->m_pFile, f->m_file_size)) {
delete f;
return nullptr;
}
f->m_magic = MAGIC;
return f;
}
void transcoder_deinit(void *h) {
auto f = static_cast<basis_file *>(h);
delete f;
}
uint32_t transcoder_get_images_count(void *h) {
auto f = static_cast<basis_file *>(h);
return f->m_transcoder.get_total_images(f->m_pFile, f->m_file_size);
}
uint32_t transcoder_get_levels_count(void *h, uint32_t image_index) {
auto f = static_cast<basis_file *>(h);
return f->m_transcoder.get_total_image_levels(f->m_pFile, f->m_file_size,
image_index);
}
// true - success
// false - OutOfBoundsLevelIndex
bool transcoder_get_image_level_desc(void *h, uint32_t image_index,
uint32_t level_index, uint32_t &orig_width,
uint32_t &orig_height,
uint32_t &block_count) {
auto f = static_cast<basis_file *>(h);
return f->m_transcoder.get_image_level_desc(
f->m_pFile, f->m_file_size, image_index, level_index, orig_width,
orig_height, block_count);
}
// true - success
// false - OutOfBoundsLevelIndex
bool transcoder_get_image_transcoded_size(void *h, uint32_t image_index,
uint32_t level_index, uint32_t format,
uint32_t &size) {
auto f = static_cast<basis_file *>(h);
uint32_t orig_width, orig_height, total_blocks;
if (!f->m_transcoder.get_image_level_desc(
f->m_pFile, f->m_file_size, image_index, level_index, orig_width,
orig_height, total_blocks))
return false;
uint8_t bytes_per_block_or_pixel = basis_get_bytes_per_block_or_pixel(
(basist::transcoder_texture_format)format);
if (basis_transcoder_format_is_uncompressed(
(basist::transcoder_texture_format)format)) {
size = orig_width * orig_height * bytes_per_block_or_pixel;
} else {
size = total_blocks * bytes_per_block_or_pixel;
}
return true;
}
// true - success
// false - unknown
bool transcoder_start_transcoding(void *h) {
auto f = static_cast<basis_file *>(h);
return f->m_transcoder.start_transcoding(f->m_pFile, f->m_file_size);
}
// true - success
// false - unknown
bool transcoder_stop_transcoding(void *h) {
auto f = static_cast<basis_file *>(h);
return f->m_transcoder.stop_transcoding();
}
// true - success
// false - unknown
bool transcoder_transcode(void *h, void *out, uint32_t out_size,
uint32_t image_index, uint32_t level_index,
uint32_t format, uint32_t decode_flags,
uint32_t output_row_pitch_in_blocks_or_pixels,
uint32_t output_rows_in_pixels) {
auto f = static_cast<basis_file *>(h);
uint32_t bytes_per_block = basis_get_bytes_per_block_or_pixel(
(basist::transcoder_texture_format)format);
return f->m_transcoder.transcode_image_level(
f->m_pFile, f->m_file_size, image_index, level_index, out,
out_size / bytes_per_block, (basist::transcoder_texture_format)format,
decode_flags, output_row_pitch_in_blocks_or_pixels, nullptr,
output_rows_in_pixels);
}

BIN
basisu/test/ziggy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

1
basisu/upstream Submodule

@ -0,0 +1 @@
Subproject commit d55a3f9f06adf9cc8c31f58e894d947cc6026da5

View file

@ -6,6 +6,7 @@ const system_sdk = @import("glfw/system_sdk.zig");
const glfw = @import("glfw/build.zig");
const ecs = @import("ecs/build.zig");
const freetype = @import("freetype/build.zig");
const basisu = @import("basisu/build.zig");
const sysaudio = @import("sysaudio/build.zig");
const sysjs = @import("sysjs/build.zig");
const Pkg = std.build.Pkg;
@ -43,6 +44,7 @@ pub fn build(b: *std.build.Builder) void {
test_step.dependOn(&glfw.testStep(b, mode, target).step);
test_step.dependOn(&ecs.testStep(b, mode, target).step);
test_step.dependOn(&freetype.testStep(b, mode, target).step);
test_step.dependOn(&basisu.testStep(b, mode, target).step);
test_step.dependOn(&sysaudio.testStep(b, mode, target, .{}).step);
// TODO: we need a way to test wasm stuff
// test_mach_step.dependOn(&sysjs.testStep(b, mode, target).step);