WebGLContext.cpp (94244B)
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 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 "WebGLContext.h" 7 8 #include <algorithm> 9 #include <array> 10 #include <bitset> 11 #include <cctype> 12 #include <queue> 13 14 #include "AccessCheck.h" 15 #include "CompositableHost.h" 16 #include "GLBlitHelper.h" 17 #include "GLContext.h" 18 #include "GLContextProvider.h" 19 #include "GLReadTexImageHelper.h" 20 #include "GLScreenBuffer.h" 21 #include "ImageContainer.h" 22 #include "ImageEncoder.h" 23 #include "LayerUserData.h" 24 #include "MozFramebuffer.h" 25 #include "ScopedGLHelpers.h" 26 #include "SharedSurfaceGL.h" 27 #include "VRManagerChild.h" 28 #include "gfxConfig.h" 29 #include "gfxContext.h" 30 #include "gfxCrashReporterUtils.h" 31 #include "gfxEnv.h" 32 #include "gfxPattern.h" 33 #include "mozilla/EnumeratedArrayCycleCollection.h" 34 #include "mozilla/Preferences.h" 35 #include "mozilla/ProcessPriorityManager.h" 36 #include "mozilla/ResultVariant.h" 37 #include "mozilla/SVGObserverUtils.h" 38 #include "mozilla/ScopeExit.h" 39 #include "mozilla/Services.h" 40 #include "mozilla/StaticPrefs_webgl.h" 41 #include "mozilla/dom/BindingUtils.h" 42 #include "mozilla/dom/ContentChild.h" 43 #include "mozilla/dom/Document.h" 44 #include "mozilla/dom/Event.h" 45 #include "mozilla/dom/HTMLVideoElement.h" 46 #include "mozilla/dom/ImageData.h" 47 #include "mozilla/dom/WebGLContextEvent.h" 48 #include "mozilla/gfx/Swizzle.h" 49 #include "mozilla/gfx/gfxVars.h" 50 #include "mozilla/glean/DomCanvasMetrics.h" 51 #include "mozilla/layers/BufferTexture.h" 52 #include "mozilla/layers/CompositorBridgeChild.h" 53 #include "mozilla/layers/ImageBridgeChild.h" 54 #include "mozilla/layers/RemoteTextureMap.h" 55 #include "mozilla/layers/WebRenderCanvasRenderer.h" 56 #include "mozilla/layers/WebRenderUserData.h" 57 #include "nsContentUtils.h" 58 #include "nsDisplayList.h" 59 #include "nsError.h" 60 #include "nsIClassInfoImpl.h" 61 #include "nsIWidget.h" 62 #include "nsServiceManagerUtils.h" 63 #include "prenv.h" 64 65 // Local 66 #include "CanvasUtils.h" 67 #include "ClientWebGLContext.h" 68 #include "HostWebGLContext.h" 69 #include "WebGLBuffer.h" 70 #include "WebGLChild.h" 71 #include "WebGLContextLossHandler.h" 72 #include "WebGLContextUtils.h" 73 #include "WebGLExtensions.h" 74 #include "WebGLFormats.h" 75 #include "WebGLFramebuffer.h" 76 #include "WebGLMemoryTracker.h" 77 #include "WebGLObjectModel.h" 78 #include "WebGLParent.h" 79 #include "WebGLProgram.h" 80 #include "WebGLQuery.h" 81 #include "WebGLSampler.h" 82 #include "WebGLShader.h" 83 #include "WebGLShaderValidator.h" 84 #include "WebGLSync.h" 85 #include "WebGLTransformFeedback.h" 86 #include "WebGLValidateStrings.h" 87 #include "WebGLVertexArray.h" 88 89 #ifdef XP_WIN 90 # include "WGLLibrary.h" 91 #endif 92 93 // Generated 94 #include "mozilla/dom/WebGLRenderingContextBinding.h" 95 96 namespace mozilla { 97 98 WebGLContextOptions::WebGLContextOptions() { 99 // Set default alpha state based on preference. 100 alpha = !StaticPrefs::webgl_default_no_alpha(); 101 antialias = StaticPrefs::webgl_default_antialias(); 102 } 103 104 StaticMutex WebGLContext::sLruMutex; 105 MOZ_RUNINIT std::list<WebGLContext*> WebGLContext::sLru; 106 107 WebGLContext::LruPosition::LruPosition() { 108 StaticMutexAutoLock lock(sLruMutex); 109 mItr = sLru.end(); 110 } // NOLINT 111 112 WebGLContext::LruPosition::LruPosition(WebGLContext& context) { 113 StaticMutexAutoLock lock(sLruMutex); 114 mItr = sLru.insert(sLru.end(), &context); 115 } 116 117 void WebGLContext::LruPosition::AssignLocked(WebGLContext& aContext) { 118 ResetLocked(); 119 mItr = sLru.insert(sLru.end(), &aContext); 120 } 121 122 void WebGLContext::LruPosition::ResetLocked() { 123 const auto end = sLru.end(); 124 if (mItr != end) { 125 sLru.erase(mItr); 126 mItr = end; 127 } 128 } 129 130 void WebGLContext::LruPosition::Reset() { 131 StaticMutexAutoLock lock(sLruMutex); 132 ResetLocked(); 133 } 134 135 bool WebGLContext::LruPosition::IsInsertedLocked() const { 136 return mItr != sLru.end(); 137 } 138 139 WebGLContext::WebGLContext(HostWebGLContext* host, 140 const webgl::InitContextDesc& desc) 141 : gl(mGL_OnlyClearInDestroyResourcesAndContext), // const reference 142 mHost(host), 143 mResistFingerprinting(desc.resistFingerprinting), 144 mOptions(desc.options), 145 mPrincipalKey(desc.principalKey), 146 mContextLossHandler(this), 147 mRequestedSize(desc.size) { 148 if (host) { 149 host->mContext = this; 150 } 151 const FuncScope funcScope(*this, "<Create>"); 152 WebGLMemoryTracker::EnsureRegistered(); 153 } 154 155 WebGLContext::~WebGLContext() { DestroyResourcesAndContext(); } 156 157 void WebGLContext::DestroyResourcesAndContext() { 158 if (mRemoteTextureOwner) { 159 // Clean up any remote textures registered for framebuffer swap chains. 160 mRemoteTextureOwner->UnregisterAllTextureOwners(); 161 mRemoteTextureOwner = nullptr; 162 } 163 164 if (!gl) return; 165 166 mDefaultFB = nullptr; 167 mResolvedDefaultFB = nullptr; 168 169 mBound2DTextures.Clear(); 170 mBoundCubeMapTextures.Clear(); 171 mBound3DTextures.Clear(); 172 mBound2DArrayTextures.Clear(); 173 mBoundSamplers.Clear(); 174 mBoundArrayBuffer = nullptr; 175 mBoundCopyReadBuffer = nullptr; 176 mBoundCopyWriteBuffer = nullptr; 177 mBoundPixelPackBuffer = nullptr; 178 mBoundPixelUnpackBuffer = nullptr; 179 mBoundTransformFeedbackBuffer = nullptr; 180 mBoundUniformBuffer = nullptr; 181 mCurrentProgram = nullptr; 182 mActiveProgramLinkInfo = nullptr; 183 mBoundDrawFramebuffer = nullptr; 184 mBoundReadFramebuffer = nullptr; 185 mBoundVertexArray = nullptr; 186 mDefaultVertexArray = nullptr; 187 mBoundTransformFeedback = nullptr; 188 mDefaultTransformFeedback = nullptr; 189 190 mQuerySlot_SamplesPassed = nullptr; 191 mQuerySlot_TFPrimsWritten = nullptr; 192 mQuerySlot_TimeElapsed = nullptr; 193 194 mIndexedUniformBufferBindings.clear(); 195 196 ////// 197 198 if (mEmptyTFO) { 199 gl->fDeleteTransformFeedbacks(1, &mEmptyTFO); 200 mEmptyTFO = 0; 201 } 202 203 ////// 204 205 if (mFakeVertexAttrib0BufferObject) { 206 gl->fDeleteBuffers(1, &mFakeVertexAttrib0BufferObject); 207 mFakeVertexAttrib0BufferObject = 0; 208 } 209 210 // disable all extensions except "WEBGL_lose_context". see bug #927969 211 // spec: http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2 212 for (size_t i = 0; i < size_t(WebGLExtensionID::Max); ++i) { 213 WebGLExtensionID extension = WebGLExtensionID(i); 214 if (extension == WebGLExtensionID::WEBGL_lose_context) continue; 215 mExtensions[extension] = nullptr; 216 } 217 218 // We just got rid of everything, so the context had better 219 // have been going away. 220 if (gl::GLContext::ShouldSpew()) { 221 printf_stderr("--- WebGL context destroyed: %p\n", gl.get()); 222 } 223 224 MOZ_ASSERT(gl); 225 gl->MarkDestroyed(); 226 mGL_OnlyClearInDestroyResourcesAndContext = nullptr; 227 MOZ_ASSERT(!gl); 228 } 229 230 void ClientWebGLContext::MarkCanvasDirty() { 231 if (!mCanvasElement && !mOffscreenCanvas) return; 232 233 mFrameCaptureState = FrameCaptureState::DIRTY; 234 235 if (mIsCanvasDirty) return; 236 mIsCanvasDirty = true; 237 238 if (mCanvasElement) { 239 SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement); 240 mCanvasElement->InvalidateCanvasContent(nullptr); 241 } else if (mOffscreenCanvas) { 242 mOffscreenCanvas->QueueCommitToCompositor(); 243 } 244 } 245 246 void WebGLContext::OnMemoryPressure() { 247 bool shouldLoseContext = mLoseContextOnMemoryPressure; 248 249 if (!mCanLoseContextInForeground && 250 ProcessPriorityManager::CurrentProcessIsForeground()) { 251 shouldLoseContext = false; 252 } 253 254 if (shouldLoseContext) LoseContext(); 255 } 256 257 // -- 258 259 bool WebGLContext::CreateAndInitGL( 260 bool forceEnabled, std::vector<FailureReason>* const out_failReasons) { 261 const FuncScope funcScope(*this, "<Create>"); 262 263 // WebGL2 is separately blocked: 264 if (IsWebGL2() && !forceEnabled) { 265 FailureReason reason; 266 if (!gfx::gfxVars::AllowWebgl2()) { 267 reason.info = 268 "AllowWebgl2:false restricts context creation on this system."; 269 out_failReasons->push_back(reason); 270 GenerateWarning("%s", reason.info.BeginReading()); 271 return false; 272 } 273 } 274 275 auto flags = gl::CreateContextFlags::PREFER_ROBUSTNESS; 276 277 if (StaticPrefs::webgl_gl_khr_no_error()) { 278 flags |= gl::CreateContextFlags::NO_VALIDATION; 279 } 280 281 // - 282 283 if (StaticPrefs::webgl_forbid_hardware()) { 284 flags |= gl::CreateContextFlags::FORBID_HARDWARE; 285 } 286 if (StaticPrefs::webgl_forbid_software()) { 287 flags |= gl::CreateContextFlags::FORBID_SOFTWARE; 288 } 289 290 if (mOptions.forceSoftwareRendering) { 291 flags |= gl::CreateContextFlags::FORBID_HARDWARE; 292 flags &= ~gl::CreateContextFlags::FORBID_SOFTWARE; 293 } 294 295 if (forceEnabled) { 296 flags &= ~gl::CreateContextFlags::FORBID_HARDWARE; 297 flags &= ~gl::CreateContextFlags::FORBID_SOFTWARE; 298 } 299 300 if ((flags & gl::CreateContextFlags::FORBID_HARDWARE) && 301 (flags & gl::CreateContextFlags::FORBID_SOFTWARE)) { 302 FailureReason reason; 303 reason.info = "Both hardware and software were forbidden by config."; 304 out_failReasons->push_back(reason); 305 GenerateWarning("%s", reason.info.BeginReading()); 306 return false; 307 } 308 309 // - 310 311 if (StaticPrefs::webgl_cgl_multithreaded()) { 312 flags |= gl::CreateContextFlags::PREFER_MULTITHREADED; 313 } 314 315 if (IsWebGL2()) { 316 flags |= gl::CreateContextFlags::PREFER_ES3; 317 } else { 318 if (StaticPrefs::webgl_1_request_es2()) { 319 // Request and prefer ES2 context for WebGL1. 320 flags |= gl::CreateContextFlags::PREFER_EXACT_VERSION; 321 } 322 323 if (!StaticPrefs::webgl_1_allow_core_profiles()) { 324 flags |= gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE; 325 } 326 } 327 328 { 329 auto powerPref = mOptions.powerPreference; 330 331 // If "Use hardware acceleration when available" option is disabled: 332 if (!gfx::gfxConfig::IsEnabled(gfx::Feature::HW_COMPOSITING)) { 333 powerPref = dom::WebGLPowerPreference::Low_power; 334 } 335 336 const auto overrideVal = StaticPrefs::webgl_power_preference_override(); 337 if (overrideVal > 0) { 338 powerPref = dom::WebGLPowerPreference::High_performance; 339 } else if (overrideVal < 0) { 340 powerPref = dom::WebGLPowerPreference::Low_power; 341 } 342 343 if (powerPref == dom::WebGLPowerPreference::High_performance) { 344 flags |= gl::CreateContextFlags::HIGH_POWER; 345 } 346 } 347 348 if (!gfx::gfxVars::WebglAllowCoreProfile()) { 349 flags |= gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE; 350 } 351 352 // -- 353 354 const bool useEGL = PR_GetEnv("MOZ_WEBGL_FORCE_EGL"); 355 356 bool tryNativeGL = true; 357 bool tryANGLE = false; 358 359 #ifdef XP_WIN 360 tryNativeGL = false; 361 tryANGLE = true; 362 363 if (StaticPrefs::webgl_disable_wgl()) { 364 tryNativeGL = false; 365 } 366 367 if (StaticPrefs::webgl_disable_angle() || 368 PR_GetEnv("MOZ_WEBGL_FORCE_OPENGL") || useEGL) { 369 tryNativeGL = true; 370 tryANGLE = false; 371 } 372 #endif 373 374 if (tryNativeGL && !forceEnabled) { 375 FailureReason reason; 376 if (!gfx::gfxVars::WebglAllowWindowsNativeGl()) { 377 reason.info = 378 "WebglAllowWindowsNativeGl:false restricts context creation on this " 379 "system."; 380 381 out_failReasons->push_back(reason); 382 383 GenerateWarning("%s", reason.info.BeginReading()); 384 tryNativeGL = false; 385 } 386 } 387 388 // -- 389 390 using fnCreateT = decltype(gl::GLContextProviderEGL::CreateHeadless); 391 const auto fnCreate = [&](fnCreateT* const pfnCreate, 392 const char* const info) { 393 nsCString failureId; 394 const RefPtr<gl::GLContext> gl = pfnCreate({flags}, &failureId); 395 if (!gl) { 396 out_failReasons->push_back(WebGLContext::FailureReason(failureId, info)); 397 } 398 return gl; 399 }; 400 401 const auto newGL = [&]() -> RefPtr<gl::GLContext> { 402 if (tryNativeGL) { 403 if (useEGL) 404 return fnCreate(&gl::GLContextProviderEGL::CreateHeadless, "useEGL"); 405 406 const auto ret = 407 fnCreate(&gl::GLContextProvider::CreateHeadless, "tryNativeGL"); 408 if (ret) return ret; 409 } 410 411 if (tryANGLE) { 412 return fnCreate(&gl::GLContextProviderEGL::CreateHeadless, "tryANGLE"); 413 } 414 return nullptr; 415 }(); 416 417 if (!newGL) { 418 out_failReasons->push_back( 419 FailureReason("FEATURE_FAILURE_WEBGL_EXHAUSTED_DRIVERS", 420 "Exhausted GL driver options.")); 421 return false; 422 } 423 424 // -- 425 426 FailureReason reason; 427 428 mGL_OnlyClearInDestroyResourcesAndContext = newGL; 429 MOZ_RELEASE_ASSERT(gl); 430 if (!InitAndValidateGL(&reason)) { 431 DestroyResourcesAndContext(); 432 MOZ_RELEASE_ASSERT(!gl); 433 434 // The fail reason here should be specific enough for now. 435 out_failReasons->push_back(reason); 436 return false; 437 } 438 439 const auto val = StaticPrefs::webgl_debug_incomplete_tex_color(); 440 if (val) { 441 mIncompleteTexOverride.reset(new gl::Texture(*gl)); 442 const gl::ScopedBindTexture autoBind(gl, mIncompleteTexOverride->name); 443 const auto heapVal = std::make_unique<uint32_t>(val); 444 gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, 1, 1, 0, 445 LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, heapVal.get()); 446 } 447 448 return true; 449 } 450 451 // Fallback for resizes: 452 453 bool WebGLContext::EnsureDefaultFB() { 454 if (mDefaultFB) { 455 MOZ_ASSERT(*uvec2::FromSize(mDefaultFB->mSize) == mRequestedSize); 456 return true; 457 } 458 459 const bool depthStencil = mOptions.depth || mOptions.stencil; 460 auto attemptSize = gfx::IntSize{mRequestedSize.x, mRequestedSize.y}; 461 462 while (attemptSize.width || attemptSize.height) { 463 attemptSize.width = std::max(attemptSize.width, 1); 464 attemptSize.height = std::max(attemptSize.height, 1); 465 466 [&]() { 467 if (mOptions.antialias) { 468 MOZ_ASSERT(!mDefaultFB); 469 mDefaultFB = gl::MozFramebuffer::Create(gl, attemptSize, mMsaaSamples, 470 depthStencil); 471 if (mDefaultFB) return; 472 if (mOptionsFrozen) return; 473 } 474 475 MOZ_ASSERT(!mDefaultFB); 476 mDefaultFB = gl::MozFramebuffer::Create(gl, attemptSize, 0, depthStencil); 477 }(); 478 479 if (mDefaultFB) break; 480 481 attemptSize.width /= 2; 482 attemptSize.height /= 2; 483 } 484 485 if (!mDefaultFB) { 486 GenerateWarning("Backbuffer resize failed. Losing context."); 487 LoseContext(); 488 return false; 489 } 490 491 mDefaultFB_IsInvalid = true; 492 493 const auto actualSize = *uvec2::FromSize(mDefaultFB->mSize); 494 if (actualSize != mRequestedSize) { 495 GenerateWarning( 496 "Requested size %ux%u was too large, but resize" 497 " to %ux%u succeeded.", 498 mRequestedSize.x, mRequestedSize.y, actualSize.x, actualSize.y); 499 } 500 mRequestedSize = actualSize; 501 return true; 502 } 503 504 void WebGLContext::Resize(uvec2 requestedSize) { 505 // Zero-sized surfaces can cause problems. 506 if (!requestedSize.x) { 507 requestedSize.x = 1; 508 } 509 if (!requestedSize.y) { 510 requestedSize.y = 1; 511 } 512 513 // Kill our current default fb(s), for later lazy allocation. 514 mRequestedSize = requestedSize; 515 mDefaultFB = nullptr; 516 mResolvedDefaultFB = nullptr; 517 mResetLayer = true; // New size means new Layer. 518 } 519 520 std::unique_ptr<webgl::FormatUsageAuthority> WebGLContext::CreateFormatUsage( 521 gl::GLContext* gl) const { 522 return webgl::FormatUsageAuthority::CreateForWebGL1(gl); 523 } 524 525 /*static*/ 526 RefPtr<WebGLContext> WebGLContext::Create(HostWebGLContext* host, 527 const webgl::InitContextDesc& desc, 528 webgl::InitContextResult* const out) { 529 AUTO_PROFILER_LABEL("WebGLContext::Create", GRAPHICS); 530 nsCString failureId = "FEATURE_FAILURE_WEBGL_UNKOWN"_ns; 531 const bool forceEnabled = StaticPrefs::webgl_force_enabled(); 532 ScopedGfxFeatureReporter reporter("WebGL", forceEnabled); 533 534 auto res = [&]() -> Result<RefPtr<WebGLContext>, std::string> { 535 bool disabled = StaticPrefs::webgl_disabled(); 536 537 // TODO: When we have software webgl support we should use that instead. 538 disabled |= gfxPlatform::InSafeMode(); 539 540 if (disabled) { 541 if (gfxPlatform::InSafeMode()) { 542 failureId = "FEATURE_FAILURE_WEBGL_SAFEMODE"_ns; 543 } else { 544 failureId = "FEATURE_FAILURE_WEBGL_DISABLED"_ns; 545 } 546 return Err("WebGL is currently disabled."); 547 } 548 549 // Alright, now let's start trying. 550 551 RefPtr<WebGLContext> webgl; 552 if (desc.isWebgl2) { 553 webgl = new WebGL2Context(host, desc); 554 } else { 555 webgl = new WebGLContext(host, desc); 556 } 557 558 MOZ_ASSERT(!webgl->gl); 559 std::vector<FailureReason> failReasons; 560 if (!webgl->CreateAndInitGL(forceEnabled, &failReasons)) { 561 nsCString text("WebGL creation failed: "); 562 for (const auto& cur : failReasons) { 563 // Don't try to accumulate using an empty key if |cur.key| is empty. 564 if (cur.key.IsEmpty()) { 565 glean::canvas::webgl_failure_id 566 .Get("FEATURE_FAILURE_REASON_UNKNOWN"_ns) 567 .Add(1); 568 } else { 569 glean::canvas::webgl_failure_id.Get(cur.key).Add(1); 570 } 571 572 const auto str = nsPrintfCString("\n* %s (%s)", cur.info.BeginReading(), 573 cur.key.BeginReading()); 574 text.Append(str); 575 } 576 failureId = "FEATURE_FAILURE_REASON"_ns; 577 return Err(text.BeginReading()); 578 } 579 MOZ_ASSERT(webgl->gl); 580 581 if (desc.options.failIfMajorPerformanceCaveat) { 582 if (webgl->gl->IsWARP()) { 583 failureId = "FEATURE_FAILURE_WEBGL_PERF_WARP"_ns; 584 return Err( 585 "failIfMajorPerformanceCaveat: Driver is not" 586 " hardware-accelerated."); 587 } 588 589 #ifdef XP_WIN 590 if (webgl->gl->GetContextType() == gl::GLContextType::WGL && 591 !gl::sWGLLib.HasDXInterop2()) { 592 failureId = "FEATURE_FAILURE_WEBGL_DXGL_INTEROP2"_ns; 593 return Err("failIfMajorPerformanceCaveat: WGL without DXGLInterop2."); 594 } 595 #endif 596 } 597 598 const FuncScope funcScope(*webgl, "getContext/restoreContext"); 599 600 MOZ_ASSERT(!webgl->mDefaultFB); 601 if (!webgl->EnsureDefaultFB()) { 602 MOZ_ASSERT(!webgl->mDefaultFB); 603 MOZ_ASSERT(webgl->IsContextLost()); 604 failureId = "FEATURE_FAILURE_WEBGL_BACKBUFFER"_ns; 605 return Err("Initializing WebGL backbuffer failed."); 606 } 607 608 return webgl; 609 }(); 610 if (res.isOk()) { 611 failureId = "SUCCESS"_ns; 612 } 613 glean::canvas::webgl_failure_id.Get(failureId).Add(1); 614 615 if (!res.isOk()) { 616 out->error = res.unwrapErr(); 617 return nullptr; 618 } 619 const auto webgl = res.unwrap(); 620 621 // Update our internal stuff: 622 webgl->FinishInit(); 623 624 reporter.SetSuccessful(); 625 if (gl::GLContext::ShouldSpew()) { 626 printf_stderr("--- WebGL context created: %p\n", webgl.get()); 627 } 628 629 // - 630 631 const auto UploadableSdTypes = [&]() { 632 webgl::EnumMask<layers::SurfaceDescriptor::Type> types; 633 types[layers::SurfaceDescriptor::TSurfaceDescriptorBuffer] = true; 634 // Only support canvas surface interchange if using AC2D. This guarantees 635 // that WebGL and AC2D commands are sequenced and processed on the same 636 // thread, so that there is no mal-ordering between AC2D and WebGL 637 // processing. We can flush out AC2D commands to produce a surface in time 638 // for WebGL to use without requiring any blocking to occur. 639 types[layers::SurfaceDescriptor::TSurfaceDescriptorCanvasSurface] = 640 gfx::gfxVars::UseAcceleratedCanvas2D(); 641 // This is conditional on not using the Compositor thread because we may 642 // need to synchronize with the RDD process over the PVideoBridge protocol 643 // to wait for the texture to be available in the compositor process. We 644 // cannot block on the Compositor thread, so in that configuration, we would 645 // prefer to do the readback from the RDD which is guaranteed to work, and 646 // only block the owning thread for WebGL. 647 const bool offCompositorThread = gfx::gfxVars::UseCanvasRenderThread() || 648 !gfx::gfxVars::SupportsThreadsafeGL(); 649 types[layers::SurfaceDescriptor::TSurfaceDescriptorGPUVideo] = 650 offCompositorThread; 651 // Similarly to the PVideoBridge protocol, we may need to synchronize with 652 // the content process over the PCompositorManager protocol to wait for the 653 // shared surface to be available in the compositor process, and we cannot 654 // block on the Compositor thread. 655 types[layers::SurfaceDescriptor::TSurfaceDescriptorExternalImage] = 656 offCompositorThread; 657 if (webgl->gl->IsANGLE()) { 658 types[layers::SurfaceDescriptor::TSurfaceDescriptorD3D10] = true; 659 types[layers::SurfaceDescriptor::TSurfaceDescriptorDXGIYCbCr] = true; 660 } 661 if (kIsMacOS) { 662 types[layers::SurfaceDescriptor::TSurfaceDescriptorMacIOSurface] = true; 663 } 664 if (kIsAndroid) { 665 types[layers::SurfaceDescriptor::TSurfaceTextureDescriptor] = true; 666 } 667 if (kIsLinux) { 668 types[layers::SurfaceDescriptor::TSurfaceDescriptorDMABuf] = true; 669 } 670 return types; 671 }; 672 673 // - 674 675 constexpr GLenum SHADER_TYPES[] = { 676 LOCAL_GL_VERTEX_SHADER, 677 LOCAL_GL_FRAGMENT_SHADER, 678 }; 679 constexpr GLenum PRECISIONS[] = { 680 LOCAL_GL_LOW_FLOAT, LOCAL_GL_MEDIUM_FLOAT, LOCAL_GL_HIGH_FLOAT, 681 LOCAL_GL_LOW_INT, LOCAL_GL_MEDIUM_INT, LOCAL_GL_HIGH_INT, 682 }; 683 for (const auto& shaderType : SHADER_TYPES) { 684 for (const auto& precisionType : PRECISIONS) { 685 auto spf = webgl::ShaderPrecisionFormat{}; 686 687 GLint range[2] = {}; 688 GLint precision = 0; 689 webgl->gl->fGetShaderPrecisionFormat(shaderType, precisionType, range, 690 &precision); 691 spf.rangeMin = LazyAssertedCast(range[0]); 692 spf.rangeMax = LazyAssertedCast(range[1]); 693 spf.precision = LazyAssertedCast(precision); 694 695 out->shaderPrecisions->insert({{shaderType, precisionType}, spf}); 696 } 697 } 698 699 if (webgl->mDisableFragHighP) { 700 out->shaderPrecisions->at( 701 {LOCAL_GL_FRAGMENT_SHADER, LOCAL_GL_HIGH_FLOAT}) = {}; 702 out->shaderPrecisions->at( 703 {LOCAL_GL_FRAGMENT_SHADER, LOCAL_GL_HIGH_INT}) = {}; 704 } 705 706 // - 707 708 out->options = webgl->mOptions; 709 out->limits = *webgl->mLimits; 710 out->uploadableSdTypes = UploadableSdTypes(); 711 out->vendor = webgl->gl->Vendor(); 712 out->optionalRenderableFormatBits = webgl->mOptionalRenderableFormatBits; 713 714 return webgl; 715 } 716 717 void WebGLContext::FinishInit() { 718 mOptions.antialias &= bool(mDefaultFB->mSamples); 719 720 if (!mOptions.alpha) { 721 // We always have alpha. 722 mNeedsFakeNoAlpha = true; 723 } 724 725 if (mOptions.depth || mOptions.stencil) { 726 // We always have depth+stencil if we have either. 727 if (!mOptions.depth) { 728 mNeedsFakeNoDepth = true; 729 } 730 if (!mOptions.stencil) { 731 mNeedsFakeNoStencil = true; 732 } 733 } 734 735 mResetLayer = true; 736 mOptionsFrozen = true; 737 738 ////// 739 // Initial setup. 740 741 gl->mImplicitMakeCurrent = true; 742 gl->mElideDuplicateBindFramebuffers = true; 743 744 const auto& size = mDefaultFB->mSize; 745 746 mViewportX = mViewportY = 0; 747 mViewportWidth = size.width; 748 mViewportHeight = size.height; 749 gl->fViewport(mViewportX, mViewportY, mViewportWidth, mViewportHeight); 750 751 mScissorRect = {0, 0, size.width, size.height}; 752 mScissorRect.Apply(*gl); 753 754 { 755 const auto& isEnabledMap = webgl::MakeIsEnabledMap(IsWebGL2()); 756 for (const auto& pair : isEnabledMap) { 757 mIsEnabledMapKeys.insert(pair.first); 758 } 759 } 760 761 ////// 762 // Check everything 763 764 AssertCachedBindings(); 765 AssertCachedGlobalState(); 766 767 mShouldPresent = true; 768 769 ////// 770 // mIsRgb8Renderable 771 772 { 773 const auto tex = gl::ScopedTexture(gl); 774 const auto fb = gl::ScopedFramebuffer(gl); 775 gl->fBindTexture(LOCAL_GL_TEXTURE_2D, tex); 776 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, fb); 777 gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, 778 LOCAL_GL_TEXTURE_2D, tex, 0); 779 780 const auto IsRenderable = [&](const GLint internalFormat, 781 const GLenum unpackFormat) { 782 gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, internalFormat, 1, 1, 0, 783 unpackFormat, LOCAL_GL_UNSIGNED_BYTE, nullptr); 784 const auto status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); 785 return (status == LOCAL_GL_FRAMEBUFFER_COMPLETE); 786 }; 787 788 if (IsRenderable(LOCAL_GL_RGB, LOCAL_GL_RGB)) { 789 mOptionalRenderableFormatBits |= 790 webgl::OptionalRenderableFormatBits::RGB8; 791 } 792 if (gl->IsSupported(gl::GLFeature::sRGB)) { 793 struct { 794 GLint internal; 795 GLenum unpack; 796 } formats = {LOCAL_GL_SRGB8, LOCAL_GL_RGB}; 797 const bool isEs2 = (gl->IsGLES() && gl->Version() < 300); 798 if (isEs2) { 799 formats = {LOCAL_GL_SRGB, LOCAL_GL_SRGB}; 800 } 801 if (IsRenderable(formats.internal, formats.unpack)) { 802 mOptionalRenderableFormatBits |= 803 webgl::OptionalRenderableFormatBits::SRGB8; 804 } 805 } 806 } 807 808 ////// 809 810 gl->ResetSyncCallCount("WebGLContext Initialization"); 811 LoseLruContextIfLimitExceeded(); 812 } 813 814 void WebGLContext::SetCompositableHost( 815 RefPtr<layers::CompositableHost>& aCompositableHost) { 816 mCompositableHost = aCompositableHost; 817 } 818 819 void WebGLContext::BumpLruLocked() { 820 if (!mIsContextLost && !mPendingContextLoss) { 821 mLruPosition.AssignLocked(*this); 822 } else { 823 MOZ_ASSERT(!mLruPosition.IsInsertedLocked()); 824 } 825 } 826 827 void WebGLContext::BumpLru() { 828 StaticMutexAutoLock lock(sLruMutex); 829 BumpLruLocked(); 830 } 831 832 void WebGLContext::LoseLruContextIfLimitExceeded() { 833 StaticMutexAutoLock lock(sLruMutex); 834 835 const auto maxContexts = std::max(1u, StaticPrefs::webgl_max_contexts()); 836 const auto maxContextsPerPrincipal = 837 std::max(1u, StaticPrefs::webgl_max_contexts_per_principal()); 838 839 // it's important to update the index on a new context before losing old 840 // contexts, otherwise new unused contexts would all have index 0 and we 841 // couldn't distinguish older ones when choosing which one to lose first. 842 BumpLruLocked(); 843 844 { 845 size_t forPrincipal = 0; 846 for (const auto& context : sLru) { 847 if (context->mPrincipalKey == mPrincipalKey) { 848 forPrincipal += 1; 849 } 850 } 851 852 while (forPrincipal > maxContextsPerPrincipal) { 853 const auto text = nsPrintfCString( 854 "Exceeded %u live WebGL contexts for this principal, losing the " 855 "least recently used one.", 856 maxContextsPerPrincipal); 857 JsWarning(ToString(text)); 858 859 for (const auto& context : sLru) { 860 if (context->mPrincipalKey == mPrincipalKey) { 861 MOZ_ASSERT(context != this); 862 context->LoseContextLruLocked(webgl::ContextLossReason::None); 863 forPrincipal -= 1; 864 break; 865 } 866 } 867 } 868 } 869 870 auto total = sLru.size(); 871 while (total > maxContexts) { 872 const auto text = nsPrintfCString( 873 "Exceeded %u live WebGL contexts, losing the least " 874 "recently used one.", 875 maxContexts); 876 JsWarning(ToString(text)); 877 878 const auto& context = sLru.front(); 879 MOZ_ASSERT(context != this); 880 context->LoseContextLruLocked(webgl::ContextLossReason::None); 881 total -= 1; 882 } 883 } 884 885 // - 886 887 namespace webgl { 888 889 ScopedPrepForResourceClear::ScopedPrepForResourceClear( 890 const WebGLContext& webgl_) 891 : webgl(webgl_) { 892 const auto& gl = webgl.gl; 893 894 if (webgl.mScissorTestEnabled) { 895 gl->fDisable(LOCAL_GL_SCISSOR_TEST); 896 } 897 if (webgl.mRasterizerDiscardEnabled) { 898 gl->fDisable(LOCAL_GL_RASTERIZER_DISCARD); 899 } 900 901 // "The clear operation always uses the front stencil write mask 902 // when clearing the stencil buffer." 903 webgl.DoColorMask(Some(0), 0b1111); 904 gl->fDepthMask(true); 905 gl->fStencilMaskSeparate(LOCAL_GL_FRONT, 0xffffffff); 906 907 gl->fClearColor(0.0f, 0.0f, 0.0f, 0.0f); 908 gl->fClearDepth(1.0f); // Depth formats are always cleared to 1.0f, not 0.0f. 909 gl->fClearStencil(0); 910 } 911 912 ScopedPrepForResourceClear::~ScopedPrepForResourceClear() { 913 const auto& gl = webgl.gl; 914 915 if (webgl.mScissorTestEnabled) { 916 gl->fEnable(LOCAL_GL_SCISSOR_TEST); 917 } 918 if (webgl.mRasterizerDiscardEnabled) { 919 gl->fEnable(LOCAL_GL_RASTERIZER_DISCARD); 920 } 921 922 webgl.DoColorMask(Some(0), webgl.mColorWriteMask0); 923 gl->fDepthMask(webgl.mDepthWriteMask); 924 gl->fStencilMaskSeparate(LOCAL_GL_FRONT, webgl.mStencilWriteMaskFront); 925 926 gl->fClearColor(webgl.mColorClearValue[0], webgl.mColorClearValue[1], 927 webgl.mColorClearValue[2], webgl.mColorClearValue[3]); 928 gl->fClearDepth(webgl.mDepthClearValue); 929 gl->fClearStencil(webgl.mStencilClearValue); 930 } 931 932 } // namespace webgl 933 934 // - 935 936 void WebGLContext::OnEndOfFrame() { 937 if (StaticPrefs::webgl_perf_spew_frame_allocs()) { 938 GeneratePerfWarning("[webgl.perf.spew-frame-allocs] %" PRIu64 939 " data allocations this frame.", 940 mDataAllocGLCallCount); 941 } 942 mDataAllocGLCallCount = 0; 943 gl->ResetSyncCallCount("WebGLContext PresentScreenBuffer"); 944 945 mDrawCallsSinceLastFlush = 0; 946 947 PollPendingSyncs(); 948 949 BumpLru(); 950 } 951 952 void WebGLContext::BlitBackbufferToCurDriverFB( 953 WebGLFramebuffer* const srcAsWebglFb, 954 const gl::MozFramebuffer* const srcAsMozFb, bool srcIsBGRA, bool yFlip, 955 Maybe<gfxAlphaType> convertAlpha) const { 956 // BlitFramebuffer ignores ColorMask(). 957 958 if (mScissorTestEnabled) { 959 gl->fDisable(LOCAL_GL_SCISSOR_TEST); 960 } 961 const auto cleanup = MakeScopeExit([&]() { 962 if (mScissorTestEnabled) { 963 gl->fEnable(LOCAL_GL_SCISSOR_TEST); 964 } 965 }); 966 967 // If a MozFramebuffer is supplied, ensure that a WebGLFramebuffer is not 968 // used since it might not have completeness info, while the MozFramebuffer 969 // can still supply the needed information. 970 MOZ_ASSERT(!(srcAsMozFb && srcAsWebglFb)); 971 const auto* mozFb = srcAsMozFb ? srcAsMozFb : mDefaultFB.get(); 972 GLuint fbo = 0; 973 gfx::IntSize size; 974 if (srcAsWebglFb) { 975 fbo = srcAsWebglFb->mGLName; 976 const auto* info = srcAsWebglFb->GetCompletenessInfo(); 977 MOZ_ASSERT(info); 978 size = gfx::IntSize(info->width, info->height); 979 } else { 980 fbo = mozFb->mFB; 981 size = mozFb->mSize; 982 } 983 984 // If no format conversion is necessary, then attempt to directly blit 985 // between framebuffers. Otherwise, if we need to convert to RGBA from 986 // the source format or do other conversions, then we will need to use 987 // the texture blit path below. 988 if (!srcIsBGRA && !yFlip && !convertAlpha) { 989 if (gl->IsSupported(gl::GLFeature::framebuffer_blit)) { 990 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fbo); 991 gl->fBlitFramebuffer(0, 0, size.width, size.height, 0, 0, size.width, 992 size.height, LOCAL_GL_COLOR_BUFFER_BIT, 993 LOCAL_GL_NEAREST); 994 return; 995 } 996 if (mDefaultFB->mSamples && 997 gl->IsExtensionSupported( 998 gl::GLContext::APPLE_framebuffer_multisample)) { 999 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fbo); 1000 gl->fResolveMultisampleFramebufferAPPLE(); 1001 return; 1002 } 1003 } 1004 1005 GLuint colorTex = 0; 1006 if (srcAsWebglFb) { 1007 const auto& attach = srcAsWebglFb->ColorAttachment0(); 1008 MOZ_ASSERT(attach.Texture()); 1009 colorTex = attach.Texture()->mGLName; 1010 } else { 1011 colorTex = mozFb->ColorTex(); 1012 } 1013 1014 // DrawBlit handles ColorMask itself. 1015 gl->BlitHelper()->DrawBlitTextureToFramebuffer(colorTex, size, size, 1016 LOCAL_GL_TEXTURE_2D, srcIsBGRA, 1017 yFlip, convertAlpha); 1018 } 1019 1020 // - 1021 1022 template <typename T, typename... Args> 1023 constexpr auto MakeArray(Args... args) -> std::array<T, sizeof...(Args)> { 1024 return {{static_cast<T>(args)...}}; 1025 } 1026 1027 inline gfx::ColorSpace2 ToColorSpace2ForOutput( 1028 const std::optional<dom::PredefinedColorSpace> chosenCspace) { 1029 const auto cmsMode = GfxColorManagementMode(); 1030 switch (cmsMode) { 1031 case CMSMode::Off: 1032 return gfx::ColorSpace2::Display; 1033 case CMSMode::TaggedOnly: 1034 if (!chosenCspace) { 1035 return gfx::ColorSpace2::Display; 1036 } 1037 break; 1038 case CMSMode::All: 1039 if (!chosenCspace) { 1040 return gfx::ColorSpace2::SRGB; 1041 } 1042 break; 1043 } 1044 return gfx::ToColorSpace2(*chosenCspace); 1045 } 1046 1047 // - 1048 1049 template <class T> 1050 GLuint GLNameOrZero(const T& t) { 1051 if (t) return t->mGLName; 1052 return 0; 1053 } 1054 1055 // For an overview of how WebGL compositing works, see: 1056 // https://wiki.mozilla.org/Platform/GFX/WebGL/Compositing 1057 bool WebGLContext::PresentInto(gl::SwapChain& swapChain) { 1058 OnEndOfFrame(); 1059 1060 if (!ValidateAndInitFB(nullptr)) return false; 1061 1062 const auto size = mDefaultFB->mSize; 1063 1064 const auto error = [&]() -> std::optional<std::string> { 1065 const auto canvasCspace = ToColorSpace2ForOutput(mDrawingBufferColorSpace); 1066 auto presenter = swapChain.Acquire(size, canvasCspace); 1067 if (!presenter) { 1068 return "Swap chain surface creation failed."; 1069 } 1070 const auto outputCspace = presenter->BackBuffer()->mDesc.colorSpace; 1071 const auto destFb = presenter->Fb(); 1072 1073 // - 1074 1075 bool colorManage = (canvasCspace != gfx::ColorSpace2::Display); 1076 if (canvasCspace == outputCspace) { 1077 colorManage = false; 1078 } 1079 if (!gl->IsSupported(gl::GLFeature::texture_3D)) { 1080 NS_WARNING("Missing GLFeature::texture_3D => colorManage = false."); 1081 colorManage = false; 1082 } 1083 1084 auto colorLut = std::shared_ptr<gl::Texture>{}; 1085 if (colorManage) { 1086 MOZ_ASSERT(canvasCspace != gfx::ColorSpace2::Display); 1087 colorLut = gl->BlitHelper()->GetColorLutTex(gl::GLBlitHelper::ColorLutKey{ 1088 .src = canvasCspace, .dst = outputCspace}); 1089 if (!colorLut) { 1090 NS_WARNING("GetColorLutTex() -> nullptr => colorManage = false."); 1091 colorManage = false; 1092 } 1093 } 1094 1095 if (!colorManage) { 1096 gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, destFb); 1097 BlitBackbufferToCurDriverFB(); 1098 return {}; 1099 } 1100 1101 // - 1102 1103 const auto canvasFb = GetDefaultFBForRead({.endOfFrame = true}); 1104 if (!canvasFb) { 1105 return "[WebGLContext::PresentInto] BindDefaultFBForRead failed."; 1106 } 1107 1108 const auto& blitter = gl->BlitHelper()->GetDrawBlitProg({ 1109 .fragHeader = gl::kFragHeader_Tex2D, 1110 .fragParts = {gl::kFragSample_OnePlane, gl::kFragConvert_ColorLut3d}, 1111 }); 1112 1113 constexpr uint8_t texUnit_src = 0; 1114 constexpr uint8_t texUnit_lut = 1; 1115 gl->BindSamplerTexture(texUnit_src, SamplerLinear(), LOCAL_GL_TEXTURE_2D, 1116 canvasFb->ColorTex()); 1117 gl->BindSamplerTexture(texUnit_lut, SamplerLinear(), LOCAL_GL_TEXTURE_3D, 1118 colorLut->name); 1119 const auto texCleanup = MakeScopeExit([&]() { 1120 gl->BindSamplerTexture( 1121 texUnit_src, GLNameOrZero(mBoundSamplers[texUnit_src]), 1122 LOCAL_GL_TEXTURE_2D, GLNameOrZero(mBound2DTextures[texUnit_src])); 1123 gl->BindSamplerTexture( 1124 texUnit_lut, GLNameOrZero(mBoundSamplers[texUnit_lut]), 1125 LOCAL_GL_TEXTURE_3D, GLNameOrZero(mBound3DTextures[texUnit_lut])); 1126 gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mActiveTexture); 1127 }); 1128 1129 gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, destFb); 1130 1131 gl->fUseProgram(blitter.mProg); 1132 const auto cleanupProg = MakeScopeExit( 1133 [&]() { gl->fUseProgram(GLNameOrZero(mCurrentProgram)); }); 1134 1135 gl->fUniform1i(blitter.mLoc_uColorLut, texUnit_lut); 1136 1137 blitter.Draw({ 1138 .texMatrix0 = gl::Mat3::I(), 1139 .yFlip = false, 1140 .fbSize = size, 1141 .destRect = {}, 1142 }); 1143 1144 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, canvasFb->mFB); 1145 return {}; 1146 }(); 1147 if (error) { 1148 GenerateWarning("%s", error->c_str()); 1149 LoseContext(); 1150 return false; 1151 } 1152 1153 if (!mOptions.preserveDrawingBuffer) { 1154 gl->InvalidateFramebuffer(LOCAL_GL_READ_FRAMEBUFFER); 1155 mDefaultFB_IsInvalid = true; 1156 } 1157 1158 return true; 1159 } 1160 1161 bool WebGLContext::PresentIntoXR(gl::SwapChain& swapChain, 1162 const gl::MozFramebuffer& fb) { 1163 OnEndOfFrame(); 1164 1165 const auto colorSpace = ToColorSpace2ForOutput(mDrawingBufferColorSpace); 1166 auto presenter = swapChain.Acquire(fb.mSize, colorSpace); 1167 if (!presenter) { 1168 GenerateWarning("Swap chain surface creation failed."); 1169 LoseContext(); 1170 return false; 1171 } 1172 1173 const auto destFb = presenter->Fb(); 1174 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb); 1175 1176 BlitBackbufferToCurDriverFB(nullptr, &fb); 1177 1178 // https://immersive-web.github.io/webxr/#opaque-framebuffer 1179 // Opaque framebuffers will always be cleared regardless of the 1180 // associated WebGL context’s preserveDrawingBuffer value. 1181 if (gl->IsSupported(gl::GLFeature::invalidate_framebuffer)) { 1182 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fb.mFB); 1183 constexpr auto attachments = MakeArray<GLenum>( 1184 LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT); 1185 gl->fInvalidateFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, attachments.size(), 1186 attachments.data()); 1187 } 1188 1189 return true; 1190 } 1191 1192 // Initialize a swap chain's surface factory given the desired surface type. 1193 void InitSwapChain(gl::GLContext& gl, gl::SwapChain& swapChain, 1194 const layers::TextureType consumerType, bool useAsync) { 1195 if (!swapChain.mFactory) { 1196 auto typedFactory = gl::SurfaceFactory::Create(&gl, consumerType); 1197 if (typedFactory) { 1198 swapChain.mFactory = std::move(typedFactory); 1199 } 1200 } 1201 if (!swapChain.mFactory) { 1202 NS_WARNING("Failed to make an ideal SurfaceFactory."); 1203 swapChain.mFactory = MakeUnique<gl::SurfaceFactory_Basic>(gl); 1204 } 1205 MOZ_ASSERT(swapChain.mFactory); 1206 if (useAsync) { 1207 // RemoteTextureMap will handle recycling any surfaces, so don't rely on the 1208 // SwapChain's internal pooling. 1209 swapChain.DisablePool(); 1210 } 1211 } 1212 1213 void WebGLContext::Present(WebGLFramebuffer* const xrFb, 1214 const layers::TextureType consumerType, 1215 const bool webvr, 1216 const webgl::SwapChainOptions& options) { 1217 const FuncScope funcScope(*this, "<Present>"); 1218 if (IsContextLost()) { 1219 EnsureContextLostRemoteTextureOwner(options); 1220 return; 1221 } 1222 1223 auto swapChain = GetSwapChain(xrFb, webvr); 1224 const gl::MozFramebuffer* maybeFB = nullptr; 1225 if (xrFb) { 1226 maybeFB = xrFb->mOpaque.get(); 1227 } else { 1228 mResolvedDefaultFB = nullptr; 1229 } 1230 1231 bool useAsync = options.remoteTextureOwnerId.IsValid() && 1232 options.remoteTextureId.IsValid(); 1233 1234 InitSwapChain(*gl, *swapChain, consumerType, useAsync); 1235 1236 bool valid = 1237 maybeFB ? PresentIntoXR(*swapChain, *maybeFB) : PresentInto(*swapChain); 1238 if (!valid) { 1239 EnsureContextLostRemoteTextureOwner(options); 1240 return; 1241 } 1242 1243 if (useAsync) { 1244 PushRemoteTexture(nullptr, *swapChain, swapChain->FrontBuffer(), options); 1245 } 1246 } 1247 1248 void WebGLContext::WaitForTxn(layers::RemoteTextureOwnerId ownerId, 1249 layers::RemoteTextureTxnType txnType, 1250 layers::RemoteTextureTxnId txnId) { 1251 if (!ownerId.IsValid() || !txnType || !txnId) { 1252 return; 1253 } 1254 if (mRemoteTextureOwner && mRemoteTextureOwner->IsRegistered(ownerId)) { 1255 mRemoteTextureOwner->WaitForTxn(ownerId, txnType, txnId); 1256 } 1257 } 1258 1259 bool WebGLContext::CopyToSwapChain( 1260 WebGLFramebuffer* const srcFb, const layers::TextureType consumerType, 1261 const webgl::SwapChainOptions& options, 1262 layers::RemoteTextureOwnerClient* ownerClient) { 1263 const FuncScope funcScope(*this, "<CopyToSwapChain>"); 1264 if (IsContextLost()) { 1265 return false; 1266 } 1267 1268 OnEndOfFrame(); 1269 1270 if (!srcFb || !srcFb->IsCheckFramebufferStatusComplete()) { 1271 return false; 1272 } 1273 const auto* info = srcFb->GetCompletenessInfo(); 1274 if (!info) { 1275 return false; 1276 } 1277 gfx::IntSize size(info->width, info->height); 1278 1279 bool useAsync = options.remoteTextureOwnerId.IsValid() && 1280 options.remoteTextureId.IsValid(); 1281 1282 InitSwapChain(*gl, srcFb->mSwapChain, consumerType, useAsync); 1283 1284 // If we're using async present and if there is no way to serialize surfaces, 1285 // then a readback is required to do the copy. In this case, there's no reason 1286 // to copy into a separate shared surface for the front buffer. Just directly 1287 // read back the WebGL framebuffer into and push it as a remote texture. 1288 if (useAsync && srcFb->mSwapChain.mFactory->GetConsumerType() == 1289 layers::TextureType::Unknown) { 1290 return PushRemoteTexture(srcFb, srcFb->mSwapChain, nullptr, options, 1291 ownerClient); 1292 } 1293 1294 { 1295 // TODO: ColorSpace will need to be part of SwapChainOptions for DTWebgl. 1296 const auto colorSpace = ToColorSpace2ForOutput(mDrawingBufferColorSpace); 1297 auto presenter = srcFb->mSwapChain.Acquire(size, colorSpace); 1298 if (!presenter) { 1299 GenerateWarning("Swap chain surface creation failed."); 1300 LoseContext(); 1301 return false; 1302 } 1303 1304 const ScopedFBRebinder saveFB(this); 1305 1306 const auto destFb = presenter->Fb(); 1307 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb); 1308 1309 BlitBackbufferToCurDriverFB(srcFb, nullptr, options.bgra); 1310 } 1311 1312 if (useAsync) { 1313 return PushRemoteTexture(srcFb, srcFb->mSwapChain, 1314 srcFb->mSwapChain.FrontBuffer(), options, 1315 ownerClient); 1316 } 1317 return true; 1318 } 1319 1320 bool WebGLContext::PushRemoteTexture( 1321 WebGLFramebuffer* fb, gl::SwapChain& swapChain, 1322 std::shared_ptr<gl::SharedSurface> surf, 1323 const webgl::SwapChainOptions& options, 1324 layers::RemoteTextureOwnerClient* ownerClient) { 1325 const auto onFailure = [&]() -> bool { 1326 GenerateWarning("Remote texture creation failed."); 1327 LoseContext(); 1328 if (ownerClient && ownerClient == mRemoteTextureOwner) { 1329 ownerClient->PushDummyTexture(options.remoteTextureId, 1330 options.remoteTextureOwnerId); 1331 } 1332 return false; 1333 }; 1334 1335 if (!ownerClient) { 1336 if (!mRemoteTextureOwner) { 1337 // Ensure we have a remote texture owner client for WebGLParent. 1338 const auto* outOfProcess = 1339 mHost ? mHost->mOwnerData.outOfProcess : nullptr; 1340 if (!outOfProcess) { 1341 return onFailure(); 1342 } 1343 auto pid = outOfProcess->OtherPid(); 1344 mRemoteTextureOwner = MakeRefPtr<layers::RemoteTextureOwnerClient>(pid); 1345 } 1346 ownerClient = mRemoteTextureOwner; 1347 } 1348 1349 layers::RemoteTextureOwnerId ownerId = options.remoteTextureOwnerId; 1350 layers::RemoteTextureId textureId = options.remoteTextureId; 1351 1352 if (!ownerClient->IsRegistered(ownerId)) { 1353 // Register a texture owner to represent the swap chain. 1354 RefPtr<layers::RemoteTextureOwnerClient> textureOwner = ownerClient; 1355 auto destroyedCallback = [textureOwner, ownerId]() { 1356 textureOwner->UnregisterTextureOwner(ownerId); 1357 }; 1358 1359 swapChain.SetDestroyedCallback(destroyedCallback); 1360 ownerClient->RegisterTextureOwner(ownerId, 1361 /* aSharedRecycling */ !!fb); 1362 } 1363 1364 MOZ_ASSERT(fb || surf); 1365 gfx::IntSize size; 1366 if (surf) { 1367 size = surf->mDesc.size; 1368 } else { 1369 const auto* info = fb->GetCompletenessInfo(); 1370 MOZ_ASSERT(info); 1371 size = gfx::IntSize(info->width, info->height); 1372 } 1373 1374 const auto surfaceFormat = mOptions.alpha ? gfx::SurfaceFormat::B8G8R8A8 1375 : gfx::SurfaceFormat::B8G8R8X8; 1376 Maybe<layers::SurfaceDescriptor> desc; 1377 if (surf) { 1378 desc = surf->ToSurfaceDescriptor(); 1379 } 1380 if (!desc) { 1381 if (surf && surf->mDesc.type != gl::SharedSurfaceType::Basic) { 1382 return onFailure(); 1383 } 1384 // If we can't serialize to a surface descriptor, then we need to create 1385 // a buffer to read back into that will become the remote texture. 1386 auto data = ownerClient->CreateOrRecycleBufferTextureData( 1387 size, surfaceFormat, ownerId); 1388 if (!data) { 1389 gfxCriticalNoteOnce << "Failed to allocate BufferTextureData"; 1390 return onFailure(); 1391 } 1392 1393 layers::MappedTextureData mappedData; 1394 if (!data->BorrowMappedData(mappedData)) { 1395 return onFailure(); 1396 } 1397 1398 Range<uint8_t> range = {mappedData.data, 1399 data->AsBufferTextureData()->GetBufferSize()}; 1400 1401 // If we have a surface representing the front buffer, then try to snapshot 1402 // that. Otherwise, when there is no surface, we read back directly from the 1403 // WebGL framebuffer. 1404 auto valid = 1405 surf ? FrontBufferSnapshotInto(surf, Some(range), 1406 Some(mappedData.stride)) 1407 : SnapshotInto(fb->mGLName, size, range, Some(mappedData.stride)); 1408 if (!valid) { 1409 return onFailure(); 1410 } 1411 1412 if (!options.bgra) { 1413 // If the buffer is already BGRA, we don't need to swizzle. However, if it 1414 // is RGBA, then a swizzle to BGRA is required. 1415 bool rv = gfx::SwizzleData(mappedData.data, mappedData.stride, 1416 gfx::SurfaceFormat::R8G8B8A8, mappedData.data, 1417 mappedData.stride, 1418 gfx::SurfaceFormat::B8G8R8A8, mappedData.size); 1419 MOZ_RELEASE_ASSERT(rv, "SwizzleData failed!"); 1420 } 1421 1422 ownerClient->PushTexture(textureId, ownerId, std::move(data)); 1423 return true; 1424 } 1425 1426 // SharedSurfaces of SurfaceDescriptorD3D10 and SurfaceDescriptorMacIOSurface 1427 // need to be kept alive. They will be recycled by 1428 // RemoteTextureOwnerClient::GetRecycledSharedSurface() when their usages are 1429 // ended. 1430 std::shared_ptr<gl::SharedSurface> keepAlive; 1431 switch (desc->type()) { 1432 case layers::SurfaceDescriptor::TSurfaceDescriptorD3D10: 1433 case layers::SurfaceDescriptor::TSurfaceDescriptorMacIOSurface: 1434 case layers::SurfaceDescriptor::TSurfaceTextureDescriptor: 1435 case layers::SurfaceDescriptor::TSurfaceDescriptorAndroidHardwareBuffer: 1436 case layers::SurfaceDescriptor::TEGLImageDescriptor: 1437 case layers::SurfaceDescriptor::TSurfaceDescriptorDMABuf: 1438 keepAlive = surf; 1439 break; 1440 default: 1441 break; 1442 } 1443 1444 ownerClient->PushTexture(textureId, ownerId, keepAlive, size, surfaceFormat, 1445 *desc); 1446 1447 // Look for a recycled surface that matches the swap chain. 1448 while (auto recycledSurface = ownerClient->GetRecycledSharedSurface( 1449 size, surfaceFormat, desc->type(), ownerId)) { 1450 if (swapChain.StoreRecycledSurface(recycledSurface)) { 1451 break; 1452 } 1453 } 1454 return true; 1455 } 1456 1457 void WebGLContext::EnsureContextLostRemoteTextureOwner( 1458 const webgl::SwapChainOptions& options) { 1459 if (!options.remoteTextureOwnerId.IsValid()) { 1460 return; 1461 } 1462 1463 if (!mRemoteTextureOwner) { 1464 // Ensure we have a remote texture owner client for WebGLParent. 1465 const auto* outOfProcess = mHost ? mHost->mOwnerData.outOfProcess : nullptr; 1466 if (!outOfProcess) { 1467 return; 1468 } 1469 auto pid = outOfProcess->OtherPid(); 1470 mRemoteTextureOwner = MakeRefPtr<layers::RemoteTextureOwnerClient>(pid); 1471 } 1472 1473 layers::RemoteTextureOwnerId ownerId = options.remoteTextureOwnerId; 1474 1475 if (!mRemoteTextureOwner->IsRegistered(ownerId)) { 1476 mRemoteTextureOwner->RegisterTextureOwner(ownerId); 1477 } 1478 mRemoteTextureOwner->NotifyContextLost(); 1479 } 1480 1481 void WebGLContext::EndOfFrame() { 1482 const FuncScope funcScope(*this, "<EndOfFrame>"); 1483 if (IsContextLost()) return; 1484 1485 OnEndOfFrame(); 1486 } 1487 1488 gl::SwapChain* WebGLContext::GetSwapChain(WebGLFramebuffer* const xrFb, 1489 const bool webvr) { 1490 auto swapChain = webvr ? &mWebVRSwapChain : &mSwapChain; 1491 if (xrFb) { 1492 swapChain = &xrFb->mSwapChain; 1493 } 1494 return swapChain; 1495 } 1496 1497 Maybe<layers::SurfaceDescriptor> WebGLContext::GetFrontBuffer( 1498 WebGLFramebuffer* const xrFb, const bool webvr) { 1499 auto* swapChain = GetSwapChain(xrFb, webvr); 1500 if (!swapChain) return {}; 1501 const auto& front = swapChain->FrontBuffer(); 1502 if (!front) return {}; 1503 1504 return front->ToSurfaceDescriptor(); 1505 } 1506 1507 Maybe<uvec2> WebGLContext::FrontBufferSnapshotInto( 1508 const Maybe<Range<uint8_t>> maybeDest, const Maybe<size_t> destStride) { 1509 const auto& front = mSwapChain.FrontBuffer(); 1510 if (!front) return {}; 1511 return FrontBufferSnapshotInto(front, maybeDest, destStride); 1512 } 1513 1514 Maybe<uvec2> WebGLContext::FrontBufferSnapshotInto( 1515 const std::shared_ptr<gl::SharedSurface>& front, 1516 const Maybe<Range<uint8_t>> maybeDest, const Maybe<size_t> destStride) { 1517 const auto& size = front->mDesc.size; 1518 if (!maybeDest) return Some(*uvec2::FromSize(size)); 1519 1520 // - 1521 1522 front->BeginRead(); 1523 auto reset = MakeScopeExit([&] { front->EndRead(); }); 1524 1525 // - 1526 1527 return SnapshotInto(front->mFb ? front->mFb->mFB : 0, size, *maybeDest, 1528 destStride); 1529 } 1530 1531 Maybe<uvec2> WebGLContext::SnapshotInto(GLuint srcFb, const gfx::IntSize& size, 1532 const Range<uint8_t>& dest, 1533 const Maybe<size_t> destStride) { 1534 const auto minStride = CheckedInt<size_t>(size.width) * 4; 1535 if (!minStride.isValid()) { 1536 gfxCriticalError() << "SnapshotInto: invalid stride, width:" << size.width; 1537 return {}; 1538 } 1539 size_t stride = destStride.valueOr(minStride.value()); 1540 if (stride < minStride.value() || (stride % 4) != 0) { 1541 gfxCriticalError() << "SnapshotInto: invalid stride, width:" << size.width 1542 << ", stride:" << stride; 1543 return {}; 1544 } 1545 1546 gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 1); 1547 if (IsWebGL2()) { 1548 gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH, 1549 stride > minStride.value() ? stride / 4 : 0); 1550 gl->fPixelStorei(LOCAL_GL_PACK_SKIP_PIXELS, 0); 1551 gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, 0); 1552 } 1553 1554 // - 1555 1556 const auto readFbWas = mBoundReadFramebuffer; 1557 const auto pboWas = mBoundPixelPackBuffer; 1558 1559 GLenum fbTarget = LOCAL_GL_READ_FRAMEBUFFER; 1560 if (!IsWebGL2()) { 1561 fbTarget = LOCAL_GL_FRAMEBUFFER; 1562 } 1563 auto reset2 = MakeScopeExit([&] { 1564 DoBindFB(readFbWas, fbTarget); 1565 if (pboWas) { 1566 BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, pboWas); 1567 } 1568 }); 1569 1570 gl->fBindFramebuffer(fbTarget, srcFb); 1571 if (pboWas) { 1572 BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, nullptr); 1573 } 1574 1575 // - 1576 1577 const auto srcByteCount = CheckedInt<size_t>(stride) * size.height; 1578 if (!srcByteCount.isValid()) { 1579 gfxCriticalError() << "SnapshotInto: invalid srcByteCount, width:" 1580 << size.width << ", height:" << size.height; 1581 return {}; 1582 } 1583 const auto dstByteCount = dest.length(); 1584 if (srcByteCount.value() > dstByteCount) { 1585 gfxCriticalError() << "SnapshotInto: srcByteCount:" << srcByteCount.value() 1586 << " > dstByteCount:" << dstByteCount; 1587 return {}; 1588 } 1589 uint8_t* dstPtr = dest.begin().get(); 1590 gl->fReadPixels(0, 0, size.width, size.height, LOCAL_GL_RGBA, 1591 LOCAL_GL_UNSIGNED_BYTE, dstPtr); 1592 1593 if (!IsWebGL2() && stride > minStride.value() && size.height > 1) { 1594 // WebGL 1 doesn't support PACK_ROW_LENGTH. Instead, we read the data tight 1595 // into the front of the buffer, and use memmove (since the source and dest 1596 // may overlap) starting from the back to move it to the correct stride 1597 // offsets. We don't move the first row as it is already in the right place. 1598 uint8_t* destRow = dstPtr + stride * (size.height - 1); 1599 const uint8_t* srcRow = dstPtr + minStride.value() * (size.height - 1); 1600 while (destRow > dstPtr) { 1601 memmove(destRow, srcRow, minStride.value()); 1602 destRow -= stride; 1603 srcRow -= minStride.value(); 1604 } 1605 } 1606 1607 return Some(*uvec2::FromSize(size)); 1608 } 1609 1610 already_AddRefed<gfx::SourceSurface> WebGLContext::GetBackBufferSnapshot( 1611 const bool requireAlphaPremult) { 1612 if (IsContextLost()) { 1613 return nullptr; 1614 } 1615 1616 const auto surfSize = DrawingBufferSize(); 1617 if (surfSize.x <= 0 || surfSize.y <= 0) { 1618 return nullptr; 1619 } 1620 1621 const auto surfFormat = mOptions.alpha ? gfx::SurfaceFormat::B8G8R8A8 1622 : gfx::SurfaceFormat::B8G8R8X8; 1623 1624 RefPtr<gfx::DataSourceSurface> dataSurf = 1625 gfx::Factory::CreateDataSourceSurface( 1626 gfx::IntSize(surfSize.x, surfSize.y), surfFormat); 1627 if (!dataSurf) { 1628 NS_WARNING("Failed to alloc DataSourceSurface for GetBackBufferSnapshot"); 1629 return nullptr; 1630 } 1631 1632 { 1633 gfx::DataSourceSurface::ScopedMap map(dataSurf, 1634 gfx::DataSourceSurface::READ_WRITE); 1635 if (!map.IsMapped()) { 1636 NS_WARNING("Failed to map DataSourceSurface for GetBackBufferSnapshot"); 1637 return nullptr; 1638 } 1639 1640 // GetDefaultFBForRead might overwrite FB state if it needs to resolve a 1641 // multisampled FB, so save/restore the FB state here just in case. 1642 const gl::ScopedBindFramebuffer bindFb(gl); 1643 const auto fb = GetDefaultFBForRead(); 1644 if (!fb) { 1645 gfxCriticalNote << "GetDefaultFBForRead failed for GetBackBufferSnapshot"; 1646 return nullptr; 1647 } 1648 const auto byteCount = CheckedInt<size_t>(map.GetStride()) * surfSize.y; 1649 if (!byteCount.isValid()) { 1650 gfxCriticalNote << "Invalid byte count for GetBackBufferSnapshot"; 1651 return nullptr; 1652 } 1653 const Range<uint8_t> range = {map.GetData(), byteCount.value()}; 1654 if (!SnapshotInto(fb->mFB, fb->mSize, range, 1655 Some(size_t(map.GetStride())))) { 1656 gfxCriticalNote << "SnapshotInto failed for GetBackBufferSnapshot"; 1657 return nullptr; 1658 } 1659 1660 if (requireAlphaPremult && mOptions.alpha && !mOptions.premultipliedAlpha) { 1661 bool rv = gfx::PremultiplyYFlipData( 1662 map.GetData(), map.GetStride(), gfx::SurfaceFormat::R8G8B8A8, 1663 map.GetData(), map.GetStride(), surfFormat, dataSurf->GetSize()); 1664 MOZ_RELEASE_ASSERT(rv, "PremultiplyYFlipData failed!"); 1665 } else { 1666 bool rv = gfx::SwizzleYFlipData( 1667 map.GetData(), map.GetStride(), gfx::SurfaceFormat::R8G8B8A8, 1668 map.GetData(), map.GetStride(), surfFormat, dataSurf->GetSize()); 1669 MOZ_RELEASE_ASSERT(rv, "SwizzleYFlipData failed!"); 1670 } 1671 } 1672 1673 return dataSurf.forget(); 1674 } 1675 1676 std::shared_ptr<gl::SharedSurface> 1677 WebGLContext::GetBackBufferSnapshotSharedSurface(layers::TextureType texType, 1678 bool bgra, bool yFlip, 1679 bool requireAlphaPremult) { 1680 const FuncScope funcScope(*this, "<GetBackBufferSnapshotSharedSurface>"); 1681 if (IsContextLost()) { 1682 return nullptr; 1683 } 1684 1685 const auto surfSize = DrawingBufferSize(); 1686 if (surfSize.x <= 0 || surfSize.y <= 0) { 1687 return nullptr; 1688 } 1689 1690 InitSwapChain(*gl, mSnapshotSwapChain, texType, true); 1691 1692 { 1693 // TODO: ColorSpace will need to be part of SwapChainOptions for DTWebgl. 1694 const auto colorSpace = ToColorSpace2ForOutput(mDrawingBufferColorSpace); 1695 auto presenter = mSnapshotSwapChain.Acquire( 1696 gfx::IntSize(surfSize.x, surfSize.y), colorSpace); 1697 if (!presenter) { 1698 GenerateWarning("Swap chain surface creation failed."); 1699 return nullptr; 1700 } 1701 1702 const ScopedFBRebinder saveFB(this); 1703 const auto srcFb = GetDefaultFBForRead(); 1704 1705 const auto destFb = presenter->Fb(); 1706 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb); 1707 1708 BlitBackbufferToCurDriverFB( 1709 nullptr, srcFb, bgra, yFlip, 1710 requireAlphaPremult && mOptions.alpha && !mOptions.premultipliedAlpha 1711 ? Some(gfxAlphaType::Premult) 1712 : Nothing()); 1713 } 1714 1715 return mSnapshotSwapChain.FrontBuffer(); 1716 } 1717 1718 void WebGLContext::RecycleSnapshotSharedSurface( 1719 std::shared_ptr<gl::SharedSurface> sharedSurface) { 1720 mSnapshotSwapChain.StoreRecycledSurface(sharedSurface); 1721 } 1722 1723 void WebGLContext::ClearVRSwapChain() { mWebVRSwapChain.ClearPool(); } 1724 1725 // ------------------------ 1726 1727 RefPtr<gfx::DataSourceSurface> GetTempSurface(const gfx::IntSize& aSize, 1728 gfx::SurfaceFormat& aFormat) { 1729 uint32_t stride = 1730 gfx::GetAlignedStride<8>(aSize.width, BytesPerPixel(aFormat)); 1731 return gfx::Factory::CreateDataSourceSurfaceWithStride(aSize, aFormat, 1732 stride); 1733 } 1734 1735 void WebGLContext::DummyReadFramebufferOperation() { 1736 if (!mBoundReadFramebuffer) return; // Infallible. 1737 1738 const auto status = mBoundReadFramebuffer->CheckFramebufferStatus(); 1739 if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) { 1740 ErrorInvalidFramebufferOperation("Framebuffer must be complete."); 1741 } 1742 } 1743 1744 layers::SharedSurfacesHolder* WebGLContext::GetSharedSurfacesHolder() const { 1745 const auto* outOfProcess = mHost ? mHost->mOwnerData.outOfProcess : nullptr; 1746 if (outOfProcess) { 1747 return outOfProcess->mSharedSurfacesHolder; 1748 } 1749 MOZ_ASSERT_UNREACHABLE("Unexpected use of SharedSurfacesHolder in process!"); 1750 return nullptr; 1751 } 1752 1753 dom::ContentParentId WebGLContext::GetContentId() const { 1754 const auto* outOfProcess = mHost ? mHost->mOwnerData.outOfProcess : nullptr; 1755 if (outOfProcess) { 1756 return outOfProcess->mContentId; 1757 } 1758 if (XRE_IsContentProcess()) { 1759 return dom::ContentChild::GetSingleton()->GetID(); 1760 } 1761 return dom::ContentParentId(); 1762 } 1763 1764 bool WebGLContext::Has64BitTimestamps() const { 1765 // 'sync' provides glGetInteger64v either by supporting ARB_sync, GL3+, or 1766 // GLES3+. 1767 return gl->IsSupported(gl::GLFeature::sync); 1768 } 1769 1770 static bool CheckContextLost(gl::GLContext* gl, bool* const out_isGuilty) { 1771 MOZ_ASSERT(gl); 1772 1773 const auto resetStatus = gl->fGetGraphicsResetStatus(); 1774 if (resetStatus == LOCAL_GL_NO_ERROR) { 1775 *out_isGuilty = false; 1776 return false; 1777 } 1778 1779 // Assume guilty unless we find otherwise! 1780 bool isGuilty = true; 1781 switch (resetStatus) { 1782 case LOCAL_GL_INNOCENT_CONTEXT_RESET_ARB: 1783 case LOCAL_GL_PURGED_CONTEXT_RESET_NV: 1784 // Either nothing wrong, or not our fault. 1785 isGuilty = false; 1786 break; 1787 case LOCAL_GL_GUILTY_CONTEXT_RESET_ARB: 1788 NS_WARNING( 1789 "WebGL content on the page definitely caused the graphics" 1790 " card to reset."); 1791 break; 1792 case LOCAL_GL_UNKNOWN_CONTEXT_RESET_ARB: 1793 NS_WARNING( 1794 "WebGL content on the page might have caused the graphics" 1795 " card to reset"); 1796 // If we can't tell, assume not-guilty. 1797 // Todo: Implement max number of "unknown" resets per document or time. 1798 isGuilty = false; 1799 break; 1800 default: 1801 gfxCriticalError() << "Unexpected glGetGraphicsResetStatus: " 1802 << gfx::hexa(resetStatus); 1803 break; 1804 } 1805 1806 if (isGuilty) { 1807 NS_WARNING( 1808 "WebGL context on this page is considered guilty, and will" 1809 " not be restored."); 1810 } 1811 1812 *out_isGuilty = isGuilty; 1813 return true; 1814 } 1815 1816 void WebGLContext::RunContextLossTimer() { mContextLossHandler.RunTimer(); } 1817 1818 // We use this timer for many things. Here are the things that it is activated 1819 // for: 1820 // 1) If a script is using the MOZ_WEBGL_lose_context extension. 1821 // 2) If we are using EGL and _NOT ANGLE_, we query periodically to see if the 1822 // CONTEXT_LOST_WEBGL error has been triggered. 1823 // 3) If we are using ANGLE, or anything that supports ARB_robustness, query the 1824 // GPU periodically to see if the reset status bit has been set. 1825 // In all of these situations, we use this timer to send the script context lost 1826 // and restored events asynchronously. For example, if it triggers a context 1827 // loss, the webglcontextlost event will be sent to it the next time the 1828 // robustness timer fires. 1829 // Note that this timer mechanism is not used unless one of these 3 criteria are 1830 // met. 1831 // At a bare minimum, from context lost to context restores, it would take 3 1832 // full timer iterations: detection, webglcontextlost, webglcontextrestored. 1833 void WebGLContext::CheckForContextLoss() { 1834 bool isGuilty = true; 1835 const auto isContextLost = CheckContextLost(gl, &isGuilty); 1836 if (!isContextLost) return; 1837 1838 mWebGLError = LOCAL_GL_CONTEXT_LOST; 1839 1840 auto reason = webgl::ContextLossReason::None; 1841 if (isGuilty) { 1842 reason = webgl::ContextLossReason::Guilty; 1843 } 1844 LoseContext(reason); 1845 } 1846 1847 void WebGLContext::HandlePendingContextLoss() { 1848 mIsContextLost = true; 1849 if (mHost) { 1850 mHost->OnContextLoss(mPendingContextLossReason); 1851 } 1852 } 1853 1854 void WebGLContext::LoseContextLruLocked(const webgl::ContextLossReason reason) { 1855 printf_stderr("WebGL(%p)::LoseContext(%u)\n", this, 1856 static_cast<uint32_t>(reason)); 1857 mLruPosition.ResetLocked(); 1858 mPendingContextLossReason = reason; 1859 mPendingContextLoss = true; 1860 } 1861 1862 void WebGLContext::LoseContext(const webgl::ContextLossReason reason) { 1863 StaticMutexAutoLock lock(sLruMutex); 1864 LoseContextLruLocked(reason); 1865 HandlePendingContextLoss(); 1866 if (mRemoteTextureOwner) { 1867 mRemoteTextureOwner->NotifyContextLost(); 1868 } 1869 } 1870 1871 void WebGLContext::DidRefresh() { 1872 if (gl) { 1873 gl->FlushIfHeavyGLCallsSinceLastFlush(); 1874 } 1875 } 1876 1877 //////////////////////////////////////////////////////////////////////////////// 1878 1879 uvec2 WebGLContext::DrawingBufferSize() { 1880 const FuncScope funcScope(*this, "width/height"); 1881 if (IsContextLost()) return {}; 1882 1883 if (!EnsureDefaultFB()) return {}; 1884 1885 return *uvec2::FromSize(mDefaultFB->mSize); 1886 } 1887 1888 bool WebGLContext::ValidateAndInitFB(const WebGLFramebuffer* const fb, 1889 const GLenum incompleteFbError) { 1890 if (fb) return fb->ValidateAndInitAttachments(incompleteFbError); 1891 1892 if (!EnsureDefaultFB()) return false; 1893 1894 if (mDefaultFB_IsInvalid) { 1895 // Clear it! 1896 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mDefaultFB->mFB); 1897 const webgl::ScopedPrepForResourceClear scopedPrep(*this); 1898 if (!mOptions.alpha) { 1899 gl->fClearColor(0, 0, 0, 1); 1900 } 1901 const GLbitfield bits = LOCAL_GL_COLOR_BUFFER_BIT | 1902 LOCAL_GL_DEPTH_BUFFER_BIT | 1903 LOCAL_GL_STENCIL_BUFFER_BIT; 1904 gl->fClear(bits); 1905 1906 mDefaultFB_IsInvalid = false; 1907 } 1908 return true; 1909 } 1910 1911 void WebGLContext::DoBindFB(const WebGLFramebuffer* const fb, 1912 const GLenum target) const { 1913 const GLenum driverFB = fb ? fb->mGLName : (mDefaultFB ? mDefaultFB->mFB : 0); 1914 gl->fBindFramebuffer(target, driverFB); 1915 } 1916 1917 bool WebGLContext::BindCurFBForDraw() { 1918 const auto& fb = mBoundDrawFramebuffer; 1919 if (!ValidateAndInitFB(fb)) return false; 1920 1921 DoBindFB(fb); 1922 return true; 1923 } 1924 1925 bool WebGLContext::BindCurFBForColorRead( 1926 const webgl::FormatUsageInfo** const out_format, uint32_t* const out_width, 1927 uint32_t* const out_height, const GLenum incompleteFbError) { 1928 const auto& fb = mBoundReadFramebuffer; 1929 1930 if (fb) { 1931 if (!ValidateAndInitFB(fb, incompleteFbError)) return false; 1932 if (!fb->ValidateForColorRead(out_format, out_width, out_height)) 1933 return false; 1934 1935 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, fb->mGLName); 1936 return true; 1937 } 1938 1939 if (!BindDefaultFBForRead()) return false; 1940 1941 if (mDefaultFB_ReadBuffer == LOCAL_GL_NONE) { 1942 ErrorInvalidOperation( 1943 "Can't read from backbuffer when readBuffer mode is NONE."); 1944 return false; 1945 } 1946 1947 auto effFormat = mOptions.alpha ? webgl::EffectiveFormat::RGBA8 1948 : webgl::EffectiveFormat::RGB8; 1949 1950 *out_format = mFormatUsage->GetUsage(effFormat); 1951 MOZ_ASSERT(*out_format); 1952 1953 *out_width = mDefaultFB->mSize.width; 1954 *out_height = mDefaultFB->mSize.height; 1955 return true; 1956 } 1957 1958 const gl::MozFramebuffer* WebGLContext::GetDefaultFBForRead( 1959 const GetDefaultFBForReadDesc& desc) { 1960 if (!ValidateAndInitFB(nullptr)) return nullptr; 1961 1962 if (!mDefaultFB->mSamples) { 1963 return mDefaultFB.get(); 1964 } 1965 1966 if (!mResolvedDefaultFB) { 1967 mResolvedDefaultFB = 1968 gl::MozFramebuffer::Create(gl, mDefaultFB->mSize, 0, false); 1969 if (!mResolvedDefaultFB) { 1970 gfxCriticalNote << FuncName() << ": Failed to create mResolvedDefaultFB."; 1971 return nullptr; 1972 } 1973 } 1974 1975 gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, mResolvedDefaultFB->mFB); 1976 BlitBackbufferToCurDriverFB(); 1977 1978 if (desc.endOfFrame && !mOptions.preserveDrawingBuffer) { 1979 gl->InvalidateFramebuffer(LOCAL_GL_READ_FRAMEBUFFER); 1980 } 1981 1982 return mResolvedDefaultFB.get(); 1983 } 1984 1985 bool WebGLContext::BindDefaultFBForRead() { 1986 const auto fb = GetDefaultFBForRead(); 1987 if (!fb) return false; 1988 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fb->mFB); 1989 return true; 1990 } 1991 1992 void WebGLContext::DoColorMask(Maybe<GLuint> i, const uint8_t bitmask) const { 1993 if (!IsExtensionEnabled(WebGLExtensionID::OES_draw_buffers_indexed)) { 1994 i = Nothing(); 1995 } 1996 const auto bs = std::bitset<4>(bitmask); 1997 if (i) { 1998 gl->fColorMaski(*i, bs[0], bs[1], bs[2], bs[3]); 1999 } else { 2000 gl->fColorMask(bs[0], bs[1], bs[2], bs[3]); 2001 } 2002 } 2003 2004 //////////////////////////////////////////////////////////////////////////////// 2005 2006 ScopedDrawCallWrapper::ScopedDrawCallWrapper(WebGLContext& webgl) 2007 : mWebGL(webgl) { 2008 uint8_t driverColorMask0 = mWebGL.mColorWriteMask0; 2009 bool driverDepthTest = mWebGL.mDepthTestEnabled; 2010 bool driverStencilTest = mWebGL.mStencilTestEnabled; 2011 const auto& fb = mWebGL.mBoundDrawFramebuffer; 2012 if (!fb) { 2013 if (mWebGL.mDefaultFB_DrawBuffer0 == LOCAL_GL_NONE) { 2014 driverColorMask0 = 0; // Is this well-optimized enough for depth-first 2015 // rendering? 2016 } else { 2017 driverColorMask0 &= ~(uint8_t(mWebGL.mNeedsFakeNoAlpha) << 3); 2018 } 2019 driverDepthTest &= !mWebGL.mNeedsFakeNoDepth; 2020 driverStencilTest &= !mWebGL.mNeedsFakeNoStencil; 2021 } 2022 2023 const auto& gl = mWebGL.gl; 2024 mWebGL.DoColorMask(Some(0), driverColorMask0); 2025 if (mWebGL.mDriverDepthTest != driverDepthTest) { 2026 // "When disabled, the depth comparison and subsequent possible updates to 2027 // the 2028 // depth buffer value are bypassed and the fragment is passed to the next 2029 // operation." [GLES 3.0.5, p177] 2030 mWebGL.mDriverDepthTest = driverDepthTest; 2031 gl->SetEnabled(LOCAL_GL_DEPTH_TEST, mWebGL.mDriverDepthTest); 2032 } 2033 if (mWebGL.mDriverStencilTest != driverStencilTest) { 2034 // "When disabled, the stencil test and associated modifications are not 2035 // made, and 2036 // the fragment is always passed." [GLES 3.0.5, p175] 2037 mWebGL.mDriverStencilTest = driverStencilTest; 2038 gl->SetEnabled(LOCAL_GL_STENCIL_TEST, mWebGL.mDriverStencilTest); 2039 } 2040 } 2041 2042 ScopedDrawCallWrapper::~ScopedDrawCallWrapper() { 2043 if (mWebGL.mBoundDrawFramebuffer) return; 2044 2045 mWebGL.mResolvedDefaultFB = nullptr; 2046 mWebGL.mShouldPresent = true; 2047 } 2048 2049 // - 2050 2051 void WebGLContext::ScissorRect::Apply(gl::GLContext& gl) const { 2052 gl.fScissor(x, y, w, h); 2053 } 2054 2055 //////////////////////////////////////// 2056 2057 IndexedBufferBinding::IndexedBufferBinding() = default; 2058 IndexedBufferBinding::~IndexedBufferBinding() = default; 2059 2060 uint64_t IndexedBufferBinding::ByteCount() const { 2061 if (!mBufferBinding) return 0; 2062 2063 uint64_t bufferSize = mBufferBinding->ByteLength(); 2064 if (!mRangeSize) // BindBufferBase 2065 return bufferSize; 2066 2067 if (mRangeStart >= bufferSize) return 0; 2068 bufferSize -= mRangeStart; 2069 2070 return std::min(bufferSize, mRangeSize); 2071 } 2072 2073 //////////////////////////////////////// 2074 2075 ScopedFBRebinder::~ScopedFBRebinder() { 2076 const auto fnName = [&](WebGLFramebuffer* fb) { 2077 return fb ? fb->mGLName : 0; 2078 }; 2079 2080 const auto& gl = mWebGL->gl; 2081 if (mWebGL->IsWebGL2()) { 2082 gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, 2083 fnName(mWebGL->mBoundDrawFramebuffer)); 2084 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, 2085 fnName(mWebGL->mBoundReadFramebuffer)); 2086 } else { 2087 MOZ_ASSERT(mWebGL->mBoundDrawFramebuffer == mWebGL->mBoundReadFramebuffer); 2088 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 2089 fnName(mWebGL->mBoundDrawFramebuffer)); 2090 } 2091 } 2092 2093 //////////////////// 2094 2095 void DoBindBuffer(gl::GLContext& gl, const GLenum target, 2096 const WebGLBuffer* const buffer) { 2097 gl.fBindBuffer(target, buffer ? buffer->mGLName : 0); 2098 } 2099 2100 //////////////////////////////////////// 2101 2102 bool Intersect(const int32_t srcSize, const int32_t read0, 2103 const int32_t readSize, int32_t* const out_intRead0, 2104 int32_t* const out_intWrite0, int32_t* const out_intSize) { 2105 MOZ_ASSERT(srcSize >= 0); 2106 MOZ_ASSERT(readSize >= 0); 2107 const auto read1 = int64_t(read0) + readSize; 2108 2109 int32_t intRead0 = read0; // Clearly doesn't need validation. 2110 int64_t intWrite0 = 0; 2111 int64_t intSize = readSize; 2112 2113 if (read1 <= 0 || read0 >= srcSize) { 2114 // Disjoint ranges. 2115 intSize = 0; 2116 } else { 2117 if (read0 < 0) { 2118 const auto diff = int64_t(0) - read0; 2119 MOZ_ASSERT(diff >= 0); 2120 intRead0 = 0; 2121 intWrite0 = diff; 2122 intSize -= diff; 2123 } 2124 if (read1 > srcSize) { 2125 const auto diff = int64_t(read1) - srcSize; 2126 MOZ_ASSERT(diff >= 0); 2127 intSize -= diff; 2128 } 2129 2130 if (!CheckedInt<int32_t>(intWrite0).isValid() || 2131 !CheckedInt<int32_t>(intSize).isValid()) { 2132 return false; 2133 } 2134 } 2135 2136 *out_intRead0 = intRead0; 2137 *out_intWrite0 = intWrite0; 2138 *out_intSize = intSize; 2139 return true; 2140 } 2141 2142 // -- 2143 2144 uint64_t AvailGroups(const uint64_t totalAvailItems, 2145 const uint64_t firstItemOffset, const uint32_t groupSize, 2146 const uint32_t groupStride) { 2147 MOZ_ASSERT(groupSize && groupStride); 2148 MOZ_ASSERT(groupSize <= groupStride); 2149 2150 if (totalAvailItems <= firstItemOffset) return 0; 2151 const size_t availItems = totalAvailItems - firstItemOffset; 2152 2153 size_t availGroups = availItems / groupStride; 2154 const size_t tailItems = availItems % groupStride; 2155 if (tailItems >= groupSize) { 2156 availGroups += 1; 2157 } 2158 return availGroups; 2159 } 2160 2161 //////////////////////////////////////////////////////////////////////////////// 2162 2163 const char* WebGLContext::FuncName() const { 2164 const char* ret; 2165 if (MOZ_LIKELY(mFuncScope)) { 2166 ret = mFuncScope->mFuncName; 2167 } else { 2168 ret = "<unknown function>"; 2169 } 2170 return ret; 2171 } 2172 2173 // - 2174 2175 WebGLContext::FuncScope::FuncScope(const WebGLContext& webgl, 2176 const char* const funcName) 2177 : mWebGL(webgl), mFuncName(bool(mWebGL.mFuncScope) ? nullptr : funcName) { 2178 if (!mFuncName) return; 2179 mWebGL.mFuncScope = this; 2180 } 2181 2182 WebGLContext::FuncScope::~FuncScope() { 2183 if (mBindFailureGuard) { 2184 gfxCriticalError() << "mBindFailureGuard failure: Early exit from " 2185 << mWebGL.FuncName(); 2186 } 2187 2188 if (!mFuncName) return; 2189 mWebGL.mFuncScope = nullptr; 2190 } 2191 2192 // -- 2193 2194 bool ClientWebGLContext::IsXRCompatible() const { return mXRCompatible; } 2195 2196 already_AddRefed<dom::Promise> ClientWebGLContext::MakeXRCompatible( 2197 ErrorResult& aRv) { 2198 const FuncScope funcScope(*this, "MakeXRCompatible"); 2199 nsCOMPtr<nsIGlobalObject> global = GetParentObject(); 2200 if (!global) { 2201 aRv.ThrowInvalidAccessError( 2202 "Using a WebGL context that is not attached to either a canvas or an " 2203 "OffscreenCanvas"); 2204 return nullptr; 2205 } 2206 RefPtr<dom::Promise> promise = dom::Promise::Create(global, aRv); 2207 NS_ENSURE_TRUE(!aRv.Failed(), nullptr); 2208 2209 if (IsContextLost()) { 2210 promise->MaybeRejectWithInvalidStateError( 2211 "Can not make context XR compatible when context is already lost."); 2212 return promise.forget(); 2213 } 2214 2215 // TODO: Bug 1580258 - WebGLContext.MakeXRCompatible needs to switch to 2216 // the device connected to the XR hardware 2217 // This should update `options` and lose+restore the context. 2218 mXRCompatible = true; 2219 promise->MaybeResolveWithUndefined(); 2220 return promise.forget(); 2221 } 2222 2223 // -- 2224 2225 webgl::AvailabilityRunnable& ClientWebGLContext::EnsureAvailabilityRunnable() 2226 const { 2227 if (!mAvailabilityRunnable) { 2228 mAvailabilityRunnable = new webgl::AvailabilityRunnable(this); 2229 auto forgettable = mAvailabilityRunnable; 2230 NS_DispatchToCurrentThread(forgettable.forget()); 2231 } 2232 return *mAvailabilityRunnable; 2233 } 2234 2235 webgl::AvailabilityRunnable::AvailabilityRunnable( 2236 const ClientWebGLContext* const webgl) 2237 : DiscardableRunnable("webgl::AvailabilityRunnable"), mWebGL(webgl) {} 2238 2239 webgl::AvailabilityRunnable::~AvailabilityRunnable() { 2240 MOZ_ASSERT(mQueries.empty()); 2241 MOZ_ASSERT(mSyncs.empty()); 2242 } 2243 2244 nsresult webgl::AvailabilityRunnable::Run() { 2245 for (const auto& cur : mQueries) { 2246 if (!cur) continue; 2247 cur->mCanBeAvailable = true; 2248 } 2249 mQueries.clear(); 2250 2251 for (const auto& cur : mSyncs) { 2252 if (!cur) continue; 2253 cur->mCanBeAvailable = true; 2254 } 2255 mSyncs.clear(); 2256 2257 if (mWebGL) { 2258 mWebGL->mAvailabilityRunnable = nullptr; 2259 } 2260 return NS_OK; 2261 } 2262 2263 // - 2264 2265 void WebGLContext::JsWarning(const std::string& text) const { 2266 if (mHost) { 2267 mHost->JsWarning(text); 2268 } 2269 #ifdef DEBUG 2270 if (!mHost) { 2271 NS_WARNING(text.c_str()); 2272 } 2273 #endif 2274 } 2275 2276 void WebGLContext::GenerateErrorImpl(const GLenum errOrWarning, 2277 const std::string& text) const { 2278 auto err = errOrWarning; 2279 bool isPerfWarning = false; 2280 if (err == webgl::kErrorPerfWarning) { 2281 err = 0; 2282 isPerfWarning = true; 2283 } 2284 2285 if (err && mFuncScope && mFuncScope->mBindFailureGuard) { 2286 gfxCriticalError() << "mBindFailureGuard failure: Generating error " 2287 << EnumString(err) << ": " << text; 2288 } 2289 2290 /* ES2 section 2.5 "GL Errors" states that implementations can have 2291 * multiple 'flags', as errors might be caught in different parts of 2292 * a distributed implementation. 2293 * We're signing up as a distributed implementation here, with 2294 * separate flags for WebGL and the underlying GLContext. 2295 */ 2296 if (!mWebGLError) mWebGLError = err; 2297 2298 if (!mHost) return; // Impossible? 2299 2300 // - 2301 2302 const auto ShouldWarn = [&]() { 2303 if (isPerfWarning) { 2304 return ShouldGeneratePerfWarnings(); 2305 } 2306 return ShouldGenerateWarnings(); 2307 }; 2308 if (!ShouldWarn()) return; 2309 2310 // - 2311 2312 auto* pNumWarnings = &mWarningCount; 2313 const char* warningsType = "warnings"; 2314 if (isPerfWarning) { 2315 pNumWarnings = &mNumPerfWarnings; 2316 warningsType = "perf warnings"; 2317 } 2318 2319 if (isPerfWarning) { 2320 const auto perfText = std::string("WebGL perf warning: ") + text; 2321 JsWarning(perfText); 2322 } else { 2323 JsWarning(text); 2324 } 2325 *pNumWarnings += 1; 2326 2327 if (!ShouldWarn()) { 2328 const auto& msg = nsPrintfCString( 2329 "After reporting %i, no further %s will be reported for this WebGL " 2330 "context.", 2331 int(*pNumWarnings), warningsType); 2332 JsWarning(ToString(msg)); 2333 } 2334 } 2335 2336 // - 2337 2338 Maybe<std::string> WebGLContext::GetString(const GLenum pname) const { 2339 const WebGLContext::FuncScope funcScope(*this, "getParameter"); 2340 if (IsContextLost()) return {}; 2341 2342 const auto FromRaw = [](const char* const raw) -> Maybe<std::string> { 2343 if (!raw) return {}; 2344 return Some(std::string(raw)); 2345 }; 2346 2347 switch (pname) { 2348 case LOCAL_GL_EXTENSIONS: { 2349 if (!gl->IsCoreProfile()) { 2350 const auto rawExt = (const char*)gl->fGetString(LOCAL_GL_EXTENSIONS); 2351 return FromRaw(rawExt); 2352 } 2353 std::string ret; 2354 const auto& numExts = gl->GetIntAs<GLuint>(LOCAL_GL_NUM_EXTENSIONS); 2355 for (GLuint i = 0; i < numExts; i++) { 2356 const auto rawExt = 2357 (const char*)gl->fGetStringi(LOCAL_GL_EXTENSIONS, i); 2358 if (!rawExt) continue; 2359 2360 if (i > 0) { 2361 ret += " "; 2362 } 2363 ret += rawExt; 2364 } 2365 return Some(std::move(ret)); 2366 } 2367 2368 case LOCAL_GL_RENDERER: 2369 case LOCAL_GL_VENDOR: 2370 case LOCAL_GL_VERSION: { 2371 const auto raw = (const char*)gl->fGetString(pname); 2372 return FromRaw(raw); 2373 } 2374 2375 case dom::MOZ_debug_Binding::WSI_INFO: { 2376 nsCString info; 2377 gl->GetWSIInfo(&info); 2378 return Some(std::string(info.BeginReading())); 2379 } 2380 2381 case dom::MOZ_debug_Binding::CONTEXT_TYPE: { 2382 gl::GLContextType ctxType = gl->GetContextType(); 2383 switch (ctxType) { 2384 case gl::GLContextType::Unknown: 2385 return Some("unknown"_ns); 2386 case gl::GLContextType::WGL: 2387 return Some("wgl"_ns); 2388 case gl::GLContextType::CGL: 2389 return Some("cgl"_ns); 2390 case gl::GLContextType::GLX: 2391 return Some("glx"_ns); 2392 case gl::GLContextType::EGL: 2393 return Some("egl"_ns); 2394 case gl::GLContextType::EAGL: 2395 return Some("eagl"_ns); 2396 } 2397 return Some("unknown"_ns); 2398 } 2399 2400 default: 2401 ErrorInvalidEnumArg("pname", pname); 2402 return {}; 2403 } 2404 } 2405 2406 // --------------------------------- 2407 2408 Maybe<webgl::IndexedName> webgl::ParseIndexed(const std::string& str) { 2409 // Check if the string ends with a close bracket 2410 if (str.size() < 2 || str.back() != ']') { 2411 return {}; 2412 } 2413 // Search for the open bracket, only allow digits between brackets 2414 const size_t closeBracket = str.size() - 1; 2415 size_t openBracket = closeBracket; 2416 for (;;) { 2417 char c = str[--openBracket]; 2418 if (isdigit(c)) { 2419 if (openBracket <= 0) { 2420 // At the beginning of string without an open bracket 2421 return {}; 2422 } 2423 } else if (c == '[') { 2424 // Found the open bracket 2425 break; 2426 } else { 2427 // Found a non-digit 2428 return {}; 2429 } 2430 } 2431 2432 // Ensure non-empty digit sequence 2433 size_t firstDigit = openBracket + 1; 2434 if (firstDigit >= closeBracket) { 2435 return {}; 2436 } 2437 const auto index = 2438 std::stoull(str.substr(firstDigit, closeBracket - firstDigit)); 2439 std::string name = str.substr(0, openBracket); 2440 return Some(webgl::IndexedName{name, index}); 2441 } 2442 2443 // ExplodeName("foo.bar[3].x") -> ["foo", ".", "bar", "[", "3", "]", ".", "x"] 2444 static std::vector<std::string> ExplodeName(const std::string& str) { 2445 std::vector<std::string> ret; 2446 size_t curPos = 0; 2447 while (curPos < str.size()) { 2448 // Find the next separator 2449 size_t nextPos = str.find_first_of(".[]", curPos); 2450 if (nextPos == std::string::npos) { 2451 // If no separator found, add remaining substring 2452 ret.push_back(str.substr(curPos)); 2453 break; 2454 } 2455 // Add string between separators, if not empty 2456 if (curPos < nextPos) { 2457 ret.push_back(str.substr(curPos, nextPos - curPos)); 2458 } 2459 // Add the separator 2460 ret.push_back(str.substr(nextPos, 1)); 2461 curPos = nextPos + 1; 2462 } 2463 return ret; 2464 } 2465 2466 //- 2467 2468 // #define DUMP_MakeLinkResult 2469 2470 webgl::LinkActiveInfo GetLinkActiveInfo( 2471 gl::GLContext& gl, const GLuint prog, const bool webgl2, 2472 const std::unordered_map<std::string, std::string>& nameUnmap) { 2473 webgl::LinkActiveInfo ret; 2474 [&]() { 2475 const auto fnGetProgramui = [&](const GLenum pname) { 2476 GLint ret = 0; 2477 gl.fGetProgramiv(prog, pname, &ret); 2478 return static_cast<uint32_t>(ret); 2479 }; 2480 2481 std::vector<char> stringBuffer(1); 2482 const auto fnEnsureCapacity = [&](const GLenum pname) { 2483 const auto maxWithNull = fnGetProgramui(pname); 2484 if (maxWithNull > stringBuffer.size()) { 2485 stringBuffer.resize(maxWithNull); 2486 } 2487 }; 2488 2489 fnEnsureCapacity(LOCAL_GL_ACTIVE_ATTRIBUTE_MAX_LENGTH); 2490 fnEnsureCapacity(LOCAL_GL_ACTIVE_UNIFORM_MAX_LENGTH); 2491 if (webgl2) { 2492 fnEnsureCapacity(LOCAL_GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH); 2493 fnEnsureCapacity(LOCAL_GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH); 2494 } 2495 2496 // - 2497 2498 const auto fnUnmapName = [&](const std::string& mappedName) { 2499 const auto parts = ExplodeName(mappedName); 2500 2501 std::ostringstream ret; 2502 for (const auto& part : parts) { 2503 const auto maybe = MaybeFind(nameUnmap, part); 2504 if (maybe) { 2505 ret << *maybe; 2506 } else { 2507 ret << part; 2508 } 2509 } 2510 return ret.str(); 2511 }; 2512 2513 // - 2514 2515 { 2516 const auto count = fnGetProgramui(LOCAL_GL_ACTIVE_ATTRIBUTES); 2517 ret.activeAttribs.reserve(count); 2518 for (const auto i : IntegerRange(count)) { 2519 GLsizei lengthWithoutNull = 0; 2520 GLint elemCount = 0; // `size` 2521 GLenum elemType = 0; // `type` 2522 gl.fGetActiveAttrib(prog, i, stringBuffer.size(), &lengthWithoutNull, 2523 &elemCount, &elemType, stringBuffer.data()); 2524 if (!elemType) { 2525 const auto error = gl.fGetError(); 2526 if (error != LOCAL_GL_CONTEXT_LOST) { 2527 gfxCriticalError() << "Failed to do glGetActiveAttrib: " << error; 2528 } 2529 return; 2530 } 2531 const auto mappedName = 2532 std::string(stringBuffer.data(), lengthWithoutNull); 2533 const auto userName = fnUnmapName(mappedName); 2534 2535 auto loc = gl.fGetAttribLocation(prog, mappedName.c_str()); 2536 if (mappedName.find("gl_") == 0) { 2537 // Bug 1328559: Appears problematic on ANGLE and OSX, but not Linux or 2538 // Win+GL. 2539 loc = -1; 2540 } 2541 2542 #ifdef DUMP_MakeLinkResult 2543 printf_stderr("[attrib %u/%u] @%i %s->%s\n", i, count, loc, 2544 userName.c_str(), mappedName.c_str()); 2545 #endif 2546 webgl::ActiveAttribInfo info; 2547 info.elemType = elemType; 2548 info.elemCount = elemCount; 2549 info.name = userName; 2550 info.location = loc; 2551 info.baseType = webgl::ToAttribBaseType(info.elemType); 2552 ret.activeAttribs.push_back(std::move(info)); 2553 } 2554 } 2555 2556 // - 2557 2558 { 2559 const auto count = fnGetProgramui(LOCAL_GL_ACTIVE_UNIFORMS); 2560 ret.activeUniforms.reserve(count); 2561 2562 std::vector<GLint> blockIndexList(count, -1); 2563 std::vector<GLint> blockOffsetList(count, -1); 2564 std::vector<GLint> blockArrayStrideList(count, -1); 2565 std::vector<GLint> blockMatrixStrideList(count, -1); 2566 std::vector<GLint> blockIsRowMajorList(count, 0); 2567 2568 if (webgl2 && count) { 2569 std::vector<GLuint> activeIndices; 2570 activeIndices.reserve(count); 2571 for (const auto i : IntegerRange(count)) { 2572 activeIndices.push_back(i); 2573 } 2574 2575 gl.fGetActiveUniformsiv( 2576 prog, activeIndices.size(), activeIndices.data(), 2577 LOCAL_GL_UNIFORM_BLOCK_INDEX, blockIndexList.data()); 2578 2579 gl.fGetActiveUniformsiv(prog, activeIndices.size(), 2580 activeIndices.data(), LOCAL_GL_UNIFORM_OFFSET, 2581 blockOffsetList.data()); 2582 2583 gl.fGetActiveUniformsiv( 2584 prog, activeIndices.size(), activeIndices.data(), 2585 LOCAL_GL_UNIFORM_ARRAY_STRIDE, blockArrayStrideList.data()); 2586 2587 gl.fGetActiveUniformsiv( 2588 prog, activeIndices.size(), activeIndices.data(), 2589 LOCAL_GL_UNIFORM_MATRIX_STRIDE, blockMatrixStrideList.data()); 2590 2591 gl.fGetActiveUniformsiv( 2592 prog, activeIndices.size(), activeIndices.data(), 2593 LOCAL_GL_UNIFORM_IS_ROW_MAJOR, blockIsRowMajorList.data()); 2594 } 2595 2596 for (const auto i : IntegerRange(count)) { 2597 GLsizei lengthWithoutNull = 0; 2598 GLint elemCount = 0; // `size` 2599 GLenum elemType = 0; // `type` 2600 gl.fGetActiveUniform(prog, i, stringBuffer.size(), &lengthWithoutNull, 2601 &elemCount, &elemType, stringBuffer.data()); 2602 if (!elemType) { 2603 const auto error = gl.fGetError(); 2604 if (error != LOCAL_GL_CONTEXT_LOST) { 2605 gfxCriticalError() << "Failed to do glGetActiveUniform: " << error; 2606 } 2607 return; 2608 } 2609 auto mappedName = std::string(stringBuffer.data(), lengthWithoutNull); 2610 2611 // Get true name 2612 2613 auto baseMappedName = mappedName; 2614 2615 const bool isArray = [&]() { 2616 const auto maybe = webgl::ParseIndexed(mappedName); 2617 if (maybe) { 2618 MOZ_ASSERT(maybe->index == 0); 2619 baseMappedName = std::move(maybe->name); 2620 return true; 2621 } 2622 return false; 2623 }(); 2624 2625 const auto userName = fnUnmapName(mappedName); 2626 if (StartsWith(userName, "webgl_")) continue; 2627 2628 // - 2629 2630 webgl::ActiveUniformInfo info; 2631 info.elemType = elemType; 2632 info.elemCount = static_cast<uint32_t>(elemCount); 2633 info.name = userName; 2634 info.block_index = blockIndexList[i]; 2635 info.block_offset = blockOffsetList[i]; 2636 info.block_arrayStride = blockArrayStrideList[i]; 2637 info.block_matrixStride = blockMatrixStrideList[i]; 2638 info.block_isRowMajor = bool(blockIsRowMajorList[i]); 2639 2640 #ifdef DUMP_MakeLinkResult 2641 printf_stderr("[uniform %u/%u] %s->%s\n", i + 1, count, 2642 userName.c_str(), mappedName.c_str()); 2643 #endif 2644 2645 // Get uniform locations 2646 { 2647 auto locName = baseMappedName; 2648 const auto baseLength = locName.size(); 2649 for (const auto i : IntegerRange(info.elemCount)) { 2650 if (isArray) { 2651 locName.erase( 2652 baseLength); // Erase previous [N], but retain capacity. 2653 locName += '['; 2654 locName += std::to_string(i); 2655 locName += ']'; 2656 } 2657 const auto loc = gl.fGetUniformLocation(prog, locName.c_str()); 2658 if (loc != -1) { 2659 info.locByIndex[i] = static_cast<uint32_t>(loc); 2660 #ifdef DUMP_MakeLinkResult 2661 printf_stderr(" [%u] @%i\n", i, loc); 2662 #endif 2663 } 2664 } 2665 } // anon 2666 2667 ret.activeUniforms.push_back(std::move(info)); 2668 } // for i 2669 } // anon 2670 2671 if (webgl2) { 2672 // ------------------------------------- 2673 // active uniform blocks 2674 { 2675 const auto count = fnGetProgramui(LOCAL_GL_ACTIVE_UNIFORM_BLOCKS); 2676 ret.activeUniformBlocks.reserve(count); 2677 2678 for (const auto i : IntegerRange(count)) { 2679 GLsizei lengthWithoutNull = 0; 2680 gl.fGetActiveUniformBlockName(prog, i, stringBuffer.size(), 2681 &lengthWithoutNull, 2682 stringBuffer.data()); 2683 const auto mappedName = 2684 std::string(stringBuffer.data(), lengthWithoutNull); 2685 const auto userName = fnUnmapName(mappedName); 2686 2687 // - 2688 2689 auto info = webgl::ActiveUniformBlockInfo{userName}; 2690 GLint val = 0; 2691 2692 gl.fGetActiveUniformBlockiv(prog, i, LOCAL_GL_UNIFORM_BLOCK_DATA_SIZE, 2693 &val); 2694 info.dataSize = static_cast<uint32_t>(val); 2695 2696 gl.fGetActiveUniformBlockiv( 2697 prog, i, LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &val); 2698 info.activeUniformIndices.resize(val); 2699 gl.fGetActiveUniformBlockiv( 2700 prog, i, LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, 2701 reinterpret_cast<GLint*>(info.activeUniformIndices.data())); 2702 2703 gl.fGetActiveUniformBlockiv( 2704 prog, i, LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER, 2705 &val); 2706 info.referencedByVertexShader = bool(val); 2707 2708 gl.fGetActiveUniformBlockiv( 2709 prog, i, LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER, 2710 &val); 2711 info.referencedByFragmentShader = bool(val); 2712 2713 ret.activeUniformBlocks.push_back(std::move(info)); 2714 } // for i 2715 } // anon 2716 2717 // ------------------------------------- 2718 // active tf varyings 2719 { 2720 const auto count = fnGetProgramui(LOCAL_GL_TRANSFORM_FEEDBACK_VARYINGS); 2721 ret.activeTfVaryings.reserve(count); 2722 2723 for (const auto i : IntegerRange(count)) { 2724 GLsizei lengthWithoutNull = 0; 2725 GLsizei elemCount = 0; // `size` 2726 GLenum elemType = 0; // `type` 2727 gl.fGetTransformFeedbackVarying(prog, i, stringBuffer.size(), 2728 &lengthWithoutNull, &elemCount, 2729 &elemType, stringBuffer.data()); 2730 const auto mappedName = 2731 std::string(stringBuffer.data(), lengthWithoutNull); 2732 const auto userName = fnUnmapName(mappedName); 2733 2734 ret.activeTfVaryings.push_back( 2735 {elemType, static_cast<uint32_t>(elemCount), userName}); 2736 } 2737 } 2738 } // if webgl2 2739 }(); 2740 return ret; 2741 } 2742 2743 nsCString ToCString(const std::string& s) { 2744 return nsCString(s.data(), s.size()); 2745 } 2746 2747 webgl::CompileResult WebGLContext::GetCompileResult( 2748 const WebGLShader& shader) const { 2749 webgl::CompileResult ret; 2750 [&]() { 2751 ret.pending = false; 2752 const auto& info = shader.CompileResults(); 2753 if (!info) return; 2754 if (!info->mValid) { 2755 ret.log = info->mInfoLog.c_str(); 2756 return; 2757 } 2758 // TODO: These could be large and should be made fallible. 2759 ret.translatedSource = ToCString(info->mObjectCode); 2760 ret.log = ToCString(shader.CompileLog()); 2761 if (!shader.IsCompiled()) return; 2762 ret.success = true; 2763 }(); 2764 return ret; 2765 } 2766 2767 webgl::LinkResult WebGLContext::GetLinkResult(const WebGLProgram& prog) const { 2768 webgl::LinkResult ret; 2769 [&]() { 2770 ret.pending = false; // Link status polling not yet implemented. 2771 ret.log = ToCString(prog.LinkLog()); 2772 const auto& info = prog.LinkInfo(); 2773 if (!info) return; 2774 ret.success = true; 2775 ret.active = info->active; 2776 ret.tfBufferMode = info->transformFeedbackBufferMode; 2777 }(); 2778 return ret; 2779 } 2780 2781 // - 2782 2783 GLint WebGLContext::GetFragDataLocation(const WebGLProgram& prog, 2784 const std::string& userName) const { 2785 const auto err = CheckGLSLVariableName(IsWebGL2(), userName); 2786 if (err) { 2787 GenerateError(err->type, "%s", err->info.c_str()); 2788 return -1; 2789 } 2790 2791 const auto& info = prog.LinkInfo(); 2792 if (!info) return -1; 2793 const auto& nameMap = info->nameMap; 2794 2795 const auto parts = ExplodeName(userName); 2796 2797 std::ostringstream ret; 2798 for (const auto& part : parts) { 2799 const auto maybe = MaybeFind(nameMap, part); 2800 if (maybe) { 2801 ret << *maybe; 2802 } else { 2803 ret << part; 2804 } 2805 } 2806 const auto mappedName = ret.str(); 2807 2808 if (gl->WorkAroundDriverBugs() && gl->IsMesa()) { 2809 // Mesa incorrectly generates INVALID_OPERATION for gl_ prefixes here. 2810 if (mappedName.find("gl_") == 0) { 2811 return -1; 2812 } 2813 } 2814 2815 return gl->fGetFragDataLocation(prog.mGLName, mappedName.c_str()); 2816 } 2817 2818 // - 2819 2820 WebGLContextBoundObject::WebGLContextBoundObject(WebGLContext* webgl) 2821 : mContext(webgl) {} 2822 2823 // - 2824 2825 Result<webgl::ExplicitPixelPackingState, std::string> 2826 webgl::ExplicitPixelPackingState::ForUseWith( 2827 const webgl::PixelPackingState& stateOrZero, const GLenum target, 2828 const uvec3& subrectSize, const webgl::PackingInfo& pi, 2829 const Maybe<size_t> bytesPerRowStrideOverride) { 2830 auto state = stateOrZero; 2831 2832 if (!IsTexTarget3D(target)) { 2833 state.skipImages = 0; 2834 state.imageHeight = 0; 2835 } 2836 if (!state.rowLength) { 2837 state.rowLength = subrectSize.x; 2838 } 2839 if (!state.imageHeight) { 2840 state.imageHeight = subrectSize.y; 2841 } 2842 2843 // - 2844 2845 const auto mpii = PackingInfoInfo::For(pi); 2846 if (!mpii) { 2847 const auto text = 2848 nsPrintfCString("Invalid pi: { 0x%x, 0x%x}", pi.format, pi.type); 2849 return Err(mozilla::ToString(text)); 2850 } 2851 const auto pii = *mpii; 2852 const auto bytesPerPixel = pii.BytesPerPixel(); 2853 2854 const auto ElemsPerRowStride = [&]() { 2855 // GLES 3.0.6 p116: 2856 // p: `Elem*` pointer to the first element of the first row 2857 // N: row number, starting at 0 2858 // l: groups (pixels) per row 2859 // n: elements per group (pixel) in [1,2,3,4] 2860 // s: bytes per element in [1,2,4,8] 2861 // a: UNPACK_ALIGNMENT in [1,2,4,8] 2862 // Pointer to first element of Nth row: p + N*k 2863 // k(s>=a): n*l 2864 // k(s<a): a/s * ceil(s*n*l/a) 2865 const auto n__elemsPerPixel = pii.elementsPerPixel; 2866 const auto l__pixelsPerRow = state.rowLength; 2867 const auto a__alignment = state.alignmentInTypeElems; 2868 const auto s__bytesPerElem = pii.bytesPerElement; 2869 2870 const auto nl = CheckedInt<size_t>(n__elemsPerPixel) * l__pixelsPerRow; 2871 auto k__elemsPerRowStride = nl; 2872 if (s__bytesPerElem < a__alignment) { 2873 // k = a/s * ceil(s*n*l/a) 2874 k__elemsPerRowStride = 2875 a__alignment / s__bytesPerElem * 2876 ((nl * s__bytesPerElem + a__alignment - 1) / a__alignment); 2877 } 2878 return k__elemsPerRowStride; 2879 }; 2880 2881 // - 2882 2883 if (bytesPerRowStrideOverride) { // E.g. HTMLImageElement 2884 const size_t bytesPerRowStrideRequired = *bytesPerRowStrideOverride; 2885 // We have to reverse-engineer an ALIGNMENT and ROW_LENGTH for this. 2886 2887 // GL does this in elems not bytes, so we should too. 2888 MOZ_RELEASE_ASSERT(bytesPerRowStrideRequired % pii.bytesPerElement == 0); 2889 const auto elemsPerRowStrideRequired = 2890 bytesPerRowStrideRequired / pii.bytesPerElement; 2891 2892 state.rowLength = bytesPerRowStrideRequired / bytesPerPixel; 2893 state.alignmentInTypeElems = 8; 2894 while (true) { 2895 const auto elemPerRowStride = ElemsPerRowStride(); 2896 if (elemPerRowStride.isValid() && 2897 elemPerRowStride.value() == elemsPerRowStrideRequired) { 2898 break; 2899 } 2900 state.alignmentInTypeElems /= 2; 2901 if (!state.alignmentInTypeElems) { 2902 const auto text = nsPrintfCString( 2903 "No valid alignment found: pi: { 0x%x, 0x%x}," 2904 " bytesPerRowStrideRequired: %zu", 2905 pi.format, pi.type, bytesPerRowStrideRequired); 2906 return Err(mozilla::ToString(text)); 2907 } 2908 } 2909 } 2910 2911 // - 2912 2913 const auto usedPixelsPerRow = 2914 CheckedInt<size_t>(state.skipPixels) + subrectSize.x; 2915 if (!usedPixelsPerRow.isValid() || 2916 usedPixelsPerRow.value() > state.rowLength) { 2917 return Err("UNPACK_SKIP_PIXELS + width > UNPACK_ROW_LENGTH."); 2918 } 2919 2920 if (subrectSize.y > state.imageHeight) { 2921 return Err("height > UNPACK_IMAGE_HEIGHT."); 2922 } 2923 // The spec doesn't bound SKIP_ROWS + height <= IMAGE_HEIGHT, unfortunately. 2924 2925 // - 2926 2927 auto metrics = Metrics{}; 2928 2929 metrics.usedSize = subrectSize; 2930 metrics.bytesPerPixel = BytesPerPixel(pi); 2931 2932 // - 2933 2934 const auto elemsPerRowStride = ElemsPerRowStride(); 2935 const auto bytesPerRowStride = pii.bytesPerElement * elemsPerRowStride; 2936 if (!bytesPerRowStride.isValid()) { 2937 return Err("ROW_LENGTH or width too large for packing."); 2938 } 2939 metrics.bytesPerRowStride = bytesPerRowStride.value(); 2940 2941 // - 2942 2943 const auto firstImageTotalRows = 2944 CheckedInt<size_t>(state.skipRows) + metrics.usedSize.y; 2945 const auto totalImages = 2946 CheckedInt<size_t>(state.skipImages) + metrics.usedSize.z; 2947 auto totalRows = CheckedInt<size_t>(0); 2948 if (metrics.usedSize.y && metrics.usedSize.z) { 2949 totalRows = firstImageTotalRows + state.imageHeight * (totalImages - 1); 2950 } 2951 if (!totalRows.isValid()) { 2952 return Err( 2953 "SKIP_ROWS, height, IMAGE_HEIGHT, SKIP_IMAGES, or depth too large for " 2954 "packing."); 2955 } 2956 metrics.totalRows = totalRows.value(); 2957 2958 // - 2959 2960 const auto totalBytesStrided = totalRows * metrics.bytesPerRowStride; 2961 if (!totalBytesStrided.isValid()) { 2962 return Err("Total byte count too large for packing."); 2963 } 2964 metrics.totalBytesStrided = totalBytesStrided.value(); 2965 2966 metrics.totalBytesUsed = metrics.totalBytesStrided; 2967 if (metrics.usedSize.x && metrics.usedSize.y && metrics.usedSize.z) { 2968 const auto usedBytesPerRow = 2969 usedPixelsPerRow.value() * metrics.bytesPerPixel; 2970 metrics.totalBytesUsed -= metrics.bytesPerRowStride; 2971 metrics.totalBytesUsed += usedBytesPerRow; 2972 } 2973 2974 // - 2975 2976 return {{state, metrics}}; 2977 } 2978 2979 GLuint WebGLContext::SamplerLinear() const { 2980 if (!mSamplerLinear) { 2981 mSamplerLinear = std::make_unique<gl::Sampler>(*gl); 2982 gl->fSamplerParameteri(mSamplerLinear->name, LOCAL_GL_TEXTURE_MAG_FILTER, 2983 LOCAL_GL_LINEAR); 2984 gl->fSamplerParameteri(mSamplerLinear->name, LOCAL_GL_TEXTURE_MIN_FILTER, 2985 LOCAL_GL_LINEAR); 2986 gl->fSamplerParameteri(mSamplerLinear->name, LOCAL_GL_TEXTURE_WRAP_S, 2987 LOCAL_GL_CLAMP_TO_EDGE); 2988 gl->fSamplerParameteri(mSamplerLinear->name, LOCAL_GL_TEXTURE_WRAP_T, 2989 LOCAL_GL_CLAMP_TO_EDGE); 2990 gl->fSamplerParameteri(mSamplerLinear->name, LOCAL_GL_TEXTURE_WRAP_R, 2991 LOCAL_GL_CLAMP_TO_EDGE); 2992 } 2993 return mSamplerLinear->name; 2994 } 2995 2996 } // namespace mozilla