plug-ins: Initial DDS BC7 export support

We use Richard Geldreich's bc7enc_rdo
library to minimize the code changes required
in the existing DDS plug-in, and so it can
more easily be swapped out in the future.
This commit is contained in:
Alx Sa 2026-02-15 02:20:07 +00:00
parent d35893890d
commit 009aa52cbb
6 changed files with 2815 additions and 16 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,113 @@
/* File: bc7enc.h - Richard Geldreich, Jr. - MIT license or public domain (see end of bc7enc.c)
* If you use this software in a product, attribution / credits is requested but not required. */
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <glib/gstdio.h>
#define BC7ENC_BLOCK_SIZE (16)
#define BC7ENC_MAX_PARTITIONS (64)
#define BC7ENC_MAX_UBER_LEVEL (4)
typedef struct
{
uint8_t m_c[4];
} color_rgba;
typedef struct
{
uint32_t m_mode_mask;
/* m_max_partitions may range from 0 (disables mode 1) to BC7ENC_MAX_PARTITIONS.
* The higher this value, the slower the compressor, but the higher the quality. */
uint32_t m_max_partitions;
/* Relative RGBA or YCbCrA weights. */
uint32_t m_weights[4];
/* m_uber_level may range from 0 to BC7ENC_MAX_UBER_LEVEL.
* The higher this value, the slower the compressor, but the higher the quality. */
uint32_t m_uber_level;
/* If m_perceptual is TRUE, colorspace error is computed in YCbCr space, otherwise RGB. */
gboolean m_perceptual;
/* Set m_try_least_squares to FALSE for slightly faster/lower quality compression. */
gboolean m_try_least_squares;
/* When m_mode17_partition_estimation_filterbank, the mode1 partition estimator skips
* lesser used partition patterns unless they are strongly predicted to be potentially useful.
* There's a slight loss in quality with this enabled (around .08 dB RGB PSNR or .05 dB Y PSNR),
* but up to a 11% gain in speed depending on the other settings. */
gboolean m_mode17_partition_estimation_filterbank;
gboolean m_force_alpha;
gboolean m_force_selectors;
uint8_t m_selectors[16];
gboolean m_quant_mode6_endpoints;
gboolean m_bias_mode1_pbits;
float m_pbit1_weight;
float m_mode1_error_weight;
float m_mode5_error_weight;
float m_mode6_error_weight;
float m_mode7_error_weight;
float m_low_frequency_partition_weight;
} bc7enc_compress_block_params;
inline void bc7enc_compress_block_params_init_linear_weights(bc7enc_compress_block_params *p)
{
p->m_perceptual = FALSE;
p->m_weights[0] = 1;
p->m_weights[1] = 1;
p->m_weights[2] = 1;
p->m_weights[3] = 1;
}
inline void bc7enc_compress_block_params_init_perceptual_weights(bc7enc_compress_block_params *p)
{
p->m_perceptual = TRUE;
p->m_weights[0] = 128;
p->m_weights[1] = 64;
p->m_weights[2] = 16;
p->m_weights[3] = 32;
}
inline void bc7enc_compress_block_params_init(bc7enc_compress_block_params *p)
{
p->m_mode_mask = UINT32_MAX;
p->m_max_partitions = BC7ENC_MAX_PARTITIONS;
p->m_try_least_squares = TRUE;
p->m_mode17_partition_estimation_filterbank = FALSE;
p->m_uber_level = 4;
p->m_force_selectors = FALSE;
p->m_force_alpha = FALSE;
p->m_quant_mode6_endpoints = TRUE;
p->m_bias_mode1_pbits = TRUE;
p->m_pbit1_weight = 1.0f;
p->m_mode1_error_weight = 1.0f;
p->m_mode5_error_weight = 1.0f;
p->m_mode6_error_weight = 1.0f;
p->m_mode7_error_weight = 1.0f;
p->m_low_frequency_partition_weight = 1.0f;
if (p->m_perceptual)
bc7enc_compress_block_params_init_perceptual_weights (p);
else
bc7enc_compress_block_params_init_linear_weights (p);
}
/* bc7enc_compress_block_init() MUST be called before calling
* bc7enc_compress_block() (or you'll get artifacts). */
void bc7enc_compress_block_init (void);
/* Packs a single block of 16x16 RGBA pixels (R first in memory) to 128-bit BC7 block pBlock, using either mode 1 and/or 6.
* Alpha blocks will always use mode 6, and by default opaque blocks will use either modes 1 or 6.
* Returns TRUE if the block had any pixels with alpha < 255, otherwise it return FALSE. (This is not an error code - a block is always encoded.) */
gboolean bc7enc_compress_block(void *pBlock, const void *pPixelsRGBA, const bc7enc_compress_block_params *pComp_params);

