Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
embedder_external_view_embedder.cc
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
6
7#include <cassert>
8#include <utility>
9
13#include "third_party/skia/include/gpu/ganesh/GrDirectContext.h"
14
15#ifdef IMPELLER_SUPPORTS_RENDERING
16#include "impeller/display_list/dl_dispatcher.h" // nogncheck
17#endif // IMPELLER_SUPPORTS_RENDERING
18
19namespace flutter {
20
22
24 bool avoid_backing_store_cache,
25 const CreateRenderTargetCallback& create_render_target_callback,
26 const PresentCallback& present_callback)
27 : avoid_backing_store_cache_(avoid_backing_store_cache),
28 create_render_target_callback_(create_render_target_callback),
29 present_callback_(present_callback) {
30 FML_DCHECK(create_render_target_callback_);
31 FML_DCHECK(present_callback_);
32}
33
35
37 render_target_caches_.erase(view_id);
38}
39
41 SurfaceTransformationCallback surface_transformation_callback) {
42 surface_transformation_callback_ = std::move(surface_transformation_callback);
43}
44
45DlMatrix EmbedderExternalViewEmbedder::GetSurfaceTransformation() const {
46 if (!surface_transformation_callback_) {
47 return DlMatrix{};
48 }
49
50 return surface_transformation_callback_();
51}
52
53void EmbedderExternalViewEmbedder::Reset() {
54 pending_views_.clear();
55 composition_order_.clear();
56}
57
58// |ExternalViewEmbedder|
59void EmbedderExternalViewEmbedder::CancelFrame() {
60 Reset();
61}
62
63// |ExternalViewEmbedder|
64void EmbedderExternalViewEmbedder::BeginFrame(
65 GrDirectContext* context,
66 const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {}
67
68// |ExternalViewEmbedder|
69void EmbedderExternalViewEmbedder::PrepareFlutterView(
70 DlISize frame_size,
71 double device_pixel_ratio) {
72 Reset();
73
74 pending_frame_size_ = frame_size;
75 pending_device_pixel_ratio_ = device_pixel_ratio;
76 pending_surface_transformation_ = GetSurfaceTransformation();
77
78 pending_views_[kRootViewIdentifier] = std::make_unique<EmbedderExternalView>(
79 pending_frame_size_, pending_surface_transformation_);
80 composition_order_.push_back(kRootViewIdentifier);
81}
82
83// |ExternalViewEmbedder|
84void EmbedderExternalViewEmbedder::PrerollCompositeEmbeddedView(
85 int64_t view_id,
86 std::unique_ptr<EmbeddedViewParams> params) {
87 auto vid = EmbedderExternalView::ViewIdentifier(view_id);
88 FML_DCHECK(pending_views_.count(vid) == 0);
89
90 pending_views_[vid] = std::make_unique<EmbedderExternalView>(
91 pending_frame_size_, // frame size
92 pending_surface_transformation_, // surface xformation
93 vid, // view identifier
94 std::move(params) // embedded view params
95 );
96 composition_order_.push_back(vid);
97}
98
99// |ExternalViewEmbedder|
100DlCanvas* EmbedderExternalViewEmbedder::GetRootCanvas() {
101 auto found = pending_views_.find(kRootViewIdentifier);
102 if (found == pending_views_.end()) {
103 FML_DLOG(WARNING)
104 << "No root canvas could be found. This is extremely unlikely and "
105 "indicates that the external view embedder did not receive the "
106 "notification to begin the frame.";
107 return nullptr;
108 }
109 return found->second->GetCanvas();
110}
111
112// |ExternalViewEmbedder|
113DlCanvas* EmbedderExternalViewEmbedder::CompositeEmbeddedView(int64_t view_id) {
114 auto vid = EmbedderExternalView::ViewIdentifier(view_id);
115 auto found = pending_views_.find(vid);
116 if (found == pending_views_.end()) {
117 FML_DCHECK(false) << "Attempted to composite a view that was not "
118 "pre-rolled.";
119 return nullptr;
120 }
121 return found->second->GetCanvas();
122}
123
125 int64_t view_id,
126 const DlISize& backing_store_size) {
127 FlutterBackingStoreConfig config = {};
128
129 config.struct_size = sizeof(config);
130
131 config.size.width = backing_store_size.width;
132 config.size.height = backing_store_size.height;
133 config.view_id = view_id;
134
135 return config;
136}
137
138namespace {
139
140struct PlatformView {
141 EmbedderExternalView::ViewIdentifier view_identifier;
142 const EmbeddedViewParams* params;
143
144 // The frame of the platform view, after clipping, in screen coordinates.
146
147 explicit PlatformView(const EmbedderExternalView* view) {
148 FML_DCHECK(view->HasPlatformView());
149 view_identifier = view->GetViewIdentifier();
150 params = view->GetEmbeddedViewParams();
151
152 DlRect clip = view->GetEmbeddedViewParams()->finalBoundingRect();
153 DlMatrix matrix;
154 for (auto i = params->mutatorsStack().Begin();
155 i != params->mutatorsStack().End(); ++i) {
156 const auto& m = *i;
157 switch (m->GetType()) {
159 auto rect = m->GetRect().TransformAndClipBounds(matrix);
160 clip = clip.IntersectionOrEmpty(rect);
161 break;
162 }
164 auto rect = m->GetRRect().GetBounds().TransformAndClipBounds(matrix);
165 clip = clip.IntersectionOrEmpty(rect);
166 break;
167 }
169 auto rect = m->GetRSE().GetBounds().TransformAndClipBounds(matrix);
170 clip = clip.IntersectionOrEmpty(rect);
171 break;
172 }
174 auto rect = m->GetPath().GetBounds().TransformAndClipBounds(matrix);
175 clip = clip.IntersectionOrEmpty(rect);
176 break;
177 }
179 matrix = matrix * m->GetMatrix();
180 break;
181 }
188 break;
189 }
190 }
191 clipped_frame = ToSkRect(clip);
192 }
193};
194
195/// Each layer will result in a single physical surface that contains Flutter
196/// contents. It may contain multiple platform views and the slices
197/// that would be otherwise rendered between these platform views will be
198/// collapsed into this layer, as long as they do not intersect any of the
199/// platform views.
200/// In Z order the Flutter contents of Layer is above the platform views.
201class Layer {
202 public:
203 /// Returns whether the rectangle intersects any of the platform views of
204 /// this layer.
205 bool IntersectsPlatformView(const SkRect& rect) {
206 for (auto& platform_view : platform_views_) {
207 if (platform_view.clipped_frame.intersects(rect)) {
208 return true;
209 }
210 }
211 return false;
212 }
213
214 /// Returns whether the region intersects any of the platform views of this
215 /// layer.
216 bool IntersectsPlatformView(const DlRegion& region) {
217 for (auto& platform_view : platform_views_) {
218 auto clipped_frame = ToDlIRect(platform_view.clipped_frame.roundOut());
219 if (region.intersects(clipped_frame)) {
220 return true;
221 }
222 }
223 return false;
224 }
225
226 /// Returns whether the rectangle intersects any of the Flutter contents of
227 /// this layer.
228 bool IntersectsFlutterContents(const SkRect& rect) {
229 return flutter_contents_region_.intersects(ToDlIRect(rect.roundOut()));
230 }
231
232 /// Returns whether the region intersects any of the Flutter contents of this
233 /// layer.
234 bool IntersectsFlutterContents(const DlRegion& region) {
235 return flutter_contents_region_.intersects(region);
236 }
237
238 /// Adds a platform view to this layer.
239 void AddPlatformView(const PlatformView& platform_view) {
240 platform_views_.push_back(platform_view);
241 }
242
243 /// Adds Flutter contents to this layer.
244 void AddFlutterContents(EmbedderExternalView* contents,
245 const DlRegion& contents_region) {
246 flutter_contents_.push_back(contents);
247 flutter_contents_region_ =
248 DlRegion::MakeUnion(flutter_contents_region_, contents_region);
249 }
250
251 bool has_flutter_contents() const { return !flutter_contents_.empty(); }
252
253 void SetRenderTarget(std::unique_ptr<EmbedderRenderTarget> target) {
254 FML_DCHECK(render_target_ == nullptr);
255 FML_DCHECK(has_flutter_contents());
256 render_target_ = std::move(target);
257 }
258
259 /// Renders this layer Flutter contents to the render target previously
260 /// assigned with SetRenderTarget.
261 void RenderFlutterContents(bool frame_boundary) {
262 FML_DCHECK(has_flutter_contents());
263 if (!render_target_) {
264 return;
265 }
266
267#ifdef IMPELLER_SUPPORTS_RENDERING
268 if (render_target_->GetImpellerRenderTarget()) {
269 RenderFlutterContentsImpeller(frame_boundary);
270 return;
271 }
272#endif // IMPELLER_SUPPORTS_RENDERING
273
274#if SLIMPELLER
275 FML_LOG(FATAL) << "Impeller opt-out unavailable.";
276#else // SLIMPELLER
277 RenderFlutterContentsSkia();
278#endif // SLIMPELLER
279 }
280
281 /// Returns platform views for this layer. In Z-order the platform views are
282 /// positioned *below* this layer's Flutter contents.
283 const std::vector<PlatformView>& platform_views() const {
284 return platform_views_;
285 }
286
287 EmbedderRenderTarget* render_target() { return render_target_.get(); }
288
289 std::vector<DlIRect> coverage() {
290 return flutter_contents_region_.getRects();
291 }
292
293 private:
294#if !SLIMPELLER
295 // TODO(https://github.com/flutter/flutter/issues/151670): Implement this
296 // for Impeller as well.
297 static void InvalidateApiState(SkSurface& skia_surface) {
298 auto recording_context = skia_surface.recordingContext();
299
300 // Should never happen.
301 FML_DCHECK(recording_context) << "Recording context was null.";
302
303 auto direct_context = recording_context->asDirectContext();
304 if (direct_context == nullptr) {
305 // Can happen when using software rendering.
306 // Print an error but otherwise continue in that case.
307 FML_LOG(ERROR) << "Embedder asked to invalidate cached graphics API "
308 "state but Flutter is not using a graphics API.";
309 } else {
310 direct_context->resetContext(kAll_GrBackendState);
311 }
312 }
313
314 void RenderFlutterContentsSkia() {
315 auto skia_surface = render_target_->GetSkiaSurface();
316 if (!skia_surface) {
317 return;
318 }
319
320 auto [ok, invalidate_api_state] = render_target_->MaybeMakeCurrent();
321
322 if (invalidate_api_state) {
323 InvalidateApiState(*skia_surface);
324 }
325 if (!ok) {
326 FML_LOG(ERROR) << "Could not make the surface current.";
327 return;
328 }
329
330 // Clear the current render target (most likely EGLSurface) at the
331 // end of this scope.
332 fml::ScopedCleanupClosure clear_current_surface([&]() {
333 auto [ok, invalidate_api_state] = render_target_->MaybeClearCurrent();
334 if (invalidate_api_state) {
335 InvalidateApiState(*skia_surface);
336 }
337 if (!ok) {
338 FML_LOG(ERROR) << "Could not clear the current surface.";
339 }
340 });
341
342 auto canvas = skia_surface->getCanvas();
343 if (!canvas) {
344 return;
345 }
346
347 DlSkCanvasAdapter dl_canvas(canvas);
348 bool clear_surface = true;
349 for (auto c : flutter_contents_) {
350 FML_DCHECK(render_target_->GetRenderTargetSize() ==
351 c->GetRenderSurfaceSize());
352 c->Render(dl_canvas, clear_surface);
353 clear_surface = false;
354 }
355 dl_canvas.Flush();
356 }
357#endif // !SLIMPELLER
358
359#ifdef IMPELLER_SUPPORTS_RENDERING
360 void RenderFlutterContentsImpeller(bool frame_boundary) {
361 auto dl_builder = DisplayListBuilder();
362 bool clear_surface = true;
363 for (auto c : flutter_contents_) {
364 FML_DCHECK(render_target_->GetRenderTargetSize() ==
365 c->GetRenderSurfaceSize());
366 c->Render(dl_builder, clear_surface);
367 clear_surface = false;
368 }
369 auto display_list = dl_builder.Build();
370
371 auto* impeller_target = render_target_->GetImpellerRenderTarget();
372 auto aiks_context = render_target_->GetAiksContext();
373 auto cull_rect =
374 impeller::Rect::MakeSize(impeller_target->GetRenderTargetSize());
375
376 impeller::RenderToTarget(aiks_context->GetContentContext(), //
377 *impeller_target, //
378 display_list, //
379 cull_rect, //
380 /*reset_host_buffer=*/frame_boundary, //
381 /*is_onscreen=*/false //
382 );
383 }
384#endif // IMPELLER_SUPPORTS_RENDERING
385
386 std::vector<PlatformView> platform_views_;
387 std::vector<EmbedderExternalView*> flutter_contents_;
388 DlRegion flutter_contents_region_;
389 std::unique_ptr<EmbedderRenderTarget> render_target_;
390 friend class LayerBuilder;
391};
392
393/// A layout builder is responsible for building an optimized list of Layers
394/// from a list of `EmbedderExternalView`s. Single EmbedderExternalView contains
395/// at most one platform view and at most one layer of Flutter contents
396/// ('slice'). LayerBuilder is responsible for producing as few Layers from the
397/// list of EmbedderExternalViews as possible while maintaining identical visual
398/// result.
399///
400/// Implements https://flutter.dev/go/optimized-platform-view-layers
401class LayerBuilder {
402 public:
403 using RenderTargetProvider =
404 std::function<std::unique_ptr<EmbedderRenderTarget>(
405 const DlISize& frame_size)>;
406
407 explicit LayerBuilder(DlISize frame_size) : frame_size_(frame_size) {
408 layers_.push_back(Layer());
409 }
410
411 /// Adds the platform view and/or flutter contents from the
412 /// EmbedderExternalView instance.
413 ///
414 /// This will try to add the content and platform view to an existing layer
415 /// if possible. If not, a new layer will be created.
416 void AddExternalView(EmbedderExternalView* view) {
417 if (view->HasPlatformView()) {
418 PlatformView platform_view(view);
419 AddPlatformView(platform_view);
420 }
421 if (view->HasEngineRenderedContents()) {
422 AddFlutterContents(view);
423 }
424 }
425
426 /// Prepares the render targets for all layers that have Flutter contents.
427 void PrepareBackingStore(const RenderTargetProvider& target_provider) {
428 for (auto& layer : layers_) {
429 if (layer.has_flutter_contents()) {
430 layer.SetRenderTarget(target_provider(frame_size_));
431 }
432 }
433 }
434
435 /// Renders all layers with Flutter contents to their respective render
436 /// targets.
437 void Render() {
438 // Find the last layer that has Flutter contents. The frame boundary flag
439 // will be set for this layer.
440 auto last_flutter_layer_rev_iter =
441 std::find_if(layers_.rbegin(), layers_.rend(),
442 [](const Layer& l) { return l.has_flutter_contents(); });
443 if (last_flutter_layer_rev_iter == layers_.rend()) {
444 return;
445 }
446 auto last_flutter_layer_iter = last_flutter_layer_rev_iter.base() - 1;
447
448 for (auto iter = layers_.begin(); iter != layers_.end(); iter++) {
449 bool frame_boundary = iter == last_flutter_layer_iter;
450 if (iter->has_flutter_contents()) {
451 iter->RenderFlutterContents(frame_boundary);
452 }
453 }
454 }
455
456 /// Populates EmbedderLayers from layer builder's layers.
457 void PushLayers(EmbedderLayers& layers) {
458 for (auto& layer : layers_) {
459 for (auto& view : layer.platform_views()) {
460 auto platform_view_id = view.view_identifier.platform_view_id;
461 if (platform_view_id.has_value()) {
462 layers.PushPlatformViewLayer(platform_view_id.value(), *view.params);
463 }
464 }
465 if (layer.render_target() != nullptr) {
466 layers.PushBackingStoreLayer(layer.render_target()->GetBackingStore(),
467 layer.coverage());
468 }
469 }
470 }
471
472 /// Removes the render targets from layers and returns them for collection.
473 std::vector<std::unique_ptr<EmbedderRenderTarget>>
474 ClearAndCollectRenderTargets() {
475 std::vector<std::unique_ptr<EmbedderRenderTarget>> result;
476 for (auto& layer : layers_) {
477 if (layer.render_target() != nullptr) {
478 result.push_back(std::move(layer.render_target_));
479 }
480 }
481 layers_.clear();
482 return result;
483 }
484
485 private:
486 void AddPlatformView(PlatformView view) {
487 GetLayerForPlatformView(view).AddPlatformView(view);
488 }
489
490 void AddFlutterContents(EmbedderExternalView* contents) {
491 FML_DCHECK(contents->HasEngineRenderedContents());
492
493 DlRegion region = contents->GetDlRegion();
494 GetLayerForFlutterContentsRegion(region).AddFlutterContents(contents,
495 region);
496 }
497
498 /// Returns the deepest layer to which the platform view can be added. That
499 /// would be (whichever comes first):
500 /// - First layer from back that has platform view that intersects with this
501 /// view
502 /// - Very last layer from back that has surface that doesn't intersect with
503 /// this. That is because layer content renders on top of the platform view.
504 Layer& GetLayerForPlatformView(PlatformView view) {
505 for (auto iter = layers_.rbegin(); iter != layers_.rend(); ++iter) {
506 // This layer has surface that intersects with this view. That means we
507 // went one too far and need the layer before this.
508 if (iter->IntersectsFlutterContents(view.clipped_frame)) {
509 if (iter == layers_.rbegin()) {
510 layers_.emplace_back();
511 return layers_.back();
512 } else {
513 --iter;
514 return *iter;
515 }
516 }
517 if (iter->IntersectsPlatformView(view.clipped_frame)) {
518 return *iter;
519 }
520 }
521 return layers_.front();
522 }
523
524 /// Finds layer to which the Flutter content can be added. That would
525 /// be first layer from back that has any intersection with this region.
526 Layer& GetLayerForFlutterContentsRegion(const DlRegion& region) {
527 for (auto iter = layers_.rbegin(); iter != layers_.rend(); ++iter) {
528 if (iter->IntersectsPlatformView(region) ||
529 iter->IntersectsFlutterContents(region)) {
530 return *iter;
531 }
532 }
533 return layers_.front();
534 }
535
536 std::vector<Layer> layers_;
537 DlISize frame_size_;
538};
539
540}; // namespace
541
542void EmbedderExternalViewEmbedder::SubmitFlutterView(
543 int64_t flutter_view_id,
544 GrDirectContext* context,
545 const std::shared_ptr<impeller::AiksContext>& aiks_context,
546 std::unique_ptr<SurfaceFrame> frame) {
547 // The unordered_map render_target_cache creates a new entry if the view ID is
548 // unrecognized.
549 EmbedderRenderTargetCache& render_target_cache =
550 render_target_caches_[flutter_view_id];
551 DlRect _rect = DlRect::MakeSize(pending_frame_size_)
552 .TransformAndClipBounds(pending_surface_transformation_);
553
554 LayerBuilder builder(DlIRect::RoundOut(_rect).GetSize());
555
556 for (auto view_id : composition_order_) {
557 auto& view = pending_views_[view_id];
558 builder.AddExternalView(view.get());
559 }
560
561 builder.PrepareBackingStore([&](const DlISize& frame_size) {
562 if (!avoid_backing_store_cache_) {
563 std::unique_ptr<EmbedderRenderTarget> target =
564 render_target_cache.GetRenderTarget(
565 EmbedderExternalView::RenderTargetDescriptor(frame_size));
566 if (target != nullptr) {
567 return target;
568 }
569 }
570 auto config = MakeBackingStoreConfig(flutter_view_id, frame_size);
571 return create_render_target_callback_(context, aiks_context, config);
572 });
573
574 // This is where unused render targets will be collected. Control may flow
575 // to the embedder. Here, the embedder has the opportunity to trample on the
576 // OpenGL context.
577 //
578 // For optimum performance, we should tell the render target cache to clear
579 // its unused entries before allocating new ones. This collection step
580 // before allocating new render targets ameliorates peak memory usage within
581 // the frame. But, this causes an issue in a known internal embedder. To
582 // work around this issue while that embedder migrates, collection of render
583 // targets is deferred after the presentation.
584 //
585 // @warning: Embedder may trample on our OpenGL context here.
586 auto deferred_cleanup_render_targets =
587 render_target_cache.ClearAllRenderTargetsInCache();
588
589#if !SLIMPELLER
590 // The OpenGL context could have been trampled by the embedder at this point
591 // as it attempted to collect old render targets and create new ones. Tell
592 // Skia to not rely on existing bindings.
593 if (context) {
594 context->resetContext(kAll_GrBackendState);
595 }
596#endif // !SLIMPELLER
597
598 builder.Render();
599
600#if !SLIMPELLER
601 // We are going to be transferring control back over to the embedder there
602 // the context may be trampled upon again. Flush all operations to the
603 // underlying rendering API.
604 //
605 // @warning: Embedder may trample on our OpenGL context here.
606 if (context) {
607 context->flushAndSubmit();
608 }
609#endif // !SLIMPELLER
610
611 {
612 auto presentation_time_optional = frame->submit_info().presentation_time;
613 uint64_t presentation_time =
614 presentation_time_optional.has_value()
615 ? presentation_time_optional->ToEpochDelta().ToNanoseconds()
616 : 0;
617
618 // Submit the scribbled layer to the embedder for presentation.
619 //
620 // @warning: Embedder may trample on our OpenGL context here.
621 EmbedderLayers presented_layers(
622 pending_frame_size_, pending_device_pixel_ratio_,
623 pending_surface_transformation_, presentation_time);
624
625 builder.PushLayers(presented_layers);
626
627 presented_layers.InvokePresentCallback(flutter_view_id, present_callback_);
628 }
629
630 // See why this is necessary in the comment where this collection in
631 // realized.
632 //
633 // @warning: Embedder may trample on our OpenGL context here.
634 deferred_cleanup_render_targets.clear();
635
636 auto render_targets = builder.ClearAndCollectRenderTargets();
637 for (auto& render_target : render_targets) {
638 if (!avoid_backing_store_cache_) {
639 render_target_cache.CacheRenderTarget(std::move(render_target));
640 }
641 }
642
643 frame->Submit();
644}
645
646} // namespace flutter
std::unique_ptr< flutter::PlatformViewIOS > platform_view
static DlRegion MakeUnion(const DlRegion &a, const DlRegion &b)
Definition dl_region.cc:405
EmbedderExternalViewEmbedder(bool avoid_backing_store_cache, const CreateRenderTargetCallback &create_render_target_callback, const PresentCallback &present_callback)
Creates an external view embedder used by the generic embedder API.
~EmbedderExternalViewEmbedder() override
Collects the external view embedder.
std::function< bool(FlutterViewId view_id, const std::vector< const FlutterLayer * > &layers)> PresentCallback
std::function< std::unique_ptr< EmbedderRenderTarget >(GrDirectContext *context, const std::shared_ptr< impeller::AiksContext > &aiks_context, const FlutterBackingStoreConfig &config)> CreateRenderTargetCallback
void SetSurfaceTransformationCallback(SurfaceTransformationCallback surface_transformation_callback)
Sets the surface transformation callback used by the external view embedder to ask the platform for t...
std::function< DlMatrix(void)> SurfaceTransformationCallback
Wraps a closure that is invoked in the destructor unless released by the caller.
Definition closure.h:32
EmbedderExternalView::ViewIdentifier view_identifier
const EmbeddedViewParams * params
FlView * view
const FlutterLayer ** layers
uint32_t * target
G_BEGIN_DECLS FlutterViewId view_id
#define FML_DLOG(severity)
Definition logging.h:121
#define FML_LOG(severity)
Definition logging.h:101
#define FML_DCHECK(condition)
Definition logging.h:122
impeller::Matrix DlMatrix
impeller::Rect DlRect
impeller::ISize32 DlISize
static FlutterBackingStoreConfig MakeBackingStoreConfig(int64_t view_id, const DlISize &backing_store_size)
const DlIRect & ToDlIRect(const SkIRect &rect)
static const auto kRootViewIdentifier
const SkRect & ToSkRect(const DlRect &rect)
bool RenderToTarget(ContentContext &context, RenderTarget render_target, const sk_sp< flutter::DisplayList > &display_list, Rect cull_rect, bool reset_host_buffer, bool is_onscreen)
Render the provided display list to the render target.
flutter::DlCanvas DlCanvas
size_t struct_size
The size of this struct. Must be sizeof(FlutterBackingStoreConfig).
Definition embedder.h:2129
FlutterSize size
The size of the render target the engine expects to render into.
Definition embedder.h:2131
double height
Definition embedder.h:636
double width
Definition embedder.h:635
A 4x4 matrix using column-major storage.
Definition matrix.h:37
RoundOut(const TRect< U > &r)
Definition rect.h:713
constexpr TRect TransformAndClipBounds(const Matrix &transform) const
Creates a new bounding box that contains this transformed rectangle, clipped against the near clippin...
Definition rect.h:472
static constexpr TRect MakeSize(const TSize< U > &size)
Definition rect.h:150
Type height
Definition size.h:29
Type width
Definition size.h:28