12#include "flutter/fml/closure.h"
13#include "flutter/fml/logging.h"
14#include "flutter/fml/trace_event.h"
34std::tuple<std::optional<Color>,
BlendMode> ElementAsBackgroundColor(
37 if (
const Entity* entity = std::get_if<Entity>(&element)) {
38 std::optional<Color> entity_color = entity->AsBackgroundColor(target_size);
39 if (entity_color.has_value()) {
40 return {entity_color.value(), entity->GetBlendMode()};
55 delegate_ = std::move(delegate);
60 bounds_limit_ = bounds_limit;
61 bounds_promise_ = bounds_limit.has_value() ? bounds_promise
70 switch (bounds_promise_) {
77 return bounds_limit_.has_value();
89 switch (bounds_promise_) {
107 advanced_blend_reads_from_pass_texture_ =
true;
109 elements_.emplace_back(std::move(entity));
113 elements_.emplace_back(std::move(entity));
114 active_clips_.emplace_back(elements_.size() - 1);
118 if (num_clips > active_clips_.size()) {
120 <<
"Attempted to pop more clips than are currently active. Active: "
121 << active_clips_.size() <<
", Popped: " << num_clips
122 <<
", Depth: " << depth;
125 size_t max =
std::min(num_clips, active_clips_.size());
126 for (
size_t i = 0;
i <
max;
i++) {
127 FML_DCHECK(active_clips_.back() < elements_.size());
128 Entity* element = std::get_if<Entity>(&elements_[active_clips_.back()]);
131 active_clips_.pop_back();
136 PopClips(active_clips_.size(), depth);
140 elements_ = std::move(elements);
144 size_t max_subpass_depth = 0u;
145 for (
const auto& element : elements_) {
146 if (
auto subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
148 std::max(max_subpass_depth, subpass->get()->GetSubpassesDepth());
151 return max_subpass_depth + 1u;
155 std::optional<Rect> coverage_limit)
const {
156 std::optional<Rect> accumulated_coverage;
157 for (
const auto& element : elements_) {
158 std::optional<Rect> element_coverage;
160 if (
auto entity = std::get_if<Entity>(&element)) {
161 element_coverage = entity->GetCoverage();
165 if (element_coverage.has_value() && coverage_limit.has_value()) {
166 const auto* filter = entity->GetContents()->AsFilter();
167 if (!filter || filter->IsTranslationOnly()) {
169 element_coverage->Intersection(coverage_limit.value());
172 }
else if (
auto subpass_ptr =
173 std::get_if<std::unique_ptr<EntityPass>>(&element)) {
174 auto& subpass = *subpass_ptr->get();
176 std::optional<Rect> unfiltered_coverage =
182 if (accumulated_coverage.has_value() && subpass.backdrop_filter_proc_) {
183 std::shared_ptr<FilterContents> backdrop_filter =
184 subpass.backdrop_filter_proc_(
188 if (backdrop_filter) {
189 auto backdrop_coverage = backdrop_filter->GetCoverage({});
190 unfiltered_coverage =
191 Rect::Union(unfiltered_coverage, backdrop_coverage);
193 VALIDATION_LOG <<
"The EntityPass backdrop filter proc didn't return "
198 if (!unfiltered_coverage.has_value()) {
210 std::shared_ptr<FilterContents> image_filter =
211 subpass.delegate_->WithImageFilter(*unfiltered_coverage,
216 element_coverage = image_filter->GetCoverage(subpass_entity);
218 element_coverage = unfiltered_coverage;
226 accumulated_coverage =
Rect::Union(accumulated_coverage, element_coverage);
228 return accumulated_coverage;
233 std::optional<Rect> coverage_limit)
const {
235 return subpass.bounds_limit_->TransformBounds(subpass.transform_);
238 std::shared_ptr<FilterContents> image_filter =
239 subpass.delegate_->WithImageFilter(
Rect(), subpass.transform_);
244 if (image_filter && coverage_limit.has_value()) {
245 coverage_limit = image_filter->GetSourceCoverage(subpass.transform_,
246 coverage_limit.value());
251 if (!entities_coverage.has_value()) {
255 if (!subpass.bounds_limit_.has_value()) {
256 return entities_coverage;
258 auto user_bounds_coverage =
259 subpass.bounds_limit_->TransformBounds(subpass.transform_);
260 return entities_coverage->Intersection(user_bounds_coverage);
272 pass->superpass_ =
this;
274 if (pass->backdrop_filter_proc_) {
275 backdrop_filter_reads_from_pass_texture_ =
true;
278 advanced_blend_reads_from_pass_texture_ =
true;
281 auto subpass_pointer = pass.get();
282 elements_.emplace_back(std::move(pass));
283 return subpass_pointer;
296 const Color& clear_color) {
297 const std::shared_ptr<Context>& context =
renderer.GetContext();
311 if (context->GetCapabilities()->SupportsOffscreenMSAA()) {
323 .clear_color = clear_color},
336 .clear_color = clear_color,
343 target,
renderer.GetDeviceCapabilities().SupportsReadFromResolve(),
344 renderer.GetDeviceCapabilities().SupportsImplicitResolvingMSAA());
347bool EntityPass::DoesBackdropGetRead(ContentContext&
renderer)
const {
348 return renderer.GetDeviceCapabilities().SupportsFramebufferFetch()
349 ? backdrop_filter_reads_from_pass_texture_
350 : backdrop_filter_reads_from_pass_texture_ ||
351 advanced_blend_reads_from_pass_texture_;
356 renderer.GetRenderTargetCache()->Start();
358 renderer.GetLazyGlyphAtlas()->ResetTextFrames();
359 renderer.GetRenderTargetCache()->End();
362 auto root_render_target = render_target;
364 if (root_render_target.GetColorAttachments().find(0u) ==
365 root_render_target.GetColorAttachments().end()) {
366 VALIDATION_LOG <<
"The root RenderTarget must have a color attachment.";
369 if (root_render_target.GetDepthAttachment().has_value() !=
370 root_render_target.GetStencilAttachment().has_value()) {
371 VALIDATION_LOG <<
"The root RenderTarget should have a stencil attachment "
372 "iff it has a depth attachment.";
376 const auto& lazy_glyph_atlas =
renderer.GetLazyGlyphAtlas();
379 contents->PopulateGlyphAtlas(lazy_glyph_atlas, entity.DeriveTextScale());
390 if (DoesBackdropGetRead(
renderer)) {
392 renderer, root_render_target.GetRenderTargetSize(),
410 auto command_buffer =
renderer.GetContext()->CreateCommandBuffer();
411 command_buffer->SetLabel(
"EntityPass Root Command Buffer");
418 ->SupportsTextureToTextureBlits()) {
419 auto blit_pass = command_buffer->CreateBlitPass();
422 root_render_target.GetRenderTargetTexture());
423 if (!blit_pass->EncodeCommands(
424 renderer.GetContext()->GetResourceAllocator())) {
430 ->Submit({command_buffer})
435 auto render_pass = command_buffer->CreateRenderPass(root_render_target);
436 render_pass->SetLabel(
"EntityPass Root Render Pass");
442 contents->SetTexture(
444 contents->SetSourceRect(size_rect);
445 contents->SetLabel(
"Root pass blit");
457 if (!render_pass->EncodeCommands()) {
463 ->Submit({command_buffer})
477 auto color0 = root_render_target.GetColorAttachments().find(0u)->second;
479 auto stencil_attachment = root_render_target.GetStencilAttachment();
480 auto depth_attachment = root_render_target.GetDepthAttachment();
481 if (!stencil_attachment.has_value() || !depth_attachment.has_value()) {
484 root_render_target.SetupDepthStencilAttachments(
486 color0.texture->GetSize(),
487 renderer.GetContext()->GetCapabilities()->SupportsOffscreenMSAA(),
494 root_render_target.SetColorAttachment(color0, 0);
498 renderer.GetDeviceCapabilities().SupportsReadFromResolve(),
499 renderer.GetDeviceCapabilities().SupportsImplicitResolvingMSAA());
503 root_render_target.GetRenderTargetSize(),
511EntityPass::EntityResult EntityPass::GetEntityForElement(
515 ISize root_pass_size,
516 Point global_pass_position,
519 size_t clip_height_floor)
const {
523 if (
const auto& entity = std::get_if<Entity>(&element)) {
526 if (!global_pass_position.
IsZero()) {
534 return EntityPass::EntityResult::Success(std::move(element_entity));
540 if (
const auto& subpass_ptr =
541 std::get_if<std::unique_ptr<EntityPass>>(&element)) {
542 auto subpass = subpass_ptr->get();
543 if (subpass->delegate_->CanElide()) {
544 return EntityPass::EntityResult::Skip();
547 if (!subpass->backdrop_filter_proc_ &&
548 subpass->delegate_->CanCollapseIntoParentPass(subpass)) {
550 if (!subpass->OnRender(
554 global_pass_position,
566 return EntityPass::EntityResult::Skip();
569 std::shared_ptr<Contents> subpass_backdrop_filter_contents =
nullptr;
570 if (subpass->backdrop_filter_proc_) {
573 const auto& proc = subpass->backdrop_filter_proc_;
575 subpass_backdrop_filter_contents = proc(
579 subpass->transform_.HasTranslation()
600 return EntityPass::EntityResult::Skip();
603 if (!clip_coverage_back.has_value()) {
604 return EntityPass::EntityResult::Skip();
614 if (!coverage_limit.has_value()) {
615 return EntityPass::EntityResult::Skip();
620 if (!coverage_limit.has_value()) {
621 return EntityPass::EntityResult::Skip();
624 auto subpass_coverage =
625 (subpass->flood_clip_ || subpass_backdrop_filter_contents)
628 if (!subpass_coverage.has_value()) {
629 return EntityPass::EntityResult::Skip();
632 auto subpass_size =
ISize(subpass_coverage->GetSize());
633 if (subpass_size.IsEmpty()) {
634 return EntityPass::EntityResult::Skip();
640 subpass->GetRequiredMipCount(),
641 subpass->GetClearColorOrDefault(subpass_size));
643 if (!subpass_target.IsValid()) {
653 clip_coverage_stack.
PushSubpass(subpass_coverage, subpass->clip_height_);
657 if (!subpass->OnRender(
661 subpass_coverage->GetOrigin(),
662 subpass_coverage->GetOrigin() -
663 global_pass_position,
666 subpass->clip_height_,
667 subpass_backdrop_filter_contents
677 auto subpass_texture =
678 subpass_target.GetRenderTarget().GetRenderTargetTexture();
680 auto offscreen_texture_contents =
681 subpass->delegate_->CreateContentsForSubpassTarget(
684 subpass->transform_);
686 if (!offscreen_texture_contents) {
710 Point subpass_texture_position =
711 (subpass_coverage->GetOrigin() - global_pass_position).Round();
713 Entity element_entity;
715 element_entity.SetContents(std::move(offscreen_texture_contents));
716 element_entity.SetBlendMode(subpass->blend_mode_);
717 element_entity.SetTransform(
720 return EntityPass::EntityResult::Success(std::move(element_entity));
727 Point global_pass_position) {
731 if (clip_coverage.has_value()) {
732 clip_coverage = clip_coverage->
Shift(-global_pass_position);
741bool EntityPass::RenderElement(Entity& element_entity,
742 size_t clip_height_floor,
743 InlinePassContext& pass_context,
746 EntityPassClipStack& clip_coverage_stack,
747 Point global_pass_position)
const {
748 auto result = pass_context.GetRenderPass(pass_depth);
761 if (
result.backdrop_texture) {
764 msaa_backdrop_contents->SetStencilEnabled(
false);
765 msaa_backdrop_contents->SetLabel(
"MSAA backdrop");
766 msaa_backdrop_contents->SetSourceRect(size_rect);
767 msaa_backdrop_contents->SetTexture(
result.backdrop_texture);
769 Entity msaa_backdrop_entity;
770 msaa_backdrop_entity.SetContents(std::move(msaa_backdrop_contents));
774 VALIDATION_LOG <<
"Failed to render MSAA backdrop filter entity.";
779 if (
result.just_created) {
782 auto& replay_entities = clip_coverage_stack.GetReplayEntities();
783 for (
const auto& replay : replay_entities) {
785 global_pass_position);
792 auto current_clip_coverage = clip_coverage_stack.CurrentClipCoverage();
793 if (current_clip_coverage.has_value()) {
796 current_clip_coverage = current_clip_coverage->Shift(-global_pass_position);
799 if (!element_entity.ShouldRender(current_clip_coverage)) {
803 auto clip_coverage = element_entity.GetClipCoverage(current_clip_coverage);
804 if (clip_coverage.coverage.has_value()) {
805 clip_coverage.coverage =
806 clip_coverage.coverage->Shift(global_pass_position);
813 auto element_coverage_hint = element_entity.GetContents()->GetCoverageHint();
814 element_entity.GetContents()->SetCoverageHint(
817 EntityPassClipStack::ClipStateResult clip_state_result =
818 clip_coverage_stack.ApplyClipState(clip_coverage, element_entity,
820 global_pass_position);
822 if (clip_state_result.clip_did_change) {
825 global_pass_position);
828 if (!clip_state_result.should_render) {
839bool EntityPass::OnRender(
841 ISize root_pass_size,
842 EntityPassTarget& pass_target,
843 Point global_pass_position,
844 Point local_pass_position,
846 EntityPassClipStack& clip_coverage_stack,
847 size_t clip_height_floor,
848 std::shared_ptr<Contents> backdrop_filter_contents,
849 const std::optional<InlinePassContext::RenderPassResult>&
850 collapsed_parent_pass)
const {
853 if (!active_clips_.empty()) {
855 "EntityPass (Depth=%d) contains one or more clips with an unresolved "
861 collapsed_parent_pass);
862 if (!pass_context.IsValid()) {
866 auto clear_color_size = pass_target.GetRenderTarget().GetRenderTargetSize();
868 if (!collapsed_parent_pass) {
872 pass_context.GetRenderPass(pass_depth);
875 if (backdrop_filter_proc_) {
876 if (!backdrop_filter_contents) {
878 <<
"EntityPass contains a backdrop filter, but no backdrop filter "
879 "contents was supplied by the parent pass at render time. This is "
880 "a bug in EntityPass. Parent passes are responsible for setting "
881 "up backdrop filters for their children.";
885 Entity backdrop_entity;
886 backdrop_entity.SetContents(std::move(backdrop_filter_contents));
887 backdrop_entity.SetTransform(
891 RenderElement(backdrop_entity, clip_height_floor, pass_context, pass_depth,
892 renderer, clip_coverage_stack, global_pass_position);
895 bool is_collapsing_clear_colors = !collapsed_parent_pass &&
898 !backdrop_filter_proc_;
899 for (
const auto& element : elements_) {
901 if (is_collapsing_clear_colors) {
902 auto [entity_color, _] =
903 ElementAsBackgroundColor(element, clear_color_size);
904 if (entity_color.has_value()) {
907 is_collapsing_clear_colors =
false;
911 GetEntityForElement(element,
915 global_pass_position,
923 case EntityResult::kFailure:
927 case EntityResult::kSkip:
936 if (
renderer.GetDeviceCapabilities().SupportsFramebufferFetch()) {
937 auto src_contents =
result.entity.GetContents();
938 auto contents = std::make_shared<FramebufferBlendContents>();
939 contents->SetChildContents(src_contents);
940 contents->SetBlendMode(
result.entity.GetBlendMode());
941 result.entity.SetContents(std::move(contents));
954 if (!pass_context.EndPass()) {
956 <<
"Failed to end the current render pass in order to read from "
957 "the backdrop texture and apply an advanced blend.";
963 auto texture = pass_context.GetTexture();
965 VALIDATION_LOG <<
"Failed to fetch the color texture in order to "
966 "apply an advanced blend.";
975 contents->SetCoverageHint(
result.entity.GetCoverage());
976 result.entity.SetContents(std::move(contents));
984 if (!RenderElement(
result.entity, clip_height_floor, pass_context,
985 pass_depth,
renderer, clip_coverage_stack,
986 global_pass_position)) {
1001 for (
auto& element : elements_) {
1002 if (!iterator(element)) {
1005 if (
auto subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
1006 subpass->get()->IterateAllElements(iterator);
1019 for (
auto& element : elements_) {
1020 if (!iterator(element)) {
1023 if (
auto subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
1024 const EntityPass* entity_pass = subpass->get();
1036 for (
auto& element : elements_) {
1037 if (
auto entity = std::get_if<Entity>(&element)) {
1038 if (!iterator(*entity)) {
1043 if (
auto subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
1044 subpass->get()->IterateAllEntities(iterator);
1057 for (
const auto& element : elements_) {
1058 if (
auto entity = std::get_if<Entity>(&element)) {
1059 if (!iterator(*entity)) {
1064 if (
auto subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
1065 const EntityPass* entity_pass = subpass->get();
1079 for (
auto& element : elements_) {
1080 if (
auto entity = std::get_if<Entity>(&element)) {
1081 if (!iterator(*entity)) {
1092 return elements_.size();
1100 clip_height_ = clip_height;
1104 return clip_height_;
1108 clip_depth_ = clip_depth;
1116 blend_mode_ = blend_mode;
1125 if (backdrop_filter_proc_) {
1126 return std::nullopt;
1129 std::optional<Color>
result = std::nullopt;
1130 for (
const Element& element : elements_) {
1131 auto [entity_color, blend_mode] =
1132 ElementAsBackgroundColor(element, target_size);
1133 if (!entity_color.has_value()) {
1137 .Blend(entity_color.value(), blend_mode);
1139 if (
result.has_value()) {
1140 return result->Premultiply();
1147 VALIDATION_LOG <<
"Backdrop filters cannot be set on EntityPasses that "
1148 "have already been appended to another pass.";
1151 backdrop_filter_proc_ = std::move(proc);
Wraps a closure that is invoked in the destructor unless released by the caller.
static std::shared_ptr< ColorFilterContents > MakeBlend(BlendMode blend_mode, FilterInput::Vector inputs, std::optional< Color > foreground_color=std::nullopt)
the [inputs] are expected to be in the order of dst, src.
A class that tracks all clips that have been recorded in the current entity pass stencil.
std::optional< Rect > CurrentClipCoverage() const
void PushSubpass(std::optional< Rect > subpass_coverage, size_t clip_height)
const RenderTarget & GetRenderTarget() const
bool IterateUntilSubpass(const std::function< bool(Entity &)> &iterator)
Iterate entities in this pass up until the first subpass is found. This is useful for limiting look-a...
void SetElements(std::vector< Element > elements)
void IterateAllElements(const std::function< bool(Element &)> &iterator)
Iterate all elements (entities and subpasses) in this pass, recursively including elements of child p...
std::variant< Entity, std::unique_ptr< EntityPass > > Element
size_t GetElementCount() const
Return the number of elements on this pass.
bool GetBoundsLimitMightClipContent() const
Indicates if the bounds limit set using |SetBoundsLimit()| might clip the contents of the pass.
void SetBoundsLimit(std::optional< Rect > bounds_limit, ContentBoundsPromise bounds_promise=ContentBoundsPromise::kUnknown)
Set the bounds limit, which is provided by the user when creating a SaveLayer. This is a hint that al...
Color GetClearColorOrDefault(ISize size=ISize::Infinite()) const
Return the premultiplied clear color of the pass entities.
void SetClipDepth(size_t clip_depth)
void AddEntity(Entity entity)
Add an entity to the current entity pass.
size_t GetClipHeight() const
void SetBackdropFilter(BackdropFilterProc proc)
bool GetBoundsLimitIsSnug() const
Indicates if the bounds limit set using |SetBoundsLimit()| is a reasonably tight estimate of the boun...
std::optional< Rect > GetBoundsLimit() const
Get the bounds limit, which is provided by the user when creating a SaveLayer.
EntityPass * GetSuperpass() const
size_t GetSubpassesDepth() const
void PopClips(size_t num_clips, uint64_t depth)
int32_t GetRequiredMipCount() const
void SetClipHeight(size_t clip_height)
std::optional< Color > GetClearColor(ISize size=ISize::Infinite()) const
Return the premultiplied clear color of the pass entities, if any.
void SetBlendMode(BlendMode blend_mode)
void SetTransform(Matrix transform)
void IterateAllEntities(const std::function< bool(Entity &)> &iterator)
Iterate all entities in this pass, recursively including entities of child passes....
std::optional< Rect > GetSubpassCoverage(const EntityPass &subpass, std::optional< Rect > coverage_limit) const
Computes the coverage of a given subpass. This is used to determine the texture size of a given subpa...
void PopAllClips(uint64_t depth)
EntityPass * AddSubpass(std::unique_ptr< EntityPass > pass)
Appends a given pass as a subpass.
void SetDelegate(std::shared_ptr< EntityPassDelegate > delgate)
bool Render(ContentContext &renderer, const RenderTarget &render_target) const
std::optional< Rect > GetElementsCoverage(std::optional< Rect > coverage_limit) const
void PushClip(Entity entity)
uint32_t GetClipDepth() const
std::function< std::shared_ptr< FilterContents >(FilterInput::Ref, const Matrix &effect_transform, Entity::RenderingMode rendering_mode)> BackdropFilterProc
void SetTransform(const Matrix &transform)
Set the global transform matrix for this Entity.
@ kSubpassPrependSnapshotTransform
@ kSubpassAppendSnapshotTransform
const std::shared_ptr< Contents > & GetContents() const
void SetClipDepth(uint32_t clip_depth)
BlendMode GetBlendMode() const
void SetContents(std::shared_ptr< Contents > contents)
void SetBlendMode(BlendMode blend_mode)
bool Render(const ContentContext &renderer, RenderPass &parent_pass) const
const Matrix & GetTransform() const
Get the global transform matrix for this Entity.
static constexpr BlendMode kLastPipelineBlendMode
static bool IsBlendModeDestructive(BlendMode blend_mode)
Returns true if the blend mode is "destructive", meaning that even fully transparent source colors wo...
EntityPassTarget & GetPassTarget() const
std::shared_ptr< Texture > GetTexture()
RenderPassResult GetRenderPass(uint32_t pass_depth)
Render passes encode render commands directed as one specific render target into an underlying comman...
virtual void SetScissor(IRect scissor)
ISize GetRenderTargetSize() const
std::shared_ptr< Texture > GetRenderTargetTexture() const
ISize GetRenderTargetSize() const
static std::shared_ptr< TextureContents > MakeRect(Rect destination)
A common case factory that marks the texture contents as having a destination rectangle....
#define FML_UNREACHABLE()
#define FML_DCHECK(condition)
Dart_NativeFunction function
static float max(float r, float g, float b)
static float min(float r, float g, float b)
static TTSTestCase Failure(const TTSTestCase &original)
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
static void SetClipScissor(std::optional< Rect > clip_coverage, RenderPass &pass, Point global_pass_position)
static std::unique_ptr< EntityPassTarget > CreateRenderTarget(ContentContext &renderer, ISize size, int mip_count, const Color &clear_color)
std::string SPrintF(const char *format,...)
@ kUnknown
The caller makes no claims related to the size of the bounds.
@ kMayClipContents
The caller claims the bounds are a subset of an estimate of the reasonably tight bounds but likely cl...
@ kContainsContents
The caller claims the bounds are a reasonably tight estimate of the coverage of the contents and shou...
static const constexpr RenderTarget::AttachmentConfig kDefaultStencilConfig
static SkColor4f transform(SkColor4f c, SkColorSpace *src, SkColorSpace *dst)
static constexpr Color BlackTransparent()
A 4x4 matrix using column-major storage.
static constexpr Matrix MakeTranslation(const Vector3 &t)
constexpr bool IsZero() const
constexpr std::optional< TRect > Intersection(const TRect &o) const
constexpr TRect Union(const TRect &o) const
RoundOut(const TRect< U > &r)
static constexpr TRect MakeOriginSize(const TPoint< Type > &origin, const TSize< Type > &size)
static constexpr TRect MakeSize(const TSize< U > &size)
constexpr TRect< T > Shift(T dx, T dy) const
Returns a new rectangle translated by the given offset.
#define TRACE_EVENT0(category_group, name)