Line | Branch | Exec | Source |
---|---|---|---|
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/mem.h" | ||
27 | #include "libavutil/opt.h" | ||
28 | |||
29 | #include "bytestream.h" | ||
30 | #include "codec_internal.h" | ||
31 | #include "dxv.h" | ||
32 | #include "encode.h" | ||
33 | #include "texturedsp.h" | ||
34 | |||
35 | #define DXV_HEADER_LENGTH 12 | ||
36 | |||
37 | /* | ||
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. | ||
41 | */ | ||
42 | #define LOOKBACK_HT_ELEMS 0x40000 | ||
43 | #define LOOKBACK_WORDS 0x20202 | ||
44 | |||
45 | typedef struct HTEntry { | ||
46 | uint32_t key; | ||
47 | uint32_t pos; | ||
48 | } HTEntry; | ||
49 | |||
50 | 2 | static void ht_init(HTEntry *ht) | |
51 | { | ||
52 |
2/2✓ Branch 0 taken 524288 times.
✓ Branch 1 taken 2 times.
|
524290 | for (size_t i = 0; i < LOOKBACK_HT_ELEMS; i++) { |
53 | 524288 | ht[i].pos = -1; | |
54 | } | ||
55 | 2 | } | |
56 | |||
57 | 135986 | static uint32_t ht_lookup_and_upsert(HTEntry *ht, const AVCRC *hash_ctx, | |
58 | uint32_t key, uint32_t pos) | ||
59 | { | ||
60 | 135986 | uint32_t ret = -1; | |
61 | 135986 | size_t hash = av_crc(hash_ctx, 0, (uint8_t*)&key, 4) % LOOKBACK_HT_ELEMS; | |
62 |
1/2✓ Branch 0 taken 136009 times.
✗ Branch 1 not taken.
|
136009 | for (size_t i = hash; i < hash + LOOKBACK_HT_ELEMS; i++) { |
63 | 136009 | size_t wrapped_index = i % LOOKBACK_HT_ELEMS; | |
64 | 136009 | HTEntry *entry = &ht[wrapped_index]; | |
65 |
4/4✓ Branch 0 taken 5904 times.
✓ Branch 1 taken 130105 times.
✓ Branch 2 taken 5881 times.
✓ Branch 3 taken 23 times.
|
136009 | if (entry->key == key || entry->pos == -1) { |
66 | 135986 | ret = entry->pos; | |
67 | 135986 | entry->key = key; | |
68 | 135986 | entry->pos = pos; | |
69 | 135986 | break; | |
70 | } | ||
71 | } | ||
72 | 135986 | return ret; | |
73 | } | ||
74 | |||
75 | 127614 | static void ht_delete(HTEntry *ht, const AVCRC *hash_ctx, | |
76 | uint32_t key, uint32_t pos) | ||
77 | { | ||
78 | 127614 | HTEntry *removed_entry = NULL; | |
79 | size_t removed_hash; | ||
80 | 127614 | size_t hash = av_crc(hash_ctx, 0, (uint8_t*)&key, 4) % LOOKBACK_HT_ELEMS; | |
81 | |||
82 |
1/2✓ Branch 0 taken 128361 times.
✗ Branch 1 not taken.
|
128361 | for (size_t i = hash; i < hash + LOOKBACK_HT_ELEMS; i++) { |
83 | 128361 | size_t wrapped_index = i % LOOKBACK_HT_ELEMS; | |
84 | 128361 | HTEntry *entry = &ht[wrapped_index]; | |
85 |
2/2✓ Branch 0 taken 796 times.
✓ Branch 1 taken 127565 times.
|
128361 | if (entry->pos == -1) |
86 | 796 | return; | |
87 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 127560 times.
|
127565 | if (removed_entry) { |
88 | 5 | size_t candidate_hash = av_crc(hash_ctx, 0, (uint8_t*)&entry->key, 4) % LOOKBACK_HT_ELEMS; | |
89 |
4/8✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 5 times.
|
5 | 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; | |
92 | ✗ | entry->pos = -1; | |
93 | ✗ | removed_entry = entry; | |
94 | ✗ | removed_hash = wrapped_index; | |
95 | } | ||
96 |
1/2✓ Branch 0 taken 127560 times.
✗ Branch 1 not taken.
|
127560 | } else if (entry->key == key) { |
97 |
2/2✓ Branch 0 taken 742 times.
✓ Branch 1 taken 126818 times.
|
127560 | if (entry->pos <= pos) { |
98 | 742 | entry->pos = -1; | |
99 | 742 | removed_entry = entry; | |
100 | 742 | removed_hash = wrapped_index; | |
101 | } else { | ||
102 | 126818 | return; | |
103 | } | ||
104 | } | ||
105 | } | ||
106 | } | ||
107 | |||
108 | typedef struct DXVEncContext { | ||
109 | AVClass *class; | ||
110 | |||
111 | PutByteContext pbc; | ||
112 | |||
113 | uint8_t *tex_data; // Compressed texture | ||
114 | int64_t tex_size; // Texture size | ||
115 | |||
116 | /* Optimal number of slices for parallel decoding */ | ||
117 | int slice_count; | ||
118 | |||
119 | TextureDSPThreadContext enc; | ||
120 | |||
121 | DXVTextureFormat tex_fmt; | ||
122 | int (*compress_tex)(AVCodecContext *avctx); | ||
123 | |||
124 | const AVCRC *crc_ctx; | ||
125 | |||
126 | HTEntry color_lookback_ht[LOOKBACK_HT_ELEMS]; | ||
127 | HTEntry lut_lookback_ht[LOOKBACK_HT_ELEMS]; | ||
128 | } DXVEncContext; | ||
129 | |||
130 | /* Converts an index offset value to a 2-bit opcode and pushes it to a stream. | ||
131 | * Inverse of CHECKPOINT in dxv.c. */ | ||
132 | #define PUSH_OP(x) \ | ||
133 | do { \ | ||
134 | if (state == 16) { \ | ||
135 | if (bytestream2_get_bytes_left_p(pbc) < 4) { \ | ||
136 | return AVERROR_INVALIDDATA; \ | ||
137 | } \ | ||
138 | value = pbc->buffer; \ | ||
139 | bytestream2_put_le32(pbc, 0); \ | ||
140 | state = 0; \ | ||
141 | } \ | ||
142 | if (idx >= 0x102 * x) { \ | ||
143 | op = 3; \ | ||
144 | bytestream2_put_le16(pbc, (idx / x) - 0x102); \ | ||
145 | } else if (idx >= 2 * x) { \ | ||
146 | op = 2; \ | ||
147 | bytestream2_put_byte(pbc, (idx / x) - 2); \ | ||
148 | } else if (idx == x) { \ | ||
149 | op = 1; \ | ||
150 | } else { \ | ||
151 | op = 0; \ | ||
152 | } \ | ||
153 | AV_WL32(value, AV_RL32(value) | (op << (state * 2))); \ | ||
154 | state++; \ | ||
155 | } while (0) | ||
156 | |||
157 | 1 | static int dxv_compress_dxt1(AVCodecContext *avctx) | |
158 | { | ||
159 | 1 | DXVEncContext *ctx = avctx->priv_data; | |
160 | 1 | PutByteContext *pbc = &ctx->pbc; | |
161 | void *value; | ||
162 | 1 | uint32_t color, lut, idx, color_idx, lut_idx, prev_pos, state = 16, pos = 2, op = 0; | |
163 | |||
164 | 1 | ht_init(ctx->color_lookback_ht); | |
165 | 1 | ht_init(ctx->lut_lookback_ht); | |
166 | |||
167 | 1 | bytestream2_put_le32(pbc, AV_RL32(ctx->tex_data)); | |
168 | 1 | bytestream2_put_le32(pbc, AV_RL32(ctx->tex_data + 4)); | |
169 | |||
170 | 1 | ht_lookup_and_upsert(ctx->color_lookback_ht, ctx->crc_ctx, AV_RL32(ctx->tex_data), 0); | |
171 | 1 | ht_lookup_and_upsert(ctx->lut_lookback_ht, ctx->crc_ctx, AV_RL32(ctx->tex_data + 4), 1); | |
172 | |||
173 |
2/2✓ Branch 0 taken 129599 times.
✓ Branch 1 taken 1 times.
|
129600 | while (pos + 2 <= ctx->tex_size / 4) { |
174 | 129599 | idx = 0; | |
175 | |||
176 | 129599 | color = AV_RL32(ctx->tex_data + pos * 4); | |
177 | 129599 | prev_pos = ht_lookup_and_upsert(ctx->color_lookback_ht, ctx->crc_ctx, color, pos); | |
178 |
2/2✓ Branch 0 taken 127924 times.
✓ Branch 1 taken 1675 times.
|
129599 | color_idx = prev_pos != -1 ? pos - prev_pos : 0; |
179 |
2/2✓ Branch 0 taken 63807 times.
✓ Branch 1 taken 65792 times.
|
129599 | if (pos >= LOOKBACK_WORDS) { |
180 | 63807 | uint32_t old_pos = pos - LOOKBACK_WORDS; | |
181 | 63807 | uint32_t old_color = AV_RL32(ctx->tex_data + old_pos * 4); | |
182 | 63807 | ht_delete(ctx->color_lookback_ht, ctx->crc_ctx, old_color, old_pos); | |
183 | } | ||
184 | 129599 | pos++; | |
185 | |||
186 | 129599 | lut = AV_RL32(ctx->tex_data + pos * 4); | |
187 |
4/4✓ Branch 0 taken 127924 times.
✓ Branch 1 taken 1675 times.
✓ Branch 2 taken 123214 times.
✓ Branch 3 taken 4710 times.
|
129599 | if (color_idx && lut == AV_RL32(ctx->tex_data + (pos - color_idx) * 4)) { |
188 | 123214 | idx = color_idx; | |
189 | } else { | ||
190 | 6385 | idx = 0; | |
191 | 6385 | prev_pos = ht_lookup_and_upsert(ctx->lut_lookback_ht, ctx->crc_ctx, lut, pos); | |
192 |
2/2✓ Branch 0 taken 2179 times.
✓ Branch 1 taken 4206 times.
|
6385 | lut_idx = prev_pos != -1 ? pos - prev_pos : 0; |
193 | } | ||
194 |
2/2✓ Branch 0 taken 63807 times.
✓ Branch 1 taken 65792 times.
|
129599 | if (pos >= LOOKBACK_WORDS) { |
195 | 63807 | uint32_t old_pos = pos - LOOKBACK_WORDS; | |
196 | 63807 | uint32_t old_lut = AV_RL32(ctx->tex_data + old_pos * 4); | |
197 | 63807 | ht_delete(ctx->lut_lookback_ht, ctx->crc_ctx, old_lut, old_pos); | |
198 | } | ||
199 | 129599 | pos++; | |
200 | |||
201 |
9/10✓ Branch 0 taken 8111 times.
✓ Branch 1 taken 121488 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8111 times.
✓ Branch 6 taken 2535 times.
✓ Branch 7 taken 127064 times.
✓ Branch 9 taken 798 times.
✓ Branch 10 taken 126266 times.
✓ Branch 12 taken 119881 times.
✓ Branch 13 taken 6385 times.
|
129599 | PUSH_OP(2); |
202 | |||
203 |
2/2✓ Branch 0 taken 6385 times.
✓ Branch 1 taken 123214 times.
|
129599 | if (!idx) { |
204 | 6385 | idx = color_idx; | |
205 |
9/10✓ Branch 0 taken 385 times.
✓ Branch 1 taken 6000 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 385 times.
✓ Branch 6 taken 3456 times.
✓ Branch 7 taken 2929 times.
✓ Branch 9 taken 730 times.
✓ Branch 10 taken 2199 times.
✓ Branch 12 taken 524 times.
✓ Branch 13 taken 1675 times.
|
6385 | PUSH_OP(2); |
206 |
2/2✓ Branch 0 taken 1675 times.
✓ Branch 1 taken 4710 times.
|
6385 | if (!idx) |
207 | 1675 | bytestream2_put_le32(pbc, color); | |
208 | |||
209 | 6385 | idx = lut_idx; | |
210 |
9/10✓ Branch 0 taken 403 times.
✓ Branch 1 taken 5982 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 403 times.
✓ Branch 6 taken 1797 times.
✓ Branch 7 taken 4588 times.
✓ Branch 9 taken 277 times.
✓ Branch 10 taken 4311 times.
✓ Branch 12 taken 105 times.
✓ Branch 13 taken 4206 times.
|
6385 | PUSH_OP(2); |
211 |
2/2✓ Branch 0 taken 4206 times.
✓ Branch 1 taken 2179 times.
|
6385 | if (!idx) |
212 | 4206 | bytestream2_put_le32(pbc, lut); | |
213 | } | ||
214 | } | ||
215 | |||
216 | 1 | return 0; | |
217 | } | ||
218 | |||
219 | 1 | static int dxv_encode(AVCodecContext *avctx, AVPacket *pkt, | |
220 | const AVFrame *frame, int *got_packet) | ||
221 | { | ||
222 | 1 | DXVEncContext *ctx = avctx->priv_data; | |
223 | 1 | PutByteContext *pbc = &ctx->pbc; | |
224 | int ret; | ||
225 | |||
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 | 1 | ret = ff_alloc_packet(avctx, pkt, DXV_HEADER_LENGTH + ctx->tex_size + AV_CEIL_RSHIFT(ctx->tex_size - 8, 7) * 12); | |
230 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (ret < 0) |
231 | ✗ | return ret; | |
232 | |||
233 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (ctx->enc.tex_funct) { |
234 | 1 | ctx->enc.tex_data.out = ctx->tex_data; | |
235 | 1 | ctx->enc.frame_data.in = frame->data[0]; | |
236 | 1 | ctx->enc.stride = frame->linesize[0]; | |
237 | 1 | ctx->enc.width = avctx->width; | |
238 | 1 | ctx->enc.height = avctx->height; | |
239 | 1 | ff_texturedsp_exec_compress_threads(avctx, &ctx->enc); | |
240 | } else { | ||
241 | /* unimplemented: YCoCg formats */ | ||
242 | ✗ | return AVERROR_INVALIDDATA; | |
243 | } | ||
244 | |||
245 | 1 | bytestream2_init_writer(pbc, pkt->data, pkt->size); | |
246 | |||
247 | 1 | bytestream2_put_le32(pbc, ctx->tex_fmt); | |
248 | 1 | bytestream2_put_byte(pbc, 4); | |
249 | 1 | bytestream2_put_byte(pbc, 0); | |
250 | 1 | bytestream2_put_byte(pbc, 0); | |
251 | 1 | bytestream2_put_byte(pbc, 0); | |
252 | /* Fill in compressed size later */ | ||
253 | 1 | bytestream2_skip_p(pbc, 4); | |
254 | |||
255 | 1 | ret = ctx->compress_tex(avctx); | |
256 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (ret < 0) |
257 | ✗ | return ret; | |
258 | |||
259 | 1 | AV_WL32(pkt->data + 8, bytestream2_tell_p(pbc) - DXV_HEADER_LENGTH); | |
260 | 1 | av_shrink_packet(pkt, bytestream2_tell_p(pbc)); | |
261 | |||
262 | 1 | *got_packet = 1; | |
263 | 1 | return 0; | |
264 | } | ||
265 | |||
266 | 9 | static av_cold int dxv_init(AVCodecContext *avctx) | |
267 | { | ||
268 | 9 | DXVEncContext *ctx = avctx->priv_data; | |
269 | TextureDSPEncContext texdsp; | ||
270 | 9 | int ret = av_image_check_size(avctx->width, avctx->height, 0, avctx); | |
271 | |||
272 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (ret < 0) { |
273 | ✗ | av_log(avctx, AV_LOG_ERROR, "Invalid image size %dx%d.\n", | |
274 | avctx->width, avctx->height); | ||
275 | ✗ | return ret; | |
276 | } | ||
277 | |||
278 |
2/4✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
|
9 | if (avctx->width % TEXTURE_BLOCK_W || avctx->height % TEXTURE_BLOCK_H) { |
279 | ✗ | av_log(avctx, | |
280 | AV_LOG_ERROR, | ||
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; | |
284 | } | ||
285 | |||
286 | 9 | ff_texturedspenc_init(&texdsp); | |
287 | |||
288 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | switch (ctx->tex_fmt) { |
289 | 9 | case DXV_FMT_DXT1: | |
290 | 9 | ctx->compress_tex = dxv_compress_dxt1; | |
291 | 9 | ctx->enc.tex_funct = texdsp.dxt1_block; | |
292 | 9 | ctx->enc.tex_ratio = 8; | |
293 | 9 | break; | |
294 | ✗ | default: | |
295 | ✗ | av_log(avctx, AV_LOG_ERROR, "Invalid format %08X\n", ctx->tex_fmt); | |
296 | ✗ | return AVERROR_INVALIDDATA; | |
297 | } | ||
298 | 9 | ctx->enc.raw_ratio = 16; | |
299 | 9 | ctx->tex_size = avctx->width / TEXTURE_BLOCK_W * | |
300 | 9 | avctx->height / TEXTURE_BLOCK_H * | |
301 | 9 | ctx->enc.tex_ratio; | |
302 | 9 | ctx->enc.slice_count = av_clip(avctx->thread_count, 1, avctx->height / TEXTURE_BLOCK_H); | |
303 | |||
304 | 9 | ctx->tex_data = av_malloc(ctx->tex_size); | |
305 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (!ctx->tex_data) { |
306 | ✗ | return AVERROR(ENOMEM); | |
307 | } | ||
308 | |||
309 | 9 | ctx->crc_ctx = av_crc_get_table(AV_CRC_32_IEEE); | |
310 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (!ctx->crc_ctx) { |
311 | ✗ | av_log(avctx, AV_LOG_ERROR, "Could not initialize CRC table.\n"); | |
312 | ✗ | return AVERROR_BUG; | |
313 | } | ||
314 | |||
315 | 9 | return 0; | |
316 | } | ||
317 | |||
318 | 9 | static av_cold int dxv_close(AVCodecContext *avctx) | |
319 | { | ||
320 | 9 | DXVEncContext *ctx = avctx->priv_data; | |
321 | |||
322 | 9 | av_freep(&ctx->tex_data); | |
323 | |||
324 | 9 | return 0; | |
325 | } | ||
326 | |||
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" }, | ||
332 | { NULL }, | ||
333 | }; | ||
334 | |||
335 | static const AVClass dxvenc_class = { | ||
336 | .class_name = "DXV encoder", | ||
337 | .option = options, | ||
338 | .version = LIBAVUTIL_VERSION_INT, | ||
339 | }; | ||
340 | |||
341 | const FFCodec ff_dxv_encoder = { | ||
342 | .p.name = "dxv", | ||
343 | CODEC_LONG_NAME("Resolume DXV"), | ||
344 | .p.type = AVMEDIA_TYPE_VIDEO, | ||
345 | .p.id = AV_CODEC_ID_DXV, | ||
346 | .init = dxv_init, | ||
347 | FF_CODEC_ENCODE_CB(dxv_encode), | ||
348 | .close = dxv_close, | ||
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, | ||
356 | }, | ||
357 | .caps_internal = FF_CODEC_CAP_INIT_CLEANUP, | ||
358 | }; | ||
359 |