Files
basis_universal/encoder/basisu_xbc7_encode.h
Richard Geldreich 3cfdd2240b new files for v2.5
2026-07-01 13:20:12 -04:00

201 lines
10 KiB
C++

// File: basisu_xbc7_encode.h
// XBC7: experimental supercompressed-BC7 codec (prototype moved out of basisu_tool.cpp).
#pragma once
#include "basisu_enc.h"
#include "../transcoder/basisu_transcoder_internal.h"
namespace basisu {
namespace xbc7 {
const uint32_t DEFAULT_EFFORT_LEVEL = 3;
// Default quality "level" for the optional bc7e_scalar BC7 base encoder.
const uint32_t DEFAULT_BC7E_SCALAR_LEVEL = 2;
// Supported bc7e_scalar quality level range (higher = slower/better). Maps to the
// encoder's ultrafast..slowest presets where it's invoked; clamp to this range.
const uint32_t BC7E_SCALAR_MIN_LEVEL = 0;
const uint32_t BC7E_SCALAR_MAX_LEVEL = 6;
// Which BC7 base encoder XBC7 uses to pack the initial BC7 blocks before
// supercompression. cBC7F is the built-in fast real-time packer (the default);
// cBC7E_Scalar is the higher-quality (slower) scalar bc7e encoder.
enum class bc7_encoder_type
{
cBC7F = 0,
cBC7E_Scalar = 1
};
struct pack_options
{
uint32_t m_dct_q = 100; // [1,100]; 100 == lossless mode (weights always residual DPCM)
uint32_t m_weights[4] = { 1, 1, 1, 1 };
// Encoder effort/speed knob [0,10]: 0 == fastest, 10 == slowest/best
// (9 is the usual practical max; 10 exists for very high core counts).
// Currently controls how many weight-grid predictors the per-block DCT and
// DPCM sweeps evaluate: at 10 the full candidate set is searched (identical
// output to before this knob existed); as effort drops the sweep is pruned
// down to a small core of empirically high-value predictors (~6 at effort
// 0), trading a little ratio for a large drop in encode time -- the DCT
// predictor search (Q < 100) is the dominant cost. Default
// DEFAULT_EFFORT_LEVEL (a balanced speed/quality point, NOT the full
// search); pass 10 to reproduce the pre-knob output exactly.
uint32_t m_effort_level = DEFAULT_EFFORT_LEVEL;
// Optimize weights after encoding with bc7f. bc7f is a fast real-time BC7
// packer whose per-texel weights aren't optimal; when true, every block's
// weights are exhaustively re-derived for its (unchanged) config+endpoints
// right after the base pack. Slower (but threaded with the base pack);
// endpoints/config are untouched, so quality only holds or improves.
// Default off.
bool m_optimize_weights_after_bc7f = false;
// Flags for the underlying real-time BC7 base packer
// (basist::bc7f::fast_pack_bc7_auto_rgba). See bc7f::cPackBC7Flag* /
// cPackBC7FlagDefault* in basisu_transcoder_internal.h -- controls the
// quality/speed of the BC7 blocks XBC7 then supercompresses.
uint32_t m_bc7_pack_flags = basist::bc7f::cPackBC7FlagDefaultPartiallyAnalytical;
// Selects which BC7 base encoder packs the initial blocks. Default cBC7F
// (the existing fast built-in packer); cBC7E_Scalar selects the higher-quality
// scalar bc7e encoder at m_bc7e_scalar_level. Affects only the primary base
// pack -- the optional alt-pack RDO path still uses bc7f for now.
bc7_encoder_type m_bc7_encoder = bc7_encoder_type::cBC7F;
// Quality level for the bc7e_scalar encoder (only used when m_bc7_encoder ==
// cBC7E_Scalar). Higher = slower/better. Default DEFAULT_BC7E_SCALAR_LEVEL (2).
uint32_t m_bc7e_scalar_level = DEFAULT_BC7E_SCALAR_LEVEL;
// True if the source is perceptual (sRGB) data. Currently only consulted by
// the bc7e_scalar base encoder: when true it runs in its built-in perceptual
// error mode (and ignores m_weights); when false it runs linear and honors
// m_weights. Mirror basis_compressor_params::m_perceptual here.
bool m_perceptual = false;
// When true (the default), the encoder decodes the stream it just produced
// (via the transcoder) and verifies every logical BC7 block round-trips
// exactly to the coded blocks; a failure fails the encode. Guarantees every
// emitted stream is valid/decodable. Disable only to skip the extra decode.
bool m_self_validate = true;
// Optional "poor man's RDO" on the BC7 base pack. When enabled, every
// block is ALSO packed with m_bc7_pack_flags_alt (typically a cheaper,
// e.g. mode-6-only set). Both candidates are unpacked and their RGBA PSNR
// vs the source measured; the cheaper alternate is KEPT as long as it's no
// more than m_bc7_alt_max_psnr_drop dB worse than the primary. Trades a
// little quality for fewer command/config bits downstream. Default off.
bool m_bc7_alt_pack_enabled = false;
uint32_t m_bc7_pack_flags_alt = basist::bc7f::cPackBC7FlagPBitOpt | basist::bc7f::cPackBC7FlagUseTrivialMode6;
float m_bc7_alt_max_psnr_drop = 0.5f; // dB the alternate may be worse than the primary and still be kept
// Optional block-reuse RDO pre-pass (runs after the BC7 base pack, BEFORE
// the endpoint RDO -- once a whole block is reused there's no point trying
// cheaper endpoints). For each non-solid block it tries replacing it with:
// - REPEAT: an exact copy of its left/upper causal neighbor (the cheapest
// command -- one byte, no config/endpoints/weights), kept if within
// m_repeat_rdo_max_psnr_drop dB; and/or
// - SOLID: its mean color (cheap SolidDPCM command), kept if within
// m_solid_rdo_max_psnr_drop dB.
// Repeat is preferred over Solid (cheaper). Each independently enabled; off
// by default.
bool m_repeat_rdo_enabled = false;
float m_repeat_rdo_max_psnr_drop = 0.5f; // dB a block may drop to become a Repeat of a neighbor
bool m_solid_rdo_enabled = false;
float m_solid_rdo_max_psnr_drop = 0.5f; // dB a block may drop to become a solid-color block
// Optional endpoint-prediction RDO pre-pass (runs after the BC7 base pack,
// before stripe coding). For each block it tries forcing the endpoints to
// each valid causal neighbor's prediction (left/upper/left-diag/right-diag)
// via a zero-residual endpoint DPCM and keeps the best whose weighted RGBA
// PSNR drops by no more than m_endpoint_rdo_max_psnr_drop dB -- those zero
// residuals then cost almost nothing downstream. Default off.
bool m_endpoint_rdo_enabled = false;
float m_endpoint_rdo_max_psnr_drop = 0.5f; // dB the block PSNR may drop to adopt a neighbor's endpoints
// Optional weight-DCT AC-truncation RDO (DCT-coded blocks only). After a
// block is DCT-coded, its highest-frequency weight-DCT AC coefficients are
// zeroed one at a time in reverse zigzag order (the 2x2 low-freq corner DC,
// (1,0),(0,1),(1,1) is protected) while the decoded PSNR stays within this
// dB drop -- fewer coded ACs. 0 == disabled.
float m_ac_trunc_rdo_max_psnr_drop = 0.0f;
// Quality floor shared by ALL RDO passes (block-reuse + endpoint + alt-pack):
// a candidate whose absolute weighted RGBA PSNR is below this is rejected
// outright, even if its drop is within tolerance -- prevents accepting
// excessive distortion in already-degraded blocks.
float m_rdo_min_block_psnr = 33.0f; // dB
// Master switch for ALL development/debug console output (printed via
// fmt_debug_printf, so it also respects the global enable_debug_printf()
// and stays silent in normal builds). Default false. Nothing prints
// unless this is true.
bool m_debug_output = false;
// Print the detailed pack statistics dashboard. Requires m_debug_output
// to also be set (m_debug_output gates everything).
bool m_print_stats = false;
// When true, the encoder writes visualization PNG(s) -- one pixel block
// per BC7 block -- to (m_debug_file_prefix + "<name>.png"). Encode-time
// only; never affects the compressed output. Default off.
bool m_debug_images = false;
std::string m_debug_file_prefix;
// Weight DCT-vs-DPCM decision: choose lossless DPCM when
// dpcm_cost * 100 <= dct_cost * alpha. Both costs are measured-rate
// estimates (see g_dpcm_resid_cost_obits + the DCT byte cost
// constants), so 100 is the neutral operating point; raising alpha
// flips more blocks to lossless DPCM (quality up, size up).
uint32_t m_wt_dpcm_alpha_pct = 100;
// REQUIRED, never null: the caller's job pool (total threads INCLUDES
// the caller, basisu convention). The encoder always uses it; a pool of 1
// just runs serially. Pool size affects scheduling only, never the emitted
// bytes (the stripe count is independent of it).
job_pool* m_pJob_pool = nullptr;
// Stripe count for the main coding pass: 0 == auto (derived from image
// dimensions); else force exactly this many, clamped to
// [1, min(block_rows, XBC7_MAX_ENCODER_STRIPES)]. 1 == single-stripe (max
// ratio: no seam cost, no seek table). More stripes == more decode
// parallelism but a slightly larger file (per-seam prediction reset +
// seek-table cost); more stripes than pool threads simply queue. Prefer
// set_num_stripes_for_image() over assigning this directly.
uint32_t m_num_stripes = 0;
// Validate a desired stripe count against an image and store it in
// m_num_stripes. Clamps so every stripe holds at least the minimum
// efficient number of block rows (never tiny or empty stripes) and never
// exceeds the format max; images too short to stripe usefully collapse to
// a single stripe. Returns the actual count stored (>= 1).
uint32_t set_num_stripes_for_image(const image& img, uint32_t desired_num_stripes);
// Configure ALL "poor man's RDO" knobs from a single level [0,100].
// 0 -> RDO fully OFF: every enable flag false, every PSNR-drop 0.
// [1,100] -> enables the RDO passes and linearly ramps their tolerated
// per-block PSNR drop with the level: the general passes
// (BC7 alt-pack, endpoint-DPCM, weight-DCT AC-truncation) up to
// 10 dB at 100, and the cheaper block-reuse passes (repeat,
// solid) up to 4 dB at 100.
// The absolute quality floor (m_rdo_min_block_psnr) is left untouched -- it
// still gates every candidate regardless of level.
void set_rdo_level(uint32_t rdo_level);
};
// coded_log_blocks (REQUIRED, by reference): receives the final coded logical
// BC7 blocks (resized + filled by the encoder). It's mandatory because the
// encoder always decodes the stream it just produced and validates every block
// round-trips to these -- see the self-validation at the end of pack_image().
bool pack_image(
const image& orig_img,
const pack_options& opts,
uint8_vec& comp_bytes,
vector2D<basist::bc7u::log_bc7_block>& coded_log_blocks);
// NOTE: the decoder API (unpack_image / unpack_image_threaded) now lives in
// basisu_xbc7_decode.h.
} // namespace xbc7
} // namespace basisu