View file

@ -195,16 +195,17 @@ dds_create_procedure (GimpPlugIn *plug_in,
gimp_procedure_add_choice_argument (procedure, "compression-format",
_("Compressio_n"),
_("Compression format"),
gimp_choice_new_with_values ("none", DDS_COMPRESS_NONE, _("None"), NULL,
gimp_choice_new_with_values ("none", DDS_COMPRESS_NONE, _("None"), NULL,
"bc1", DDS_COMPRESS_BC1, _("BC1 / DXT1"), NULL,
"bc2", DDS_COMPRESS_BC2, _("BC2 / DXT3"), NULL,
"bc3, ", DDS_COMPRESS_BC3, _("BC3 / DXT5"), NULL,
"bc3n", DDS_COMPRESS_BC3N, _("BC3nm / DXT5nm"), NULL,
"bc4", DDS_COMPRESS_BC4, _("BC4 / ATI1 (3Dc+)"), NULL,
"bc5", DDS_COMPRESS_BC5, _("BC5 / ATI2 (3Dc)"), NULL,
"bc7", DDS_COMPRESS_BC7, "BC7", NULL,
"rxgb", DDS_COMPRESS_RXGB, _("RXGB (DXT5)"), NULL,
"aexp", DDS_COMPRESS_AEXP, _("Alpha Exponent (DXT5)"), NULL,
"ycocg", DDS_COMPRESS_YCOCG, _("YCoCg (DXT5)"), NULL,
"ycocg", DDS_COMPRESS_YCOCG, _("YCoCg (DXT5)"), NULL,
"ycocgs", DDS_COMPRESS_YCOCGS, _("YCoCg scaled (DXT5)"), NULL,
NULL),
"none",

View file

@ -891,8 +891,8 @@ write_layer (FILE *fp,
}
}
/* We want and assume BGRA ordered pixels for bpp >= 3 from here on */
if (bpp >= 3)
/* We want and assume BGRA ordered pixels for bpp >= 3 from here on */
if (bpp >= 3 && compression != DDS_COMPRESS_BC7)
swap_rb (src, w * h, bpp);
if (compression == DDS_COMPRESS_BC3N)
@ -1232,7 +1232,7 @@ write_volume_mipmaps (FILE *fp,
}
/* We want and assume BGRA ordered pixels for bpp >= 3 from here on */
if (bpp >= 3)
if (bpp >= 3 && compression != DDS_COMPRESS_BC7)
swap_rb (src, w * h * d, bpp);
/* Pre-convert indexed images to RGB if not exporting as indexed */
@ -1575,6 +1575,10 @@ write_image (FILE *fp,
dxgi_format = DXGI_FORMAT_BC5_UNORM;
/*is_dx10 = TRUE;*/
break;
case DDS_COMPRESS_BC7:
dxgi_format = DXGI_FORMAT_BC7_UNORM;
is_dx10 = TRUE;
}
if ((compression == DDS_COMPRESS_BC3N) ||

View file

@ -45,6 +45,8 @@
#include "misc.h"
#include "vec.h"
#include "bc7enc_rdo/bc7enc.h"
#include "dxt_tables.h"
#define SWAP(a, b) do { typeof(a) t; t = a; a = b; b = t; } while(0)
@ -869,7 +871,7 @@ compress_BC1 (unsigned char *dst,
x = (i % ((w + 3) >> 2)) << 2;
y = (i / ((w + 3) >> 2)) << 2;
p = dst + BLOCK_OFFSET(x, y, w, 8);
extract_block(src, x, y, w, h, block);
extract_block (src, x, y, w, h, block);
encode_color_block(p, block, DXT_BC1 | flags);
}
}
@ -894,7 +896,7 @@ compress_BC2 (unsigned char *dst,
x = (i % ((w + 3) >> 2)) << 2;
y = (i / ((w + 3) >> 2)) << 2;
p = dst + BLOCK_OFFSET(x, y, w, 16);
extract_block(src, x, y, w, h, block);
extract_block (src, x, y, w, h, block);
encode_alpha_block_BC2(p, block);
encode_color_block(p + 8, block, DXT_BC2 | flags);
}
@ -920,7 +922,7 @@ compress_BC3 (unsigned char *dst,
x = (i % ((w + 3) >> 2)) << 2;
y = (i / ((w + 3) >> 2)) << 2;
p = dst + BLOCK_OFFSET(x, y, w, 16);
extract_block(src, x, y, w, h, block);
extract_block (src, x, y, w, h, block);
encode_alpha_block_BC3(p, block, 0);
encode_color_block(p + 8, block, DXT_BC3 | flags);
}
@ -945,7 +947,7 @@ compress_BC4 (unsigned char *dst,
x = (i % ((w + 3) >> 2)) << 2;
y = (i / ((w + 3) >> 2)) << 2;
p = dst + BLOCK_OFFSET(x, y, w, 8);
extract_block(src, x, y, w, h, block);
extract_block (src, x, y, w, h, block);
encode_alpha_block_BC3(p, block, -1);
}
}
@ -969,7 +971,7 @@ compress_BC5 (unsigned char *dst,
x = (i % ((w + 3) >> 2)) << 2;
y = (i / ((w + 3) >> 2)) << 2;
p = dst + BLOCK_OFFSET(x, y, w, 16);
extract_block(src, x, y, w, h, block);
extract_block (src, x, y, w, h, block);
/* Pixels are ordered as BGRA (see write_layer)
* First we encode red -1+3: channel 2;
* then we encode green -2+3: channel 1.
@ -979,6 +981,34 @@ compress_BC5 (unsigned char *dst,
}
}
static void
compress_BC7 (guchar *dst,
const guchar *src,
gint w,
gint h,
bc7enc_compress_block_params *params)
{
const guint block_count = BLOCK_COUNT (w, h);
guchar block[64] = { 0 };
guint i;
guchar *p;
gint x;
gint y;
#ifdef _OPENMP
#pragma omp parallel for schedule(dynamic, 256) private(block, p, x, y)
#endif
for (i = 0; i < block_count; ++i)
{
x = (i % ((w + 3) >> 2)) << 2;
y = (i / ((w + 3) >> 2)) << 2;
p = dst + BLOCK_OFFSET (x, y, w, 16);
extract_block (src, x, y, w, h, block);
bc7enc_compress_block (p, block, params);
}
}
static void
compress_YCoCg (unsigned char *dst,
const unsigned char *src,
@ -998,7 +1028,7 @@ compress_YCoCg (unsigned char *dst,
x = (i % ((w + 3) >> 2)) << 2;
y = (i / ((w + 3) >> 2)) << 2;
p = dst + BLOCK_OFFSET(x, y, w, 16);
extract_block(src, x, y, w, h, block);
extract_block (src, x, y, w, h, block);
encode_alpha_block_BC3(p, block, 0);
encode_YCoCg_block(p + 8, block);
}
@ -1014,11 +1044,15 @@ dxt_compress (unsigned char *dst,
int mipmaps,
int flags)
{
int i, size, w, h;
unsigned int offset;
unsigned char *tmp = NULL;
int j;
unsigned char *s;
gint i;
gint size;
gint w;
gint h;
guint offset;
guchar *tmp = NULL;
gint j;
guchar *s;
bc7enc_compress_block_params bc7_params;
if (bpp == 1)
{
@ -1078,6 +1112,13 @@ dxt_compress (unsigned char *dst,
h = height;
s = tmp ? tmp : src;
bc7_params.m_perceptual = (flags & DXT_PERCEPTUAL);
if (format == DDS_COMPRESS_BC7)
{
bc7enc_compress_block_init ();
bc7enc_compress_block_params_init (&bc7_params);
}
for (i = 0; i < mipmaps; ++i)
{
switch (format)
@ -1101,6 +1142,9 @@ dxt_compress (unsigned char *dst,
case DDS_COMPRESS_BC5:
compress_BC5(dst + offset, s, w, h);
break;
case DDS_COMPRESS_BC7:
compress_BC7 (dst + offset, s, w, h, &bc7_params);
break;
case DDS_COMPRESS_YCOCGS:
compress_YCoCg(dst + offset, s, w, h);
break;

View file

@ -1,6 +1,7 @@
plugin_name = 'file-dds'
plugin_sourcecode = [
'bc7enc_rdo/bc7enc.c',
'bc7.c',
'dds.c',
'ddsread.c',