3 * Copyright (C) 2024 Connor Worley <connorbworley@gmail.com>
5 * This file is part of FFmpeg.
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * FFmpeg is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
24 #include "libavutil/crc.h"
25 #include "libavutil/imgutils.h"
26 #include "libavutil/mem.h"
27 #include "libavutil/opt.h"
29 #include "bytestream.h"
30 #include "codec_internal.h"
33 #include "texturedsp.h"
35 #define DXV_HEADER_LENGTH 12
38 * DXV uses LZ-like back-references to avoid copying words that have already
39 * appeared in the decompressed stream. Using a simple hash table (HT)
40 * significantly speeds up the lookback process while encoding.
42 #define LOOKBACK_HT_ELEMS 0x40000
43 #define LOOKBACK_WORDS 0x20202
45 typedef struct HTEntry
{
50 static void ht_init(HTEntry
*ht
)
52 for (size_t i
= 0; i
< LOOKBACK_HT_ELEMS
; i
++) {
57 static uint32_t ht_lookup_and_upsert(HTEntry
*ht
, const AVCRC
*hash_ctx
,
58 uint32_t key
, uint32_t pos
)
61 size_t hash
= av_crc(hash_ctx
, 0, (uint8_t*)&key
, 4) % LOOKBACK_HT_ELEMS
;
62 for (size_t i
= hash
; i
< hash
+ LOOKBACK_HT_ELEMS
; i
++) {
63 size_t wrapped_index
= i
% LOOKBACK_HT_ELEMS
;
64 HTEntry
*entry
= &ht
[wrapped_index
];
65 if (entry
->key
== key
|| entry
->pos
== -1) {
75 static void ht_delete(HTEntry
*ht
, const AVCRC
*hash_ctx
,
76 uint32_t key
, uint32_t pos
)
78 HTEntry
*removed_entry
= NULL
;
80 size_t hash
= av_crc(hash_ctx
, 0, (uint8_t*)&key
, 4) % LOOKBACK_HT_ELEMS
;
82 for (size_t i
= hash
; i
< hash
+ LOOKBACK_HT_ELEMS
; i
++) {
83 size_t wrapped_index
= i
% LOOKBACK_HT_ELEMS
;
84 HTEntry
*entry
= &ht
[wrapped_index
];
88 size_t candidate_hash
= av_crc(hash_ctx
, 0, (uint8_t*)&entry
->key
, 4) % LOOKBACK_HT_ELEMS
;
89 if ((wrapped_index
> removed_hash
&& (candidate_hash
<= removed_hash
|| candidate_hash
> wrapped_index
)) ||
90 (wrapped_index
< removed_hash
&& (candidate_hash
<= removed_hash
&& candidate_hash
> wrapped_index
))) {
91 *removed_entry
= *entry
;
93 removed_entry
= entry
;
94 removed_hash
= wrapped_index
;
96 } else if (entry
->key
== key
) {
97 if (entry
->pos
<= pos
) {
99 removed_entry
= entry
;
100 removed_hash
= wrapped_index
;
108 typedef struct DXVEncContext
{
113 uint8_t *tex_data
; // Compressed texture
114 int64_t tex_size
; // Texture size
116 /* Optimal number of slices for parallel decoding */
119 TextureDSPThreadContext enc
;
121 DXVTextureFormat tex_fmt
;
122 int (*compress_tex
)(AVCodecContext
*avctx
);
124 const AVCRC
*crc_ctx
;
126 HTEntry color_lookback_ht
[LOOKBACK_HT_ELEMS
];
127 HTEntry lut_lookback_ht
[LOOKBACK_HT_ELEMS
];
130 /* Converts an index offset value to a 2-bit opcode and pushes it to a stream.
131 * Inverse of CHECKPOINT in dxv.c. */
135 if (bytestream2_get_bytes_left_p(pbc) < 4) { \
136 return AVERROR_INVALIDDATA; \
138 value = pbc->buffer; \
139 bytestream2_put_le32(pbc, 0); \
142 if (idx >= 0x102 * x) { \
144 bytestream2_put_le16(pbc, (idx / x) - 0x102); \
145 } else if (idx >= 2 * x) { \
147 bytestream2_put_byte(pbc, (idx / x) - 2); \
148 } else if (idx == x) { \
153 AV_WL32(value, AV_RL32(value) | (op << (state * 2))); \
157 static int dxv_compress_dxt1(AVCodecContext
*avctx
)
159 DXVEncContext
*ctx
= avctx
->priv_data
;
160 PutByteContext
*pbc
= &ctx
->pbc
;
162 uint32_t color
, lut
, idx
, color_idx
, lut_idx
, prev_pos
, state
= 16, pos
= 2, op
= 0;
164 ht_init(ctx
->color_lookback_ht
);
165 ht_init(ctx
->lut_lookback_ht
);
167 bytestream2_put_le32(pbc
, AV_RL32(ctx
->tex_data
));
168 bytestream2_put_le32(pbc
, AV_RL32(ctx
->tex_data
+ 4));
170 ht_lookup_and_upsert(ctx
->color_lookback_ht
, ctx
->crc_ctx
, AV_RL32(ctx
->tex_data
), 0);
171 ht_lookup_and_upsert(ctx
->lut_lookback_ht
, ctx
->crc_ctx
, AV_RL32(ctx
->tex_data
+ 4), 1);
173 while (pos
+ 2 <= ctx
->tex_size
/ 4) {
176 color
= AV_RL32(ctx
->tex_data
+ pos
* 4);
177 prev_pos
= ht_lookup_and_upsert(ctx
->color_lookback_ht
, ctx
->crc_ctx
, color
, pos
);
178 color_idx
= prev_pos
!= -1 ? pos
- prev_pos
: 0;
179 if (pos
>= LOOKBACK_WORDS
) {
180 uint32_t old_pos
= pos
- LOOKBACK_WORDS
;
181 uint32_t old_color
= AV_RL32(ctx
->tex_data
+ old_pos
* 4);
182 ht_delete(ctx
->color_lookback_ht
, ctx
->crc_ctx
, old_color
, old_pos
);
186 lut
= AV_RL32(ctx
->tex_data
+ pos
* 4);
187 if (color_idx
&& lut
== AV_RL32(ctx
->tex_data
+ (pos
- color_idx
) * 4)) {
191 prev_pos
= ht_lookup_and_upsert(ctx
->lut_lookback_ht
, ctx
->crc_ctx
, lut
, pos
);
192 lut_idx
= prev_pos
!= -1 ? pos
- prev_pos
: 0;
194 if (pos
>= LOOKBACK_WORDS
) {
195 uint32_t old_pos
= pos
- LOOKBACK_WORDS
;
196 uint32_t old_lut
= AV_RL32(ctx
->tex_data
+ old_pos
* 4);
197 ht_delete(ctx
->lut_lookback_ht
, ctx
->crc_ctx
, old_lut
, old_pos
);
207 bytestream2_put_le32(pbc
, color
);
212 bytestream2_put_le32(pbc
, lut
);
219 static int dxv_encode(AVCodecContext
*avctx
, AVPacket
*pkt
,
220 const AVFrame
*frame
, int *got_packet
)
222 DXVEncContext
*ctx
= avctx
->priv_data
;
223 PutByteContext
*pbc
= &ctx
->pbc
;
226 /* unimplemented: needs to depend on compression ratio of tex format */
227 /* under DXT1, we need 3 words to encode load ops for 32 words.
228 * the first 2 words of the texture do not need load ops. */
229 ret
= ff_alloc_packet(avctx
, pkt
, DXV_HEADER_LENGTH
+ ctx
->tex_size
+ AV_CEIL_RSHIFT(ctx
->tex_size
- 8, 7) * 12);
233 if (ctx
->enc
.tex_funct
) {
234 ctx
->enc
.tex_data
.out
= ctx
->tex_data
;
235 ctx
->enc
.frame_data
.in
= frame
->data
[0];
236 ctx
->enc
.stride
= frame
->linesize
[0];
237 ctx
->enc
.width
= avctx
->width
;
238 ctx
->enc
.height
= avctx
->height
;
239 ff_texturedsp_exec_compress_threads(avctx
, &ctx
->enc
);
241 /* unimplemented: YCoCg formats */
242 return AVERROR_INVALIDDATA
;
245 bytestream2_init_writer(pbc
, pkt
->data
, pkt
->size
);
247 bytestream2_put_le32(pbc
, ctx
->tex_fmt
);
248 bytestream2_put_byte(pbc
, 4);
249 bytestream2_put_byte(pbc
, 0);
250 bytestream2_put_byte(pbc
, 0);
251 bytestream2_put_byte(pbc
, 0);
252 /* Fill in compressed size later */
253 bytestream2_skip_p(pbc
, 4);
255 ret
= ctx
->compress_tex(avctx
);
259 AV_WL32(pkt
->data
+ 8, bytestream2_tell_p(pbc
) - DXV_HEADER_LENGTH
);
260 av_shrink_packet(pkt
, bytestream2_tell_p(pbc
));
266 static av_cold
int dxv_init(AVCodecContext
*avctx
)
268 DXVEncContext
*ctx
= avctx
->priv_data
;
269 TextureDSPEncContext texdsp
;
270 int ret
= av_image_check_size(avctx
->width
, avctx
->height
, 0, avctx
);
273 av_log(avctx
, AV_LOG_ERROR
, "Invalid image size %dx%d.\n",
274 avctx
->width
, avctx
->height
);
278 if (avctx
->width
% TEXTURE_BLOCK_W
|| avctx
->height
% TEXTURE_BLOCK_H
) {
281 "Video size %dx%d is not multiple of "AV_STRINGIFY(TEXTURE_BLOCK_W
)"x"AV_STRINGIFY(TEXTURE_BLOCK_H
)".\n",
282 avctx
->width
, avctx
->height
);
283 return AVERROR_INVALIDDATA
;
286 ff_texturedspenc_init(&texdsp
);
288 switch (ctx
->tex_fmt
) {
290 ctx
->compress_tex
= dxv_compress_dxt1
;
291 ctx
->enc
.tex_funct
= texdsp
.dxt1_block
;
292 ctx
->enc
.tex_ratio
= 8;
295 av_log(avctx
, AV_LOG_ERROR
, "Invalid format %08X\n", ctx
->tex_fmt
);
296 return AVERROR_INVALIDDATA
;
298 ctx
->enc
.raw_ratio
= 16;
299 ctx
->tex_size
= avctx
->width
/ TEXTURE_BLOCK_W
*
300 avctx
->height
/ TEXTURE_BLOCK_H
*
302 ctx
->enc
.slice_count
= av_clip(avctx
->thread_count
, 1, avctx
->height
/ TEXTURE_BLOCK_H
);
304 ctx
->tex_data
= av_malloc(ctx
->tex_size
);
305 if (!ctx
->tex_data
) {
306 return AVERROR(ENOMEM
);
309 ctx
->crc_ctx
= av_crc_get_table(AV_CRC_32_IEEE
);
311 av_log(avctx
, AV_LOG_ERROR
, "Could not initialize CRC table.\n");
318 static av_cold
int dxv_close(AVCodecContext
*avctx
)
320 DXVEncContext
*ctx
= avctx
->priv_data
;
322 av_freep(&ctx
->tex_data
);
327 #define OFFSET(x) offsetof(DXVEncContext, x)
328 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
329 static const AVOption options
[] = {
330 { "format", NULL
, OFFSET(tex_fmt
), AV_OPT_TYPE_INT
, { .i64
= DXV_FMT_DXT1
}, DXV_FMT_DXT1
, DXV_FMT_DXT1
, FLAGS
, .unit
= "format" },
331 { "dxt1", "DXT1 (Normal Quality, No Alpha)", 0, AV_OPT_TYPE_CONST
, { .i64
= DXV_FMT_DXT1
}, 0, 0, FLAGS
, .unit
= "format" },
335 static const AVClass dxvenc_class
= {
336 .class_name
= "DXV encoder",
338 .version
= LIBAVUTIL_VERSION_INT
,
341 const FFCodec ff_dxv_encoder
= {
343 CODEC_LONG_NAME("Resolume DXV"),
344 .p
.type
= AVMEDIA_TYPE_VIDEO
,
345 .p
.id
= AV_CODEC_ID_DXV
,
347 FF_CODEC_ENCODE_CB(dxv_encode
),
349 .priv_data_size
= sizeof(DXVEncContext
),
350 .p
.capabilities
= AV_CODEC_CAP_DR1
|
351 AV_CODEC_CAP_SLICE_THREADS
|
352 AV_CODEC_CAP_FRAME_THREADS
,
353 .p
.priv_class
= &dxvenc_class
,
354 .p
.pix_fmts
= (const enum AVPixelFormat
[]) {
355 AV_PIX_FMT_RGBA
, AV_PIX_FMT_NONE
,
357 .caps_internal
= FF_CODEC_CAP_INIT_CLEANUP
,