mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-18 00:58:30 +09:00
309 lines
26 KiB
Modula-2
309 lines
26 KiB
Modula-2
// MobileGL - MobileGL/MG_Pipe/FieldOwnership.def
|
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
|
// Licensed under the GNU Lesser General Public License v3.0:
|
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// End of Source File Header
|
|
|
|
// The hand-maintained half of TABLE 2 (CONTRACT-P5.md section 3, R-7): where every
|
|
// PipeInputs field's value comes from once the backend is a server.
|
|
//
|
|
// scripts/gen_pipe_field_ownership.py joins this file against the DERIVED half and writes
|
|
// generated/PipeFieldOwnership.inc. CI runs `--check` and `--self-test` beside gen_pipe.py's.
|
|
//
|
|
// THE FOUR CLASSES (CONTRACT-P5.md section 3):
|
|
//
|
|
// RECORD_SUPPLIED a pushed record supplies the WHOLE field, so the server never needs
|
|
// the client for it. THIS CLASS IS DERIVED, NOT LISTED: it is
|
|
// kMGPipeFieldEmittedBy != kNone (Coverage.def's MGP_COVERAGE_EMITTED_LIST)
|
|
// minus the fields EmittedCallSuppliesTheWholeField refuses
|
|
// (MG_Impl/Pipe/PipeFill.cpp). 32 fields today. A row below that names a
|
|
// field the derivation already placed here is a CONTRADICTION and stops
|
|
// the generator - which is the only way this file can stay true as the
|
|
// emitted list grows.
|
|
//
|
|
// APPLIER_DERIVED the applier writes it out of records it already applies, but no row of
|
|
// the emitted list claims it, so the derivation above cannot see it.
|
|
//
|
|
// BARRIER_PULLED P5's DEBT. The server answers by reading a value the client's residual
|
|
// fill (PipeFill.cpp step 4) left in the single shared gPipeInputs while
|
|
// the verb barrier holds both threads apart (R-1). Correct only because
|
|
// of that barrier, which is why the barrier is load-bearing rather than
|
|
// cautious. Every row NAMES THE PHASE THAT RETIRES IT; a row with no
|
|
// phase stops the generator. Each such read increments
|
|
// PipeStats::CallClass::ResidualPulls (`rsp`) and, under
|
|
// MOBILEGL_IPC_STRICT_ERRORS=1, is Fatal.
|
|
//
|
|
// FATAL no carrier, and the reduced path never reads it, so a read is a real
|
|
// defect: Fatal{UnmigratedPipeInput, "<Field>@<verb>"}.
|
|
//
|
|
// A field in NONE of the four is a generator error and therefore a build failure (R-7.1).
|
|
// A field in TWO is the same. That is the whole mechanism: a hand-maintained table would be
|
|
// wrong within a week, and this one cannot be silently incomplete.
|
|
//
|
|
// clang-format off
|
|
|
|
// X(Field, Class, RetiringPhase, Why)
|
|
//
|
|
// RetiringPhase is `-` for every class but BARRIER_PULLED, where it is the ROADMAP phase
|
|
// whose row stops the pull. It is a string, not an enum, because two of them name two
|
|
// phases for the two backends and flattening that would lose the half that matters.
|
|
#define MGP_FIELD_OWNERSHIP_LIST(X) \
|
|
/* ---- BARRIER_PULLED: the 20 non-sticky rows the reduced path actually reads ---- */ \
|
|
/* scout-unmigrated-census section 3 intersects each verb class's may-read mask with "no */ \
|
|
/* record supplies it" and unions kClear (7 of 18), kDraw (19 of 47) and kReadback (12 of */ \
|
|
/* 17). That union is 21 fields; GetPixelStoreParameters is the 21st and it is */ \
|
|
/* APPLIER_DERIVED below, for the reason written there. OpenRA adds no field to this set - */ \
|
|
/* it widens the SITE set, not the field set. */ \
|
|
X(GetActiveTextureUnit, BARRIER_PULLED, "P3b/P4b", \
|
|
"the server answers from its own state; Coverage.def:215-219 says no call carries it") \
|
|
/* DirectGLES.cpp:4486, PrepareForDraw, UNCONDITIONAL ON EVERY DRAW. No #if, no arm guard, */ \
|
|
/* no record fallback, and PipeFill.cpp:1902-1905 says outright that what retires the pull */ \
|
|
/* is P8, not a better applier: the storage is a SharedPtr<VertexArrayObject> and */ \
|
|
/* bind_vertex_elements carries an eight-byte {slot, gen}. */ \
|
|
X(GetBoundVertexArray, BARRIER_PULLED, "P8", \
|
|
"frontend heap reference; the record carries a handle, the mirror is a pointer") \
|
|
/* 18 Espryt sites + 11 Magma. Coverage.def:37-70 splits the 15 BufferTargets across three */ \
|
|
/* calls and leaves SEVEN with no carrier at all (CopyRead, CopyWrite, PixelPack, */ \
|
|
/* PixelUnpack, Texture, DispatchIndirect, Query), which is why the FIELD is pulled even */ \
|
|
/* though eight targets are covered. */ \
|
|
X(GetBufferBindingSlot, BARRIER_PULLED, "P8 (indirect), P9 (readback), P13 (transfer)", \
|
|
"7 of 15 BufferTargets have no call; the field is one array over all 15") \
|
|
X(GetBufferBindingPoint, BARRIER_PULLED, "P3b/P4b, P7", \
|
|
"frontend BindingSlotRange1D pointer") \
|
|
X(GetTouchedBufferBindingPointCount, BARRIER_PULLED, "P3b/P4b", \
|
|
"no call carries the touched-count high-water mark") \
|
|
/* The applier CANNOT reproduce GLContext's cross-view conversion: the frontend writes */ \
|
|
/* (Int32)value into intValue while MGPipeApplySetVertexAttribDefaults memcpys one Data[4] */ \
|
|
/* into all three views (PipeFill.cpp:1884-1896). So the record exists, the applier writes */ \
|
|
/* the field, and the value is still wrong - which is BARRIER_PULLED, not APPLIER_DERIVED. */ \
|
|
X(GetCurrentVertexAttribute, BARRIER_PULLED, "P3b/P4b", \
|
|
"the applier writes it but cannot reproduce the cross-view conversion") \
|
|
/* 8 Espryt sites through GetFramebufferBindingSlotChecked + 13 Magma. SyncCurrentFBO */ \
|
|
/* (:2995) is SELF-DECLARED monolith glue (DirectGLES.cpp:2961-2965) while BindCurrentFBO */ \
|
|
/* (:4303-4353) is already split-clean - the bind is migrated, the sync is not. */ \
|
|
X(GetFramebufferBindingSlot, BARRIER_PULLED, "P3b/P4b (Espryt), P7 (Magma)", \
|
|
"frontend BindingSlot<FramebufferObject> pointer") \
|
|
X(GetImageTextureBinding, BARRIER_PULLED, "P3b/P4b, P7", \
|
|
"frontend ImageTextureBinding base pointer") \
|
|
/* set_sampler_views' Count IS this value plus one, but the set is suppressed on an */ \
|
|
/* unchanged content hash while the high-water mark still moves (PipeFill.cpp:1917-1923). */ \
|
|
X(GetMaxTouchedTextureUnit, BARRIER_PULLED, "P3b/P4b", \
|
|
"hash-suppressed set, high-water mark still moves") \
|
|
/* The three texture SHUTTERS. Coverage.def:220-224 is explicit that no call carries them */ \
|
|
/* and none should: what replaces them server-side is the applier's own Serial, a different */ \
|
|
/* value with a different owner. So these are not values to migrate. */ \
|
|
X(GetSamplingResolutionGeneration, BARRIER_PULLED, "P3b/P4b", \
|
|
"a shutter, not a value: the server answers from its own Serial") \
|
|
X(GetTextureBindGeneration, BARRIER_PULLED, "P3b/P4b", \
|
|
"a shutter, not a value: the server answers from its own Serial") \
|
|
X(GetTextureContextId, BARRIER_PULLED, "P3b/P4b", \
|
|
"a shutter, not a value: the server answers from its own Serial") \
|
|
X(GetTextureUnitObject, BARRIER_PULLED, "P3b/P4b, P7", \
|
|
"frontend TextureUnit base pointer; 13 Espryt + 8 Magma sites") \
|
|
/* DirectGLES.cpp:4497, PrepareForDraw, the second unconditional pointer read of every draw. */ \
|
|
X(GetProgramForDraw, BARRIER_PULLED, "P8 (Espryt), P7 (Magma)", \
|
|
"frontend SharedPtr<ProgramObject>; the record carries a handle") \
|
|
/* P5b package i1 (CONTRACT-P5B.md §6.9, the one row the contract grants a package). This */ \
|
|
/* was FATAL "reachable only from kDispatch; there is no compute on the reduced path" - and */ \
|
|
/* i1 IS what puts compute on the path: launch_grid (60) now crosses and the backend's */ \
|
|
/* PrepareForCompute pulls this inside it (DirectGLES.cpp:5779, VulkanRenderer.cpp:7327). */ \
|
|
/* It is GetProgramForDraw's twin in every respect, so it takes its class and its retiring */ \
|
|
/* phases. Overturned by: nothing in P5b; P7/P8 retire both rows together. */ \
|
|
X(GetProgramForDispatch, BARRIER_PULLED, "P7 (Magma), P8 (Espryt)", \
|
|
"frontend SharedPtr<ProgramObject>; the record carries a handle") \
|
|
/* The XFB six. XFB itself is off the reduced path, but kDraw's may-read mask carries all */ \
|
|
/* six and the draw walk reads them regardless - which is exactly the case a field census */ \
|
|
/* taken from "what the scenario does" rather than from the mask would miss. */ \
|
|
X(IsTransformFeedbackActive, BARRIER_PULLED, "P3b/P4b (Espryt), P7 (Magma)", \
|
|
"read on every kDraw walk although XFB is off the reduced path") \
|
|
X(IsTransformFeedbackPaused, BARRIER_PULLED, "P3b/P4b (Espryt), P7 (Magma)", \
|
|
"read on every kDraw walk although XFB is off the reduced path") \
|
|
X(GetTransformFeedbackProgram, BARRIER_PULLED, "P3b/P4b (Espryt), P7 (Magma)", \
|
|
"frontend SharedPtr<ProgramObject>") \
|
|
X(GetTransformFeedbackGeneration, BARRIER_PULLED, "P3b/P4b (Espryt), P7 (Magma)", \
|
|
"no call carries it") \
|
|
X(GetBoundTransformFeedbackLifetimeId, BARRIER_PULLED, "P3b/P4b (Espryt), P7 (Magma)", \
|
|
"the D21 counter-slot rekey's key; no call carries it") \
|
|
X(GetTransformFeedbackCapturedVertices, BARRIER_PULLED, "P3b/P4b (Espryt), P7 (Magma)", \
|
|
"no call carries it") \
|
|
\
|
|
/* ---- APPLIER_DERIVED ---- */ \
|
|
/* THE ONE ROW, and it is the one the contract asked P5 to split into two field ids. It is */ \
|
|
/* split HERE INSTEAD, by ARGUMENT, in the argument-exception list below - see that list's */ \
|
|
/* header for the evidence and for what would overturn the decision. The field itself is */ \
|
|
/* APPLIER_DERIVED because set_pixel_pack_state arrives and MGPipeApplyAccess::PackState */ \
|
|
/* writes m_pixelStore[0] out of it (PipeApply.cpp:1373), while no row of the emitted list */ \
|
|
/* claims the field (Coverage.def:173 keeps it out deliberately, because the record supplies */ \
|
|
/* half of it). */ \
|
|
X(GetPixelStoreParameters, APPLIER_DERIVED, "-", \
|
|
"set_pixel_pack_state; the applier writes m_pixelStore[0] (PipeApply.cpp:1373)") \
|
|
\
|
|
/* ---- FATAL: two non-sticky fields, each off the reduced path for a checkable reason ----- */ \
|
|
/* Three until P5b: GetProgramForDispatch moved up to BARRIER_PULLED when package i1 put */ \
|
|
/* compute on the path (CONTRACT-P5B.md §6.9). */ \
|
|
X(GetBoundTransformFeedbackName, FATAL, "-", \
|
|
"DEAD: read by no backend since the D21 rekey (PipeInputs.h:232-234)") \
|
|
X(GetTransformFeedbackPausedPrimitiveCounter, FATAL, "-", \
|
|
"reachable only from class kQuery, which the reduced path never enters") \
|
|
\
|
|
/* ---- the seven sticky forwards, as FIELD rows ---- */ \
|
|
/* They have no storage, so a read of the FIELD is a call of the FORWARD; the field row and */ \
|
|
/* the forward row below carry the same class by construction, and the generator refuses a */ \
|
|
/* pair that disagrees. Their poison exemption (generated/PipeFilled.inc:422's sticky arm) */ \
|
|
/* is cancelled under split by the server stamp, which zeroes their FilledGen - and 0 loses */ \
|
|
/* to nothing, because :419's "never filled" test runs first. */ \
|
|
X(GetBufferBindingPointCount, BARRIER_PULLED, "P7/P13", \
|
|
"an argument-keyed lookup into the frontend's binding-point table") \
|
|
X(GetProgramObject, BARRIER_PULLED, "P9", \
|
|
"hands the backend a frontend SharedPtr<ProgramObject> keyed by GL name") \
|
|
X(GetTextureObject, BARRIER_PULLED, "P7", \
|
|
"hands the backend a frontend SharedPtr<ITextureObject> keyed by GL name") \
|
|
X(HasOpenTransformFeedbackSpan, BARRIER_PULLED, "P7/P9", \
|
|
"a lookup into frontend XFB span state keyed by lifetime id") \
|
|
X(ValidateProgramName, BARRIER_PULLED, "P9", \
|
|
"a frontend name-table probe; Coverage.def calls it kClientResolved") \
|
|
X(InvalidateCompileEnv, BARRIER_PULLED, "P5", \
|
|
"a WRITE INTO THE FRONTEND; R-12 replaces it with the re-arriving caps snapshot") \
|
|
X(RecordError, BARRIER_PULLED, "P9", \
|
|
"a WRITE INTO THE FRONTEND; R-12's OnGlError, whose ordering is P9's")
|
|
|
|
// X(Field, Class, RetiringPhase, Mechanism) - the SEVEN STICKY FORWARDS as their own rows.
|
|
//
|
|
// CONTRACT-P5.md section 3: "The domain is 63 fields plus the 7 sticky forwards, which are
|
|
// among those 63 but are exempted from the poison and so need their own row. 70 rows."
|
|
// The reason they need a second row is that they are the seven that hand the server a raw
|
|
// FRONTEND OBJECT or write INTO the frontend, so "the exit gate is structurally blind on the
|
|
// seven most dangerous fields" - and the row that fixes that is about the FORWARD (a live
|
|
// call with no stored value), not about the field's storage, which does not exist.
|
|
//
|
|
// Mechanism is what replaces the forward, which is NOT the same question as which phase
|
|
// retires the pull.
|
|
#define MGP_FIELD_OWNERSHIP_FORWARD_LIST(X) \
|
|
X(GetBufferBindingPointCount, BARRIER_PULLED, "P7/P13", \
|
|
"a server-side binding-point table") \
|
|
X(GetProgramObject, BARRIER_PULLED, "P9", \
|
|
"a client-resolved program handle table (ARCHITECTURE 3.2 explicitly-not-ported)") \
|
|
X(GetTextureObject, BARRIER_PULLED, "P7", \
|
|
"a server-side texture handle table") \
|
|
X(HasOpenTransformFeedbackSpan, BARRIER_PULLED, "P7/P9", \
|
|
"server-side XFB span state") \
|
|
X(ValidateProgramName, BARRIER_PULLED, "P9", \
|
|
"a client-resolved program name probe") \
|
|
X(InvalidateCompileEnv, BARRIER_PULLED, "P5", \
|
|
"OnCapsInvalidated - the re-arriving caps snapshot IS the invalidation (R-12)") \
|
|
X(RecordError, BARRIER_PULLED, "P9", \
|
|
"OnGlError, the ordered reverse-channel error post (R-12); its ordering is P9's")
|
|
|
|
// X(Field, Arg0, Class, Why) - ARGUMENT-KEYED EXCEPTIONS.
|
|
//
|
|
// A row here narrows ONE argument value of ONE field to a different class. The field keeps
|
|
// its single row above; this is the same shape Coverage.def:62-69 already rules for
|
|
// GetBufferBindingSlot - "THE ROW STAYS ONE ROW, and that is structural rather than a
|
|
// shortcut: this list IS the MGPipeInputField enum and the PipeInputs field set, and the
|
|
// field is ONE array that a second row of the same name could only duplicate".
|
|
//
|
|
// GetPixelStoreParameters is m_pixelStore[2] indexed by its own `isUnpack` argument, exactly
|
|
// as GetBufferBindingSlot is m_bufferBindingSlot[15] indexed by its BufferTarget. Only [0]
|
|
// (pack) has a carrier, and the applier writes it.
|
|
//
|
|
// AND THE UNPACK HALF HAS NO BACKEND READER AT ALL. Every MGB_CTX->GetPixelStoreParameters
|
|
// call site in the tree passes `false`: DirectGLES.cpp:7924, :9399, :10893, :11272,
|
|
// Utils.cpp:2302 and VulkanRenderer.cpp:10980 - six, not the five the scout named, and the
|
|
// scout did not open them. PipeFill.cpp's own EmitPixelPackState says the same thing from
|
|
// the other side: "PACK only, deliberately: nothing on the far side of the boundary reads
|
|
// unpack state". So the honest class for the unpack half is FATAL, not BARRIER_PULLED: a
|
|
// future backend read of it would otherwise be served a stale struct in silence, and this
|
|
// way it is a named abort on the first read.
|
|
#define MGP_FIELD_OWNERSHIP_ARG_LIST(X) \
|
|
X(GetPixelStoreParameters, 1, FATAL, \
|
|
"the unpack half has no carrier AND no backend reader; all six MGB_CTX sites pass false")
|
|
|
|
// X(WireOp, Verb) - WHERE THE SERVER STAMPS.
|
|
//
|
|
// The stamp rule needs one thing the wire does not carry: which MGPipeVerb a record belongs
|
|
// to. The two name spaces are not the same and do not line up by name - the DRAW verb is
|
|
// `DrawArrays` and its record is `draw_vbo`, the BLIT verb is `BlitFramebuffer` and its record
|
|
// is `blit` - so the map is written here and checked against both sources (PipeCalls.def for
|
|
// the op, FillPoints.def for the verb) rather than believed.
|
|
//
|
|
// EXACTLY THE OPS THAT ARE VERB BOUNDARIES. CONTRACT-P5.md section 7 puts five slots in class
|
|
// B (emitted in P5): Clear, DrawArrays, ReadPixels, BlitFramebuffer and Present. FOUR OF THEM
|
|
// ARE HERE AND PRESENT IS NOT, and that is a ruling rather than an omission: FillPoints.def:21
|
|
// says in so many words that "Present and SetSwapInterval go through BackendObject virtuals
|
|
// and read no frontend state, so they are not verbs here". There is no MGPipeVerb::Present to
|
|
// stamp for, MGPipeValidateForVerb is never called for it, and stamping at Present would
|
|
// retire the previous verb's answers with nothing to put in their place.
|
|
//
|
|
// An op that is not in this list is NOT a verb boundary and the applier must not stamp on it:
|
|
// a set_dynamic_state between two draws is part of the draw's verb, not a new one.
|
|
//
|
|
// THE GENERATOR REFUSES AN OMISSION, NOT ONLY A TYPO, and that is the half the first version of
|
|
// this file did not have. Every call that is VERB-SHAPED must have a row here or an exemption
|
|
// row below with a reason. "Verb-shaped" is the union of two mechanical tests, both derived:
|
|
// (a) PipeCalls.def gives the call kind kCtxVerb - the catalogue's own word for it;
|
|
// (b) the call's name is also an MGPipeVerb name in FillPoints.def.
|
|
// Eighteen rows and three exemptions cover all twenty-one (P5: twelve and three; P5b appended
|
|
// the five kCtxVerb rows 72..76 and settled ResourceCopyRegion). Only four of the rows could
|
|
// arrive in P5 (CONTRACT §7 class B minus Present); the rest are Fatal{UnmigratedVerb} on the
|
|
// client until a P5b migration package flips them and are mapped ANYWAY, because the failure
|
|
// mode of an absent row is silent: the applier would run the record under the PREVIOUS verb's
|
|
// serial, mask and name, and a field inside that mask would read FRESH while holding the
|
|
// previous verb's value.
|
|
//
|
|
// NOT MECHANICALLY DETECTABLE, and so not claimed: a call that is a verb boundary, is not
|
|
// kCtxVerb, and whose name differs from its verb's. `ResourceCopyRegion` WAS the one in the
|
|
// tree (its verbs could have been CopyImageSubData / CopyTexSubImage2D / CopyTexImage2D). P5b
|
|
// decided: resource_copy_region is glCopyImageSubData only, and the two framebuffer-sourced
|
|
// copies have their own row (copy_framebuffer_to_texture), so the choice is a row here rather
|
|
// than a rule in a reader's head.
|
|
#define MGP_VERB_OP_LIST(X) \
|
|
/* CONTRACT §7 class B - the four that can actually arrive in P5. */ \
|
|
X(Clear, Clear) \
|
|
X(DrawVbo, DrawArrays) \
|
|
X(ReadPixels, ReadPixels) \
|
|
X(Blit, BlitFramebuffer) \
|
|
/* Class C today (Fatal{UnmigratedVerb}); mapped so the phase that emits one cannot get NO */ \
|
|
/* stamp by omission. DrawVbo above has the same shape and a worse case: it stands for all */ \
|
|
/* twenty draw verbs, which share the kDraw mask but not the NAME a Fatal prints. */ \
|
|
X(LaunchGrid, DispatchCompute) \
|
|
X(MemoryBarrier, MemoryBarrier) \
|
|
X(BeginStreamOutput, BeginTransformFeedback) \
|
|
X(EndStreamOutput, EndTransformFeedback) \
|
|
X(PauseStreamOutput, PauseTransformFeedback) \
|
|
X(ResumeStreamOutput, ResumeTransformFeedback) \
|
|
X(GenerateMipmap, GenerateMipmap) \
|
|
X(GetTextureImage, GetTextureImage) \
|
|
/* P5b (MG_Remote/CONTRACT-P5B.md). The ONE renamed, non-kCtxVerb boundary the file above */ \
|
|
/* said "the phase that emits it adds its row and decides which verb it is": P5b rules */ \
|
|
/* resource_copy_region = glCopyImageSubData ONLY; the two framebuffer-sourced copies */ \
|
|
/* (CopyTexImage2D / CopyTexSubImage2D) are copy_framebuffer_to_texture below, stamped as */ \
|
|
/* CopyTexImage2D - both are kBlitOrCopy, one mask, and a row can name one verb. */ \
|
|
X(ResourceCopyRegion, CopyImageSubData) \
|
|
/* The five P5b-appended verbs, opcodes 72..76, each stamped as the GLFunctionsTable verb */ \
|
|
/* it reproduces. */ \
|
|
X(BindShaderImage, BindImageTexture) \
|
|
X(PatchParameter, PatchParameteri) \
|
|
X(BindStreamOutput, BindTransformFeedback) \
|
|
X(SetStorageBlockBinding, ShaderStorageBlockBinding) \
|
|
X(CopyFramebufferToTexture, CopyTexImage2D)
|
|
|
|
// X(Op, Why) - verb-shaped calls that are deliberately NOT stamp points.
|
|
//
|
|
// An exemption is a ROW, not an absence, so that the reason is in the file rather than in a
|
|
// reviewer's head and so that the generator's completeness check has something to accept.
|
|
#define MGP_VERB_OP_EXEMPT_LIST(X) \
|
|
X(Present, \
|
|
"FillPoints.def:21 - Present and SetSwapInterval go through BackendObject virtuals and " \
|
|
"read no frontend state, so they are not verbs here. There is no MGPipeVerb::Present, " \
|
|
"MGPipeValidateForVerb is never called for it, and stamping would retire the previous " \
|
|
"verb's answers with nothing to put in their place. It is class B all the same.") \
|
|
X(SetSwapInterval, \
|
|
"the same sentence of FillPoints.def:21, and it is class C besides") \
|
|
X(Flush, \
|
|
"not a verb at all: the verb census (BRIEF §12 C-3) found Flush is not a GLFunctionsTable " \
|
|
"slot and glFlush/glFinish are empty function bodies (Definitions.cpp:111-112), so there " \
|
|
"is no MGPipeVerb::Flush for a row to name")
|
|
// clang-format on
|