FFmpeg
Loading...
Searching...
No Matches
dxvenc.c
Go to the documentation of this file.
1/*
2 * Resolume DXV encoder
3 * Copyright (C) 2024 Emma Worley <emma@emma.gg>
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
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 * Resolume will refuse to display frames that are not padded to 16x16 pixels.
39 */
40#define DXV_ALIGN(x) FFALIGN(x, 16)
41
42/*
43 * DXV uses LZ-like back-references to avoid copying words that have already
44 * appeared in the decompressed stream. Using a simple hash table (HT)
45 * significantly speeds up the lookback process while encoding.
46 */
47#define LOOKBACK_HT_ELEMS 0x20202
48#define LOOKBACK_WORDS 0x20202
49
50typedef struct DXVEncContext {
51 AVClass *class;
52
54
55 uint8_t *tex_data; // Compressed texture
56 int64_t tex_size; // Texture size
57
58 /* Optimal number of slices for parallel decoding */
60
62
65
70
71/* Converts an index offset value to a 2-bit opcode and pushes it to a stream.
72 * Inverse of CHECKPOINT in dxv.c. */
73#define PUSH_OP(x) \
74 do { \
75 if (state == 16) { \
76 if (bytestream2_get_bytes_left_p(pbc) < 4) { \
77 return AVERROR_INVALIDDATA; \
78 } \
79 value = pbc->buffer; \
80 bytestream2_put_le32(pbc, 0); \
81 state = 0; \
82 } \
83 if (idx >= 0x102 * x) { \
84 op = 3; \
85 bytestream2_put_le16(pbc, (idx / x) - 0x102); \
86 } else if (idx >= 2 * x) { \
87 op = 2; \
88 bytestream2_put_byte(pbc, (idx / x) - 2); \
89 } else if (idx == x) { \
90 op = 1; \
91 } else { \
92 op = 0; \
93 } \
94 AV_WL32(value, AV_RL32(value) | (op << (state * 2))); \
95 state++; \
96 } while (0)
97
99{
100 DXVEncContext *ctx = avctx->priv_data;
101 PutByteContext *pbc = &ctx->pbc;
102 void *value;
103 uint32_t idx, combo_idx, prev_pos, old_pos, state = 16, pos = 0, op = 0;
104
105 ff_hashtable_clear(ctx->color_ht);
106 ff_hashtable_clear(ctx->lut_ht);
107 ff_hashtable_clear(ctx->combo_ht);
108
109 ff_hashtable_set(ctx->combo_ht, ctx->tex_data, &pos);
110
111 bytestream2_put_le32(pbc, AV_RL32(ctx->tex_data));
112 ff_hashtable_set(ctx->color_ht, ctx->tex_data, &pos);
113 pos++;
114 bytestream2_put_le32(pbc, AV_RL32(ctx->tex_data + 4));
115 ff_hashtable_set(ctx->lut_ht, ctx->tex_data + 4, &pos);
116 pos++;
117
118 while (pos + 2 <= ctx->tex_size / 4) {
119 combo_idx = ff_hashtable_get(ctx->combo_ht, ctx->tex_data + pos * 4, &prev_pos) ? pos - prev_pos : 0;
120 idx = combo_idx;
121 PUSH_OP(2);
122 if (pos >= LOOKBACK_WORDS) {
123 old_pos = pos - LOOKBACK_WORDS;
124 if (ff_hashtable_get(ctx->combo_ht, ctx->tex_data + old_pos * 4, &prev_pos) && prev_pos <= old_pos)
125 ff_hashtable_delete(ctx->combo_ht, ctx->tex_data + old_pos * 4);
126 }
127 ff_hashtable_set(ctx->combo_ht, ctx->tex_data + pos * 4, &pos);
128
129 if (!combo_idx) {
130 idx = ff_hashtable_get(ctx->color_ht, ctx->tex_data + pos * 4, &prev_pos) ? pos - prev_pos : 0;
131 PUSH_OP(2);
132 if (!idx)
133 bytestream2_put_le32(pbc, AV_RL32(ctx->tex_data + pos * 4));
134 }
135 if (pos >= LOOKBACK_WORDS) {
136 old_pos = pos - LOOKBACK_WORDS;
137 if (ff_hashtable_get(ctx->color_ht, ctx->tex_data + old_pos * 4, &prev_pos) && prev_pos <= old_pos)
138 ff_hashtable_delete(ctx->color_ht, ctx->tex_data + old_pos * 4);
139 }
140 ff_hashtable_set(ctx->color_ht, ctx->tex_data + pos * 4, &pos);
141 pos++;
142
143 if (!combo_idx) {
144 idx = ff_hashtable_get(ctx->lut_ht, ctx->tex_data + pos * 4, &prev_pos) ? pos - prev_pos : 0;
145 PUSH_OP(2);
146 if (!idx)
147 bytestream2_put_le32(pbc, AV_RL32(ctx->tex_data + pos * 4));
148 }
149 if (pos >= LOOKBACK_WORDS) {
150 old_pos = pos - LOOKBACK_WORDS;
151 if (ff_hashtable_get(ctx->lut_ht, ctx->tex_data + old_pos * 4, &prev_pos) && prev_pos <= old_pos)
152 ff_hashtable_delete(ctx->lut_ht, ctx->tex_data + old_pos * 4);
153 }
154 ff_hashtable_set(ctx->lut_ht, ctx->tex_data + pos * 4, &pos);
155 pos++;
156 }
157
158 return 0;
159}
160
162 const AVFrame *frame, int *got_packet)
163{
164 DXVEncContext *ctx = avctx->priv_data;
165 PutByteContext *pbc = &ctx->pbc;
166 int ret;
167
168 /* unimplemented: needs to depend on compression ratio of tex format */
169 /* under DXT1, we need 3 words to encode load ops for 32 words.
170 * the first 2 words of the texture do not need load ops. */
171 ret = ff_alloc_packet(avctx, pkt, DXV_HEADER_LENGTH + ctx->tex_size + AV_CEIL_RSHIFT(ctx->tex_size - 8, 7) * 12);
172 if (ret < 0)
173 return ret;
174
175 if (ctx->enc.tex_funct) {
176 uint8_t *safe_data[4] = {frame->data[0], 0, 0, 0};
177 int safe_linesize[4] = {frame->linesize[0], 0, 0, 0};
178
179 if (avctx->width != DXV_ALIGN(avctx->width) || avctx->height != DXV_ALIGN(avctx->height)) {
180 ret = av_image_alloc(
181 safe_data,
182 safe_linesize,
183 DXV_ALIGN(avctx->width),
184 DXV_ALIGN(avctx->height),
185 avctx->pix_fmt,
186 1);
187 if (ret < 0)
188 return ret;
189
191 safe_data,
192 safe_linesize,
193 frame->data,
194 frame->linesize,
195 avctx->pix_fmt,
196 avctx->width,
197 avctx->height);
198
199 if (avctx->width != DXV_ALIGN(avctx->width)) {
200 av_assert0(frame->format == AV_PIX_FMT_RGBA);
201 for (int y = 0; y < avctx->height; y++) {
202 memset(safe_data[0] + y * safe_linesize[0] + 4*avctx->width, 0, safe_linesize[0] - 4*avctx->width);
203 }
204 }
205 if (avctx->height != DXV_ALIGN(avctx->height)) {
206 for (int y = avctx->height; y < DXV_ALIGN(avctx->height); y++) {
207 memset(safe_data[0] + y * safe_linesize[0], 0, safe_linesize[0]);
208 }
209 }
210 }
211
212 ctx->enc.tex_data.out = ctx->tex_data;
213 ctx->enc.frame_data.in = safe_data[0];
214 ctx->enc.stride = safe_linesize[0];
215 ctx->enc.width = DXV_ALIGN(avctx->width);
216 ctx->enc.height = DXV_ALIGN(avctx->height);
218
219 if (safe_data[0] != frame->data[0])
220 av_freep(&safe_data[0]);
221 } else {
222 /* unimplemented: YCoCg formats */
223 return AVERROR_INVALIDDATA;
224 }
225
226 bytestream2_init_writer(pbc, pkt->data, pkt->size);
227
228 bytestream2_put_le32(pbc, ctx->tex_fmt);
229 bytestream2_put_byte(pbc, 4);
230 bytestream2_put_byte(pbc, 0);
231 bytestream2_put_byte(pbc, 0);
232 bytestream2_put_byte(pbc, 0);
233 /* Fill in compressed size later */
234 bytestream2_skip_p(pbc, 4);
235
236 ret = ctx->compress_tex(avctx);
237 if (ret < 0)
238 return ret;
239
242
243 *got_packet = 1;
244 return 0;
245}
246
248{
249 DXVEncContext *ctx = avctx->priv_data;
251 int ret = av_image_check_size(avctx->width, avctx->height, 0, avctx);
252
253 if (ret < 0) {
254 av_log(avctx, AV_LOG_ERROR, "Invalid image size %dx%d.\n",
255 avctx->width, avctx->height);
256 return ret;
257 }
258
259 ff_texturedspenc_init(&texdsp);
260
261 switch (ctx->tex_fmt) {
262 case DXV_FMT_DXT1:
263 ctx->compress_tex = dxv_compress_dxt1;
264 ctx->enc.tex_funct = texdsp.dxt1_block;
265 ctx->enc.tex_ratio = 8;
266 break;
267 default:
268 av_log(avctx, AV_LOG_ERROR, "Invalid format %08X\n", ctx->tex_fmt);
269 return AVERROR_INVALIDDATA;
270 }
271 ctx->enc.raw_ratio = 16;
272 ctx->tex_size = DXV_ALIGN(avctx->width) / TEXTURE_BLOCK_W *
274 ctx->enc.tex_ratio;
275 ctx->enc.slice_count = av_clip(avctx->thread_count, 1, DXV_ALIGN(avctx->height) / TEXTURE_BLOCK_H);
276
277 ctx->tex_data = av_malloc(ctx->tex_size);
278 if (!ctx->tex_data) {
279 return AVERROR(ENOMEM);
280 }
281
282 ret = ff_hashtable_alloc(&ctx->color_ht, sizeof(uint32_t), sizeof(uint32_t), LOOKBACK_HT_ELEMS);
283 if (ret < 0)
284 return ret;
285 ret = ff_hashtable_alloc(&ctx->lut_ht, sizeof(uint32_t), sizeof(uint32_t), LOOKBACK_HT_ELEMS);
286 if (ret < 0)
287 return ret;
288 ret = ff_hashtable_alloc(&ctx->combo_ht, sizeof(uint64_t), sizeof(uint32_t), LOOKBACK_HT_ELEMS);
289 if (ret < 0)
290 return ret;
291
292 return 0;
293}
294
296{
297 DXVEncContext *ctx = avctx->priv_data;
298
299 av_freep(&ctx->tex_data);
300
301 ff_hashtable_freep(&ctx->color_ht);
302 ff_hashtable_freep(&ctx->lut_ht);
303 ff_hashtable_freep(&ctx->combo_ht);
304
305 return 0;
306}
307
308#define OFFSET(x) offsetof(DXVEncContext, x)
309#define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
310static const AVOption options[] = {
311 { "format", NULL, OFFSET(tex_fmt), AV_OPT_TYPE_INT, { .i64 = DXV_FMT_DXT1 }, DXV_FMT_DXT1, DXV_FMT_DXT1, FLAGS, .unit = "format" },
312 { "dxt1", "DXT1 (Normal Quality, No Alpha)", 0, AV_OPT_TYPE_CONST, { .i64 = DXV_FMT_DXT1 }, 0, 0, FLAGS, .unit = "format" },
313 { NULL },
314};
315
316static const AVClass dxvenc_class = {
317 .class_name = "DXV encoder",
318 .option = options,
319 .version = LIBAVUTIL_VERSION_INT,
320};
321
323 .p.name = "dxv",
324 CODEC_LONG_NAME("Resolume DXV"),
325 .p.type = AVMEDIA_TYPE_VIDEO,
326 .p.id = AV_CODEC_ID_DXV,
327 .init = dxv_init,
329 .close = dxv_close,
330 .priv_data_size = sizeof(DXVEncContext),
331 .p.capabilities = AV_CODEC_CAP_DR1 |
334 .p.priv_class = &dxvenc_class,
336 .caps_internal = FF_CODEC_CAP_INIT_CLEANUP,
337};
const FFCodec ff_dxv_encoder
Definition dxvenc.c:322
#define av_assert0(cond)
assert() equivalent, that is always enabled.
Definition avassert.h:42
static av_always_inline int bytestream2_tell_p(const PutByteContext *p)
Definition bytestream.h:197
static av_always_inline void bytestream2_init_writer(PutByteContext *p, uint8_t *buf, int buf_size)
Definition bytestream.h:147
static av_always_inline void bytestream2_skip_p(PutByteContext *p, unsigned int size)
Definition bytestream.h:180
#define FLAGS
Definition cmdutils.c:598
#define CODEC_PIXFMTS(...)
#define FF_CODEC_ENCODE_CB(func)
#define CODEC_LONG_NAME(str)
#define FF_CODEC_CAP_INIT_CLEANUP
The codec allows calling the close function for deallocation even if the init function returned a fai...
#define AV_CEIL_RSHIFT(a, b)
Definition common.h:60
#define av_clip
Definition common.h:100
#define NULL
Definition coverity.c:32
long long int64_t
Definition coverity.c:34
static AVPacket * pkt
static AVFrame * frame
static av_cold int dxv_init(AVCodecContext *avctx)
Definition dxv.c:1081
static av_cold int dxv_close(AVCodecContext *avctx)
Definition dxv.c:1101
DXVTextureFormat
Definition dxv.h:27
@ DXV_FMT_DXT1
Definition dxv.h:28
static av_cold int dxv_init(AVCodecContext *avctx)
Definition dxvenc.c:247
static const AVClass dxvenc_class
Definition dxvenc.c:316
#define DXV_ALIGN(x)
Definition dxvenc.c:40
static int dxv_encode(AVCodecContext *avctx, AVPacket *pkt, const AVFrame *frame, int *got_packet)
Definition dxvenc.c:161
#define OFFSET(x)
Definition dxvenc.c:308
static av_cold int dxv_close(AVCodecContext *avctx)
Definition dxvenc.c:295
#define PUSH_OP(x)
Definition dxvenc.c:73
static int dxv_compress_dxt1(AVCodecContext *avctx)
Definition dxvenc.c:98
#define LOOKBACK_WORDS
Definition dxvenc.c:48
#define DXV_HEADER_LENGTH
Definition dxvenc.c:35
#define LOOKBACK_HT_ELEMS
Definition dxvenc.c:47
int ff_alloc_packet(AVCodecContext *avctx, AVPacket *avpkt, int64_t size)
Check AVPacket size and allocate data.
Definition encode.c:62
double value
Definition eval.c:102
static struct @346255127015250356166251341105367306144006377143 state
@ AV_OPT_TYPE_CONST
Special option type for declaring named constants.
Definition opt.h:298
@ AV_OPT_TYPE_INT
Underlying C type is int.
Definition opt.h:258
#define AV_CODEC_CAP_DR1
Codec uses get_buffer() or get_encode_buffer() for allocating buffers and supports custom allocators.
Definition codec.h:49
#define AV_CODEC_CAP_SLICE_THREADS
Codec supports slice-based (or partition-based) multithreading.
Definition codec.h:102
#define AV_CODEC_CAP_FRAME_THREADS
Codec supports frame-level multithreading.
Definition codec.h:98
@ AV_CODEC_ID_DXV
Definition codec_id.h:240
void av_shrink_packet(AVPacket *pkt, int size)
Reduce packet size, correctly zeroing padding.
Definition packet.c:113
#define AVERROR_INVALIDDATA
Invalid data found when processing input.
Definition error.h:61
#define AVERROR(e)
Definition error.h:45
#define AV_LOG_ERROR
Something went wrong and cannot losslessly be recovered.
Definition log.h:210
@ AVMEDIA_TYPE_VIDEO
Definition avutil.h:200
int av_image_check_size(unsigned int w, unsigned int h, int log_offset, void *log_ctx)
Check if the given dimension of an image is valid, meaning that all bytes of the image can be address...
Definition imgutils.c:318
int av_image_alloc(uint8_t *pointers[4], int linesizes[4], int w, int h, enum AVPixelFormat pix_fmt, int align)
Allocate an image with size w and h and pixel format pix_fmt, and fill pointers and linesizes accordi...
Definition imgutils.c:218
static void av_image_copy2(uint8_t *const dst_data[4], const int dst_linesizes[4], uint8_t *const src_data[4], const int src_linesizes[4], enum AVPixelFormat pix_fmt, int width, int height)
Wrapper around av_image_copy() to workaround the limitation that the conversion from uint8_t * const ...
Definition imgutils.h:184
#define LIBAVUTIL_VERSION_INT
Definition version.h:85
av_cold int ff_hashtable_alloc(FFHashtableContext **ctx, size_t key_size, size_t val_size, size_t max_entries)
Create a fixed-sized Robin Hood hash table.
Definition hashtable.c:59
int ff_hashtable_set(struct FFHashtableContext *ctx, const void *key, const void *val)
Store a value in a hash table given a key.
Definition hashtable.c:119
int ff_hashtable_delete(struct FFHashtableContext *ctx, const void *key)
Delete a value from a hash table given a key.
Definition hashtable.c:163
void ff_hashtable_clear(struct FFHashtableContext *ctx)
Delete all values from a hash table.
Definition hashtable.c:201
int ff_hashtable_get(const struct FFHashtableContext *ctx, const void *key, void *val)
Look up a value from a hash table given a key.
Definition hashtable.c:97
av_cold void ff_hashtable_freep(FFHashtableContext **ctx)
Free a hash table.
Definition hashtable.c:206
misc image utilities
#define AV_WL32(p, v)
#define AV_RL32(p)
static int op(uint8_t **dst, const uint8_t *dst_end, GetByteContext *gb, int pixel, int count, int *x, int width, int linesize)
Perform decode operation.
Definition anm.c:76
#define av_cold
Definition attributes.h:117
Memory handling functions.
#define av_malloc(s)
Definition ops_static.c:52
AVOptions.
@ AV_PIX_FMT_RGBA
packed RGBA 8:8:8:8, 32bpp, RGBARGBA...
Definition pixfmt.h:100
unsigned int pos
Definition spdifenc.c:431
Describe the class of an AVClass context structure.
Definition log.h:76
main external API structure.
Definition avcodec.h:443
enum AVPixelFormat pix_fmt
Pixel format, see AV_PIX_FMT_xxx.
Definition avcodec.h:643
int width
picture width / height.
Definition avcodec.h:604
int thread_count
thread count is used to decide how many independent tasks should be passed to execute()
Definition avcodec.h:1579
void * priv_data
Definition avcodec.h:470
This structure describes decoded (raw) audio or video data.
Definition frame.h:472
AVOption.
Definition opt.h:428
This structure stores compressed data.
Definition packet.h:580
FFHashtableContext * lut_ht
Definition dxvenc.c:67
FFHashtableContext * combo_ht
Definition dxvenc.c:68
PutByteContext pbc
Definition dxvenc.c:53
TextureDSPThreadContext enc
Definition dxvenc.c:61
DXVTextureFormat tex_fmt
Definition dxvenc.c:63
FFHashtableContext * color_ht
Definition dxvenc.c:66
int slice_count
Definition dxvenc.c:59
uint8_t * tex_data
Definition dxvenc.c:55
int(* compress_tex)(AVCodecContext *avctx)
Definition dxvenc.c:64
int64_t tex_size
Definition dxvenc.c:56
int(* dxt1_block)(uint8_t *dst, ptrdiff_t stride, const uint8_t *block)
Definition texturedsp.h:64
#define av_freep(p)
#define av_log(a,...)
static AVFormatContext * ctx
Definition movenc.c:49
Texture block (4x4) module.
#define TEXTURE_BLOCK_W
Definition texturedsp.h:42
void ff_texturedspenc_init(TextureDSPEncContext *c)
#define TEXTURE_BLOCK_H
Definition texturedsp.h:43
int ff_texturedsp_exec_compress_threads(struct AVCodecContext *avctx, TextureDSPThreadContext *ctx)