1 | /* |
2 | Simple DirectMedia Layer |
3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> |
4 | |
5 | This software is provided 'as-is', without any express or implied |
6 | warranty. In no event will the authors be held liable for any damages |
7 | arising from the use of this software. |
8 | |
9 | Permission is granted to anyone to use this software for any purpose, |
10 | including commercial applications, and to alter it and redistribute it |
11 | freely, subject to the following restrictions: |
12 | |
13 | 1. The origin of this software must not be misrepresented; you must not |
14 | claim that you wrote the original software. If you use this software |
15 | in a product, an acknowledgment in the product documentation would be |
16 | appreciated but is not required. |
17 | 2. Altered source versions must be plainly marked as such, and must not be |
18 | misrepresented as being the original software. |
19 | 3. This notice may not be removed or altered from any source distribution. |
20 | */ |
21 | #include "SDL_internal.h" |
22 | |
23 | // This is the software implementation of the YUV texture support |
24 | |
25 | #ifdef SDL_HAVE_YUV |
26 | |
27 | #include "SDL_yuv_sw_c.h" |
28 | #include "../video/SDL_surface_c.h" |
29 | #include "../video/SDL_yuv_c.h" |
30 | |
31 | SDL_SW_YUVTexture *SDL_SW_CreateYUVTexture(SDL_PixelFormat format, SDL_Colorspace colorspace, int w, int h) |
32 | { |
33 | SDL_SW_YUVTexture *swdata; |
34 | |
35 | switch (format) { |
36 | case SDL_PIXELFORMAT_YV12: |
37 | case SDL_PIXELFORMAT_IYUV: |
38 | case SDL_PIXELFORMAT_YUY2: |
39 | case SDL_PIXELFORMAT_UYVY: |
40 | case SDL_PIXELFORMAT_YVYU: |
41 | case SDL_PIXELFORMAT_NV12: |
42 | case SDL_PIXELFORMAT_NV21: |
43 | break; |
44 | default: |
45 | SDL_SetError("Unsupported YUV format" ); |
46 | return NULL; |
47 | } |
48 | |
49 | swdata = (SDL_SW_YUVTexture *)SDL_calloc(1, sizeof(*swdata)); |
50 | if (!swdata) { |
51 | return NULL; |
52 | } |
53 | |
54 | swdata->format = format; |
55 | swdata->colorspace = colorspace; |
56 | swdata->target_format = SDL_PIXELFORMAT_UNKNOWN; |
57 | swdata->w = w; |
58 | swdata->h = h; |
59 | { |
60 | size_t dst_size; |
61 | if (!SDL_CalculateYUVSize(format, w, h, &dst_size, NULL)) { |
62 | SDL_SW_DestroyYUVTexture(swdata); |
63 | return NULL; |
64 | } |
65 | swdata->pixels = (Uint8 *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), dst_size); |
66 | if (!swdata->pixels) { |
67 | SDL_SW_DestroyYUVTexture(swdata); |
68 | return NULL; |
69 | } |
70 | } |
71 | |
72 | // Find the pitch and offset values for the texture |
73 | switch (format) { |
74 | case SDL_PIXELFORMAT_YV12: |
75 | case SDL_PIXELFORMAT_IYUV: |
76 | swdata->pitches[0] = w; |
77 | swdata->pitches[1] = (swdata->pitches[0] + 1) / 2; |
78 | swdata->pitches[2] = (swdata->pitches[0] + 1) / 2; |
79 | swdata->planes[0] = swdata->pixels; |
80 | swdata->planes[1] = swdata->planes[0] + swdata->pitches[0] * h; |
81 | swdata->planes[2] = swdata->planes[1] + swdata->pitches[1] * ((h + 1) / 2); |
82 | break; |
83 | case SDL_PIXELFORMAT_YUY2: |
84 | case SDL_PIXELFORMAT_UYVY: |
85 | case SDL_PIXELFORMAT_YVYU: |
86 | swdata->pitches[0] = ((w + 1) / 2) * 4; |
87 | swdata->planes[0] = swdata->pixels; |
88 | break; |
89 | |
90 | case SDL_PIXELFORMAT_NV12: |
91 | case SDL_PIXELFORMAT_NV21: |
92 | swdata->pitches[0] = w; |
93 | swdata->pitches[1] = 2 * ((swdata->pitches[0] + 1) / 2); |
94 | swdata->planes[0] = swdata->pixels; |
95 | swdata->planes[1] = swdata->planes[0] + swdata->pitches[0] * h; |
96 | break; |
97 | |
98 | default: |
99 | SDL_assert(!"We should never get here (caught above)" ); |
100 | break; |
101 | } |
102 | |
103 | // We're all done.. |
104 | return swdata; |
105 | } |
106 | |
107 | bool SDL_SW_QueryYUVTexturePixels(SDL_SW_YUVTexture *swdata, void **pixels, |
108 | int *pitch) |
109 | { |
110 | *pixels = swdata->planes[0]; |
111 | *pitch = swdata->pitches[0]; |
112 | return true; |
113 | } |
114 | |
115 | bool SDL_SW_UpdateYUVTexture(SDL_SW_YUVTexture *swdata, const SDL_Rect *rect, |
116 | const void *pixels, int pitch) |
117 | { |
118 | switch (swdata->format) { |
119 | case SDL_PIXELFORMAT_YV12: |
120 | case SDL_PIXELFORMAT_IYUV: |
121 | if (rect->x == 0 && rect->y == 0 && |
122 | rect->w == swdata->w && rect->h == swdata->h) { |
123 | SDL_memcpy(swdata->pixels, pixels, |
124 | (size_t)(swdata->h * swdata->w) + 2 * ((swdata->h + 1) / 2) * ((swdata->w + 1) / 2)); |
125 | } else { |
126 | Uint8 *src, *dst; |
127 | int row; |
128 | size_t length; |
129 | |
130 | // Copy the Y plane |
131 | src = (Uint8 *)pixels; |
132 | dst = swdata->pixels + rect->y * swdata->w + rect->x; |
133 | length = rect->w; |
134 | for (row = 0; row < rect->h; ++row) { |
135 | SDL_memcpy(dst, src, length); |
136 | src += pitch; |
137 | dst += swdata->w; |
138 | } |
139 | |
140 | // Copy the next plane |
141 | src = (Uint8 *)pixels + rect->h * pitch; |
142 | dst = swdata->pixels + swdata->h * swdata->w; |
143 | dst += rect->y / 2 * ((swdata->w + 1) / 2) + rect->x / 2; |
144 | length = (rect->w + 1) / 2; |
145 | for (row = 0; row < (rect->h + 1) / 2; ++row) { |
146 | SDL_memcpy(dst, src, length); |
147 | src += (pitch + 1) / 2; |
148 | dst += (swdata->w + 1) / 2; |
149 | } |
150 | |
151 | // Copy the next plane |
152 | src = (Uint8 *)pixels + rect->h * pitch + ((rect->h + 1) / 2) * ((pitch + 1) / 2); |
153 | dst = swdata->pixels + swdata->h * swdata->w + |
154 | ((swdata->h + 1) / 2) * ((swdata->w + 1) / 2); |
155 | dst += rect->y / 2 * ((swdata->w + 1) / 2) + rect->x / 2; |
156 | length = (rect->w + 1) / 2; |
157 | for (row = 0; row < (rect->h + 1) / 2; ++row) { |
158 | SDL_memcpy(dst, src, length); |
159 | src += (pitch + 1) / 2; |
160 | dst += (swdata->w + 1) / 2; |
161 | } |
162 | } |
163 | break; |
164 | case SDL_PIXELFORMAT_YUY2: |
165 | case SDL_PIXELFORMAT_UYVY: |
166 | case SDL_PIXELFORMAT_YVYU: |
167 | { |
168 | Uint8 *src, *dst; |
169 | int row; |
170 | size_t length; |
171 | |
172 | src = (Uint8 *)pixels; |
173 | dst = |
174 | swdata->planes[0] + rect->y * swdata->pitches[0] + |
175 | rect->x * 2; |
176 | length = 4 * (((size_t)rect->w + 1) / 2); |
177 | for (row = 0; row < rect->h; ++row) { |
178 | SDL_memcpy(dst, src, length); |
179 | src += pitch; |
180 | dst += swdata->pitches[0]; |
181 | } |
182 | } break; |
183 | case SDL_PIXELFORMAT_NV12: |
184 | case SDL_PIXELFORMAT_NV21: |
185 | { |
186 | if (rect->x == 0 && rect->y == 0 && rect->w == swdata->w && rect->h == swdata->h) { |
187 | SDL_memcpy(swdata->pixels, pixels, |
188 | (size_t)(swdata->h * swdata->w) + 2 * ((swdata->h + 1) / 2) * ((swdata->w + 1) / 2)); |
189 | } else { |
190 | |
191 | Uint8 *src, *dst; |
192 | int row; |
193 | size_t length; |
194 | |
195 | // Copy the Y plane |
196 | src = (Uint8 *)pixels; |
197 | dst = swdata->pixels + rect->y * swdata->w + rect->x; |
198 | length = rect->w; |
199 | for (row = 0; row < rect->h; ++row) { |
200 | SDL_memcpy(dst, src, length); |
201 | src += pitch; |
202 | dst += swdata->w; |
203 | } |
204 | |
205 | // Copy the next plane |
206 | src = (Uint8 *)pixels + rect->h * pitch; |
207 | dst = swdata->pixels + swdata->h * swdata->w; |
208 | dst += 2 * ((rect->y + 1) / 2) * ((swdata->w + 1) / 2) + 2 * (rect->x / 2); |
209 | length = 2 * (((size_t)rect->w + 1) / 2); |
210 | for (row = 0; row < (rect->h + 1) / 2; ++row) { |
211 | SDL_memcpy(dst, src, length); |
212 | src += 2 * ((pitch + 1) / 2); |
213 | dst += 2 * ((swdata->w + 1) / 2); |
214 | } |
215 | } |
216 | } break; |
217 | default: |
218 | return SDL_SetError("Unsupported YUV format" ); |
219 | } |
220 | return true; |
221 | } |
222 | |
223 | bool SDL_SW_UpdateYUVTexturePlanar(SDL_SW_YUVTexture *swdata, const SDL_Rect *rect, |
224 | const Uint8 *Yplane, int Ypitch, |
225 | const Uint8 *Uplane, int Upitch, |
226 | const Uint8 *Vplane, int Vpitch) |
227 | { |
228 | const Uint8 *src; |
229 | Uint8 *dst; |
230 | int row; |
231 | size_t length; |
232 | |
233 | // Copy the Y plane |
234 | src = Yplane; |
235 | dst = swdata->pixels + rect->y * swdata->w + rect->x; |
236 | length = rect->w; |
237 | for (row = 0; row < rect->h; ++row) { |
238 | SDL_memcpy(dst, src, length); |
239 | src += Ypitch; |
240 | dst += swdata->w; |
241 | } |
242 | |
243 | // Copy the U plane |
244 | src = Uplane; |
245 | if (swdata->format == SDL_PIXELFORMAT_IYUV) { |
246 | dst = swdata->pixels + swdata->h * swdata->w; |
247 | } else { |
248 | dst = swdata->pixels + swdata->h * swdata->w + |
249 | ((swdata->h + 1) / 2) * ((swdata->w + 1) / 2); |
250 | } |
251 | dst += rect->y / 2 * ((swdata->w + 1) / 2) + rect->x / 2; |
252 | length = (rect->w + 1) / 2; |
253 | for (row = 0; row < (rect->h + 1) / 2; ++row) { |
254 | SDL_memcpy(dst, src, length); |
255 | src += Upitch; |
256 | dst += (swdata->w + 1) / 2; |
257 | } |
258 | |
259 | // Copy the V plane |
260 | src = Vplane; |
261 | if (swdata->format == SDL_PIXELFORMAT_YV12) { |
262 | dst = swdata->pixels + swdata->h * swdata->w; |
263 | } else { |
264 | dst = swdata->pixels + swdata->h * swdata->w + |
265 | ((swdata->h + 1) / 2) * ((swdata->w + 1) / 2); |
266 | } |
267 | dst += rect->y / 2 * ((swdata->w + 1) / 2) + rect->x / 2; |
268 | length = (rect->w + 1) / 2; |
269 | for (row = 0; row < (rect->h + 1) / 2; ++row) { |
270 | SDL_memcpy(dst, src, length); |
271 | src += Vpitch; |
272 | dst += (swdata->w + 1) / 2; |
273 | } |
274 | return true; |
275 | } |
276 | |
277 | bool SDL_SW_UpdateNVTexturePlanar(SDL_SW_YUVTexture *swdata, const SDL_Rect *rect, |
278 | const Uint8 *Yplane, int Ypitch, |
279 | const Uint8 *UVplane, int UVpitch) |
280 | { |
281 | const Uint8 *src; |
282 | Uint8 *dst; |
283 | int row; |
284 | size_t length; |
285 | |
286 | // Copy the Y plane |
287 | src = Yplane; |
288 | dst = swdata->pixels + rect->y * swdata->w + rect->x; |
289 | length = rect->w; |
290 | for (row = 0; row < rect->h; ++row) { |
291 | SDL_memcpy(dst, src, length); |
292 | src += Ypitch; |
293 | dst += swdata->w; |
294 | } |
295 | |
296 | // Copy the UV or VU plane |
297 | src = UVplane; |
298 | dst = swdata->pixels + swdata->h * swdata->w; |
299 | dst += rect->y * ((swdata->w + 1) / 2) + rect->x; |
300 | length = (rect->w + 1) / 2; |
301 | length *= 2; |
302 | for (row = 0; row < (rect->h + 1) / 2; ++row) { |
303 | SDL_memcpy(dst, src, length); |
304 | src += UVpitch; |
305 | dst += 2 * ((swdata->w + 1) / 2); |
306 | } |
307 | |
308 | return true; |
309 | } |
310 | |
311 | bool SDL_SW_LockYUVTexture(SDL_SW_YUVTexture *swdata, const SDL_Rect *rect, |
312 | void **pixels, int *pitch) |
313 | { |
314 | switch (swdata->format) { |
315 | case SDL_PIXELFORMAT_YV12: |
316 | case SDL_PIXELFORMAT_IYUV: |
317 | case SDL_PIXELFORMAT_NV12: |
318 | case SDL_PIXELFORMAT_NV21: |
319 | if (rect && (rect->x != 0 || rect->y != 0 || rect->w != swdata->w || rect->h != swdata->h)) { |
320 | return SDL_SetError("YV12, IYUV, NV12, NV21 textures only support full surface locks" ); |
321 | } |
322 | break; |
323 | default: |
324 | return SDL_SetError("Unsupported YUV format" ); |
325 | } |
326 | |
327 | if (rect) { |
328 | *pixels = swdata->planes[0] + rect->y * swdata->pitches[0] + rect->x * 2; |
329 | } else { |
330 | *pixels = swdata->planes[0]; |
331 | } |
332 | *pitch = swdata->pitches[0]; |
333 | return true; |
334 | } |
335 | |
336 | void SDL_SW_UnlockYUVTexture(SDL_SW_YUVTexture *swdata) |
337 | { |
338 | } |
339 | |
340 | bool SDL_SW_CopyYUVToRGB(SDL_SW_YUVTexture *swdata, const SDL_Rect *srcrect, SDL_PixelFormat target_format, int w, int h, void *pixels, int pitch) |
341 | { |
342 | int stretch; |
343 | |
344 | // Make sure we're set up to display in the desired format |
345 | if (target_format != swdata->target_format && swdata->display) { |
346 | SDL_DestroySurface(swdata->display); |
347 | swdata->display = NULL; |
348 | } |
349 | |
350 | stretch = 0; |
351 | if (srcrect->x || srcrect->y || srcrect->w < swdata->w || srcrect->h < swdata->h) { |
352 | /* The source rectangle has been clipped. |
353 | Using a scratch surface is easier than adding clipped |
354 | source support to all the blitters, plus that would |
355 | slow them down in the general unclipped case. |
356 | */ |
357 | stretch = 1; |
358 | } else if ((srcrect->w != w) || (srcrect->h != h)) { |
359 | stretch = 1; |
360 | } |
361 | if (stretch) { |
362 | if (swdata->display) { |
363 | swdata->display->w = w; |
364 | swdata->display->h = h; |
365 | swdata->display->pixels = pixels; |
366 | swdata->display->pitch = pitch; |
367 | } else { |
368 | swdata->display = SDL_CreateSurfaceFrom(w, h, target_format, pixels, pitch); |
369 | if (!swdata->display) { |
370 | return false; |
371 | } |
372 | swdata->target_format = target_format; |
373 | } |
374 | if (!swdata->stretch) { |
375 | swdata->stretch = SDL_CreateSurface(swdata->w, swdata->h, target_format); |
376 | if (!swdata->stretch) { |
377 | return false; |
378 | } |
379 | } |
380 | pixels = swdata->stretch->pixels; |
381 | pitch = swdata->stretch->pitch; |
382 | } |
383 | if (!SDL_ConvertPixelsAndColorspace(swdata->w, swdata->h, swdata->format, swdata->colorspace, 0, swdata->planes[0], swdata->pitches[0], target_format, SDL_COLORSPACE_SRGB, 0, pixels, pitch)) { |
384 | return false; |
385 | } |
386 | if (stretch) { |
387 | SDL_Rect rect = *srcrect; |
388 | return SDL_StretchSurface(swdata->stretch, &rect, swdata->display, NULL, SDL_SCALEMODE_NEAREST); |
389 | } else { |
390 | return true; |
391 | } |
392 | } |
393 | |
394 | void SDL_SW_DestroyYUVTexture(SDL_SW_YUVTexture *swdata) |
395 | { |
396 | if (swdata) { |
397 | SDL_aligned_free(swdata->pixels); |
398 | SDL_DestroySurface(swdata->stretch); |
399 | SDL_DestroySurface(swdata->display); |
400 | SDL_free(swdata); |
401 | } |
402 | } |
403 | |
404 | #endif // SDL_HAVE_YUV |
405 | |