/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "TexUnpackBlob.h" #include "GLBlitHelper.h" #include "GLContext.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/HTMLCanvasElement.h" #include "mozilla/gfx/Logging.h" #include "mozilla/layers/ImageDataSerializer.h" #include "mozilla/RefPtr.h" #include "nsLayoutUtils.h" #include "WebGLBuffer.h" #include "WebGLContext.h" #include "WebGLFormats.h" #include "WebGLTexelConversions.h" #include "WebGLTexture.h" namespace mozilla { bool webgl::PixelPackingState::AssertCurrentUnpack(gl::GLContext& gl, const bool isWebgl2) const { auto actual = PixelPackingState{}; gl.GetInt(LOCAL_GL_UNPACK_ALIGNMENT, &actual.alignmentInTypeElems); if (isWebgl2) { gl.GetInt(LOCAL_GL_UNPACK_ROW_LENGTH, &actual.rowLength); gl.GetInt(LOCAL_GL_UNPACK_IMAGE_HEIGHT, &actual.imageHeight); gl.GetInt(LOCAL_GL_UNPACK_SKIP_PIXELS, &actual.skipPixels); gl.GetInt(LOCAL_GL_UNPACK_SKIP_ROWS, &actual.skipRows); gl.GetInt(LOCAL_GL_UNPACK_SKIP_IMAGES, &actual.skipImages); } if (*this == actual) return true; const auto ToStr = [](const PixelPackingState& x) { const auto text = nsPrintfCString( "%u,%u,%u;%u,%u,%u", x.alignmentInTypeElems, x.rowLength, x.imageHeight, x.skipPixels, x.skipRows, x.skipImages); return mozilla::ToString(text); }; const auto was = ToStr(actual); const auto expected = ToStr(*this); gfxCriticalError() << "PixelUnpackStateGl was not current. Was " << was << ". Expected << " << expected << "."; return false; } void webgl::PixelPackingState::ApplyUnpack(gl::GLContext& gl, const bool isWebgl2, const uvec3& uploadSize) const { gl.fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, AssertedCast(alignmentInTypeElems)); if (!isWebgl2) return; // Re-simplify. (ANGLE seems to have an issue with imageHeight == // uploadSize.y) auto rowLengthOrZero = rowLength; auto imageHeightOrZero = imageHeight; if (rowLengthOrZero == uploadSize.x) { rowLengthOrZero = 0; } if (imageHeightOrZero == uploadSize.y) { imageHeightOrZero = 0; } gl.fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, AssertedCast(rowLengthOrZero)); gl.fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, AssertedCast(imageHeightOrZero)); gl.fPixelStorei(LOCAL_GL_UNPACK_SKIP_PIXELS, AssertedCast(skipPixels)); gl.fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS, AssertedCast(skipRows)); gl.fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, AssertedCast(skipImages)); } namespace webgl { static bool IsPIValidForDOM(const webgl::PackingInfo& pi) { // https://www.khronos.org/registry/webgl/specs/latest/2.0/#TEXTURE_TYPES_FORMATS_FROM_DOM_ELEMENTS_TABLE // Just check for invalid individual formats and types, not combinations. switch (pi.format) { case LOCAL_GL_RGB: case LOCAL_GL_RGBA: case LOCAL_GL_LUMINANCE_ALPHA: case LOCAL_GL_LUMINANCE: case LOCAL_GL_ALPHA: case LOCAL_GL_RED: case LOCAL_GL_RED_INTEGER: case LOCAL_GL_RG: case LOCAL_GL_RG_INTEGER: case LOCAL_GL_RGB_INTEGER: case LOCAL_GL_RGBA_INTEGER: break; case LOCAL_GL_SRGB: case LOCAL_GL_SRGB_ALPHA: // Allowed in WebGL1+EXT_srgb break; default: return false; } switch (pi.type) { case LOCAL_GL_UNSIGNED_BYTE: case LOCAL_GL_UNSIGNED_SHORT_5_6_5: case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4: case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1: case LOCAL_GL_HALF_FLOAT: case LOCAL_GL_HALF_FLOAT_OES: case LOCAL_GL_FLOAT: case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV: break; default: return false; } return true; } static bool ValidatePIForDOM(const WebGLContext* const webgl, const webgl::PackingInfo& pi) { if (!IsPIValidForDOM(pi)) { webgl->ErrorInvalidValue("Format or type is invalid for DOM sources."); return false; } return true; } static WebGLTexelFormat FormatForPackingInfo(const PackingInfo& pi) { switch (pi.type) { case LOCAL_GL_UNSIGNED_BYTE: switch (pi.format) { case LOCAL_GL_RED: case LOCAL_GL_LUMINANCE: case LOCAL_GL_RED_INTEGER: return WebGLTexelFormat::R8; case LOCAL_GL_ALPHA: return WebGLTexelFormat::A8; case LOCAL_GL_LUMINANCE_ALPHA: return WebGLTexelFormat::RA8; case LOCAL_GL_RGB: case LOCAL_GL_RGB_INTEGER: case LOCAL_GL_SRGB: return WebGLTexelFormat::RGB8; case LOCAL_GL_RGBA: case LOCAL_GL_RGBA_INTEGER: case LOCAL_GL_SRGB_ALPHA: return WebGLTexelFormat::RGBA8; case LOCAL_GL_RG: case LOCAL_GL_RG_INTEGER: return WebGLTexelFormat::RG8; default: break; } break; case LOCAL_GL_UNSIGNED_SHORT_5_6_5: if (pi.format == LOCAL_GL_RGB) return WebGLTexelFormat::RGB565; break; case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1: if (pi.format == LOCAL_GL_RGBA) return WebGLTexelFormat::RGBA5551; break; case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4: if (pi.format == LOCAL_GL_RGBA) return WebGLTexelFormat::RGBA4444; break; case LOCAL_GL_HALF_FLOAT: case LOCAL_GL_HALF_FLOAT_OES: switch (pi.format) { case LOCAL_GL_RED: case LOCAL_GL_LUMINANCE: return WebGLTexelFormat::R16F; case LOCAL_GL_ALPHA: return WebGLTexelFormat::A16F; case LOCAL_GL_LUMINANCE_ALPHA: return WebGLTexelFormat::RA16F; case LOCAL_GL_RG: return WebGLTexelFormat::RG16F; case LOCAL_GL_RGB: return WebGLTexelFormat::RGB16F; case LOCAL_GL_RGBA: return WebGLTexelFormat::RGBA16F; default: break; } break; case LOCAL_GL_FLOAT: switch (pi.format) { case LOCAL_GL_RED: case LOCAL_GL_LUMINANCE: return WebGLTexelFormat::R32F; case LOCAL_GL_ALPHA: return WebGLTexelFormat::A32F; case LOCAL_GL_LUMINANCE_ALPHA: return WebGLTexelFormat::RA32F; case LOCAL_GL_RG: return WebGLTexelFormat::RG32F; case LOCAL_GL_RGB: return WebGLTexelFormat::RGB32F; case LOCAL_GL_RGBA: return WebGLTexelFormat::RGBA32F; default: break; } break; case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV: if (pi.format == LOCAL_GL_RGB) return WebGLTexelFormat::RGB11F11F10F; break; default: break; } return WebGLTexelFormat::FormatNotSupportingAnyConversion; } //////////////////// static uint32_t ZeroOn2D(const GLenum target, const uint32_t val) { const bool is2d = !IsTexTarget3D(target); if (is2d) return 0; return val; } static bool ValidateUnpackPixels(const WebGLContext* webgl, const webgl::PackingInfo& pi, const uint32_t availRows, const webgl::TexUnpackBlob& blob) { const auto& unpackingRes = blob.mDesc.ExplicitUnpacking(pi, {}); if (!unpackingRes.isOk()) { webgl->ErrorInvalidOperation("%s", unpackingRes.inspectErr().c_str()); return false; } const auto& unpacking = unpackingRes.inspect(); if (availRows < unpacking.metrics.totalRows) { webgl->ErrorInvalidOperation( "Desired upload requires more rows (%zu) than is" " available (%zu).", unpacking.metrics.totalRows, availRows); return false; } return true; } static bool ValidateUnpackBytes(const WebGLContext* const webgl, const webgl::PackingInfo& pi, const size_t availByteCount, const webgl::TexUnpackBlob& blob) { const auto& unpackingRes = blob.mDesc.ExplicitUnpacking(pi, {}); if (!unpackingRes.isOk()) { webgl->ErrorInvalidOperation("%s", unpackingRes.inspectErr().c_str()); return false; } const auto& unpacking = unpackingRes.inspect(); if (availByteCount < unpacking.metrics.totalBytesUsed) { webgl->ErrorInvalidOperation( "Desired upload requires more bytes (%zu) than are" " available (%zu).", unpacking.metrics.totalBytesUsed, availByteCount); return false; } return true; } //////////////////// // Check if the surface descriptor describes a memory which contains a single // RGBA data source. static bool SDIsRGBBuffer(const layers::SurfaceDescriptor& sd) { return sd.type() == layers::SurfaceDescriptor::TSurfaceDescriptorBuffer && sd.get_SurfaceDescriptorBuffer().desc().type() == layers::BufferDescriptor::TRGBDescriptor; } // static std::unique_ptr TexUnpackBlob::Create( const TexUnpackBlobDesc& desc) { return std::unique_ptr{[&]() -> TexUnpackBlob* { if (!IsTarget3D(desc.imageTarget) && desc.size.z != 1) { MOZ_ASSERT(false); return nullptr; } switch (desc.unpacking.alignmentInTypeElems) { case 1: case 2: case 4: case 8: break; default: MOZ_ASSERT(false); return nullptr; } if (desc.sd) { // Shmem buffers need to be treated as if they were a DataSourceSurface. // Otherwise, TexUnpackImage will try to blit the surface descriptor as // if it can be mapped as a framebuffer, whereas the Shmem is still CPU // data. if (SDIsRGBBuffer(*desc.sd)) return new TexUnpackSurface(desc); return new TexUnpackImage(desc); } if (desc.dataSurf) { return new TexUnpackSurface(desc); } if (desc.srcAlphaType != gfxAlphaType::NonPremult) { MOZ_ASSERT(false); return nullptr; } return new TexUnpackBytes(desc); }()}; } static bool HasColorAndAlpha(const WebGLTexelFormat format) { switch (format) { case WebGLTexelFormat::RA8: case WebGLTexelFormat::RA16F: case WebGLTexelFormat::RA32F: case WebGLTexelFormat::RGBA8: case WebGLTexelFormat::RGBA5551: case WebGLTexelFormat::RGBA4444: case WebGLTexelFormat::RGBA16F: case WebGLTexelFormat::RGBA32F: case WebGLTexelFormat::BGRA8: return true; default: return false; } } bool TexUnpackBlob::ConvertIfNeeded( const WebGLContext* const webgl, const uint32_t rowLength, const uint32_t rowCount, WebGLTexelFormat srcFormat, const uint8_t* const srcBegin, const ptrdiff_t srcStride, WebGLTexelFormat dstFormat, const ptrdiff_t dstStride, const uint8_t** const out_begin, UniqueBuffer* const out_anchoredBuffer) const { MOZ_ASSERT(srcFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion); MOZ_ASSERT(dstFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion); *out_begin = srcBegin; const auto& unpacking = mDesc.unpacking; if (!rowLength || !rowCount) return true; const auto srcIsPremult = (mDesc.srcAlphaType == gfxAlphaType::Premult); auto dstIsPremult = unpacking.premultiplyAlpha; const auto fnHasPremultMismatch = [&]() { if (mDesc.srcAlphaType == gfxAlphaType::Opaque) return false; if (!HasColorAndAlpha(srcFormat)) return false; return srcIsPremult != dstIsPremult; }; const auto srcOrigin = (unpacking.flipY ? gl::OriginPos::TopLeft : gl::OriginPos::BottomLeft); auto dstOrigin = gl::OriginPos::BottomLeft; if (!mDesc.applyUnpackTransforms) { dstIsPremult = srcIsPremult; dstOrigin = srcOrigin; } if (srcFormat != dstFormat) { webgl->GeneratePerfWarning( "Conversion requires pixel reformatting. (%u->%u)", uint32_t(srcFormat), uint32_t(dstFormat)); } else if (fnHasPremultMismatch()) { webgl->GeneratePerfWarning( "Conversion requires change in" " alpha-premultiplication."); } else if (srcOrigin != dstOrigin) { webgl->GeneratePerfWarning("Conversion requires y-flip."); } else if (srcStride != dstStride) { webgl->GeneratePerfWarning("Conversion requires change in stride. (%u->%u)", uint32_t(srcStride), uint32_t(dstStride)); } else { return true; } //// const auto dstTotalBytes = CheckedUint32(rowCount) * dstStride; if (!dstTotalBytes.isValid()) { webgl->ErrorOutOfMemory("Calculation failed."); return false; } auto dstBuffer = UniqueBuffer::Take(calloc(1u, dstTotalBytes.value())); if (!dstBuffer.get()) { webgl->ErrorOutOfMemory("Failed to allocate dest buffer."); return false; } const auto dstBegin = static_cast(dstBuffer.get()); //// // And go!: bool wasTrivial; if (!ConvertImage(rowLength, rowCount, srcBegin, srcStride, srcOrigin, srcFormat, srcIsPremult, dstBegin, dstStride, dstOrigin, dstFormat, dstIsPremult, &wasTrivial)) { webgl->ErrorImplementationBug("ConvertImage failed."); return false; } *out_begin = dstBegin; *out_anchoredBuffer = std::move(dstBuffer); return true; } static GLenum DoTexOrSubImage(bool isSubImage, gl::GLContext* gl, TexImageTarget target, GLint level, const DriverUnpackInfo* dui, GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width, GLsizei height, GLsizei depth, const void* data) { if (isSubImage) { return DoTexSubImage(gl, target, level, xOffset, yOffset, zOffset, width, height, depth, dui->ToPacking(), data); } else { return DoTexImage(gl, target, level, dui, width, height, depth, data); } } ////////////////////////////////////////////////////////////////////////////////////////// // TexUnpackBytes bool TexUnpackBytes::Validate(const WebGLContext* const webgl, const webgl::PackingInfo& pi) { if (!HasData()) return true; CheckedInt availBytes = 0; if (mDesc.cpuData) { const auto& range = mDesc.cpuData->Data(); availBytes = range.length(); } else if (mDesc.pboOffset) { const auto& pboOffset = *mDesc.pboOffset; const auto& pbo = webgl->ValidateBufferSelection(LOCAL_GL_PIXEL_UNPACK_BUFFER); if (!pbo) return false; // Might be invalid e.g. due to in-use by TF. availBytes = pbo->ByteLength(); availBytes -= pboOffset; } else { MOZ_ASSERT(false, "Must be one of the above"); } if (!availBytes.isValid()) { webgl->ErrorInvalidOperation("Offset is passed end of buffer."); return false; } return ValidateUnpackBytes(webgl, pi, availBytes.value(), *this); } bool TexUnpackBytes::TexOrSubImage(bool isSubImage, bool needsRespec, WebGLTexture* tex, GLint level, const webgl::DriverUnpackInfo* dui, GLint xOffset, GLint yOffset, GLint zOffset, const webgl::PackingInfo& pi, GLenum* const out_error) const { const auto& webgl = tex->mContext; const auto& target = mDesc.imageTarget; const auto& size = mDesc.size; const auto& webglUnpackState = mDesc.unpacking; const auto unpackingRes = mDesc.ExplicitUnpacking(pi, {}); const auto format = FormatForPackingInfo(pi); const uint8_t* uploadPtr = nullptr; if (mDesc.cpuData) { const auto range = mDesc.cpuData->Data(); uploadPtr = range.begin().get(); if (!uploadPtr) { MOZ_ASSERT(!range.length()); } } else if (mDesc.pboOffset) { uploadPtr = reinterpret_cast(*mDesc.pboOffset); } UniqueBuffer tempBuffer; do { if (mDesc.pboOffset || !uploadPtr) break; if (!webglUnpackState.flipY && !webglUnpackState.premultiplyAlpha) { break; } webgl->GenerateWarning( "Alpha-premult and y-flip are deprecated for" " non-DOM-Element uploads."); MOZ_RELEASE_ASSERT(unpackingRes.isOk()); const auto& unpacking = unpackingRes.inspect(); const auto stride = unpacking.metrics.bytesPerRowStride; // clang-format off if (!ConvertIfNeeded(webgl, unpacking.state.rowLength, unpacking.metrics.totalRows, format, uploadPtr, AutoAssertCast(stride), format, AutoAssertCast(stride), &uploadPtr, &tempBuffer)) { return false; } // clang-format on } while (false); ////// const auto& gl = webgl->gl; bool useParanoidHandling = false; if (mNeedsExactUpload && webgl->mBoundPixelUnpackBuffer) { webgl->GenerateWarning( "Uploads from a buffer with a final row with a byte" " count smaller than the row stride can incur extra" " overhead."); if (gl->WorkAroundDriverBugs()) { useParanoidHandling |= (gl->Vendor() == gl::GLVendor::NVIDIA); } } if (!useParanoidHandling) { const ScopedLazyBind bindPBO(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER, webgl->mBoundPixelUnpackBuffer); *out_error = DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset, zOffset, size.x, size.y, size.z, uploadPtr); return true; } ////// MOZ_ASSERT(webgl->mBoundPixelUnpackBuffer); if (!isSubImage) { // Alloc first to catch OOMs. AssertUintParamCorrect(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER_BINDING, 0); *out_error = DoTexOrSubImage(false, gl, target, level, dui, xOffset, yOffset, zOffset, size.x, size.y, size.z, nullptr); if (*out_error) return true; } if (!size.x || !size.y || !size.z) { // Nothing to do. return true; } MOZ_RELEASE_ASSERT(unpackingRes.isOk()); const auto& unpacking = unpackingRes.inspect(); const ScopedLazyBind bindPBO(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER, webgl->mBoundPixelUnpackBuffer); ////// // Make our sometimes-implicit values explicit. Also this keeps them constant // when we ask for height=mHeight-1 and such. gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, AutoAssertCast(unpacking.state.rowLength)); gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, AutoAssertCast(unpacking.state.imageHeight)); if (size.z > 1) { *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset, zOffset, size.x, size.y, size.z - 1, uploadPtr); } // Skip the images we uploaded. const auto skipImages = ZeroOn2D(target, unpacking.state.skipImages); gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, skipImages + size.z - 1); if (size.y > 1) { *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset, zOffset + size.z - 1, size.x, size.y - 1, 1, uploadPtr); } // - const auto lastRowOffset = unpacking.metrics.totalBytesStrided - unpacking.metrics.bytesPerRowStride; const auto lastRowPtr = uploadPtr + lastRowOffset; gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); // No stride padding. gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, 0); // No padding in general. gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, 0); // Don't skip images, gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS, 0); // or rows. // Keep skipping pixels though! *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset + size.y - 1, zOffset + size.z - 1, AutoAssertCast(size.x), 1, 1, lastRowPtr); // Caller will reset all our modified PixelStorei state. return true; } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // TexUnpackImage TexUnpackImage::~TexUnpackImage() = default; bool TexUnpackImage::Validate(const WebGLContext* const webgl, const webgl::PackingInfo& pi) { if (!ValidatePIForDOM(webgl, pi)) return false; if (!mDesc.structuredSrcSize) { gfxCriticalError() << "TexUnpackImage missing structuredSrcSize."; return false; } const auto& elemSize = *mDesc.structuredSrcSize; if (mDesc.dataSurf) { const auto& surfSize = mDesc.dataSurf->GetSize(); const auto surfSize2 = ivec2::FromSize(surfSize)->StaticCast(); if (uvec2{elemSize.x, elemSize.y} != surfSize2) { gfxCriticalError() << "TexUnpackImage mismatched structuredSrcSize for dataSurf."; return false; } } const auto fullRows = elemSize.y; return ValidateUnpackPixels(webgl, pi, fullRows, *this); } Maybe BlitPreventReason(const int32_t level, const ivec3& offset, const webgl::PackingInfo& pi, const TexUnpackBlobDesc& desc) { const auto& size = desc.size; const auto& unpacking = desc.unpacking; const auto ret = [&]() -> const char* { if (size.z != 1) { return "depth is not 1"; } if (offset.x != 0 || offset.y != 0 || offset.z != 0) { return "x/y/zOffset is not 0"; } if (unpacking.skipPixels || unpacking.skipRows || unpacking.skipImages) { return "non-zero UNPACK_SKIP_* not yet supported"; } const auto premultReason = [&]() -> const char* { if (desc.srcAlphaType == gfxAlphaType::Opaque) return nullptr; const bool srcIsPremult = (desc.srcAlphaType == gfxAlphaType::Premult); const auto& dstIsPremult = unpacking.premultiplyAlpha; if (srcIsPremult == dstIsPremult) return nullptr; if (dstIsPremult) { return "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not true"; } else { return "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not false"; } }(); if (premultReason) return premultReason; if (pi.format != LOCAL_GL_RGBA) { return "`format` is not RGBA"; } if (pi.type != LOCAL_GL_UNSIGNED_BYTE) { return "`type` is not UNSIGNED_BYTE"; } return nullptr; }(); if (ret) { return Some(std::string(ret)); } return {}; } bool TexUnpackImage::TexOrSubImage(bool isSubImage, bool needsRespec, WebGLTexture* tex, GLint level, const webgl::DriverUnpackInfo* dui, GLint xOffset, GLint yOffset, GLint zOffset, const webgl::PackingInfo& pi, GLenum* const out_error) const { MOZ_ASSERT_IF(needsRespec, !isSubImage); const auto& webgl = tex->mContext; const auto& target = mDesc.imageTarget; const auto& size = mDesc.size; const auto& sd = *(mDesc.sd); const auto& unpacking = mDesc.unpacking; const auto& gl = webgl->GL(); // - const auto reason = BlitPreventReason(level, {xOffset, yOffset, zOffset}, pi, mDesc); if (reason) { webgl->GeneratePerfWarning( "Failed to hit GPU-copy fast-path." " (%s) Falling back to CPU upload.", reason->c_str()); return false; } // - if (needsRespec) { *out_error = DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset, zOffset, size.x, size.y, size.z, nullptr); if (*out_error) return true; } { gl::ScopedFramebuffer scopedFB(gl); gl::ScopedBindFramebuffer bindFB(gl, scopedFB.FB()); { gl::GLContext::LocalErrorScope errorScope(*gl); gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, target, tex->mGLName, level); const auto err = errorScope.GetError(); MOZ_ALWAYS_TRUE(!err); } const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); MOZ_ALWAYS_TRUE(status == LOCAL_GL_FRAMEBUFFER_COMPLETE); const auto dstOrigin = (unpacking.flipY ? gl::OriginPos::TopLeft : gl::OriginPos::BottomLeft); if (!gl->BlitHelper()->BlitSdToFramebuffer(sd, {size.x, size.y}, dstOrigin)) { gfxCriticalNote << "BlitSdToFramebuffer failed for type " << int(sd.type()); // Maybe the resource isn't valid anymore? gl->fClearColor(0.2, 0.0, 0.2, 1.0); gl->fClear(LOCAL_GL_COLOR_BUFFER_BIT); const auto& cur = webgl->mColorClearValue; gl->fClearColor(cur[0], cur[1], cur[2], cur[3]); webgl->GenerateWarning( "Fast Tex(Sub)Image upload failed without recourse, clearing to " "[0.2, 0.0, 0.2, 1.0]. Please file a bug!"); } } return true; } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // TexUnpackSurface TexUnpackSurface::~TexUnpackSurface() = default; ////////// static bool GetFormatForSurf(const gfx::SourceSurface* surf, WebGLTexelFormat* const out_texelFormat, uint8_t* const out_bpp) { const auto surfFormat = surf->GetFormat(); switch (surfFormat) { case gfx::SurfaceFormat::B8G8R8A8: *out_texelFormat = WebGLTexelFormat::BGRA8; *out_bpp = 4; return true; case gfx::SurfaceFormat::B8G8R8X8: *out_texelFormat = WebGLTexelFormat::BGRX8; *out_bpp = 4; return true; case gfx::SurfaceFormat::R8G8B8A8: *out_texelFormat = WebGLTexelFormat::RGBA8; *out_bpp = 4; return true; case gfx::SurfaceFormat::R8G8B8X8: *out_texelFormat = WebGLTexelFormat::RGBX8; *out_bpp = 4; return true; case gfx::SurfaceFormat::R5G6B5_UINT16: *out_texelFormat = WebGLTexelFormat::RGB565; *out_bpp = 2; return true; case gfx::SurfaceFormat::A8: *out_texelFormat = WebGLTexelFormat::A8; *out_bpp = 1; return true; case gfx::SurfaceFormat::YUV: // Ugh... NS_ERROR("We don't handle uploads from YUV sources yet."); // When we want to, check out gfx/ycbcr/YCbCrUtils.h. (specifically // GetYCbCrToRGBDestFormatAndSize and ConvertYCbCrToRGB) return false; default: return false; } } ////////// bool TexUnpackSurface::Validate(const WebGLContext* const webgl, const webgl::PackingInfo& pi) { if (!ValidatePIForDOM(webgl, pi)) return false; if (!mDesc.structuredSrcSize) { gfxCriticalError() << "TexUnpackSurface missing structuredSrcSize."; return false; } const auto& elemSize = *mDesc.structuredSrcSize; if (mDesc.dataSurf) { const auto& surfSize = mDesc.dataSurf->GetSize(); const auto surfSize2 = ivec2::FromSize(surfSize)->StaticCast(); if (uvec2{elemSize.x, elemSize.y} != surfSize2) { gfxCriticalError() << "TexUnpackSurface mismatched structuredSrcSize for dataSurf."; return false; } } const auto fullRows = elemSize.y; return ValidateUnpackPixels(webgl, pi, fullRows, *this); } bool TexUnpackSurface::TexOrSubImage(bool isSubImage, bool needsRespec, WebGLTexture* tex, GLint level, const webgl::DriverUnpackInfo* dui, GLint xOffset, GLint yOffset, GLint zOffset, const webgl::PackingInfo& dstPI, GLenum* const out_error) const { const auto& webgl = tex->mContext; const auto& size = mDesc.size; RefPtr surf; if (mDesc.sd) { // If we get here, we assume the SD describes an RGBA Shmem. const auto& sd = *(mDesc.sd); MOZ_ASSERT(SDIsRGBBuffer(sd)); const auto& sdb = sd.get_SurfaceDescriptorBuffer(); const auto& rgb = sdb.desc().get_RGBDescriptor(); const auto& data = sdb.data(); MOZ_ASSERT(data.type() == layers::MemoryOrShmem::TShmem); const auto& shmem = data.get_Shmem(); surf = gfx::Factory::CreateWrappingDataSourceSurface( shmem.get(), layers::ImageDataSerializer::GetRGBStride(rgb), rgb.size(), rgb.format()); if (!surf) { gfxCriticalError() << "TexUnpackSurface failed to create wrapping " "DataSourceSurface for Shmem."; return false; } } else { surf = mDesc.dataSurf; } //// WebGLTexelFormat srcFormat; uint8_t srcBPP; if (!GetFormatForSurf(surf, &srcFormat, &srcBPP)) { webgl->ErrorImplementationBug( "GetFormatForSurf failed for" " WebGLTexelFormat::%u.", uint32_t(surf->GetFormat())); return false; } gfx::DataSourceSurface::ScopedMap map(surf, gfx::DataSourceSurface::MapType::READ); if (!map.IsMapped()) { webgl->ErrorOutOfMemory("Failed to map source surface for upload."); return false; } const auto& srcBegin = map.GetData(); const auto srcStride = static_cast(map.GetStride()); // - const auto dstFormat = FormatForPackingInfo(dstPI); const auto dstBpp = BytesPerPixel(dstPI); const size_t dstUsedBytesPerRow = dstBpp * surf->GetSize().width; auto dstStride = dstUsedBytesPerRow; if (dstFormat == srcFormat) { dstStride = srcStride; // Try to match. } // - auto dstUnpackingRes = mDesc.ExplicitUnpacking(dstPI, Some(dstStride)); if (dstUnpackingRes.isOk()) { const auto& dstUnpacking = dstUnpackingRes.inspect(); if (!webgl->IsWebGL2() && dstUnpacking.state.rowLength != size.x) { dstUnpackingRes = Err("WebGL1 can't handle rowLength != size.x"); } } if (!dstUnpackingRes.isOk()) { dstStride = dstUsedBytesPerRow; dstUnpackingRes = mDesc.ExplicitUnpacking(dstPI, Some(dstStride)); } if (!dstUnpackingRes.isOk()) { gfxCriticalError() << dstUnpackingRes.inspectErr(); webgl->ErrorImplementationBug("ExplicitUnpacking failed: %s", dstUnpackingRes.inspectErr().c_str()); return false; } const auto& dstUnpacking = dstUnpackingRes.inspect(); MOZ_ASSERT(dstUnpacking.metrics.bytesPerRowStride == dstStride); // - const uint8_t* dstBegin = srcBegin; UniqueBuffer tempBuffer; // clang-format off if (!ConvertIfNeeded(webgl, surf->GetSize().width, surf->GetSize().height, srcFormat, srcBegin, AutoAssertCast(srcStride), dstFormat, AutoAssertCast(dstUnpacking.metrics.bytesPerRowStride), &dstBegin, &tempBuffer)) { return false; } // clang-format on //// const auto& gl = webgl->gl; if (!gl->MakeCurrent()) { *out_error = LOCAL_GL_CONTEXT_LOST; return true; } dstUnpacking.state.ApplyUnpack(*gl, webgl->IsWebGL2(), size); *out_error = DoTexOrSubImage(isSubImage, gl, mDesc.imageTarget, level, dui, xOffset, yOffset, zOffset, size.x, size.y, size.z, dstBegin); // Caller will reset all our modified PixelStorei state. return true; } } // namespace webgl } // namespace mozilla