static bool ClearDepthTexture(const WebGLContext& webgl, const GLuint tex, const TexImageTarget imageTarget, const uint32_t level, const webgl::FormatUsageInfo* const usage, const uint32_t depth) { // Depth resources actually clear to 1.0f, not 0.0f! // They are also always renderable. MOZ_ASSERT(usage->IsRenderable()); const auto& gl = webgl.gl; const auto& format = usage->format; GLenum attachPoint = LOCAL_GL_DEPTH_ATTACHMENT; GLbitfield clearBits = LOCAL_GL_DEPTH_BUFFER_BIT; if (format->s) { attachPoint = LOCAL_GL_DEPTH_STENCIL_ATTACHMENT; clearBits |= LOCAL_GL_STENCIL_BUFFER_BIT; } // - gl::ScopedFramebuffer scopedFB(gl); const gl::ScopedBindFramebuffer scopedBindFB(gl, scopedFB.FB()); const webgl::ScopedPrepForResourceClear scopedPrep(webgl); const auto fnAttach = [&](const uint32_t z) { switch (imageTarget.get()) { case LOCAL_GL_TEXTURE_3D: case LOCAL_GL_TEXTURE_2D_ARRAY: gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER, attachPoint, tex, level, z); break; default: if (attachPoint == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) { gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT, imageTarget.get(), tex, level); gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_STENCIL_ATTACHMENT, imageTarget.get(), tex, level); } else { gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, attachPoint, imageTarget.get(), tex, level); } break; } }; for (uint32_t z = 0; z < depth; ++z) { fnAttach(z); gl->fClear(clearBits); } const auto& status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); const bool isComplete = (status == LOCAL_GL_FRAMEBUFFER_COMPLETE); MOZ_ASSERT(isComplete); return isComplete; }
TexTarget TexImageTargetToTexTarget(TexImageTarget texImageTarget) { switch (texImageTarget.get()) { case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X: case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X: case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y: case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z: case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: return LOCAL_GL_TEXTURE_CUBE_MAP; default: return texImageTarget.get(); } }
void TexUnpackSurface::TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName, WebGLTexture* tex, TexImageTarget target, GLint level, const webgl::DriverUnpackInfo* dui, GLint xOffset, GLint yOffset, GLint zOffset, GLenum* const out_glError) { *out_glError = 0; WebGLContext* webgl = tex->mContext; // MakeCurrent is a big mess in here, because mapping (and presumably unmapping) on // OSX can lose our MakeCurrent. Therefore it's easiest to MakeCurrent just before we // call into GL, instead of trying to keep MakeCurrent-ed. RefPtr<gfx::DataSourceSurface> dataSurf = mSurf->GetDataSurface(); if (!dataSurf) { // Since GetDataSurface didn't return error code, assume system // is out of memory *out_glError = LOCAL_GL_OUT_OF_MEMORY; return; } GLenum error; if (UploadDataSurface(isSubImage, webgl, target, level, dui, xOffset, yOffset, zOffset, mWidth, mHeight, dataSurf, mIsAlphaPremult, &error)) { return; } if (error == LOCAL_GL_OUT_OF_MEMORY) { *out_glError = LOCAL_GL_OUT_OF_MEMORY; return; } // CPU conversion. (++numCopies) UniqueBuffer convertedBuffer; uint8_t convertedAlignment; bool outOfMemory; if (!ConvertSurface(webgl, dui, dataSurf, mIsAlphaPremult, &convertedBuffer, &convertedAlignment, &outOfMemory)) { if (outOfMemory) { *out_glError = LOCAL_GL_OUT_OF_MEMORY; } else { NS_ERROR("Failed to convert surface."); *out_glError = LOCAL_GL_OUT_OF_MEMORY; } return; } MOZ_ALWAYS_TRUE( webgl->gl->MakeCurrent() ); ScopedUnpackReset scopedReset(webgl); webgl->gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, convertedAlignment); error = DoTexOrSubImage(isSubImage, webgl->gl, target.get(), level, dui, xOffset, yOffset, zOffset, mWidth, mHeight, mDepth, convertedBuffer.get()); *out_glError = error; }
WebGLContext::FakeBlackTexture::FakeBlackTexture(gl::GLContext* gl, TexTarget target, FakeBlackType type) : mGL(gl) , mGLName(CreateGLTexture(gl)) { GLenum texFormat; switch (type) { case FakeBlackType::RGBA0000: texFormat = LOCAL_GL_RGBA; break; case FakeBlackType::RGBA0001: texFormat = LOCAL_GL_RGB; break; default: MOZ_CRASH("bad type"); } gl::ScopedBindTexture scopedBind(mGL, mGLName, target.get()); mGL->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_NEAREST); mGL->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_NEAREST); // We allocate our zeros on the heap, and we overallocate (16 bytes instead of 4) to // minimize the risk of running into a driver bug in texImage2D, as it is a bit // unusual maybe to create 1x1 textures, and the stack may not have the alignment that // TexImage2D expects. const webgl::DriverUnpackInfo dui = {texFormat, texFormat, LOCAL_GL_UNSIGNED_BYTE}; UniqueBuffer zeros = moz_xcalloc(1, 16); // Infallible allocation. if (target == LOCAL_GL_TEXTURE_CUBE_MAP) { for (int i = 0; i < 6; ++i) { const TexImageTarget curTarget = LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + i; const GLenum error = DoTexImage(mGL, curTarget.get(), 0, &dui, 1, 1, 1, zeros.get()); if (error) MOZ_CRASH("Unexpected error during FakeBlack creation."); } } else { const GLenum error = DoTexImage(mGL, target.get(), 0, &dui, 1, 1, 1, zeros.get()); if (error) MOZ_CRASH("Unexpected error during FakeBlack creation."); } }
TexTarget TexImageTargetToTexTarget(TexImageTarget texImageTarget) { switch (texImageTarget.get()) { case LOCAL_GL_TEXTURE_2D: case LOCAL_GL_TEXTURE_3D: return texImageTarget.get(); case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X: case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X: case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y: case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z: case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: return LOCAL_GL_TEXTURE_CUBE_MAP; default: MOZ_ASSERT(false, "Bad texture target"); // Should be caught by the constructor for TexTarget return LOCAL_GL_NONE; } }
// `mask` from glClear. static bool ClearWithTempFB(WebGLContext* webgl, GLuint tex, TexImageTarget texImageTarget, GLint level, TexInternalFormat baseInternalFormat, GLsizei width, GLsizei height) { MOZ_ASSERT(texImageTarget == LOCAL_GL_TEXTURE_2D); gl::GLContext* gl = webgl->GL(); MOZ_ASSERT(gl->IsCurrent()); gl::ScopedFramebuffer fb(gl); gl::ScopedBindFramebuffer autoFB(gl, fb.FB()); GLbitfield mask = 0; switch (baseInternalFormat.get()) { case LOCAL_GL_LUMINANCE: case LOCAL_GL_LUMINANCE_ALPHA: case LOCAL_GL_ALPHA: case LOCAL_GL_RGB: case LOCAL_GL_RGBA: case LOCAL_GL_BGR: case LOCAL_GL_BGRA: mask = LOCAL_GL_COLOR_BUFFER_BIT; gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, texImageTarget.get(), tex, level); break; case LOCAL_GL_DEPTH_COMPONENT32_OES: case LOCAL_GL_DEPTH_COMPONENT24_OES: case LOCAL_GL_DEPTH_COMPONENT16: case LOCAL_GL_DEPTH_COMPONENT: mask = LOCAL_GL_DEPTH_BUFFER_BIT; gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT, texImageTarget.get(), tex, level); break; case LOCAL_GL_DEPTH24_STENCIL8: case LOCAL_GL_DEPTH_STENCIL: mask = LOCAL_GL_DEPTH_BUFFER_BIT | LOCAL_GL_STENCIL_BUFFER_BIT; gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT, texImageTarget.get(), tex, level); gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_STENCIL_ATTACHMENT, texImageTarget.get(), tex, level); break; default: return false; } MOZ_ASSERT(mask); if (ClearByMask(webgl, mask)) return true; // Failed to simply build an FB from the tex, but maybe it needs a // color buffer to be complete. if (mask & LOCAL_GL_COLOR_BUFFER_BIT) { // Nope, it already had one. return false; } gl::ScopedRenderbuffer rb(gl); { // Only GLES guarantees RGBA4. GLenum format = gl->IsGLES() ? LOCAL_GL_RGBA4 : LOCAL_GL_RGBA8; gl::ScopedBindRenderbuffer rbBinding(gl, rb.RB()); gl->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, format, width, height); } gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_RENDERBUFFER, rb.RB()); mask |= LOCAL_GL_COLOR_BUFFER_BIT; // Last chance! return ClearByMask(webgl, mask); }
bool TexUnpackImage::TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName, WebGLTexture* tex, TexImageTarget target, 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); WebGLContext* webgl = tex->mContext; gl::GLContext* gl = webgl->GL(); gl->MakeCurrent(); if (needsRespec) { *out_error = DoTexOrSubImage(isSubImage, gl, target.get(), level, dui, xOffset, yOffset, zOffset, mWidth, mHeight, mDepth, nullptr); if (*out_error) return true; } const char* fallbackReason; do { if (mDepth != 1) { fallbackReason = "depth is not 1"; break; } if (webgl->mPixelStore_UnpackSkipPixels || webgl->mPixelStore_UnpackSkipRows || webgl->mPixelStore_UnpackSkipImages) { fallbackReason = "non-zero UNPACK_SKIP_* not yet supported"; break; } const auto fnHasPremultMismatch = [&]() { if (mSrcAlphaType == gfxAlphaType::Opaque) return false; const bool srcIsPremult = (mSrcAlphaType == gfxAlphaType::Premult); const auto& dstIsPremult = webgl->mPixelStore_PremultiplyAlpha; if (srcIsPremult == dstIsPremult) return false; if (dstIsPremult) { fallbackReason = "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not true"; } else { fallbackReason = "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not false"; } return true; }; if (fnHasPremultMismatch()) break; if (dui->unpackFormat != LOCAL_GL_RGB && dui->unpackFormat != LOCAL_GL_RGBA) { fallbackReason = "`format` is not RGB or RGBA"; break; } if (dui->unpackType != LOCAL_GL_UNSIGNED_BYTE) { fallbackReason = "`type` is not UNSIGNED_BYTE"; break; } 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.get(), tex->mGLName, level); if (errorScope.GetError()) { fallbackReason = "bug: failed to attach to FB for blit"; break; } } const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) { fallbackReason = "bug: failed to confirm FB for blit"; break; } const gfx::IntSize destSize(mWidth, mHeight); const auto dstOrigin = (webgl->mPixelStore_FlipY ? gl::OriginPos::TopLeft : gl::OriginPos::BottomLeft); if (!gl->BlitHelper()->BlitImageToFramebuffer(mImage, destSize, scopedFB.FB(), dstOrigin)) { fallbackReason = "likely bug: failed to blit"; break; } // Blitting was successful, so we're done! *out_error = 0; return true; } while (false); const nsPrintfCString perfMsg("%s: Failed to hit GPU-copy fast-path: %s (src type %u)", funcName, fallbackReason, uint32_t(mImage->GetFormat())); if (webgl->mPixelStore_RequireFastPath) { webgl->ErrorInvalidOperation("%s", perfMsg.BeginReading()); return false; } webgl->GeneratePerfWarning("%s Falling back to CPU upload.", perfMsg.BeginReading()); const RefPtr<gfx::SourceSurface> surf = mImage->GetAsSourceSurface(); RefPtr<gfx::DataSourceSurface> dataSurf; if (surf) { // WARNING: OSX can lose our MakeCurrent here. dataSurf = surf->GetDataSurface(); } if (!dataSurf) { webgl->ErrorOutOfMemory("%s: GetAsSourceSurface or GetDataSurface failed after" " blit failed for TexUnpackImage.", funcName); return false; } const TexUnpackSurface surfBlob(webgl, target, mWidth, mHeight, mDepth, dataSurf, mSrcAlphaType); return surfBlob.TexOrSubImage(isSubImage, needsRespec, funcName, tex, target, level, dui, xOffset, yOffset, zOffset, pi, out_error); }
bool TexUnpackSurface::TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName, WebGLTexture* tex, TexImageTarget target, 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 rowLength = mSurf->GetSize().width; const auto rowCount = mSurf->GetSize().height; const auto& dstBPP = webgl::BytesPerPixel(dstPI); const auto dstFormat = FormatForPackingInfo(dstPI); //// WebGLTexelFormat srcFormat; uint8_t srcBPP; if (!GetFormatForSurf(mSurf, &srcFormat, &srcBPP)) { webgl->ErrorImplementationBug("%s: GetFormatForSurf failed for" " WebGLTexelFormat::%u.", funcName, uint32_t(mSurf->GetFormat())); return false; } gfx::DataSourceSurface::ScopedMap map(mSurf, gfx::DataSourceSurface::MapType::READ); if (!map.IsMapped()) { webgl->ErrorOutOfMemory("%s: Failed to map source surface for upload.", funcName); return false; } const auto& srcBegin = map.GetData(); const auto& srcStride = map.GetStride(); //// const auto srcRowLengthBytes = rowLength * srcBPP; const uint8_t maxGLAlignment = 8; uint8_t srcAlignment = 1; for (; srcAlignment <= maxGLAlignment; srcAlignment *= 2) { const auto strideGuess = RoundUpToMultipleOf(srcRowLengthBytes, srcAlignment); if (strideGuess == srcStride) break; } const uint32_t dstAlignment = (srcAlignment > maxGLAlignment) ? 1 : srcAlignment; const auto dstRowLengthBytes = rowLength * dstBPP; const auto dstStride = RoundUpToMultipleOf(dstRowLengthBytes, dstAlignment); //// const uint8_t* dstBegin = srcBegin; UniqueBuffer tempBuffer; if (!ConvertIfNeeded(webgl, funcName, rowLength, rowCount, srcFormat, srcBegin, srcStride, dstFormat, dstStride, &dstBegin, &tempBuffer)) { return false; } //// const auto& gl = webgl->gl; if (!gl->MakeCurrent()) { *out_error = LOCAL_GL_CONTEXT_LOST; return true; } gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, dstAlignment); if (webgl->IsWebGL2()) { gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, rowLength); } *out_error = DoTexOrSubImage(isSubImage, gl, target.get(), level, dui, xOffset, yOffset, zOffset, mWidth, mHeight, mDepth, dstBegin); gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, webgl->mPixelStore_UnpackAlignment); if (webgl->IsWebGL2()) { gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, webgl->mPixelStore_UnpackRowLength); } return true; }
bool TexUnpackSurface::TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName, WebGLTexture* tex, TexImageTarget target, GLint level, const webgl::DriverUnpackInfo* dstDUI, GLint xOffset, GLint yOffset, GLint zOffset, GLenum* const out_error) const { WebGLContext* webgl = tex->mContext; WebGLTexelFormat srcFormat; uint8_t srcBPP; if (!GetFormatForSurf(mSurf, &srcFormat, &srcBPP)) { webgl->ErrorImplementationBug("%s: GetFormatForSurf failed for" " WebGLTexelFormat::%u.", funcName, uint32_t(mSurf->GetFormat())); return false; } gfx::DataSourceSurface::ScopedMap map(mSurf, gfx::DataSourceSurface::MapType::READ); if (!map.IsMapped()) return false; const auto srcBytes = map.GetData(); const auto srcStride = map.GetStride(); // CPU conversion. (++numCopies) webgl->GenerateWarning("%s: Incurred CPU-side conversion, which is very slow.", funcName); const uint8_t* uploadBytes; UniqueBuffer tempBuffer; if (!ConvertIfNeeded(webgl, funcName, srcBytes, srcStride, srcBPP, srcFormat, dstDUI, &uploadBytes, &tempBuffer)) { return false; } ////// gl::GLContext* const gl = webgl->gl; MOZ_ALWAYS_TRUE( gl->MakeCurrent() ); const auto curEffectiveRowLength = FallbackOnZero(webgl->mPixelStore_UnpackRowLength, mWidth); const bool changeRowLength = (mRowLength != curEffectiveRowLength); if (changeRowLength) { MOZ_ASSERT(webgl->IsWebGL2()); gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, mRowLength); } *out_error = DoTexOrSubImage(isSubImage, gl, target.get(), level, dstDUI, xOffset, yOffset, zOffset, mWidth, mHeight, mDepth, uploadBytes); if (changeRowLength) { gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, webgl->mPixelStore_UnpackRowLength); } return true; }
static bool ZeroTextureData(WebGLContext* webgl, const char* funcName, GLuint tex, TexImageTarget target, uint32_t level, const webgl::FormatUsageInfo* usage, uint32_t width, uint32_t height, uint32_t depth) { // This has two usecases: // 1. Lazy zeroing of uninitialized textures: // a. Before draw, when FakeBlack isn't viable. (TexStorage + Draw*) // b. Before partial upload. (TexStorage + TexSubImage) // 2. Zero subrects from out-of-bounds blits. (CopyTex(Sub)Image) // We have no sympathy for any of these cases. // "Doctor, it hurts when I do this!" "Well don't do that!" webgl->GenerateWarning("%s: This operation requires zeroing texture data. This is" " slow.", funcName); gl::GLContext* gl = webgl->GL(); gl->MakeCurrent(); GLenum scopeBindTarget; switch (target.get()) { case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X: case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X: case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y: case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z: case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: scopeBindTarget = LOCAL_GL_TEXTURE_CUBE_MAP; break; default: scopeBindTarget = target.get(); break; } const gl::ScopedBindTexture scopeBindTexture(gl, tex, scopeBindTarget); auto compression = usage->format->compression; if (compression) { auto sizedFormat = usage->format->sizedFormat; MOZ_RELEASE_ASSERT(sizedFormat, "GFX: texture sized format not set"); const auto fnSizeInBlocks = [](CheckedUint32 pixels, uint8_t pixelsPerBlock) { return RoundUpToMultipleOf(pixels, pixelsPerBlock) / pixelsPerBlock; }; const auto widthBlocks = fnSizeInBlocks(width, compression->blockWidth); const auto heightBlocks = fnSizeInBlocks(height, compression->blockHeight); CheckedUint32 checkedByteCount = compression->bytesPerBlock; checkedByteCount *= widthBlocks; checkedByteCount *= heightBlocks; checkedByteCount *= depth; if (!checkedByteCount.isValid()) return false; const size_t byteCount = checkedByteCount.value(); UniqueBuffer zeros = calloc(1, byteCount); if (!zeros) return false; ScopedUnpackReset scopedReset(webgl); gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); // Don't bother with striding it // well. const auto error = DoCompressedTexSubImage(gl, target.get(), level, 0, 0, 0, width, height, depth, sizedFormat, byteCount, zeros.get()); return !error; } const auto driverUnpackInfo = usage->idealUnpack; MOZ_RELEASE_ASSERT(driverUnpackInfo, "GFX: ideal unpack info not set."); if (webgl->IsExtensionEnabled(WebGLExtensionID::WEBGL_depth_texture) && gl->IsANGLE() && usage->format->d) { // ANGLE_depth_texture does not allow uploads, so we have to clear. // (Restriction because of D3D9) MOZ_ASSERT(target == LOCAL_GL_TEXTURE_2D); MOZ_ASSERT(level == 0); ZeroANGLEDepthTexture(webgl, tex, usage, width, height); return true; } const webgl::PackingInfo packing = driverUnpackInfo->ToPacking(); const auto bytesPerPixel = webgl::BytesPerPixel(packing); CheckedUint32 checkedByteCount = bytesPerPixel; checkedByteCount *= width; checkedByteCount *= height; checkedByteCount *= depth; if (!checkedByteCount.isValid()) return false; const size_t byteCount = checkedByteCount.value(); UniqueBuffer zeros = calloc(1, byteCount); if (!zeros) return false; ScopedUnpackReset scopedReset(webgl); gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); // Don't bother with striding it well. const auto error = DoTexSubImage(gl, target, level, 0, 0, 0, width, height, depth, packing, zeros.get()); return !error; }
void WebGL2Context::TexImage3D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const Nullable<dom::ArrayBufferView> &pixels, ErrorResult& rv) { if (IsContextLost()) return; void* data; size_t dataLength; js::Scalar::Type jsArrayType; if (pixels.IsNull()) { data = nullptr; dataLength = 0; jsArrayType = js::Scalar::TypeMax; } else { const ArrayBufferView& view = pixels.Value(); view.ComputeLengthAndData(); data = view.Data(); dataLength = view.Length(); jsArrayType = JS_GetArrayBufferViewType(view.Obj()); } const WebGLTexImageFunc func = WebGLTexImageFunc::TexImage; const WebGLTexDimensions dims = WebGLTexDimensions::Tex3D; if (!ValidateTexImageTarget(target, func, dims)) return; TexImageTarget texImageTarget = target; if (!ValidateTexImage(texImageTarget, level, internalformat, 0, 0, 0, width, height, depth, border, format, type, func, dims)) { return; } if (!ValidateTexInputData(type, jsArrayType, func, dims)) return; TexInternalFormat effectiveInternalFormat = EffectiveInternalFormatFromInternalFormatAndType(internalformat, type); if (effectiveInternalFormat == LOCAL_GL_NONE) { return ErrorInvalidOperation("texImage3D: bad combination of internalformat and type"); } // we need to find the exact sized format of the source data. Slightly abusing // EffectiveInternalFormatFromInternalFormatAndType for that purpose. Really, an unsized source format // is the same thing as an unsized internalformat. TexInternalFormat effectiveSourceFormat = EffectiveInternalFormatFromInternalFormatAndType(format, type); MOZ_ASSERT(effectiveSourceFormat != LOCAL_GL_NONE); // should have validated format/type combo earlier const size_t srcbitsPerTexel = GetBitsPerTexel(effectiveSourceFormat); MOZ_ASSERT((srcbitsPerTexel % 8) == 0); // should not have compressed formats here. size_t srcTexelSize = srcbitsPerTexel / 8; CheckedUint32 checked_neededByteLength = GetImageSize(height, width, depth, srcTexelSize, mPixelStoreUnpackAlignment); if (!checked_neededByteLength.isValid()) return ErrorInvalidOperation("texSubImage2D: integer overflow computing the needed buffer size"); uint32_t bytesNeeded = checked_neededByteLength.value(); if (dataLength && dataLength < bytesNeeded) return ErrorInvalidOperation("texImage3D: not enough data for operation (need %d, have %d)", bytesNeeded, dataLength); WebGLTexture* tex = activeBoundTextureForTexImageTarget(texImageTarget); if (!tex) return ErrorInvalidOperation("texImage3D: no texture is bound to this target"); if (tex->IsImmutable()) { return ErrorInvalidOperation( "texImage3D: disallowed because the texture " "bound to this target has already been made immutable by texStorage3D"); } GLenum driverType = LOCAL_GL_NONE; GLenum driverInternalFormat = LOCAL_GL_NONE; GLenum driverFormat = LOCAL_GL_NONE; DriverFormatsFromEffectiveInternalFormat(gl, effectiveInternalFormat, &driverInternalFormat, &driverFormat, &driverType); MakeContextCurrent(); GetAndFlushUnderlyingGLErrors(); gl->fTexImage3D(texImageTarget.get(), level, driverInternalFormat, width, height, depth, 0, driverFormat, driverType, data); GLenum error = GetAndFlushUnderlyingGLErrors(); if (error) { return GenerateWarning("texImage3D generated error %s", ErrorName(error)); } tex->SetImageInfo(texImageTarget, level, width, height, depth, effectiveInternalFormat, data ? WebGLImageDataStatus::InitializedImageData : WebGLImageDataStatus::UninitializedImageData); }
bool TexUnpackImage::TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName, WebGLTexture* tex, TexImageTarget target, GLint level, const webgl::DriverUnpackInfo* dui, GLint xOffset, GLint yOffset, GLint zOffset, GLenum* const out_error) const { MOZ_ASSERT_IF(needsRespec, !isSubImage); WebGLContext* webgl = tex->mContext; gl::GLContext* gl = webgl->GL(); gl->MakeCurrent(); if (needsRespec) { *out_error = DoTexOrSubImage(isSubImage, gl, target.get(), level, dui, xOffset, yOffset, zOffset, mWidth, mHeight, mDepth, nullptr); if (*out_error) return false; } do { if (mDepth != 1) break; const bool isDstPremult = webgl->mPixelStore_PremultiplyAlpha; if (mIsSrcPremult != isDstPremult) break; if (dui->unpackFormat != LOCAL_GL_RGB && dui->unpackFormat != LOCAL_GL_RGBA) break; if (dui->unpackType != LOCAL_GL_UNSIGNED_BYTE) break; 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.get(), tex->mGLName, level); if (errorScope.GetError()) break; } const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) break; const gfx::IntSize destSize(mWidth, mHeight); const auto dstOrigin = (webgl->mPixelStore_FlipY ? gl::OriginPos::TopLeft : gl::OriginPos::BottomLeft); if (!gl->BlitHelper()->BlitImageToFramebuffer(mImage, destSize, scopedFB.FB(), dstOrigin)) { break; } // Blitting was successful, so we're done! *out_error = 0; return true; } while (false); webgl->GenerateWarning("%s: Failed to hit GPU-copy fast-path. Falling back to CPU" " upload.", funcName); const RefPtr<gfx::SourceSurface> surf = mImage->GetAsSourceSurface(); RefPtr<gfx::DataSourceSurface> dataSurf; if (surf) { // WARNING: OSX can lose our MakeCurrent here. dataSurf = surf->GetDataSurface(); } if (!dataSurf) { webgl->ErrorOutOfMemory("%s: GetAsSourceSurface or GetDataSurface failed after" " blit failed for TexUnpackImage.", funcName); return false; } const TexUnpackSurface surfBlob(webgl, target, mWidth, mHeight, mDepth, dataSurf, mIsSrcPremult); return surfBlob.TexOrSubImage(isSubImage, needsRespec, funcName, tex, target, level, dui, xOffset, yOffset, zOffset, out_error); }
bool WebGLTexture::EnsureInitializedImageData(TexImageTarget imageTarget, GLint level) { const ImageInfo& imageInfo = ImageInfoAt(imageTarget, level); if (!imageInfo.HasUninitializedImageData()) return true; mContext->MakeContextCurrent(); // Try to clear with glClear. if (imageTarget == LOCAL_GL_TEXTURE_2D) { bool cleared = ClearWithTempFB(mContext, mGLName, imageTarget, level, imageInfo.mEffectiveInternalFormat, imageInfo.mHeight, imageInfo.mWidth); if (cleared) { SetImageDataStatus(imageTarget, level, WebGLImageDataStatus::InitializedImageData); return true; } } // That didn't work. Try uploading zeros then. size_t bitspertexel = GetBitsPerTexel(imageInfo.mEffectiveInternalFormat); MOZ_ASSERT((bitspertexel % 8) == 0); // That would only happen for // compressed images, which cannot use // deferred initialization. size_t bytespertexel = bitspertexel / 8; CheckedUint32 checked_byteLength = WebGLContext::GetImageSize( imageInfo.mHeight, imageInfo.mWidth, imageInfo.mDepth, bytespertexel, mContext->mPixelStoreUnpackAlignment); MOZ_ASSERT(checked_byteLength.isValid()); // Should have been checked // earlier. size_t byteCount = checked_byteLength.value(); UniquePtr<uint8_t> zeros((uint8_t*)calloc(1, byteCount)); if (zeros == nullptr) { // Failed to allocate memory. Lose the context. Return OOM error. mContext->ForceLoseContext(true); mContext->ErrorOutOfMemory("EnsureInitializedImageData: Failed to alloc %u " "bytes to clear image target `%s` level `%d`.", byteCount, mContext->EnumName(imageTarget.get()), level); return false; } gl::GLContext* gl = mContext->gl; gl::ScopedBindTexture autoBindTex(gl, mGLName, mTarget); GLenum driverInternalFormat = LOCAL_GL_NONE; GLenum driverFormat = LOCAL_GL_NONE; GLenum driverType = LOCAL_GL_NONE; DriverFormatsFromEffectiveInternalFormat(gl, imageInfo.mEffectiveInternalFormat, &driverInternalFormat, &driverFormat, &driverType); mContext->GetAndFlushUnderlyingGLErrors(); if (imageTarget == LOCAL_GL_TEXTURE_3D) { MOZ_ASSERT(mImmutable, "Shouldn't be possible to have non-immutable-format 3D" " textures in WebGL"); gl->fTexSubImage3D(imageTarget.get(), level, 0, 0, 0, imageInfo.mWidth, imageInfo.mHeight, imageInfo.mDepth, driverFormat, driverType, zeros.get()); } else { if (mImmutable) { gl->fTexSubImage2D(imageTarget.get(), level, 0, 0, imageInfo.mWidth, imageInfo.mHeight, driverFormat, driverType, zeros.get()); } else { gl->fTexImage2D(imageTarget.get(), level, driverInternalFormat, imageInfo.mWidth, imageInfo.mHeight, 0, driverFormat, driverType, zeros.get()); } } GLenum error = mContext->GetAndFlushUnderlyingGLErrors(); if (error) { // Should only be OUT_OF_MEMORY. Anyway, there's no good way to recover // from this here. gfxCriticalError() << "GL context GetAndFlushUnderlyingGLErrors " << gfx::hexa(error); printf_stderr("Error: 0x%4x\n", error); if (error != LOCAL_GL_OUT_OF_MEMORY) { // Errors on texture upload have been related to video // memory exposure in the past, which is a security issue. // Force loss of context. mContext->ForceLoseContext(true); return false; } // Out-of-memory uploading pixels to GL. Lose context and report OOM. mContext->ForceLoseContext(true); mContext->ErrorOutOfMemory("EnsureNoUninitializedImageData: Failed to " "upload texture of width: %u, height: %u, " "depth: %u to target %s level %d.", imageInfo.mWidth, imageInfo.mHeight, imageInfo.mDepth, mContext->EnumName(imageTarget.get()), level); return false; } SetImageDataStatus(imageTarget, level, WebGLImageDataStatus::InitializedImageData); return true; }
void WebGLTexture::EnsureNoUninitializedImageData(TexImageTarget imageTarget, GLint level) { const ImageInfo& imageInfo = ImageInfoAt(imageTarget, level); if (!imageInfo.HasUninitializedImageData()) return; mContext->MakeContextCurrent(); // Try to clear with glCLear. if (imageTarget == LOCAL_GL_TEXTURE_2D) { bool cleared = ClearWithTempFB(mContext, GLName(), imageTarget, level, imageInfo.mEffectiveInternalFormat, imageInfo.mHeight, imageInfo.mWidth); if (cleared) { SetImageDataStatus(imageTarget, level, WebGLImageDataStatus::InitializedImageData); return; } } // That didn't work. Try uploading zeros then. gl::ScopedBindTexture autoBindTex(mContext->gl, GLName(), mTarget.get()); size_t bitspertexel = GetBitsPerTexel(imageInfo.mEffectiveInternalFormat); MOZ_ASSERT((bitspertexel % 8) == 0); // that would only happen for compressed images, which // cannot use deferred initialization. size_t bytespertexel = bitspertexel / 8; CheckedUint32 checked_byteLength = WebGLContext::GetImageSize( imageInfo.mHeight, imageInfo.mWidth, imageInfo.mDepth, bytespertexel, mContext->mPixelStoreUnpackAlignment); MOZ_ASSERT(checked_byteLength.isValid()); // should have been checked earlier UniquePtr<uint8_t> zeros((uint8_t*)moz_xcalloc(1, checked_byteLength.value())); // Infallible for now. gl::GLContext* gl = mContext->gl; GLenum driverInternalFormat = LOCAL_GL_NONE; GLenum driverFormat = LOCAL_GL_NONE; GLenum driverType = LOCAL_GL_NONE; DriverFormatsFromEffectiveInternalFormat(gl, imageInfo.mEffectiveInternalFormat, &driverInternalFormat, &driverFormat, &driverType); mContext->GetAndFlushUnderlyingGLErrors(); if (imageTarget == LOCAL_GL_TEXTURE_3D) { MOZ_ASSERT(mImmutable, "Shouldn't be possible to have non-immutable-format 3D textures in WebGL"); gl->fTexSubImage3D(imageTarget.get(), level, 0, 0, 0, imageInfo.mWidth, imageInfo.mHeight, imageInfo.mDepth, driverFormat, driverType, zeros.get()); } else { if (mImmutable) { gl->fTexSubImage2D(imageTarget.get(), level, 0, 0, imageInfo.mWidth, imageInfo.mHeight, driverFormat, driverType, zeros.get()); } else { gl->fTexImage2D(imageTarget.get(), level, driverInternalFormat, imageInfo.mWidth, imageInfo.mHeight, 0, driverFormat, driverType, zeros.get()); } } GLenum error = mContext->GetAndFlushUnderlyingGLErrors(); if (error) { // Should only be OUT_OF_MEMORY. Anyway, there's no good way to recover from this here. printf_stderr("Error: 0x%4x\n", error); MOZ_CRASH(); // errors on texture upload have been related to video memory exposure in the past. } SetImageDataStatus(imageTarget, level, WebGLImageDataStatus::InitializedImageData); }
static bool ZeroTextureData(const WebGLContext* webgl, GLuint tex, TexImageTarget target, uint32_t level, const webgl::FormatUsageInfo* usage, uint32_t width, uint32_t height, uint32_t depth) { // This has two usecases: // 1. Lazy zeroing of uninitialized textures: // a. Before draw. // b. Before partial upload. (TexStorage + TexSubImage) // 2. Zero subrects from out-of-bounds blits. (CopyTex(Sub)Image) // We have no sympathy for any of these cases. // "Doctor, it hurts when I do this!" "Well don't do that!" const auto targetStr = EnumString(target.get()); webgl->GeneratePerfWarning( "Tex image %s level %u is incurring lazy initialization.", targetStr.c_str(), level); gl::GLContext* gl = webgl->GL(); GLenum scopeBindTarget; switch (target.get()) { case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X: case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X: case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y: case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z: case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: scopeBindTarget = LOCAL_GL_TEXTURE_CUBE_MAP; break; default: scopeBindTarget = target.get(); break; } const gl::ScopedBindTexture scopeBindTexture(gl, tex, scopeBindTarget); const auto& compression = usage->format->compression; if (compression) { auto sizedFormat = usage->format->sizedFormat; MOZ_RELEASE_ASSERT(sizedFormat, "GFX: texture sized format not set"); const auto fnSizeInBlocks = [](CheckedUint32 pixels, uint8_t pixelsPerBlock) { return RoundUpToMultipleOf(pixels, pixelsPerBlock) / pixelsPerBlock; }; const auto widthBlocks = fnSizeInBlocks(width, compression->blockWidth); const auto heightBlocks = fnSizeInBlocks(height, compression->blockHeight); CheckedUint32 checkedByteCount = compression->bytesPerBlock; checkedByteCount *= widthBlocks; checkedByteCount *= heightBlocks; checkedByteCount *= depth; if (!checkedByteCount.isValid()) return false; const size_t byteCount = checkedByteCount.value(); UniqueBuffer zeros = calloc(1, byteCount); if (!zeros) return false; ScopedUnpackReset scopedReset(webgl); gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); // Don't bother with // striding it well. const auto error = DoCompressedTexSubImage(gl, target.get(), level, 0, 0, 0, width, height, depth, sizedFormat, byteCount, zeros.get()); return !error; } const auto driverUnpackInfo = usage->idealUnpack; MOZ_RELEASE_ASSERT(driverUnpackInfo, "GFX: ideal unpack info not set."); if (usage->format->d) { // ANGLE_depth_texture does not allow uploads, so we have to clear. // (Restriction because of D3D9) // Also, depth resources are cleared to 1.0f and are always renderable, so // just use FB clears. return ClearDepthTexture(*webgl, tex, target, level, usage, depth); } const webgl::PackingInfo packing = driverUnpackInfo->ToPacking(); const auto bytesPerPixel = webgl::BytesPerPixel(packing); CheckedUint32 checkedByteCount = bytesPerPixel; checkedByteCount *= width; checkedByteCount *= height; checkedByteCount *= depth; if (!checkedByteCount.isValid()) return false; const size_t byteCount = checkedByteCount.value(); UniqueBuffer zeros = calloc(1, byteCount); if (!zeros) return false; ScopedUnpackReset scopedReset(webgl); gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); // Don't bother with striding it well. const auto error = DoTexSubImage(gl, target, level, 0, 0, 0, width, height, depth, packing, zeros.get()); return !error; }
/*static*/ bool TexUnpackSurface::UploadDataSurface(bool isSubImage, WebGLContext* webgl, TexImageTarget target, GLint level, const webgl::DriverUnpackInfo* dui, GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width, GLsizei height, gfx::DataSourceSurface* surf, bool isSurfAlphaPremult, GLenum* const out_glError) { gl::GLContext* gl = webgl->GL(); MOZ_ASSERT(gl->IsCurrent()); *out_glError = 0; if (isSurfAlphaPremult != webgl->mPixelStore_PremultiplyAlpha) return false; gl::OriginPos srcOrigin, dstOrigin; OriginsForDOM(webgl, &srcOrigin, &dstOrigin); if (srcOrigin != dstOrigin) return false; // This differs from the raw-data upload in that we choose how we do the unpack. // (alignment, etc.) // Uploading RGBX as RGBA and blitting to RGB is faster than repacking RGBX into // RGB on the CPU. However, this is optimization is out-of-scope for now. static const webgl::DriverUnpackInfo kInfoBGRA = { LOCAL_GL_BGRA, LOCAL_GL_BGRA, LOCAL_GL_UNSIGNED_BYTE, }; const webgl::DriverUnpackInfo* chosenDUI = nullptr; switch (surf->GetFormat()) { case gfx::SurfaceFormat::B8G8R8A8: if (SupportsBGRA(gl) && dui->internalFormat == LOCAL_GL_RGBA && dui->unpackFormat == LOCAL_GL_RGBA && dui->unpackType == LOCAL_GL_UNSIGNED_BYTE) { chosenDUI = &kInfoBGRA; } break; case gfx::SurfaceFormat::R8G8B8A8: if (dui->unpackFormat == LOCAL_GL_RGBA && dui->unpackType == LOCAL_GL_UNSIGNED_BYTE) { chosenDUI = dui; } break; case gfx::SurfaceFormat::R5G6B5_UINT16: if (dui->unpackFormat == LOCAL_GL_RGB && dui->unpackType == LOCAL_GL_UNSIGNED_SHORT_5_6_5) { chosenDUI = dui; } break; default: break; } if (!chosenDUI) return false; gfx::DataSourceSurface::ScopedMap map(surf, gfx::DataSourceSurface::MapType::READ); if (!map.IsMapped()) return false; const webgl::PackingInfo pi = {chosenDUI->unpackFormat, chosenDUI->unpackType}; const auto bytesPerPixel = webgl::BytesPerPixel(pi); const size_t bytesPerRow = width * bytesPerPixel; const GLint kMaxUnpackAlignment = 8; size_t unpackAlignment; if (!GuessAlignment(map.GetData(), bytesPerRow, map.GetStride(), kMaxUnpackAlignment, &unpackAlignment)) { return false; // TODO: Consider using UNPACK_ settings to set the stride based on the too-large // alignment used for some SourceSurfaces. (D2D allegedy likes alignment=16) } MOZ_ALWAYS_TRUE( webgl->gl->MakeCurrent() ); ScopedUnpackReset scopedReset(webgl); gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, unpackAlignment); const GLsizei depth = 1; GLenum error = DoTexOrSubImage(isSubImage, gl, target.get(), level, chosenDUI, xOffset, yOffset, zOffset, width, height, depth, map.GetData()); if (error) { *out_glError = error; return false; } return true; }
void TexUnpackImage::TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName, WebGLTexture* tex, TexImageTarget target, GLint level, const webgl::DriverUnpackInfo* dui, GLint xOffset, GLint yOffset, GLint zOffset, GLenum* const out_glError) { MOZ_ASSERT_IF(needsRespec, !isSubImage); *out_glError = 0; WebGLContext* webgl = tex->mContext; gl::GLContext* gl = webgl->GL(); gl->MakeCurrent(); if (needsRespec) { GLenum error = DoTexOrSubImage(isSubImage, gl, target.get(), level, dui, xOffset, yOffset, zOffset, mWidth, mHeight, mDepth, nullptr); if (error) { MOZ_ASSERT(!error); *out_glError = LOCAL_GL_OUT_OF_MEMORY; return; } } do { if (dui->unpackFormat != LOCAL_GL_RGB && dui->unpackFormat != LOCAL_GL_RGBA) break; if (dui->unpackType != LOCAL_GL_UNSIGNED_BYTE) break; 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.get(), tex->mGLName, level); if (errorScope.GetError()) break; } const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) break; gl::OriginPos srcOrigin, dstOrigin; OriginsForDOM(webgl, &srcOrigin, &dstOrigin); const gfx::IntSize destSize(mWidth, mHeight); if (!gl->BlitHelper()->BlitImageToFramebuffer(mImage, destSize, scopedFB.FB(), dstOrigin)) { break; } return; // Blitting was successful, so we're done! } while (false); TexUnpackSurface surfBlob(mImage->GetAsSourceSurface(), mIsAlphaPremult); surfBlob.TexOrSubImage(isSubImage, needsRespec, funcName, tex, target, level, dui, xOffset, yOffset, zOffset, out_glError); }