| // 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) |
| |
| // Per-channel error weights. ALWAYS used by the XBC7 encoder when evaluating block candidates vs. the |
| // original pixels during supercompression, regardless of the BC7 base encoder. (The base encoders differ: |
| // bc7f supports neither weights nor perceptual; bc7e_scalar supports either - see m_perceptual below.) |
| 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 |