/src/mozilla-central/dom/canvas/TexUnpackBlob.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
3 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
4 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
5 | | |
6 | | #include "TexUnpackBlob.h" |
7 | | |
8 | | #include "GLBlitHelper.h" |
9 | | #include "GLContext.h" |
10 | | #include "mozilla/dom/Element.h" |
11 | | #include "mozilla/dom/HTMLCanvasElement.h" |
12 | | #include "mozilla/RefPtr.h" |
13 | | #include "nsLayoutUtils.h" |
14 | | #include "WebGLBuffer.h" |
15 | | #include "WebGLContext.h" |
16 | | #include "WebGLTexelConversions.h" |
17 | | #include "WebGLTexture.h" |
18 | | |
19 | | namespace mozilla { |
20 | | namespace webgl { |
21 | | |
22 | | static bool |
23 | | IsPIValidForDOM(const webgl::PackingInfo& pi) |
24 | 0 | { |
25 | 0 | // https://www.khronos.org/registry/webgl/specs/latest/2.0/#TEXTURE_TYPES_FORMATS_FROM_DOM_ELEMENTS_TABLE |
26 | 0 |
|
27 | 0 | // Just check for invalid individual formats and types, not combinations. |
28 | 0 | switch (pi.format) { |
29 | 0 | case LOCAL_GL_RGB: |
30 | 0 | case LOCAL_GL_RGBA: |
31 | 0 | case LOCAL_GL_LUMINANCE_ALPHA: |
32 | 0 | case LOCAL_GL_LUMINANCE: |
33 | 0 | case LOCAL_GL_ALPHA: |
34 | 0 | case LOCAL_GL_RED: |
35 | 0 | case LOCAL_GL_RED_INTEGER: |
36 | 0 | case LOCAL_GL_RG: |
37 | 0 | case LOCAL_GL_RG_INTEGER: |
38 | 0 | case LOCAL_GL_RGB_INTEGER: |
39 | 0 | case LOCAL_GL_RGBA_INTEGER: |
40 | 0 | break; |
41 | 0 |
|
42 | 0 | case LOCAL_GL_SRGB: |
43 | 0 | case LOCAL_GL_SRGB_ALPHA: |
44 | 0 | // Allowed in WebGL1+EXT_srgb |
45 | 0 | break; |
46 | 0 |
|
47 | 0 | default: |
48 | 0 | return false; |
49 | 0 | } |
50 | 0 | |
51 | 0 | switch (pi.type) { |
52 | 0 | case LOCAL_GL_UNSIGNED_BYTE: |
53 | 0 | case LOCAL_GL_UNSIGNED_SHORT_5_6_5: |
54 | 0 | case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4: |
55 | 0 | case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1: |
56 | 0 | case LOCAL_GL_HALF_FLOAT: |
57 | 0 | case LOCAL_GL_HALF_FLOAT_OES: |
58 | 0 | case LOCAL_GL_FLOAT: |
59 | 0 | case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV: |
60 | 0 | break; |
61 | 0 |
|
62 | 0 | default: |
63 | 0 | return false; |
64 | 0 | } |
65 | 0 | |
66 | 0 | return true; |
67 | 0 | } |
68 | | |
69 | | static bool |
70 | | ValidatePIForDOM(WebGLContext* webgl, const webgl::PackingInfo& pi) |
71 | 0 | { |
72 | 0 | if (!IsPIValidForDOM(pi)) { |
73 | 0 | webgl->ErrorInvalidOperation("Format or type is invalid for DOM sources."); |
74 | 0 | return false; |
75 | 0 | } |
76 | 0 | return true; |
77 | 0 | } |
78 | | |
79 | | static WebGLTexelFormat |
80 | | FormatForPackingInfo(const PackingInfo& pi) |
81 | 0 | { |
82 | 0 | switch (pi.type) { |
83 | 0 | case LOCAL_GL_UNSIGNED_BYTE: |
84 | 0 | switch (pi.format) { |
85 | 0 | case LOCAL_GL_RED: |
86 | 0 | case LOCAL_GL_LUMINANCE: |
87 | 0 | case LOCAL_GL_RED_INTEGER: |
88 | 0 | return WebGLTexelFormat::R8; |
89 | 0 |
|
90 | 0 | case LOCAL_GL_ALPHA: |
91 | 0 | return WebGLTexelFormat::A8; |
92 | 0 |
|
93 | 0 | case LOCAL_GL_LUMINANCE_ALPHA: |
94 | 0 | return WebGLTexelFormat::RA8; |
95 | 0 |
|
96 | 0 | case LOCAL_GL_RGB: |
97 | 0 | case LOCAL_GL_RGB_INTEGER: |
98 | 0 | case LOCAL_GL_SRGB: |
99 | 0 | return WebGLTexelFormat::RGB8; |
100 | 0 |
|
101 | 0 | case LOCAL_GL_RGBA: |
102 | 0 | case LOCAL_GL_RGBA_INTEGER: |
103 | 0 | case LOCAL_GL_SRGB_ALPHA: |
104 | 0 | return WebGLTexelFormat::RGBA8; |
105 | 0 |
|
106 | 0 | case LOCAL_GL_RG: |
107 | 0 | case LOCAL_GL_RG_INTEGER: |
108 | 0 | return WebGLTexelFormat::RG8; |
109 | 0 |
|
110 | 0 | default: |
111 | 0 | break; |
112 | 0 | } |
113 | 0 | break; |
114 | 0 |
|
115 | 0 | case LOCAL_GL_UNSIGNED_SHORT_5_6_5: |
116 | 0 | if (pi.format == LOCAL_GL_RGB) |
117 | 0 | return WebGLTexelFormat::RGB565; |
118 | 0 | break; |
119 | 0 |
|
120 | 0 | case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1: |
121 | 0 | if (pi.format == LOCAL_GL_RGBA) |
122 | 0 | return WebGLTexelFormat::RGBA5551; |
123 | 0 | break; |
124 | 0 |
|
125 | 0 | case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4: |
126 | 0 | if (pi.format == LOCAL_GL_RGBA) |
127 | 0 | return WebGLTexelFormat::RGBA4444; |
128 | 0 | break; |
129 | 0 |
|
130 | 0 | case LOCAL_GL_HALF_FLOAT: |
131 | 0 | case LOCAL_GL_HALF_FLOAT_OES: |
132 | 0 | switch (pi.format) { |
133 | 0 | case LOCAL_GL_RED: |
134 | 0 | case LOCAL_GL_LUMINANCE: |
135 | 0 | return WebGLTexelFormat::R16F; |
136 | 0 |
|
137 | 0 | case LOCAL_GL_ALPHA: return WebGLTexelFormat::A16F; |
138 | 0 | case LOCAL_GL_LUMINANCE_ALPHA: return WebGLTexelFormat::RA16F; |
139 | 0 | case LOCAL_GL_RG: return WebGLTexelFormat::RG16F; |
140 | 0 | case LOCAL_GL_RGB: return WebGLTexelFormat::RGB16F; |
141 | 0 | case LOCAL_GL_RGBA: return WebGLTexelFormat::RGBA16F; |
142 | 0 |
|
143 | 0 | default: |
144 | 0 | break; |
145 | 0 | } |
146 | 0 | break; |
147 | 0 |
|
148 | 0 | case LOCAL_GL_FLOAT: |
149 | 0 | switch (pi.format) { |
150 | 0 | case LOCAL_GL_RED: |
151 | 0 | case LOCAL_GL_LUMINANCE: |
152 | 0 | return WebGLTexelFormat::R32F; |
153 | 0 |
|
154 | 0 | case LOCAL_GL_ALPHA: return WebGLTexelFormat::A32F; |
155 | 0 | case LOCAL_GL_LUMINANCE_ALPHA: return WebGLTexelFormat::RA32F; |
156 | 0 | case LOCAL_GL_RG: return WebGLTexelFormat::RG32F; |
157 | 0 | case LOCAL_GL_RGB: return WebGLTexelFormat::RGB32F; |
158 | 0 | case LOCAL_GL_RGBA: return WebGLTexelFormat::RGBA32F; |
159 | 0 |
|
160 | 0 | default: |
161 | 0 | break; |
162 | 0 | } |
163 | 0 | break; |
164 | 0 |
|
165 | 0 | case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV: |
166 | 0 | if (pi.format == LOCAL_GL_RGB) |
167 | 0 | return WebGLTexelFormat::RGB11F11F10F; |
168 | 0 | break; |
169 | 0 |
|
170 | 0 | default: |
171 | 0 | break; |
172 | 0 | } |
173 | 0 | |
174 | 0 | return WebGLTexelFormat::FormatNotSupportingAnyConversion; |
175 | 0 | } |
176 | | |
177 | | //////////////////// |
178 | | |
179 | | static bool |
180 | | ValidateUnpackPixels(WebGLContext* webgl, uint32_t fullRows, |
181 | | uint32_t tailPixels, webgl::TexUnpackBlob* blob) |
182 | 0 | { |
183 | 0 | if (!blob->mWidth || !blob->mHeight || !blob->mDepth) |
184 | 0 | return true; |
185 | 0 | |
186 | 0 | const auto usedPixelsPerRow = CheckedUint32(blob->mSkipPixels) + blob->mWidth; |
187 | 0 | if (!usedPixelsPerRow.isValid() || usedPixelsPerRow.value() > blob->mRowLength) { |
188 | 0 | webgl->ErrorInvalidOperation("UNPACK_SKIP_PIXELS + width >" |
189 | 0 | " UNPACK_ROW_LENGTH."); |
190 | 0 | return false; |
191 | 0 | } |
192 | 0 | |
193 | 0 | if (blob->mHeight > blob->mImageHeight) { |
194 | 0 | webgl->ErrorInvalidOperation("height > UNPACK_IMAGE_HEIGHT."); |
195 | 0 | return false; |
196 | 0 | } |
197 | 0 | |
198 | 0 | ////// |
199 | 0 | |
200 | 0 | // The spec doesn't bound SKIP_ROWS + height <= IMAGE_HEIGHT, unfortunately. |
201 | 0 | auto skipFullRows = CheckedUint32(blob->mSkipImages) * blob->mImageHeight; |
202 | 0 | skipFullRows += blob->mSkipRows; |
203 | 0 |
|
204 | 0 | MOZ_ASSERT(blob->mDepth >= 1); |
205 | 0 | MOZ_ASSERT(blob->mHeight >= 1); |
206 | 0 | auto usedFullRows = CheckedUint32(blob->mDepth - 1) * blob->mImageHeight; |
207 | 0 | usedFullRows += blob->mHeight - 1; // Full rows in the final image, excluding the tail. |
208 | 0 |
|
209 | 0 | const auto fullRowsNeeded = skipFullRows + usedFullRows; |
210 | 0 | if (!fullRowsNeeded.isValid()) { |
211 | 0 | webgl->ErrorOutOfMemory("Invalid calculation for required row count."); |
212 | 0 | return false; |
213 | 0 | } |
214 | 0 | |
215 | 0 | if (fullRows > fullRowsNeeded.value()) |
216 | 0 | return true; |
217 | 0 | |
218 | 0 | if (fullRows == fullRowsNeeded.value() && tailPixels >= usedPixelsPerRow.value()) { |
219 | 0 | blob->mNeedsExactUpload = true; |
220 | 0 | return true; |
221 | 0 | } |
222 | 0 | |
223 | 0 | webgl->ErrorInvalidOperation("Desired upload requires more data than is" |
224 | 0 | " available: (%u rows plus %u pixels needed, %u rows" |
225 | 0 | " plus %u pixels available)", |
226 | 0 | fullRowsNeeded.value(), |
227 | 0 | usedPixelsPerRow.value(), fullRows, tailPixels); |
228 | 0 | return false; |
229 | 0 | } |
230 | | |
231 | | static bool |
232 | | ValidateUnpackBytes(WebGLContext* webgl, |
233 | | const webgl::PackingInfo& pi, size_t availByteCount, |
234 | | webgl::TexUnpackBlob* blob) |
235 | 0 | { |
236 | 0 | if (!blob->mWidth || !blob->mHeight || !blob->mDepth) |
237 | 0 | return true; |
238 | 0 | |
239 | 0 | const auto bytesPerPixel = webgl::BytesPerPixel(pi); |
240 | 0 | const auto bytesPerRow = CheckedUint32(blob->mRowLength) * bytesPerPixel; |
241 | 0 | const auto rowStride = RoundUpToMultipleOf(bytesPerRow, blob->mAlignment); |
242 | 0 |
|
243 | 0 | const auto fullRows = availByteCount / rowStride; |
244 | 0 | if (!fullRows.isValid()) { |
245 | 0 | webgl->ErrorOutOfMemory("Unacceptable upload size calculated."); |
246 | 0 | return false; |
247 | 0 | } |
248 | 0 | |
249 | 0 | const auto bodyBytes = fullRows.value() * rowStride.value(); |
250 | 0 | const auto tailPixels = (availByteCount - bodyBytes) / bytesPerPixel; |
251 | 0 |
|
252 | 0 | return ValidateUnpackPixels(webgl, fullRows.value(), tailPixels, blob); |
253 | 0 | } |
254 | | |
255 | | //////////////////// |
256 | | |
257 | | static uint32_t |
258 | | ZeroOn2D(TexImageTarget target, uint32_t val) |
259 | 0 | { |
260 | 0 | return (IsTarget3D(target) ? val : 0); |
261 | 0 | } |
262 | | |
263 | | static uint32_t |
264 | | FallbackOnZero(uint32_t val, uint32_t fallback) |
265 | 0 | { |
266 | 0 | return (val ? val : fallback); |
267 | 0 | } |
268 | | |
269 | | TexUnpackBlob::TexUnpackBlob(const WebGLContext* webgl, TexImageTarget target, |
270 | | uint32_t rowLength, uint32_t width, uint32_t height, |
271 | | uint32_t depth, gfxAlphaType srcAlphaType) |
272 | | : mAlignment(webgl->mPixelStore_UnpackAlignment) |
273 | | , mRowLength(rowLength) |
274 | | , mImageHeight(FallbackOnZero(ZeroOn2D(target, webgl->mPixelStore_UnpackImageHeight), |
275 | | height)) |
276 | | |
277 | | , mSkipPixels(webgl->mPixelStore_UnpackSkipPixels) |
278 | | , mSkipRows(webgl->mPixelStore_UnpackSkipRows) |
279 | | , mSkipImages(ZeroOn2D(target, webgl->mPixelStore_UnpackSkipImages)) |
280 | | |
281 | | , mWidth(width) |
282 | | , mHeight(height) |
283 | | , mDepth(depth) |
284 | | |
285 | | , mSrcAlphaType(srcAlphaType) |
286 | | |
287 | | , mNeedsExactUpload(false) |
288 | 0 | { |
289 | 0 | MOZ_ASSERT_IF(!IsTarget3D(target), mDepth == 1); |
290 | 0 | } |
291 | | |
292 | | static bool |
293 | | HasColorAndAlpha(const WebGLTexelFormat format) |
294 | | { |
295 | | switch (format) { |
296 | | case WebGLTexelFormat::RA8: |
297 | | case WebGLTexelFormat::RA16F: |
298 | | case WebGLTexelFormat::RA32F: |
299 | | case WebGLTexelFormat::RGBA8: |
300 | | case WebGLTexelFormat::RGBA5551: |
301 | | case WebGLTexelFormat::RGBA4444: |
302 | | case WebGLTexelFormat::RGBA16F: |
303 | | case WebGLTexelFormat::RGBA32F: |
304 | | case WebGLTexelFormat::BGRA8: |
305 | | return true; |
306 | | default: |
307 | | return false; |
308 | | } |
309 | | } |
310 | | |
311 | | bool |
312 | | TexUnpackBlob::ConvertIfNeeded(WebGLContext* webgl, |
313 | | const uint32_t rowLength, const uint32_t rowCount, |
314 | | WebGLTexelFormat srcFormat, |
315 | | const uint8_t* const srcBegin, const ptrdiff_t srcStride, |
316 | | WebGLTexelFormat dstFormat, const ptrdiff_t dstStride, |
317 | | const uint8_t** const out_begin, |
318 | | UniqueBuffer* const out_anchoredBuffer) const |
319 | 0 | { |
320 | 0 | MOZ_ASSERT(srcFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion); |
321 | 0 | MOZ_ASSERT(dstFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion); |
322 | 0 |
|
323 | 0 | *out_begin = srcBegin; |
324 | 0 |
|
325 | 0 | if (!rowLength || !rowCount) |
326 | 0 | return true; |
327 | 0 | |
328 | 0 | const auto srcIsPremult = (mSrcAlphaType == gfxAlphaType::Premult); |
329 | 0 | const auto& dstIsPremult = webgl->mPixelStore_PremultiplyAlpha; |
330 | 0 | const auto fnHasPremultMismatch = [&]() { |
331 | 0 | if (mSrcAlphaType == gfxAlphaType::Opaque) |
332 | 0 | return false; |
333 | 0 | |
334 | 0 | if (!HasColorAndAlpha(srcFormat)) |
335 | 0 | return false; |
336 | 0 | |
337 | 0 | return srcIsPremult != dstIsPremult; |
338 | 0 | }; |
339 | 0 |
|
340 | 0 | const auto srcOrigin = (webgl->mPixelStore_FlipY ? gl::OriginPos::TopLeft |
341 | 0 | : gl::OriginPos::BottomLeft); |
342 | 0 | const auto dstOrigin = gl::OriginPos::BottomLeft; |
343 | 0 |
|
344 | 0 | if (srcFormat != dstFormat) { |
345 | 0 | webgl->GeneratePerfWarning("Conversion requires pixel reformatting. (%u->%u)", |
346 | 0 | uint32_t(srcFormat), |
347 | 0 | uint32_t(dstFormat)); |
348 | 0 | } else if (fnHasPremultMismatch()) { |
349 | 0 | webgl->GeneratePerfWarning("Conversion requires change in" |
350 | 0 | " alpha-premultiplication."); |
351 | 0 | } else if (srcOrigin != dstOrigin) { |
352 | 0 | webgl->GeneratePerfWarning("Conversion requires y-flip."); |
353 | 0 | } else if (srcStride != dstStride) { |
354 | 0 | webgl->GeneratePerfWarning("Conversion requires change in stride. (%u->%u)", |
355 | 0 | uint32_t(srcStride), uint32_t(dstStride)); |
356 | 0 | } else { |
357 | 0 | return true; |
358 | 0 | } |
359 | 0 | |
360 | 0 | //// |
361 | 0 | |
362 | 0 | const auto dstTotalBytes = CheckedUint32(rowCount) * dstStride; |
363 | 0 | if (!dstTotalBytes.isValid()) { |
364 | 0 | webgl->ErrorOutOfMemory("Calculation failed."); |
365 | 0 | return false; |
366 | 0 | } |
367 | 0 | |
368 | 0 | UniqueBuffer dstBuffer = calloc(1, dstTotalBytes.value()); |
369 | 0 | if (!dstBuffer.get()) { |
370 | 0 | webgl->ErrorOutOfMemory("Failed to allocate dest buffer."); |
371 | 0 | return false; |
372 | 0 | } |
373 | 0 | const auto dstBegin = static_cast<uint8_t*>(dstBuffer.get()); |
374 | 0 |
|
375 | 0 | //// |
376 | 0 |
|
377 | 0 | // And go!: |
378 | 0 | bool wasTrivial; |
379 | 0 | if (!ConvertImage(rowLength, rowCount, |
380 | 0 | srcBegin, srcStride, srcOrigin, srcFormat, srcIsPremult, |
381 | 0 | dstBegin, dstStride, dstOrigin, dstFormat, dstIsPremult, |
382 | 0 | &wasTrivial)) |
383 | 0 | { |
384 | 0 | webgl->ErrorImplementationBug("ConvertImage failed."); |
385 | 0 | return false; |
386 | 0 | } |
387 | 0 | |
388 | 0 | *out_begin = dstBegin; |
389 | 0 | *out_anchoredBuffer = std::move(dstBuffer); |
390 | 0 | return true; |
391 | 0 | } |
392 | | |
393 | | static GLenum |
394 | | DoTexOrSubImage(bool isSubImage, gl::GLContext* gl, TexImageTarget target, GLint level, |
395 | | const DriverUnpackInfo* dui, GLint xOffset, GLint yOffset, GLint zOffset, |
396 | | GLsizei width, GLsizei height, GLsizei depth, const void* data) |
397 | 0 | { |
398 | 0 | if (isSubImage) { |
399 | 0 | return DoTexSubImage(gl, target, level, xOffset, yOffset, zOffset, width, height, |
400 | 0 | depth, dui->ToPacking(), data); |
401 | 0 | } else { |
402 | 0 | return DoTexImage(gl, target, level, dui, width, height, depth, data); |
403 | 0 | } |
404 | 0 | } |
405 | | |
406 | | ////////////////////////////////////////////////////////////////////////////////////////// |
407 | | // TexUnpackBytes |
408 | | |
409 | | TexUnpackBytes::TexUnpackBytes(const WebGLContext* webgl, TexImageTarget target, |
410 | | uint32_t width, uint32_t height, uint32_t depth, |
411 | | bool isClientData, const uint8_t* ptr, size_t availBytes) |
412 | | : TexUnpackBlob(webgl, target, |
413 | | FallbackOnZero(webgl->mPixelStore_UnpackRowLength, width), |
414 | | width, height, depth, gfxAlphaType::NonPremult) |
415 | | , mIsClientData(isClientData) |
416 | | , mPtr(ptr) |
417 | | , mAvailBytes(availBytes) |
418 | 0 | { } |
419 | | |
420 | | bool |
421 | | TexUnpackBytes::Validate(WebGLContext* webgl, const webgl::PackingInfo& pi) |
422 | 0 | { |
423 | 0 | if (mIsClientData && !mPtr) |
424 | 0 | return true; |
425 | 0 | |
426 | 0 | return ValidateUnpackBytes(webgl, pi, mAvailBytes, this); |
427 | 0 | } |
428 | | |
429 | | bool |
430 | | TexUnpackBytes::TexOrSubImage(bool isSubImage, bool needsRespec, |
431 | | WebGLTexture* tex, TexImageTarget target, GLint level, |
432 | | const webgl::DriverUnpackInfo* dui, GLint xOffset, |
433 | | GLint yOffset, GLint zOffset, const webgl::PackingInfo& pi, |
434 | | GLenum* const out_error) const |
435 | 0 | { |
436 | 0 | WebGLContext* webgl = tex->mContext; |
437 | 0 |
|
438 | 0 | const auto format = FormatForPackingInfo(pi); |
439 | 0 | const auto bytesPerPixel = webgl::BytesPerPixel(pi); |
440 | 0 |
|
441 | 0 | const uint8_t* uploadPtr = mPtr; |
442 | 0 | UniqueBuffer tempBuffer; |
443 | 0 |
|
444 | 0 | do { |
445 | 0 | if (!mIsClientData || !mPtr) |
446 | 0 | break; |
447 | 0 | |
448 | 0 | if (!webgl->mPixelStore_FlipY && |
449 | 0 | !webgl->mPixelStore_PremultiplyAlpha) |
450 | 0 | { |
451 | 0 | break; |
452 | 0 | } |
453 | 0 | |
454 | 0 | if (webgl->mPixelStore_UnpackImageHeight || |
455 | 0 | webgl->mPixelStore_UnpackSkipImages || |
456 | 0 | webgl->mPixelStore_UnpackRowLength || |
457 | 0 | webgl->mPixelStore_UnpackSkipRows || |
458 | 0 | webgl->mPixelStore_UnpackSkipPixels) |
459 | 0 | { |
460 | 0 | webgl->ErrorInvalidOperation("Non-DOM-Element uploads with alpha-premult" |
461 | 0 | " or y-flip do not support subrect selection."); |
462 | 0 | return false; |
463 | 0 | } |
464 | 0 | |
465 | 0 | webgl->GenerateWarning("Alpha-premult and y-flip are deprecated for" |
466 | 0 | " non-DOM-Element uploads."); |
467 | 0 |
|
468 | 0 | const uint32_t rowLength = mWidth; |
469 | 0 | const uint32_t rowCount = mHeight * mDepth; |
470 | 0 | const auto stride = RoundUpToMultipleOf(rowLength * bytesPerPixel, mAlignment); |
471 | 0 | if (!ConvertIfNeeded(webgl, rowLength, rowCount, format, mPtr, stride, |
472 | 0 | format, stride, &uploadPtr, &tempBuffer)) |
473 | 0 | { |
474 | 0 | return false; |
475 | 0 | } |
476 | 0 | } while (false); |
477 | 0 |
|
478 | 0 | ////// |
479 | 0 |
|
480 | 0 | const auto& gl = webgl->gl; |
481 | 0 |
|
482 | 0 | bool useParanoidHandling = false; |
483 | 0 | if (mNeedsExactUpload && webgl->mBoundPixelUnpackBuffer) { |
484 | 0 | webgl->GenerateWarning("Uploads from a buffer with a final row with a byte" |
485 | 0 | " count smaller than the row stride can incur extra" |
486 | 0 | " overhead."); |
487 | 0 |
|
488 | 0 | if (gl->WorkAroundDriverBugs()) { |
489 | 0 | useParanoidHandling |= (gl->Vendor() == gl::GLVendor::NVIDIA); |
490 | 0 | } |
491 | 0 | } |
492 | 0 |
|
493 | 0 | if (!useParanoidHandling) { |
494 | 0 | if (webgl->mBoundPixelUnpackBuffer) { |
495 | 0 | gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, |
496 | 0 | webgl->mBoundPixelUnpackBuffer->mGLName); |
497 | 0 | } |
498 | 0 |
|
499 | 0 | *out_error = DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset, |
500 | 0 | zOffset, mWidth, mHeight, mDepth, uploadPtr); |
501 | 0 |
|
502 | 0 | if (webgl->mBoundPixelUnpackBuffer) { |
503 | 0 | gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, 0); |
504 | 0 | } |
505 | 0 | return true; |
506 | 0 | } |
507 | 0 |
|
508 | 0 | ////// |
509 | 0 |
|
510 | 0 | MOZ_ASSERT(webgl->mBoundPixelUnpackBuffer); |
511 | 0 |
|
512 | 0 | if (!isSubImage) { |
513 | 0 | // Alloc first to catch OOMs. |
514 | 0 | AssertUintParamCorrect(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER, 0); |
515 | 0 | *out_error = DoTexOrSubImage(false, gl, target, level, dui, xOffset, yOffset, |
516 | 0 | zOffset, mWidth, mHeight, mDepth, nullptr); |
517 | 0 | if (*out_error) |
518 | 0 | return true; |
519 | 0 | } |
520 | 0 | |
521 | 0 | const ScopedLazyBind bindPBO(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER, |
522 | 0 | webgl->mBoundPixelUnpackBuffer); |
523 | 0 |
|
524 | 0 | ////// |
525 | 0 |
|
526 | 0 | // Make our sometimes-implicit values explicit. Also this keeps them constant when we |
527 | 0 | // ask for height=mHeight-1 and such. |
528 | 0 | gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, mRowLength); |
529 | 0 | gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, mImageHeight); |
530 | 0 |
|
531 | 0 | if (mDepth > 1) { |
532 | 0 | *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset, |
533 | 0 | zOffset, mWidth, mHeight, mDepth-1, uploadPtr); |
534 | 0 | } |
535 | 0 |
|
536 | 0 | // Skip the images we uploaded. |
537 | 0 | gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, mSkipImages + mDepth - 1); |
538 | 0 |
|
539 | 0 | if (mHeight > 1) { |
540 | 0 | *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset, |
541 | 0 | zOffset+mDepth-1, mWidth, mHeight-1, 1, uploadPtr); |
542 | 0 | } |
543 | 0 |
|
544 | 0 | const auto totalSkipRows = CheckedUint32(mSkipImages) * mImageHeight + mSkipRows; |
545 | 0 | const auto totalFullRows = CheckedUint32(mDepth - 1) * mImageHeight + mHeight - 1; |
546 | 0 | const auto tailOffsetRows = totalSkipRows + totalFullRows; |
547 | 0 |
|
548 | 0 | const auto bytesPerRow = CheckedUint32(mRowLength) * bytesPerPixel; |
549 | 0 | const auto rowStride = RoundUpToMultipleOf(bytesPerRow, mAlignment); |
550 | 0 | if (!rowStride.isValid()) { |
551 | 0 | MOZ_CRASH("Should be checked earlier."); |
552 | 0 | } |
553 | 0 | const auto tailOffsetBytes = tailOffsetRows * rowStride; |
554 | 0 |
|
555 | 0 | uploadPtr += tailOffsetBytes.value(); |
556 | 0 |
|
557 | 0 | ////// |
558 | 0 |
|
559 | 0 | gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); // No stride padding. |
560 | 0 | gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, 0); // No padding in general. |
561 | 0 | gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, 0); // Don't skip images, |
562 | 0 | gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS, 0); // or rows. |
563 | 0 | // Keep skipping pixels though! |
564 | 0 |
|
565 | 0 | *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset, |
566 | 0 | yOffset+mHeight-1, zOffset+mDepth-1, mWidth, 1, 1, |
567 | 0 | uploadPtr); |
568 | 0 |
|
569 | 0 | // Reset all our modified state. |
570 | 0 | gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, webgl->mPixelStore_UnpackAlignment); |
571 | 0 | gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, webgl->mPixelStore_UnpackImageHeight); |
572 | 0 | gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, webgl->mPixelStore_UnpackRowLength); |
573 | 0 | gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, webgl->mPixelStore_UnpackSkipImages); |
574 | 0 | gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS, webgl->mPixelStore_UnpackSkipRows); |
575 | 0 |
|
576 | 0 | return true; |
577 | 0 | } |
578 | | |
579 | | //////////////////////////////////////////////////////////////////////////////// |
580 | | //////////////////////////////////////////////////////////////////////////////// |
581 | | // TexUnpackImage |
582 | | |
583 | | TexUnpackImage::TexUnpackImage(const WebGLContext* webgl, TexImageTarget target, |
584 | | uint32_t width, uint32_t height, uint32_t depth, |
585 | | layers::Image* image, gfxAlphaType srcAlphaType) |
586 | | : TexUnpackBlob(webgl, target, image->GetSize().width, width, height, depth, |
587 | | srcAlphaType) |
588 | | , mImage(image) |
589 | 0 | { } |
590 | | |
591 | | TexUnpackImage::~TexUnpackImage() |
592 | 0 | { } |
593 | | |
594 | | bool |
595 | | TexUnpackImage::Validate(WebGLContext* webgl, const webgl::PackingInfo& pi) |
596 | 0 | { |
597 | 0 | if (!ValidatePIForDOM(webgl, pi)) |
598 | 0 | return false; |
599 | 0 | |
600 | 0 | const auto fullRows = mImage->GetSize().height; |
601 | 0 | return ValidateUnpackPixels(webgl, fullRows, 0, this); |
602 | 0 | } |
603 | | |
604 | | bool |
605 | | TexUnpackImage::TexOrSubImage(bool isSubImage, bool needsRespec, |
606 | | WebGLTexture* tex, TexImageTarget target, GLint level, |
607 | | const webgl::DriverUnpackInfo* dui, GLint xOffset, |
608 | | GLint yOffset, GLint zOffset, const webgl::PackingInfo& pi, |
609 | | GLenum* const out_error) const |
610 | 0 | { |
611 | 0 | MOZ_ASSERT_IF(needsRespec, !isSubImage); |
612 | 0 |
|
613 | 0 | WebGLContext* webgl = tex->mContext; |
614 | 0 |
|
615 | 0 | gl::GLContext* gl = webgl->GL(); |
616 | 0 |
|
617 | 0 | if (needsRespec) { |
618 | 0 | *out_error = DoTexOrSubImage(isSubImage, gl, target.get(), level, dui, xOffset, |
619 | 0 | yOffset, zOffset, mWidth, mHeight, mDepth, |
620 | 0 | nullptr); |
621 | 0 | if (*out_error) |
622 | 0 | return true; |
623 | 0 | } |
624 | 0 | |
625 | 0 | const char* fallbackReason; |
626 | 0 | do { |
627 | 0 | if (mDepth != 1) { |
628 | 0 | fallbackReason = "depth is not 1"; |
629 | 0 | break; |
630 | 0 | } |
631 | 0 | if (xOffset != 0 || yOffset != 0 || zOffset != 0) { |
632 | 0 | fallbackReason = "x/y/zOffset is not 0"; |
633 | 0 | break; |
634 | 0 | } |
635 | 0 | |
636 | 0 | if (webgl->mPixelStore_UnpackSkipPixels || |
637 | 0 | webgl->mPixelStore_UnpackSkipRows || |
638 | 0 | webgl->mPixelStore_UnpackSkipImages) |
639 | 0 | { |
640 | 0 | fallbackReason = "non-zero UNPACK_SKIP_* not yet supported"; |
641 | 0 | break; |
642 | 0 | } |
643 | 0 | |
644 | 0 | const auto fnHasPremultMismatch = [&]() { |
645 | 0 | if (mSrcAlphaType == gfxAlphaType::Opaque) |
646 | 0 | return false; |
647 | 0 | |
648 | 0 | const bool srcIsPremult = (mSrcAlphaType == gfxAlphaType::Premult); |
649 | 0 | const auto& dstIsPremult = webgl->mPixelStore_PremultiplyAlpha; |
650 | 0 | if (srcIsPremult == dstIsPremult) |
651 | 0 | return false; |
652 | 0 | |
653 | 0 | if (dstIsPremult) { |
654 | 0 | fallbackReason = "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not true"; |
655 | 0 | } else { |
656 | 0 | fallbackReason = "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not false"; |
657 | 0 | } |
658 | 0 | return true; |
659 | 0 | }; |
660 | 0 | if (fnHasPremultMismatch()) |
661 | 0 | break; |
662 | 0 | |
663 | 0 | if (dui->unpackFormat != LOCAL_GL_RGB && dui->unpackFormat != LOCAL_GL_RGBA) { |
664 | 0 | fallbackReason = "`format` is not RGB or RGBA"; |
665 | 0 | break; |
666 | 0 | } |
667 | 0 | |
668 | 0 | if (dui->unpackType != LOCAL_GL_UNSIGNED_BYTE) { |
669 | 0 | fallbackReason = "`type` is not UNSIGNED_BYTE"; |
670 | 0 | break; |
671 | 0 | } |
672 | 0 | |
673 | 0 | gl::ScopedFramebuffer scopedFB(gl); |
674 | 0 | gl::ScopedBindFramebuffer bindFB(gl, scopedFB.FB()); |
675 | 0 |
|
676 | 0 | { |
677 | 0 | gl::GLContext::LocalErrorScope errorScope(*gl); |
678 | 0 |
|
679 | 0 | gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, |
680 | 0 | target.get(), tex->mGLName, level); |
681 | 0 |
|
682 | 0 | if (errorScope.GetError()) { |
683 | 0 | fallbackReason = "bug: failed to attach to FB for blit"; |
684 | 0 | break; |
685 | 0 | } |
686 | 0 | } |
687 | 0 | |
688 | 0 | const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); |
689 | 0 | if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) { |
690 | 0 | fallbackReason = "bug: failed to confirm FB for blit"; |
691 | 0 | break; |
692 | 0 | } |
693 | 0 | |
694 | 0 | const gfx::IntSize dstSize(mWidth, mHeight); |
695 | 0 | const auto dstOrigin = (webgl->mPixelStore_FlipY ? gl::OriginPos::TopLeft |
696 | 0 | : gl::OriginPos::BottomLeft); |
697 | 0 | if (!gl->BlitHelper()->BlitImageToFramebuffer(mImage, dstSize, dstOrigin)) { |
698 | 0 | fallbackReason = "likely bug: failed to blit"; |
699 | 0 | break; |
700 | 0 | } |
701 | 0 | |
702 | 0 | // Blitting was successful, so we're done! |
703 | 0 | *out_error = 0; |
704 | 0 | return true; |
705 | 0 | } while (false); |
706 | 0 |
|
707 | 0 | const nsPrintfCString perfMsg("Failed to hit GPU-copy fast-path: %s (src type %u)", |
708 | 0 | fallbackReason, uint32_t(mImage->GetFormat())); |
709 | 0 |
|
710 | 0 | if (webgl->mPixelStore_RequireFastPath) { |
711 | 0 | webgl->ErrorInvalidOperation("%s", perfMsg.BeginReading()); |
712 | 0 | return false; |
713 | 0 | } |
714 | 0 | |
715 | 0 | webgl->GeneratePerfWarning("%s Falling back to CPU upload.", |
716 | 0 | perfMsg.BeginReading()); |
717 | 0 |
|
718 | 0 | const RefPtr<gfx::SourceSurface> surf = mImage->GetAsSourceSurface(); |
719 | 0 |
|
720 | 0 | RefPtr<gfx::DataSourceSurface> dataSurf; |
721 | 0 | if (surf) { |
722 | 0 | // WARNING: OSX can lose our MakeCurrent here. |
723 | 0 | dataSurf = surf->GetDataSurface(); |
724 | 0 | } |
725 | 0 | if (!dataSurf) { |
726 | 0 | webgl->ErrorOutOfMemory("GetAsSourceSurface or GetDataSurface failed after" |
727 | 0 | " blit failed for TexUnpackImage."); |
728 | 0 | return false; |
729 | 0 | } |
730 | 0 | |
731 | 0 | const TexUnpackSurface surfBlob(webgl, target, mWidth, mHeight, mDepth, dataSurf, |
732 | 0 | mSrcAlphaType); |
733 | 0 |
|
734 | 0 | return surfBlob.TexOrSubImage(isSubImage, needsRespec, tex, target, level, |
735 | 0 | dui, xOffset, yOffset, zOffset, pi, out_error); |
736 | 0 | } |
737 | | |
738 | | //////////////////////////////////////////////////////////////////////////////// |
739 | | //////////////////////////////////////////////////////////////////////////////// |
740 | | // TexUnpackSurface |
741 | | |
742 | | TexUnpackSurface::TexUnpackSurface(const WebGLContext* webgl, TexImageTarget target, |
743 | | uint32_t width, uint32_t height, uint32_t depth, |
744 | | gfx::DataSourceSurface* surf, |
745 | | gfxAlphaType srcAlphaType) |
746 | | : TexUnpackBlob(webgl, target, surf->GetSize().width, width, height, depth, |
747 | | srcAlphaType) |
748 | | , mSurf(surf) |
749 | 0 | { } |
750 | | |
751 | | ////////// |
752 | | |
753 | | static bool |
754 | | GetFormatForSurf(gfx::SourceSurface* surf, WebGLTexelFormat* const out_texelFormat, |
755 | | uint8_t* const out_bpp) |
756 | 0 | { |
757 | 0 | const auto surfFormat = surf->GetFormat(); |
758 | 0 | switch (surfFormat) { |
759 | 0 | case gfx::SurfaceFormat::B8G8R8A8: |
760 | 0 | *out_texelFormat = WebGLTexelFormat::BGRA8; |
761 | 0 | *out_bpp = 4; |
762 | 0 | return true; |
763 | 0 |
|
764 | 0 | case gfx::SurfaceFormat::B8G8R8X8: |
765 | 0 | *out_texelFormat = WebGLTexelFormat::BGRX8; |
766 | 0 | *out_bpp = 4; |
767 | 0 | return true; |
768 | 0 |
|
769 | 0 | case gfx::SurfaceFormat::R8G8B8A8: |
770 | 0 | *out_texelFormat = WebGLTexelFormat::RGBA8; |
771 | 0 | *out_bpp = 4; |
772 | 0 | return true; |
773 | 0 |
|
774 | 0 | case gfx::SurfaceFormat::R8G8B8X8: |
775 | 0 | *out_texelFormat = WebGLTexelFormat::RGBX8; |
776 | 0 | *out_bpp = 4; |
777 | 0 | return true; |
778 | 0 |
|
779 | 0 | case gfx::SurfaceFormat::R5G6B5_UINT16: |
780 | 0 | *out_texelFormat = WebGLTexelFormat::RGB565; |
781 | 0 | *out_bpp = 2; |
782 | 0 | return true; |
783 | 0 |
|
784 | 0 | case gfx::SurfaceFormat::A8: |
785 | 0 | *out_texelFormat = WebGLTexelFormat::A8; |
786 | 0 | *out_bpp = 1; |
787 | 0 | return true; |
788 | 0 |
|
789 | 0 | case gfx::SurfaceFormat::YUV: |
790 | 0 | // Ugh... |
791 | 0 | NS_ERROR("We don't handle uploads from YUV sources yet."); |
792 | 0 | // When we want to, check out gfx/ycbcr/YCbCrUtils.h. (specifically |
793 | 0 | // GetYCbCrToRGBDestFormatAndSize and ConvertYCbCrToRGB) |
794 | 0 | return false; |
795 | 0 |
|
796 | 0 | default: |
797 | 0 | return false; |
798 | 0 | } |
799 | 0 | } |
800 | | |
801 | | ////////// |
802 | | |
803 | | bool |
804 | | TexUnpackSurface::Validate(WebGLContext* webgl, const webgl::PackingInfo& pi) |
805 | 0 | { |
806 | 0 | if (!ValidatePIForDOM(webgl, pi)) |
807 | 0 | return false; |
808 | 0 | |
809 | 0 | const auto fullRows = mSurf->GetSize().height; |
810 | 0 | return ValidateUnpackPixels(webgl, fullRows, 0, this); |
811 | 0 | } |
812 | | |
813 | | bool |
814 | | TexUnpackSurface::TexOrSubImage(bool isSubImage, bool needsRespec, |
815 | | WebGLTexture* tex, TexImageTarget target, GLint level, |
816 | | const webgl::DriverUnpackInfo* dui, GLint xOffset, |
817 | | GLint yOffset, GLint zOffset, const webgl::PackingInfo& dstPI, |
818 | | GLenum* const out_error) const |
819 | 0 | { |
820 | 0 | const auto& webgl = tex->mContext; |
821 | 0 |
|
822 | 0 | //// |
823 | 0 |
|
824 | 0 | const auto rowLength = mSurf->GetSize().width; |
825 | 0 | const auto rowCount = mSurf->GetSize().height; |
826 | 0 |
|
827 | 0 | const auto& dstBPP = webgl::BytesPerPixel(dstPI); |
828 | 0 | const auto dstFormat = FormatForPackingInfo(dstPI); |
829 | 0 |
|
830 | 0 | //// |
831 | 0 |
|
832 | 0 | WebGLTexelFormat srcFormat; |
833 | 0 | uint8_t srcBPP; |
834 | 0 | if (!GetFormatForSurf(mSurf, &srcFormat, &srcBPP)) { |
835 | 0 | webgl->ErrorImplementationBug("GetFormatForSurf failed for" |
836 | 0 | " WebGLTexelFormat::%u.", |
837 | 0 | uint32_t(mSurf->GetFormat())); |
838 | 0 | return false; |
839 | 0 | } |
840 | 0 | |
841 | 0 | gfx::DataSourceSurface::ScopedMap map(mSurf, gfx::DataSourceSurface::MapType::READ); |
842 | 0 | if (!map.IsMapped()) { |
843 | 0 | webgl->ErrorOutOfMemory("Failed to map source surface for upload."); |
844 | 0 | return false; |
845 | 0 | } |
846 | 0 | |
847 | 0 | const auto& srcBegin = map.GetData(); |
848 | 0 | const auto& srcStride = map.GetStride(); |
849 | 0 |
|
850 | 0 | //// |
851 | 0 |
|
852 | 0 | const auto srcRowLengthBytes = rowLength * srcBPP; |
853 | 0 |
|
854 | 0 | const uint8_t maxGLAlignment = 8; |
855 | 0 | uint8_t srcAlignment = 1; |
856 | 0 | for (; srcAlignment <= maxGLAlignment; srcAlignment *= 2) { |
857 | 0 | const auto strideGuess = RoundUpToMultipleOf(srcRowLengthBytes, srcAlignment); |
858 | 0 | if (strideGuess == srcStride) |
859 | 0 | break; |
860 | 0 | } |
861 | 0 | const uint32_t dstAlignment = (srcAlignment > maxGLAlignment) ? 1 : srcAlignment; |
862 | 0 |
|
863 | 0 | const auto dstRowLengthBytes = rowLength * dstBPP; |
864 | 0 | const auto dstStride = RoundUpToMultipleOf(dstRowLengthBytes, dstAlignment); |
865 | 0 |
|
866 | 0 | //// |
867 | 0 |
|
868 | 0 | const uint8_t* dstBegin = srcBegin; |
869 | 0 | UniqueBuffer tempBuffer; |
870 | 0 | if (!ConvertIfNeeded(webgl, rowLength, rowCount, srcFormat, srcBegin, |
871 | 0 | srcStride, dstFormat, dstStride, &dstBegin, &tempBuffer)) |
872 | 0 | { |
873 | 0 | return false; |
874 | 0 | } |
875 | 0 | |
876 | 0 | //// |
877 | 0 | |
878 | 0 | const auto& gl = webgl->gl; |
879 | 0 | if (!gl->MakeCurrent()) { |
880 | 0 | *out_error = LOCAL_GL_CONTEXT_LOST; |
881 | 0 | return true; |
882 | 0 | } |
883 | 0 |
|
884 | 0 | gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, dstAlignment); |
885 | 0 | if (webgl->IsWebGL2()) { |
886 | 0 | gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, rowLength); |
887 | 0 | } |
888 | 0 |
|
889 | 0 | *out_error = DoTexOrSubImage(isSubImage, gl, target.get(), level, dui, xOffset, |
890 | 0 | yOffset, zOffset, mWidth, mHeight, mDepth, dstBegin); |
891 | 0 |
|
892 | 0 | gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, webgl->mPixelStore_UnpackAlignment); |
893 | 0 | if (webgl->IsWebGL2()) { |
894 | 0 | gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, webgl->mPixelStore_UnpackRowLength); |
895 | 0 | } |
896 | 0 |
|
897 | 0 | return true; |
898 | 0 | } |
899 | | |
900 | | } // namespace webgl |
901 | | } // namespace mozilla |