/* * Copyright 2022 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #ifndef SKSL_RASTERPIPELINEBUILDER #define SKSL_RASTERPIPELINEBUILDER #include "include/core/SkTypes.h" #include "include/core/SkSpan.h" #include "include/core/SkTypes.h" #include "include/private/base/SkTArray.h" #include "src/base/SkUtils.h" #include "src/core/SkRasterPipelineOpList.h" #include #include #include #include class SkArenaAlloc; class SkRasterPipeline; class SkWStream; using SkRPOffset = uint32_t; namespace SkSL { class DebugTracePriv; class TraceHook; namespace RP { // A single scalar in our program consumes one slot. using Slot = int; constexpr Slot NA = -1; // Scalars, vectors, and matrices can be represented as a range of slot indices. struct SlotRange { Slot index = 0; int count = 0; }; #define SKRP_EXTENDED_OPS(M) \ /* branch targets */ \ M(label) \ \ /* child programs */ \ M(invoke_shader) \ M(invoke_color_filter) \ M(invoke_blender) \ \ /* color space transforms */ \ M(invoke_to_linear_srgb) \ M(invoke_from_linear_srgb) // An RP::Program will consist entirely of ProgramOps. The ProgramOps list is a superset of the // native SkRasterPipelineOps op-list. It also has a few extra ops to indicate child-effect // invocation, and a `label` op to indicate branch targets. enum class ProgramOp { #define M(stage) stage, // A finished program can contain any native Raster Pipeline op... SK_RASTER_PIPELINE_OPS_ALL(M) // ... as well as our extended ops. SKRP_EXTENDED_OPS(M) #undef M }; // BuilderOps are a superset of ProgramOps. They are used by the RP::Builder, which works in terms // of Instructions; Instructions are slightly more expressive than raw SkRasterPipelineOps. In // particular, the Builder supports stacks for pushing and popping scratch values. // RP::Program::makeStages is responsible for rewriting Instructions/BuilderOps into an array of // RP::Program::Stages, which will contain only native SkRasterPipelineOps and (optionally) // child-effect invocations. enum class BuilderOp { #define M(stage) stage, // An in-flight program can contain all the native Raster Pipeline ops... SK_RASTER_PIPELINE_OPS_ALL(M) // ... and our extended ops... SKRP_EXTENDED_OPS(M) #undef M // ... and also has Builder-specific ops. These ops generally interface with the stack, and are // converted into ProgramOps during `makeStages`. push_clone, push_clone_from_stack, push_clone_indirect_from_stack, push_constant, push_immutable, push_immutable_indirect, push_slots, push_slots_indirect, push_uniform, push_uniform_indirect, copy_stack_to_slots, copy_stack_to_slots_unmasked, copy_stack_to_slots_indirect, copy_uniform_to_slots_unmasked, store_immutable_value, swizzle_copy_stack_to_slots, swizzle_copy_stack_to_slots_indirect, discard_stack, pad_stack, select, push_condition_mask, pop_condition_mask, push_loop_mask, pop_loop_mask, pop_and_reenable_loop_mask, push_return_mask, pop_return_mask, push_src_rgba, push_dst_rgba, push_device_xy01, pop_src_rgba, pop_dst_rgba, trace_var_indirect, branch_if_no_active_lanes_on_stack_top_equal, unsupported }; // If the extended ops are not in sync between enums, program creation will not work. static_assert((int)ProgramOp::label == (int)BuilderOp::label); // Represents a single raster-pipeline SkSL instruction. struct Instruction { BuilderOp fOp; Slot fSlotA = NA; Slot fSlotB = NA; int fImmA = 0; int fImmB = 0; int fImmC = 0; int fImmD = 0; int fStackID = 0; }; class Callbacks { public: virtual ~Callbacks() = default; virtual bool appendShader(int index) = 0; virtual bool appendColorFilter(int index) = 0; virtual bool appendBlender(int index) = 0; virtual void toLinearSrgb(const void* color) = 0; virtual void fromLinearSrgb(const void* color) = 0; }; class Program { public: Program(skia_private::TArray instrs, int numValueSlots, int numUniformSlots, int numImmutableSlots, int numLabels, DebugTracePriv* debugTrace); ~Program(); bool appendStages(SkRasterPipeline* pipeline, SkArenaAlloc* alloc, Callbacks* callbacks, SkSpan uniforms) const; void dump(SkWStream* out, bool writeInstructionCount = false) const; int numUniforms() const { return fNumUniformSlots; } private: using StackDepths = skia_private::TArray; // [stack index] = depth of stack struct SlotData { SkSpan values; SkSpan stack; SkSpan immutable; }; std::optional allocateSlotData(SkArenaAlloc* alloc) const; struct Stage { ProgramOp op; void* ctx; }; void makeStages(skia_private::TArray* pipeline, SkArenaAlloc* alloc, SkSpan uniforms, const SlotData& slots) const; StackDepths tempStackMaxDepths() const; // These methods are used to split up multi-slot copies into multiple ops as needed. void appendCopy(skia_private::TArray* pipeline, SkArenaAlloc* alloc, std::byte* basePtr, ProgramOp baseStage, SkRPOffset dst, int dstStride, SkRPOffset src, int srcStride, int numSlots) const; void appendCopyImmutableUnmasked(skia_private::TArray* pipeline, SkArenaAlloc* alloc, std::byte* basePtr, SkRPOffset dst, SkRPOffset src, int numSlots) const; void appendCopySlotsUnmasked(skia_private::TArray* pipeline, SkArenaAlloc* alloc, SkRPOffset dst, SkRPOffset src, int numSlots) const; void appendCopySlotsMasked(skia_private::TArray* pipeline, SkArenaAlloc* alloc, SkRPOffset dst, SkRPOffset src, int numSlots) const; // Appends a single-slot single-input math operation to the pipeline. The op `stage` will // appended `numSlots` times, starting at position `dst` and advancing one slot for each // subsequent invocation. void appendSingleSlotUnaryOp(skia_private::TArray* pipeline, ProgramOp stage, float* dst, int numSlots) const; // Appends a multi-slot single-input math operation to the pipeline. `baseStage` must refer to // a single-slot "apply_op" stage, which must be immediately followed by specializations for // 2-4 slots. For instance, {`ceil_float`, `ceil_2_floats`, `ceil_3_floats`, `ceil_4_floats`} // must be contiguous ops in the stage list, listed in that order; pass `ceil_float` and we // pick the appropriate op based on `numSlots`. void appendMultiSlotUnaryOp(skia_private::TArray* pipeline, ProgramOp baseStage, float* dst, int numSlots) const; // Appends an immediate-mode binary operation to the pipeline. `baseStage` must refer to // a single-slot, immediate-mode "apply-imm" stage, which must be immediately preceded by // specializations for 2-4 slots if numSlots is greater than 1. For instance, {`add_imm_4_ints`, // `add_imm_3_ints`, `add_imm_2_ints`, `add_imm_int`} must be contiguous ops in the stage list, // listed in that order; pass `add_imm_int` and we pick the appropriate op based on `numSlots`. // Some immediate-mode binary ops are single-slot only in the interest of code size; in this // case, the multi-slot ops can be absent, but numSlots must be 1. void appendImmediateBinaryOp(skia_private::TArray* pipeline, SkArenaAlloc* alloc, ProgramOp baseStage, SkRPOffset dst, int32_t value, int numSlots) const; // Appends a two-input math operation to the pipeline. `src` must be _immediately_ after `dst` // in memory. `baseStage` must refer to an unbounded "apply_to_n_slots" stage. A BinaryOpCtx // will be used to pass pointers to the destination and source; the delta between the two // pointers implicitly gives the number of slots. void appendAdjacentNWayBinaryOp(skia_private::TArray* pipeline, SkArenaAlloc* alloc, ProgramOp stage, SkRPOffset dst, SkRPOffset src, int numSlots) const; // Appends a multi-slot two-input math operation to the pipeline. `src` must be _immediately_ // after `dst` in memory. `baseStage` must refer to an unbounded "apply_to_n_slots" stage, which // must be immediately followed by specializations for 1-4 slots. For instance, {`add_n_floats`, // `add_float`, `add_2_floats`, `add_3_floats`, `add_4_floats`} must be contiguous ops in the // stage list, listed in that order; pass `add_n_floats` and we pick the appropriate op based on // `numSlots`. void appendAdjacentMultiSlotBinaryOp(skia_private::TArray* pipeline, SkArenaAlloc* alloc, ProgramOp baseStage, std::byte* basePtr, SkRPOffset dst, SkRPOffset src, int numSlots) const; // Appends a multi-slot math operation having three inputs (dst, src0, src1) and one output // (dst) to the pipeline. The three inputs must be _immediately_ adjacent in memory. `baseStage` // must refer to an unbounded "apply_to_n_slots" stage, which must be immediately followed by // specializations for 1-4 slots. void appendAdjacentMultiSlotTernaryOp(skia_private::TArray* pipeline, SkArenaAlloc* alloc, ProgramOp baseStage, std::byte* basePtr, SkRPOffset dst, SkRPOffset src0, SkRPOffset src1, int numSlots) const; // Appends a math operation having three inputs (dst, src0, src1) and one output (dst) to the // pipeline. The three inputs must be _immediately_ adjacent in memory. `baseStage` must refer // to an unbounded "apply_to_n_slots" stage. A TernaryOpCtx will be used to pass pointers to the // destination and sources; the delta between the each pointer implicitly gives the slot count. void appendAdjacentNWayTernaryOp(skia_private::TArray* pipeline, SkArenaAlloc* alloc, ProgramOp stage, std::byte* basePtr, SkRPOffset dst, SkRPOffset src0, SkRPOffset src1, int numSlots) const; // Appends a stack_rewind op on platforms where it is needed (when SK_HAS_MUSTTAIL is not set). void appendStackRewindForNonTailcallers(skia_private::TArray* pipeline) const; // Appends a stack_rewind op unilaterally. void appendStackRewind(skia_private::TArray* pipeline) const; class Dumper; friend class Dumper; skia_private::TArray fInstructions; int fNumValueSlots = 0; int fNumUniformSlots = 0; int fNumImmutableSlots = 0; int fNumTempStackSlots = 0; int fNumLabels = 0; StackDepths fTempStackMaxDepths; DebugTracePriv* fDebugTrace = nullptr; std::unique_ptr fTraceHook; }; class Builder { public: /** Finalizes and returns a completed program. */ std::unique_ptr finish(int numValueSlots, int numUniformSlots, int numImmutableSlots, DebugTracePriv* debugTrace = nullptr); /** * Peels off a label ID for use in the program. Set the label's position in the program with * the `label` instruction. Actually branch to the target with an instruction like * `branch_if_any_lanes_active` or `jump`. */ int nextLabelID() { return fNumLabels++; } /** * The builder keeps track of the state of execution masks; when we know that the execution * mask is unaltered, we can generate simpler code. Code which alters the execution mask is * required to enable this flag. */ void enableExecutionMaskWrites() { ++fExecutionMaskWritesEnabled; } void disableExecutionMaskWrites() { SkASSERT(this->executionMaskWritesAreEnabled()); --fExecutionMaskWritesEnabled; } bool executionMaskWritesAreEnabled() { return fExecutionMaskWritesEnabled > 0; } /** Assemble a program from the Raster Pipeline instructions below. */ void init_lane_masks() { this->appendInstruction(BuilderOp::init_lane_masks, {}); } void store_src_rg(SlotRange slots) { SkASSERT(slots.count == 2); this->appendInstruction(BuilderOp::store_src_rg, {slots.index}); } void store_src(SlotRange slots) { SkASSERT(slots.count == 4); this->appendInstruction(BuilderOp::store_src, {slots.index}); } void store_dst(SlotRange slots) { SkASSERT(slots.count == 4); this->appendInstruction(BuilderOp::store_dst, {slots.index}); } void store_device_xy01(SlotRange slots) { SkASSERT(slots.count == 4); this->appendInstruction(BuilderOp::store_device_xy01, {slots.index}); } void load_src(SlotRange slots) { SkASSERT(slots.count == 4); this->appendInstruction(BuilderOp::load_src, {slots.index}); } void load_dst(SlotRange slots) { SkASSERT(slots.count == 4); this->appendInstruction(BuilderOp::load_dst, {slots.index}); } void set_current_stack(int stackID) { fCurrentStackID = stackID; } // Inserts a label into the instruction stream. void label(int labelID); // Unconditionally branches to a label. void jump(int labelID); // Branches to a label if the execution mask is active in every lane. void branch_if_all_lanes_active(int labelID); // Branches to a label if the execution mask is active in any lane. void branch_if_any_lanes_active(int labelID); // Branches to a label if the execution mask is inactive across all lanes. void branch_if_no_lanes_active(int labelID); // Branches to a label if the top value on the stack is _not_ equal to `value` in any lane. void branch_if_no_active_lanes_on_stack_top_equal(int value, int labelID); // We use the same SkRasterPipeline op regardless of the literal type, and bitcast the value. void push_constant_i(int32_t val, int count = 1); void push_zeros(int count) { this->push_constant_i(/*val=*/0, count); } void push_constant_f(float val) { this->push_constant_i(sk_bit_cast(val), /*count=*/1); } void push_constant_u(uint32_t val, int count = 1) { this->push_constant_i(sk_bit_cast(val), count); } // Translates into copy_uniforms (from uniforms into temp stack) in Raster Pipeline. void push_uniform(SlotRange src); // Initializes the Raster Pipeline slot with a constant value when the program is first created. // Does not add any instructions to the program. void store_immutable_value_i(Slot slot, int32_t val) { this->appendInstruction(BuilderOp::store_immutable_value, {slot}, val); } // Translates into copy_uniforms (from uniforms into value-slots) in Raster Pipeline. void copy_uniform_to_slots_unmasked(SlotRange dst, SlotRange src); // Translates into copy_from_indirect_uniform_unmasked (from values into temp stack) in Raster // Pipeline. `fixedRange` denotes a fixed set of slots; this range is pushed forward by the // value at the top of stack `dynamicStack`. Pass the range of the uniform being indexed as // `limitRange`; this is used as a hard cap, to avoid indexing outside of bounds. void push_uniform_indirect(SlotRange fixedRange, int dynamicStack, SlotRange limitRange); // Translates into copy_slots_unmasked (from values into temp stack) in Raster Pipeline. void push_slots(SlotRange src) { this->push_slots_or_immutable(src, BuilderOp::push_slots); } // Translates into copy_immutable_unmasked (from immutables into temp stack) in Raster Pipeline. void push_immutable(SlotRange src) { this->push_slots_or_immutable(src, BuilderOp::push_immutable); } void push_slots_or_immutable(SlotRange src, BuilderOp op); // Translates into copy_from_indirect_unmasked (from values into temp stack) in Raster Pipeline. // `fixedRange` denotes a fixed set of slots; this range is pushed forward by the value at the // top of stack `dynamicStack`. Pass the slot range of the variable being indexed as // `limitRange`; this is used as a hard cap, to avoid indexing outside of bounds. void push_slots_indirect(SlotRange fixedRange, int dynamicStack, SlotRange limitRange) { this->push_slots_or_immutable_indirect(fixedRange, dynamicStack, limitRange, BuilderOp::push_slots_indirect); } void push_immutable_indirect(SlotRange fixedRange, int dynamicStack, SlotRange limitRange) { this->push_slots_or_immutable_indirect(fixedRange, dynamicStack, limitRange, BuilderOp::push_immutable_indirect); } void push_slots_or_immutable_indirect(SlotRange fixedRange, int dynamicStack, SlotRange limitRange, BuilderOp op); // Translates into copy_slots_masked (from temp stack to values) in Raster Pipeline. // Does not discard any values on the temp stack. void copy_stack_to_slots(SlotRange dst) { this->copy_stack_to_slots(dst, /*offsetFromStackTop=*/dst.count); } void copy_stack_to_slots(SlotRange dst, int offsetFromStackTop); // Translates into swizzle_copy_slots_masked (from temp stack to values) in Raster Pipeline. // Does not discard any values on the temp stack. void swizzle_copy_stack_to_slots(SlotRange dst, SkSpan components, int offsetFromStackTop); // Translates into swizzle_copy_to_indirect_masked (from temp stack to values) in Raster // Pipeline. Does not discard any values on the temp stack. void swizzle_copy_stack_to_slots_indirect(SlotRange fixedRange, int dynamicStackID, SlotRange limitRange, SkSpan components, int offsetFromStackTop); // Translates into copy_slots_unmasked (from temp stack to values) in Raster Pipeline. // Does not discard any values on the temp stack. void copy_stack_to_slots_unmasked(SlotRange dst) { this->copy_stack_to_slots_unmasked(dst, /*offsetFromStackTop=*/dst.count); } void copy_stack_to_slots_unmasked(SlotRange dst, int offsetFromStackTop); // Translates into copy_to_indirect_masked (from temp stack into values) in Raster Pipeline. // `fixedRange` denotes a fixed set of slots; this range is pushed forward by the value at the // top of stack `dynamicStack`. Pass the slot range of the variable being indexed as // `limitRange`; this is used as a hard cap, to avoid indexing outside of bounds. void copy_stack_to_slots_indirect(SlotRange fixedRange, int dynamicStackID, SlotRange limitRange); // Copies from temp stack to slots, including an indirect offset, then shrinks the temp stack. void pop_slots_indirect(SlotRange fixedRange, int dynamicStackID, SlotRange limitRange) { this->copy_stack_to_slots_indirect(fixedRange, dynamicStackID, limitRange); this->discard_stack(fixedRange.count); } // Performs a unary op (like `bitwise_not`), given a slot count of `slots`. The stack top is // replaced with the result. void unary_op(BuilderOp op, int32_t slots); // Performs a binary op (like `add_n_floats` or `cmpeq_n_ints`), given a slot count of // `slots`. Two n-slot input values are consumed, and the result is pushed onto the stack. void binary_op(BuilderOp op, int32_t slots); // Performs a ternary op (like `mix` or `smoothstep`), given a slot count of // `slots`. Three n-slot input values are consumed, and the result is pushed onto the stack. void ternary_op(BuilderOp op, int32_t slots); // Computes a dot product on the stack. The slots consumed (`slots`) must be between 1 and 4. // Two n-slot input vectors are consumed, and a scalar result is pushed onto the stack. void dot_floats(int32_t slots); // Computes refract(N, I, eta) on the stack. N and I are assumed to be 4-slot vectors, and can // be padded with zeros for smaller inputs. Eta is a scalar. The result is a 4-slot vector. void refract_floats(); // Computes inverse(matN) on the stack. Pass 2, 3 or 4 for n to specify matrix size. void inverse_matrix(int32_t n); // Shrinks the temp stack, discarding values on top. void discard_stack(int32_t count, int stackID); void discard_stack(int32_t count) { this->discard_stack(count, fCurrentStackID); } // Grows the temp stack, leaving any preexisting values in place. void pad_stack(int32_t count); // Copies vales from the temp stack into slots, and then shrinks the temp stack. void pop_slots(SlotRange dst); // Creates many clones of the top single-slot item on the temp stack. void push_duplicates(int count); // Creates a single clone of an item on the current temp stack. The cloned item can consist of // any number of slots, and can be copied from an earlier position on the stack. void push_clone(int numSlots, int offsetFromStackTop = 0); // Clones a range of slots from another stack onto this stack. void push_clone_from_stack(SlotRange range, int otherStackID, int offsetFromStackTop); // Translates into copy_from_indirect_unmasked (from one temp stack to another) in Raster // Pipeline. `fixedOffset` denotes a range of slots within the top `offsetFromStackTop` slots of // `otherStackID`. This range is pushed forward by the value at the top of `dynamicStackID`. void push_clone_indirect_from_stack(SlotRange fixedOffset, int dynamicStackID, int otherStackID, int offsetFromStackTop); // Compares the stack top with the passed-in value; if it matches, enables the loop mask. void case_op(int value) { this->appendInstruction(BuilderOp::case_op, {}, value); } // Performs a `continue` in a loop. void continue_op(int continueMaskStackID) { this->appendInstruction(BuilderOp::continue_op, {}, continueMaskStackID); } void select(int slots) { // Overlays the top two entries on the stack, making one hybrid entry. The execution mask // is used to select which lanes are preserved. SkASSERT(slots > 0); this->appendInstruction(BuilderOp::select, {}, slots); } // The opposite of push_slots; copies values from the temp stack into value slots, then // shrinks the temp stack. void pop_slots_unmasked(SlotRange dst); void copy_slots_masked(SlotRange dst, SlotRange src) { SkASSERT(dst.count == src.count); this->appendInstruction(BuilderOp::copy_slot_masked, {dst.index, src.index}, dst.count); } void copy_slots_unmasked(SlotRange dst, SlotRange src); void copy_immutable_unmasked(SlotRange dst, SlotRange src); // Directly writes a constant value into a slot. void copy_constant(Slot slot, int constantValue); // Stores zeros across the entire slot range. void zero_slots_unmasked(SlotRange dst); // Consumes `consumedSlots` elements on the stack, then generates `components.size()` elements. void swizzle(int consumedSlots, SkSpan components); // Transposes a matrix of size CxR on the stack (into a matrix of size RxC). void transpose(int columns, int rows); // Generates a CxR diagonal matrix from the top two scalars on the stack. The second scalar is // used as the diagonal value; the first scalar (usually zero) fills in the rest of the slots. void diagonal_matrix(int columns, int rows); // Resizes a CxR matrix at the top of the stack to C'xR'. void matrix_resize(int origColumns, int origRows, int newColumns, int newRows); // Multiplies a CxR matrix/vector against an adjacent CxR matrix/vector on the stack. void matrix_multiply(int leftColumns, int leftRows, int rightColumns, int rightRows); void push_condition_mask(); void pop_condition_mask() { SkASSERT(this->executionMaskWritesAreEnabled()); this->appendInstruction(BuilderOp::pop_condition_mask, {}); } void merge_condition_mask(); void merge_inv_condition_mask() { SkASSERT(this->executionMaskWritesAreEnabled()); this->appendInstruction(BuilderOp::merge_inv_condition_mask, {}); } void push_loop_mask() { SkASSERT(this->executionMaskWritesAreEnabled()); this->appendInstruction(BuilderOp::push_loop_mask, {}); } void pop_loop_mask() { SkASSERT(this->executionMaskWritesAreEnabled()); this->appendInstruction(BuilderOp::pop_loop_mask, {}); } // Exchanges src.rgba with the four values at the top of the stack. void exchange_src(); void push_src_rgba() { this->appendInstruction(BuilderOp::push_src_rgba, {}); } void push_dst_rgba() { this->appendInstruction(BuilderOp::push_dst_rgba, {}); } void push_device_xy01() { this->appendInstruction(BuilderOp::push_device_xy01, {}); } void pop_src_rgba(); void pop_dst_rgba() { this->appendInstruction(BuilderOp::pop_dst_rgba, {}); } void mask_off_loop_mask() { SkASSERT(this->executionMaskWritesAreEnabled()); this->appendInstruction(BuilderOp::mask_off_loop_mask, {}); } void reenable_loop_mask(SlotRange src) { SkASSERT(this->executionMaskWritesAreEnabled()); SkASSERT(src.count == 1); this->appendInstruction(BuilderOp::reenable_loop_mask, {src.index}); } void pop_and_reenable_loop_mask() { SkASSERT(this->executionMaskWritesAreEnabled()); this->appendInstruction(BuilderOp::pop_and_reenable_loop_mask, {}); } void merge_loop_mask() { SkASSERT(this->executionMaskWritesAreEnabled()); this->appendInstruction(BuilderOp::merge_loop_mask, {}); } void push_return_mask() { SkASSERT(this->executionMaskWritesAreEnabled()); this->appendInstruction(BuilderOp::push_return_mask, {}); } void pop_return_mask(); void mask_off_return_mask() { SkASSERT(this->executionMaskWritesAreEnabled()); this->appendInstruction(BuilderOp::mask_off_return_mask, {}); } void invoke_shader(int childIdx); void invoke_color_filter(int childIdx); void invoke_blender(int childIdx); void invoke_to_linear_srgb(); void invoke_from_linear_srgb(); // Writes the current line number to the debug trace. void trace_line(int traceMaskStackID, int line) { this->appendInstruction(BuilderOp::trace_line, {}, traceMaskStackID, line); } // Writes a variable update to the debug trace. void trace_var(int traceMaskStackID, SlotRange r) { this->appendInstruction(BuilderOp::trace_var, {r.index}, traceMaskStackID, r.count); } // Writes a variable update (via indirection) to the debug trace. void trace_var_indirect(int traceMaskStackID, SlotRange fixedRange, int dynamicStackID, SlotRange limitRange); // Writes a function-entrance to the debug trace. void trace_enter(int traceMaskStackID, int funcID) { this->appendInstruction(BuilderOp::trace_enter, {}, traceMaskStackID, funcID); } // Writes a function-exit to the debug trace. void trace_exit(int traceMaskStackID, int funcID) { this->appendInstruction(BuilderOp::trace_exit, {}, traceMaskStackID, funcID); } // Writes a scope-level change to the debug trace. void trace_scope(int traceMaskStackID, int delta) { this->appendInstruction(BuilderOp::trace_scope, {}, traceMaskStackID, delta); } private: struct SlotList { SlotList(Slot a = NA, Slot b = NA) : fSlotA(a), fSlotB(b) {} Slot fSlotA = NA; Slot fSlotB = NA; }; void appendInstruction(BuilderOp op, SlotList slots, int a = 0, int b = 0, int c = 0, int d = 0); Instruction* lastInstruction(int fromBack = 0); Instruction* lastInstructionOnAnyStack(int fromBack = 0); void simplifyPopSlotsUnmasked(SlotRange* dst); bool simplifyImmediateUnmaskedOp(); skia_private::TArray fInstructions; int fNumLabels = 0; int fExecutionMaskWritesEnabled = 0; int fCurrentStackID = 0; }; } // namespace RP } // namespace SkSL #endif // SKSL_RASTERPIPELINEBUILDER