Branch data Line data Source code
1 : : /*
2 : : * This file is part of libplacebo.
3 : : *
4 : : * libplacebo is free software; you can redistribute it and/or
5 : : * modify it under the terms of the GNU Lesser General Public
6 : : * License as published by the Free Software Foundation; either
7 : : * version 2.1 of the License, or (at your option) any later version.
8 : : *
9 : : * libplacebo is distributed in the hope that it will be useful,
10 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : : * GNU Lesser General Public License for more details.
13 : : *
14 : : * You should have received a copy of the GNU Lesser General Public
15 : : * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
16 : : */
17 : :
18 : : #include "log.h"
19 : : #include "common.h"
20 : : #include "gpu.h"
21 : :
22 : : #include <libplacebo/utils/upload.h>
23 : :
24 : : #define MAX_COMPS 4
25 : :
26 : : struct comp {
27 : : int order; // e.g. 0, 1, 2, 3 for RGBA
28 : : int size; // size in bits
29 : : int shift; // bit-shift / offset in bits
30 : : };
31 : :
32 : 2088 : static int compare_comp(const void *pa, const void *pb)
33 : : {
34 : : const struct comp *a = pa, *b = pb;
35 : :
36 : : // Move all of the components with a size of 0 to the end, so they can
37 : : // be ignored outright
38 [ + + + + ]: 2088 : if (a->size && !b->size)
39 : : return -1;
40 [ + + + + ]: 1493 : if (b->size && !a->size)
41 : : return 1;
42 : :
43 : : // Otherwise, just compare based on the shift
44 : 1170 : return PL_CMP(a->shift, b->shift);
45 : : }
46 : :
47 : 475 : void pl_plane_data_from_comps(struct pl_plane_data *data, int size[4],
48 : : int shift[4])
49 : : {
50 : : struct comp comps[MAX_COMPS];
51 [ + + ]: 2375 : for (int i = 0; i < PL_ARRAY_SIZE(comps); i++) {
52 : 1900 : comps[i].order = i;
53 : 1900 : comps[i].size = size[i];
54 : 1900 : comps[i].shift = shift[i];
55 : : }
56 : :
57 : : // Sort the components by shift
58 : 475 : qsort(comps, MAX_COMPS, sizeof(struct comp), compare_comp);
59 : :
60 : : // Generate the resulting component size/pad/map
61 : : int offset = 0;
62 [ + + ]: 2375 : for (int i = 0; i < MAX_COMPS; i++) {
63 [ + + ]: 1900 : if (comps[i].size) {
64 [ - + ]: 651 : assert(comps[i].shift >= offset);
65 : 651 : data->component_size[i] = comps[i].size;
66 : 651 : data->component_pad[i] = comps[i].shift - offset;
67 : 651 : data->component_map[i] = comps[i].order;
68 : 651 : offset += data->component_size[i] + data->component_pad[i];
69 : : } else {
70 : : // Clear the superfluous entries for sanity
71 : 1249 : data->component_size[i] = 0;
72 : 1249 : data->component_pad[i] = 0;
73 : 1249 : data->component_map[i] = 0;
74 : : }
75 : : }
76 : 475 : }
77 : :
78 : 7 : void pl_plane_data_from_mask(struct pl_plane_data *data, uint64_t mask[4])
79 : : {
80 : : int size[4];
81 : : int shift[4];
82 : :
83 [ + + ]: 35 : for (int i = 0; i < PL_ARRAY_SIZE(size); i++) {
84 : 28 : size[i] = __builtin_popcountll(mask[i]);
85 [ + + ]: 28 : shift[i] = PL_MAX(0, __builtin_ffsll(mask[i]) - 1);
86 : :
87 : : // Sanity checking
88 : 28 : uint64_t mask_reconstructed = (1LLU << size[i]) - 1;
89 : 28 : mask_reconstructed <<= shift[i];
90 [ - + ]: 28 : pl_assert(mask_reconstructed == mask[i]);
91 : : }
92 : :
93 : 7 : pl_plane_data_from_comps(data, size, shift);
94 : 7 : }
95 : :
96 : 438 : bool pl_plane_data_align(struct pl_plane_data *data, struct pl_bit_encoding *out_bits)
97 : : {
98 : 438 : struct pl_plane_data aligned = *data;
99 : : struct pl_bit_encoding bits = {0};
100 : :
101 : : int offset = 0;
102 : :
103 : : #define SET_TEST(var, value) \
104 : : do { \
105 : : if (offset == 0) { \
106 : : (var) = (value); \
107 : : } else if ((var) != (value)) { \
108 : : goto misaligned; \
109 : : } \
110 : : } while (0)
111 : :
112 [ + + ]: 1031 : for (int i = 0; i < MAX_COMPS; i++) {
113 [ + + ]: 1008 : if (!aligned.component_size[i])
114 : : break;
115 : :
116 : : // Can't meaningfully align alpha channel, so just skip it. This is a
117 : : // limitation of the fact that `pl_bit_encoding` only applies to the
118 : : // main color channels, and changing this would be very nontrivial.
119 [ + + ]: 608 : if (aligned.component_map[i] == PL_CHANNEL_A)
120 : 23 : continue;
121 : :
122 : : // Color depth is the original component size, before alignment
123 [ + + + + ]: 585 : SET_TEST(bits.color_depth, aligned.component_size[i]);
124 : :
125 : : // Try consuming padding of the current component to align down. This
126 : : // corresponds to an extra bit shift to the left.
127 : 577 : int comp_start = offset + aligned.component_pad[i];
128 : 577 : int left_delta = comp_start - PL_ALIGN2(comp_start - 7, 8);
129 : 577 : left_delta = PL_MIN(left_delta, aligned.component_pad[i]);
130 : 577 : aligned.component_pad[i] -= left_delta;
131 : 577 : aligned.component_size[i] += left_delta;
132 [ + + - + ]: 577 : SET_TEST(bits.bit_shift, left_delta);
133 : :
134 : : // Try consuming padding of the next component to align up. This
135 : : // corresponds to simply ignoring some extra 0s on the end.
136 : 577 : int comp_end = comp_start + aligned.component_size[i] - left_delta;
137 : 577 : int right_delta = PL_ALIGN2(comp_end, 8) - comp_end;
138 [ + + + + ]: 577 : if (i+1 == MAX_COMPS || !aligned.component_size[i+1]) {
139 : : // This is the last component, so we can be greedy
140 : 413 : aligned.component_size[i] += right_delta;
141 : : } else {
142 : 164 : right_delta = PL_MIN(right_delta, aligned.component_pad[i+1]);
143 : 164 : aligned.component_pad[i+1] -= right_delta;
144 : 164 : aligned.component_size[i] += right_delta;
145 : : }
146 : :
147 : : // Sample depth is the new total component size, including padding
148 [ + + + + ]: 577 : SET_TEST(bits.sample_depth, aligned.component_size[i]);
149 : :
150 : 570 : offset += aligned.component_pad[i] + aligned.component_size[i];
151 : : }
152 : :
153 : : // Easy sanity check, to make sure that we don't exceed the known stride
154 [ + + - + ]: 423 : if (aligned.pixel_stride && offset > aligned.pixel_stride * 8)
155 : 0 : goto misaligned;
156 : :
157 : 423 : *data = aligned;
158 [ + - ]: 423 : if (out_bits)
159 : 423 : *out_bits = bits;
160 : : return true;
161 : :
162 : 15 : misaligned:
163 : : // Can't properly align anything, so just do a no-op
164 [ + - ]: 15 : if (out_bits)
165 : 15 : *out_bits = (struct pl_bit_encoding) {0};
166 : : return false;
167 : : }
168 : :
169 : 29 : pl_fmt pl_plane_find_fmt(pl_gpu gpu, int out_map[4], const struct pl_plane_data *data)
170 : : {
171 : 29 : int dummy[4] = {0};
172 [ + + ]: 29 : out_map = PL_DEF(out_map, dummy);
173 : :
174 : : // Endian swapping requires compute shaders (currently)
175 [ + - - - ]: 29 : if (data->swapped && !gpu->limits.max_ssbo_size)
176 : : return NULL;
177 : :
178 : : // Count the number of components and initialize out_map
179 : : int num = 0;
180 [ + + ]: 145 : for (int i = 0; i < PL_ARRAY_SIZE(data->component_size); i++) {
181 : 116 : out_map[i] = -1;
182 [ + + ]: 116 : if (data->component_size[i])
183 : 29 : num = i+1;
184 : : }
185 : :
186 [ - + ]: 242 : for (int n = 0; n < gpu->num_formats; n++) {
187 : 242 : pl_fmt fmt = gpu->formats[n];
188 [ + - - + ]: 242 : if (fmt->opaque || fmt->num_components < num)
189 : 0 : continue;
190 [ + + + + ]: 242 : if (fmt->type != data->type || fmt->texel_size != data->pixel_stride)
191 : 184 : continue;
192 [ - + ]: 58 : if (!(fmt->caps & PL_FMT_CAP_SAMPLEABLE))
193 : 0 : continue;
194 : :
195 : : int idx = 0;
196 : :
197 : : // Try mapping all pl_plane_data components to texture components
198 [ + + ]: 87 : for (int i = 0; i < num; i++) {
199 : : // If there's padding we have to map it to an unused physical
200 : : // component first
201 : 58 : int pad = data->component_pad[i];
202 [ - + - - : 58 : if (pad && (idx >= 4 || fmt->host_bits[idx++] != pad))
- - ]
203 : 0 : goto next_fmt;
204 : :
205 : : // Otherwise, try and match this component
206 : 58 : int size = data->component_size[i];
207 [ + - + - : 58 : if (size && (idx >= 4 || fmt->host_bits[idx] != size))
+ + ]
208 : 29 : goto next_fmt;
209 : 29 : out_map[idx++] = data->component_map[i];
210 : : }
211 : :
212 : : // Reject misaligned formats, check this last to only log such errors
213 : : // if this is the only thing preventing a format from being used, as
214 : : // this is likely an issue in the API usage.
215 [ - + ]: 29 : if (data->row_stride % fmt->texel_align) {
216 : 0 : PL_WARN(gpu, "Rejecting texture format '%s' due to misalignment: "
217 : : "Row stride %zu is not a clean multiple of texel size %zu! "
218 : : "This is likely an API usage bug.",
219 : : fmt->name, data->row_stride, fmt->texel_align);
220 : 0 : continue;
221 : : }
222 : :
223 : : return fmt;
224 : :
225 : 213 : next_fmt: ; // acts as `continue`
226 : : }
227 : :
228 : : return NULL;
229 : : }
230 : :
231 : 19 : bool pl_upload_plane(pl_gpu gpu, struct pl_plane *out_plane,
232 : : pl_tex *tex, const struct pl_plane_data *data)
233 : : {
234 [ - + ]: 19 : pl_assert(!data->buf ^ !data->pixels); // exactly one
235 : :
236 : : int out_map[4];
237 : 19 : pl_fmt fmt = pl_plane_find_fmt(gpu, out_map, data);
238 [ - + ]: 19 : if (!fmt) {
239 : 0 : PL_ERR(gpu, "Failed picking any compatible texture format for a plane!");
240 : 0 : return false;
241 : :
242 : : // TODO: try soft-converting to a supported format using e.g zimg?
243 : : }
244 : :
245 : 19 : bool ok = pl_tex_recreate(gpu, tex, pl_tex_params(
246 : : .w = data->width,
247 : : .h = data->height,
248 : : .format = fmt,
249 : : .sampleable = true,
250 : : .host_writable = true,
251 : : .blit_src = fmt->caps & PL_FMT_CAP_BLITTABLE,
252 : : ));
253 : :
254 [ - + ]: 19 : if (!ok) {
255 : 0 : PL_ERR(gpu, "Failed initializing plane texture!");
256 : 0 : return false;
257 : : }
258 : :
259 [ + - ]: 19 : if (out_plane) {
260 : 19 : out_plane->texture = *tex;
261 : 19 : out_plane->components = 0;
262 [ + + ]: 95 : for (int i = 0; i < PL_ARRAY_SIZE(out_map); i++) {
263 : 76 : out_plane->component_mapping[i] = out_map[i];
264 [ + + ]: 76 : if (out_map[i] >= 0)
265 : 19 : out_plane->components = i+1;
266 : : }
267 : : }
268 : :
269 : 38 : struct pl_tex_transfer_params params = {
270 : 19 : .tex = *tex,
271 : 19 : .rc.x1 = data->width, // set these for `pl_tex_transfer_size`
272 : 19 : .rc.y1 = data->height,
273 : : .rc.z1 = 1,
274 [ + + ]: 19 : .row_pitch = PL_DEF(data->row_stride, data->width * fmt->texel_size),
275 : 19 : .ptr = (void *) data->pixels,
276 : 19 : .buf = data->buf,
277 : 19 : .buf_offset = data->buf_offset,
278 : 19 : .callback = data->callback,
279 : 19 : .priv = data->priv,
280 : : };
281 : :
282 : 19 : pl_buf swapbuf = NULL;
283 [ - + ]: 19 : if (data->swapped) {
284 : 0 : const size_t aligned = PL_ALIGN2(pl_tex_transfer_size(¶ms), 4);
285 : 0 : swapbuf = pl_buf_create(gpu, pl_buf_params(
286 : : .size = aligned,
287 : : .storable = true,
288 : : .initial_data = params.ptr,
289 : :
290 : : // Note: This may over-read from `ptr` if `ptr` is not aligned to a
291 : : // word boundary, but the extra texels will be ignored by
292 : : // `pl_tex_upload` so this UB should be a non-issue in practice.
293 : : ));
294 [ # # ]: 0 : if (!swapbuf) {
295 : 0 : PL_ERR(gpu, "Failed creating endian swapping buffer!");
296 : 0 : return false;
297 : : }
298 : :
299 : 0 : struct pl_buf_copy_swap_params swap_params = {
300 : : .src = swapbuf,
301 : : .dst = swapbuf,
302 : : .size = aligned,
303 : 0 : .wordsize = fmt->texel_size / fmt->num_components,
304 : : };
305 : :
306 [ # # ]: 0 : bool can_reuse = params.buf && params.buf->params.storable &&
307 [ # # # # ]: 0 : params.buf_offset % 4 == 0 &&
308 [ # # ]: 0 : params.buf_offset + aligned <= params.buf->params.size;
309 : :
310 [ # # ]: 0 : if (params.ptr) {
311 : : // Data is already uploaded (no-op), can swap in-place
312 [ # # ]: 0 : } else if (can_reuse) {
313 : : // We can sample directly from the source buffer
314 : 0 : swap_params.src = params.buf;
315 : 0 : swap_params.src_offset = params.buf_offset;
316 : : } else {
317 : : // We sadly need to do a second memcpy
318 [ # # ]: 0 : assert(params.buf);
319 : 0 : PL_TRACE(gpu, "Double-slow path! pl_buf_copy -> pl_buf_copy_swap...");
320 : 0 : pl_buf_copy(gpu, swapbuf, 0, params.buf, params.buf_offset,
321 : 0 : PL_MIN(aligned, params.buf->params.size - params.buf_offset));
322 : : }
323 : :
324 [ # # ]: 0 : if (!pl_buf_copy_swap(gpu, &swap_params)) {
325 : 0 : PL_ERR(gpu, "Failed swapping endianness!");
326 : 0 : pl_buf_destroy(gpu, &swapbuf);
327 : 0 : return false;
328 : : }
329 : :
330 : 0 : params.ptr = NULL;
331 : 0 : params.buf = swapbuf;
332 : 0 : params.buf_offset = 0;
333 : : }
334 : :
335 : 19 : ok = pl_tex_upload(gpu, ¶ms);
336 : 19 : pl_buf_destroy(gpu, &swapbuf);
337 : 19 : return ok;
338 : : }
339 : :
340 : 5 : bool pl_recreate_plane(pl_gpu gpu, struct pl_plane *out_plane,
341 : : pl_tex *tex, const struct pl_plane_data *data)
342 : : {
343 [ - + ]: 5 : if (data->swapped) {
344 : 0 : PL_ERR(gpu, "Cannot call pl_recreate_plane on non-native endian plane "
345 : : "data, this is only supported for `pl_upload_plane`!");
346 : 0 : return false;
347 : : }
348 : :
349 : : int out_map[4];
350 : 5 : pl_fmt fmt = pl_plane_find_fmt(gpu, out_map, data);
351 [ - + ]: 5 : if (!fmt) {
352 : 0 : PL_ERR(gpu, "Failed picking any compatible texture format for a plane!");
353 : 0 : return false;
354 : : }
355 : :
356 : 5 : bool ok = pl_tex_recreate(gpu, tex, pl_tex_params(
357 : : .w = data->width,
358 : : .h = data->height,
359 : : .format = fmt,
360 : : .renderable = true,
361 : : .host_readable = fmt->caps & PL_FMT_CAP_HOST_READABLE,
362 : : .blit_dst = fmt->caps & PL_FMT_CAP_BLITTABLE,
363 : : .storable = fmt->caps & PL_FMT_CAP_STORABLE,
364 : : ));
365 : :
366 [ + + ]: 5 : if (!ok) {
367 : 1 : PL_ERR(gpu, "Failed initializing plane texture!");
368 : 1 : return false;
369 : : }
370 : :
371 [ - + ]: 4 : if (out_plane) {
372 : 0 : out_plane->texture = *tex;
373 : 0 : out_plane->components = 0;
374 [ # # ]: 0 : for (int i = 0; i < PL_ARRAY_SIZE(out_map); i++) {
375 : 0 : out_plane->component_mapping[i] = out_map[i];
376 [ # # ]: 0 : if (out_map[i] >= 0)
377 : 0 : out_plane->components = i+1;
378 : : }
379 : : }
380 : :
381 : : return true;
382 : : }
|