History

lavc/dxvenc: add DXV encoder with support for DXT1 texture format

Connor Worley committed 2 years ago

Commit dfbbd111 parent 1496ce87 files · +368 −2Browse files

Unified · Split

7 files
  1. Changelog +1 −0
  2. configure +1 −0
  3. doc/general_contents.texi +2 −1
  4. libavcodec/Makefile +1 −0
  5. libavcodec/allcodecs.c +1 −0
  6. libavcodec/dxvenc.c +361 −0
  7. libavcodec/version.h +1 −1
Changelog+1−0
@@ -2,6 +2,7 @@ Entries are sorted chronologically from oldest to youngest within each release, · 1 unmodified line
2releases are sorted from youngest to oldest.2releases are sorted from youngest to oldest.
3 3
4version <next>:4version <next>:
5- DXV DXT1 encoder
5- LEAD MCMP decoder6- LEAD MCMP decoder
6- EVC decoding using external library libxevd7- EVC decoding using external library libxevd
7- EVC encoding using external library libxeve8- EVC encoding using external library libxeve
configure+1−0
@@ -2851,6 +2851,7 @@ dvvideo_decoder_select="dvprofile idctdsp" · 2850 unmodified lines
2851dvvideo_encoder_select="dvprofile fdctdsp me_cmp pixblockdsp"2851dvvideo_encoder_select="dvprofile fdctdsp me_cmp pixblockdsp"
2852dxa_decoder_deps="zlib"2852dxa_decoder_deps="zlib"
2853dxv_decoder_select="lzf texturedsp"2853dxv_decoder_select="lzf texturedsp"
2854dxv_encoder_select="texturedspenc"
2854eac3_decoder_select="ac3_decoder"2855eac3_decoder_select="ac3_decoder"
2855eac3_encoder_select="ac3_encoder"2856eac3_encoder_select="ac3_encoder"
2856eamad_decoder_select="aandcttables blockdsp bswapdsp"2857eamad_decoder_select="aandcttables blockdsp bswapdsp"
doc/general_contents.texi+2−1
@@ -670,7 +670,8 @@ library: · 669 unmodified lines
670@item Redirector @tab @tab X670@item Redirector @tab @tab X
671@item RedSpark @tab @tab X671@item RedSpark @tab @tab X
672@item Renderware TeXture Dictionary @tab @tab X672@item Renderware TeXture Dictionary @tab @tab X
673@item Resolume DXV @tab @tab X673@item Resolume DXV @tab X @tab X
674 @tab Encoding is only supported for the DXT1 (Normal Quality, No Alpha) texture format.
674@item RF64 @tab @tab X675@item RF64 @tab @tab X
675@item RL2 @tab @tab X676@item RL2 @tab @tab X
676 @tab Audio and video format used in some games by Entertainment Software Partners.677 @tab Audio and video format used in some games by Entertainment Software Partners.
libavcodec/Makefile+1−0
@@ -341,6 +341,7 @@ OBJS-$(CONFIG_DVVIDEO_ENCODER) += dvenc.o dv.o dvdata.o · 340 unmodified lines
341OBJS-$(CONFIG_DXA_DECODER) += dxa.o341OBJS-$(CONFIG_DXA_DECODER) += dxa.o
342OBJS-$(CONFIG_DXTORY_DECODER) += dxtory.o342OBJS-$(CONFIG_DXTORY_DECODER) += dxtory.o
343OBJS-$(CONFIG_DXV_DECODER) += dxv.o343OBJS-$(CONFIG_DXV_DECODER) += dxv.o
344OBJS-$(CONFIG_DXV_ENCODER) += dxvenc.o
344OBJS-$(CONFIG_EAC3_DECODER) += eac3_data.o345OBJS-$(CONFIG_EAC3_DECODER) += eac3_data.o
345OBJS-$(CONFIG_EAC3_ENCODER) += eac3enc.o eac3_data.o346OBJS-$(CONFIG_EAC3_ENCODER) += eac3enc.o eac3_data.o
346OBJS-$(CONFIG_EACMV_DECODER) += eacmv.o347OBJS-$(CONFIG_EACMV_DECODER) += eacmv.o
libavcodec/allcodecs.c+1−0
@@ -106,6 +106,7 @@ extern const FFCodec ff_dvvideo_encoder; · 105 unmodified lines
106extern const FFCodec ff_dvvideo_decoder;106extern const FFCodec ff_dvvideo_decoder;
107extern const FFCodec ff_dxa_decoder;107extern const FFCodec ff_dxa_decoder;
108extern const FFCodec ff_dxtory_decoder;108extern const FFCodec ff_dxtory_decoder;
109extern const FFCodec ff_dxv_encoder;
109extern const FFCodec ff_dxv_decoder;110extern const FFCodec ff_dxv_decoder;
110extern const FFCodec ff_eacmv_decoder;111extern const FFCodec ff_eacmv_decoder;
111extern const FFCodec ff_eamad_decoder;112extern const FFCodec ff_eamad_decoder;
libavcodec/dxvenc.c+361−0
@@ -0,0 +1,361 @@
1/*
2 * Resolume DXV encoder
3 * Copyright (C) 2024 Connor Worley <connorbworley@gmail.com>
4 *
5 * This file is part of FFmpeg.
6 *
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.
11 *
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.
16 *
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
20 */
21
22#include <stdint.h>
23
24#include "libavutil/crc.h"
25#include "libavutil/imgutils.h"
26#include "libavutil/opt.h"
27
28#include "bytestream.h"
29#include "codec_internal.h"
30#include "encode.h"
31#include "texturedsp.h"
32
33#define DXV_HEADER_LENGTH 12
34
35/*
36 * DXV uses LZ-like back-references to avoid copying words that have already
37 * appeared in the decompressed stream. Using a simple hash table (HT)
38 * significantly speeds up the lookback process while encoding.
39 */
40#define LOOKBACK_HT_ELEMS 0x40000
41#define LOOKBACK_WORDS 0x20202
42
43enum DXVTextureFormat {
44 DXV_FMT_DXT1 = MKBETAG('D', 'X', 'T', '1'),
45};
46
47typedef struct HTEntry {
48 uint32_t key;
49 uint32_t pos;
50} HTEntry;
51
52static void ht_init(HTEntry *ht)
53{
54 for (size_t i = 0; i < LOOKBACK_HT_ELEMS; i++) {
55 ht[i].pos = -1;
56 }
57}
58
59static uint32_t ht_lookup_and_upsert(HTEntry *ht, AVCRC *hash_ctx,
60 uint32_t key, uint32_t pos)
61{
62 uint32_t ret = -1;
63 size_t hash = av_crc(hash_ctx, 0, (uint8_t*)&key, 4) % LOOKBACK_HT_ELEMS;
64 for (size_t i = hash; i < hash + LOOKBACK_HT_ELEMS; i++) {
65 size_t wrapped_index = i % LOOKBACK_HT_ELEMS;
66 HTEntry *entry = &ht[wrapped_index];
67 if (entry->key == key || entry->pos == -1) {
68 ret = entry->pos;
69 entry->key = key;
70 entry->pos = pos;
71 break;
72 }
73 }
74 return ret;
75}
76
77static void ht_delete(HTEntry *ht, AVCRC *hash_ctx,
78 uint32_t key, uint32_t pos)
79{
80 HTEntry *removed_entry = NULL;
81 size_t removed_hash;
82 size_t hash = av_crc(hash_ctx, 0, (uint8_t*)&key, 4) % LOOKBACK_HT_ELEMS;
83
84 for (size_t i = hash; i < hash + LOOKBACK_HT_ELEMS; i++) {
85 size_t wrapped_index = i % LOOKBACK_HT_ELEMS;
86 HTEntry *entry = &ht[wrapped_index];
87 if (entry->pos == -1)
88 return;
89 if (removed_entry) {
90 size_t candidate_hash = av_crc(hash_ctx, 0, (uint8_t*)&entry->key, 4) % LOOKBACK_HT_ELEMS;
91 if ((wrapped_index > removed_hash && (candidate_hash <= removed_hash || candidate_hash > wrapped_index)) ||
92 (wrapped_index < removed_hash && (candidate_hash <= removed_hash && candidate_hash > wrapped_index))) {
93 *removed_entry = *entry;
94 entry->pos = -1;
95 removed_entry = entry;
96 removed_hash = wrapped_index;
97 }
98 } else if (entry->key == key) {
99 if (entry->pos <= pos) {
100 entry->pos = -1;
101 removed_entry = entry;
102 removed_hash = wrapped_index;
103 } else {
104 return;
105 }
106 }
107 }
108}
109
110typedef struct DXVEncContext {
111 AVClass *class;
112
113 TextureDSPContext texdsp;
114 PutByteContext pbc;
115
116 uint8_t *tex_data; // Compressed texture
117 int64_t tex_size; // Texture size
118
119 /* Optimal number of slices for parallel decoding */
120 int slice_count;
121
122 TextureDSPThreadContext enc;
123
124 enum DXVTextureFormat tex_fmt;
125 int (*compress_tex)(AVCodecContext *avctx);
126
127 AVCRC *crc_ctx;
128
129 HTEntry color_lookback_ht[LOOKBACK_HT_ELEMS];
130 HTEntry lut_lookback_ht[LOOKBACK_HT_ELEMS];
131} DXVEncContext;
132
133static int compress_texture_thread(AVCodecContext *avctx, void *arg,
134 int slice, int thread_nb)
135{
136 DXVEncContext *ctx = avctx->priv_data;
137 AVFrame *frame = arg;
138
139 if (ctx->enc.tex_funct) {
140 ctx->enc.tex_data.out = ctx->tex_data;
141 ctx->enc.frame_data.in = frame->data[0];
142 ctx->enc.stride = frame->linesize[0];
143 return ff_texturedsp_compress_thread(avctx, &ctx->enc, slice, thread_nb);
144 } else {
145 /* unimplemented: YCoCg formats */
146 return AVERROR_INVALIDDATA;
147 }
148
149 return 0;
150}
151
152/* Converts an index offset value to a 2-bit opcode and pushes it to a stream.
153 * Inverse of CHECKPOINT in dxv.c. */
154#define PUSH_OP(x) \
155 do { \
156 if (state == 16) { \
157 if (bytestream2_get_bytes_left_p(pbc) < 4) { \
158 return AVERROR_INVALIDDATA; \
159 } \
160 value = (uint32_t*)pbc->buffer; \
161 bytestream2_put_le32(pbc, 0); \
162 state = 0; \
163 } \
164 if (idx >= 0x102 * x) { \
165 op = 3; \
166 bytestream2_put_le16(pbc, (idx / x) - 0x102); \
167 } else if (idx >= 2 * x) { \
168 op = 2; \
169 bytestream2_put_byte(pbc, (idx / x) - 2); \
170 } else if (idx == x) { \
171 op = 1; \
172 } else { \
173 op = 0; \
174 } \
175 *value |= (op << (state * 2)); \
176 state++; \
177 } while (0)
178
179static int dxv_compress_dxt1(AVCodecContext *avctx)
180{
181 DXVEncContext *ctx = avctx->priv_data;
182 PutByteContext *pbc = &ctx->pbc;
183 uint32_t *value;
184 uint32_t color, lut, idx, color_idx, lut_idx, prev_pos, state = 16, pos = 2, op = 0;
185
186 ht_init(ctx->color_lookback_ht);
187 ht_init(ctx->lut_lookback_ht);
188
189 bytestream2_put_le32(pbc, AV_RL32(ctx->tex_data));
190 bytestream2_put_le32(pbc, AV_RL32(ctx->tex_data + 4));
191
192 ht_lookup_and_upsert(ctx->color_lookback_ht, ctx->crc_ctx, AV_RL32(ctx->tex_data), 0);
193 ht_lookup_and_upsert(ctx->lut_lookback_ht, ctx->crc_ctx, AV_RL32(ctx->tex_data + 4), 1);
194
195 while (pos + 2 <= ctx->tex_size / 4) {
196 idx = 0;
197
198 color = AV_RL32(ctx->tex_data + pos * 4);
199 prev_pos = ht_lookup_and_upsert(ctx->color_lookback_ht, ctx->crc_ctx, color, pos);
200 color_idx = prev_pos != -1 ? pos - prev_pos : 0;
201 if (pos >= LOOKBACK_WORDS) {
202 uint32_t old_pos = pos - LOOKBACK_WORDS;
203 uint32_t old_color = AV_RL32(ctx->tex_data + old_pos * 4);
204 ht_delete(ctx->color_lookback_ht, ctx->crc_ctx, old_color, old_pos);
205 }
206 pos++;
207
208 lut = AV_RL32(ctx->tex_data + pos * 4);
209 if (color_idx && lut == AV_RL32(ctx->tex_data + (pos - color_idx) * 4)) {
210 idx = color_idx;
211 } else {
212 idx = 0;
213 prev_pos = ht_lookup_and_upsert(ctx->lut_lookback_ht, ctx->crc_ctx, lut, pos);
214 lut_idx = prev_pos != -1 ? pos - prev_pos : 0;
215 }
216 if (pos >= LOOKBACK_WORDS) {
217 uint32_t old_pos = pos - LOOKBACK_WORDS;
218 uint32_t old_lut = AV_RL32(ctx->tex_data + old_pos * 4);
219 ht_delete(ctx->lut_lookback_ht, ctx->crc_ctx, old_lut, old_pos);
220 }
221 pos++;
222
223 PUSH_OP(2);
224
225 if (!idx) {
226 idx = color_idx;
227 PUSH_OP(2);
228 if (!idx)
229 bytestream2_put_le32(pbc, color);
230
231 idx = lut_idx;
232 PUSH_OP(2);
233 if (!idx)
234 bytestream2_put_le32(pbc, lut);
235 }
236 }
237
238 return 0;
239}
240
241static int dxv_encode(AVCodecContext *avctx, AVPacket *pkt,
242 const AVFrame *frame, int *got_packet)
243{
244 DXVEncContext *ctx = avctx->priv_data;
245 PutByteContext *pbc = &ctx->pbc;
246 int ret;
247
248 /* unimplemented: needs to depend on compression ratio of tex format */
249 /* under DXT1, we need 3 words to encode load ops for 32 words.
250 * the first 2 words of the texture do not need load ops. */
251 ret = ff_alloc_packet(avctx, pkt, DXV_HEADER_LENGTH + ctx->tex_size + AV_CEIL_RSHIFT(ctx->tex_size - 8, 7) * 12);
252 if (ret < 0)
253 return ret;
254
255 avctx->execute2(avctx, compress_texture_thread, (void*)frame, NULL, ctx->enc.slice_count);
256
257 bytestream2_init_writer(pbc, pkt->data, pkt->size);
258
259 bytestream2_put_le32(pbc, ctx->tex_fmt);
260 bytestream2_put_byte(pbc, 4);
261 bytestream2_put_byte(pbc, 0);
262 bytestream2_put_byte(pbc, 0);
263 bytestream2_put_byte(pbc, 0);
264 /* Fill in compressed size later */
265 bytestream2_skip_p(pbc, 4);
266
267 ret = ctx->compress_tex(avctx);
268 if (ret < 0)
269 return ret;
270
271 AV_WL32(pkt->data + 8, bytestream2_tell_p(pbc) - DXV_HEADER_LENGTH);
272 av_shrink_packet(pkt, bytestream2_tell_p(pbc));
273
274 *got_packet = 1;
275 return 0;
276}
277
278static av_cold int dxv_init(AVCodecContext *avctx)
279{
280 DXVEncContext *ctx = avctx->priv_data;
281 int ret = av_image_check_size(avctx->width, avctx->height, 0, avctx);
282
283 if (ret < 0) {
284 av_log(avctx, AV_LOG_ERROR, "Invalid image size %dx%d.\n",
285 avctx->width, avctx->height);
286 return ret;
287 }
288
289 ff_texturedspenc_init(&ctx->texdsp);
290
291 switch (ctx->tex_fmt) {
292 case DXV_FMT_DXT1:
293 ctx->compress_tex = dxv_compress_dxt1;
294 ctx->enc.tex_funct = ctx->texdsp.dxt1_block;
295 ctx->enc.tex_ratio = 8;
296 break;
297 default:
298 av_log(avctx, AV_LOG_ERROR, "Invalid format %08X\n", ctx->tex_fmt);
299 return AVERROR_INVALIDDATA;
300 }
301 ctx->enc.raw_ratio = 16;
302 ctx->tex_size = FFALIGN(avctx->width, 16) / TEXTURE_BLOCK_W *
303 FFALIGN(avctx->height, 16) / TEXTURE_BLOCK_H *
304 ctx->enc.tex_ratio;
305 ctx->enc.slice_count = av_clip(avctx->thread_count, 1, FFALIGN(avctx->height, 16) / TEXTURE_BLOCK_H);
306
307 ctx->tex_data = av_malloc(ctx->tex_size);
308 if (!ctx->tex_data) {
309 return AVERROR(ENOMEM);
310 }
311
312 ctx->crc_ctx = (AVCRC*)av_crc_get_table(AV_CRC_32_IEEE);
313 if (!ctx->crc_ctx) {
314 av_log(avctx, AV_LOG_ERROR, "Could not initialize CRC table.\n");
315 return AVERROR_BUG;
316 }
317
318 return 0;
319}
320
321static av_cold int dxv_close(AVCodecContext *avctx)
322{
323 DXVEncContext *ctx = avctx->priv_data;
324
325 av_freep(&ctx->tex_data);
326
327 return 0;
328}
329
330#define OFFSET(x) offsetof(DXVEncContext, x)
331#define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
332static const AVOption options[] = {
333 { "format", NULL, OFFSET(tex_fmt), AV_OPT_TYPE_INT, { .i64 = DXV_FMT_DXT1 }, DXV_FMT_DXT1, DXV_FMT_DXT1, FLAGS, "format" },
334 { "dxt1", "DXT1 (Normal Quality, No Alpha)", 0, AV_OPT_TYPE_CONST, { .i64 = DXV_FMT_DXT1 }, 0, 0, FLAGS, "format" },
335 { NULL },
336};
337
338static const AVClass dxvenc_class = {
339 .class_name = "DXV encoder",
340 .option = options,
341 .version = LIBAVUTIL_VERSION_INT,
342};
343
344const FFCodec ff_dxv_encoder = {
345 .p.name = "dxv",
346 CODEC_LONG_NAME("Resolume DXV"),
347 .p.type = AVMEDIA_TYPE_VIDEO,
348 .p.id = AV_CODEC_ID_DXV,
349 .init = dxv_init,
350 FF_CODEC_ENCODE_CB(dxv_encode),
351 .close = dxv_close,
352 .priv_data_size = sizeof(DXVEncContext),
353 .p.capabilities = AV_CODEC_CAP_DR1 |
354 AV_CODEC_CAP_SLICE_THREADS |
355 AV_CODEC_CAP_FRAME_THREADS,
356 .p.priv_class = &dxvenc_class,
357 .p.pix_fmts = (const enum AVPixelFormat[]) {
358 AV_PIX_FMT_RGBA, AV_PIX_FMT_NONE,
359 },
360 .caps_internal = FF_CODEC_CAP_INIT_CLEANUP,
361};
libavcodec/version.h+1−1
@@ -29,7 +29,7 @@ · 28 unmodified lines
29 29
30#include "version_major.h"30#include "version_major.h"
31 31
32#define LIBAVCODEC_VERSION_MINOR 3732#define LIBAVCODEC_VERSION_MINOR 38
33#define LIBAVCODEC_VERSION_MICRO 10033#define LIBAVCODEC_VERSION_MICRO 100
34 34
35#define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \35#define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t