blob: e73543b777d078fa629864a5aad97b75fb829220 [file] [log] [blame]
/*
* 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 <cstddef>
#include <cstdint>
#include <memory>
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<Instruction> instrs,
int numValueSlots,
int numUniformSlots,
int numImmutableSlots,
int numLabels,
DebugTracePriv* debugTrace);
~Program();
bool appendStages(SkRasterPipeline* pipeline,
SkArenaAlloc* alloc,
Callbacks* callbacks,
SkSpan<const float> uniforms) const;
void dump(SkWStream* out, bool writeInstructionCount = false) const;
int numUniforms() const { return fNumUniformSlots; }
private:
using StackDepths = skia_private::TArray<int>; // [stack index] = depth of stack
struct SlotData {
SkSpan<float> values;
SkSpan<float> stack;
SkSpan<float> immutable;
};
SlotData allocateSlotData(SkArenaAlloc* alloc) const;
struct Stage {
ProgramOp op;
void* ctx;
};
void makeStages(skia_private::TArray<Stage>* pipeline,
SkArenaAlloc* alloc,
SkSpan<const float> uniforms,
const SlotData& slots) const;
void optimize();
StackDepths tempStackMaxDepths() const;
// These methods are used to split up multi-slot copies into multiple ops as needed.
void appendCopy(skia_private::TArray<Stage>* pipeline,
SkArenaAlloc* alloc,
std::byte* basePtr,
ProgramOp baseStage,
SkRPOffset dst, int dstStride,
SkRPOffset src, int srcStride,
int numSlots) const;
void appendCopyImmutableUnmasked(skia_private::TArray<Stage>* pipeline,
SkArenaAlloc* alloc,
std::byte* basePtr,
SkRPOffset dst,
SkRPOffset src,
int numSlots) const;
void appendCopySlotsUnmasked(skia_private::TArray<Stage>* pipeline,
SkArenaAlloc* alloc,
SkRPOffset dst,
SkRPOffset src,
int numSlots) const;
void appendCopySlotsMasked(skia_private::TArray<Stage>* 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<Stage>* 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<Stage>* 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<Stage>* 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<Stage>* 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<Stage>* 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<Stage>* 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<Stage>* 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 appendStackRewind(skia_private::TArray<Stage>* pipeline) const;
class Dumper;
friend class Dumper;
skia_private::TArray<Instruction> fInstructions;
int fNumValueSlots = 0;
int fNumUniformSlots = 0;
int fNumImmutableSlots = 0;
int fNumTempStackSlots = 0;
int fNumLabels = 0;
StackDepths fTempStackMaxDepths;
DebugTracePriv* fDebugTrace = nullptr;
std::unique_ptr<SkSL::TraceHook> fTraceHook;
};
class Builder {
public:
/** Finalizes and optimizes the program. */
std::unique_ptr<Program> 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<int32_t>(val), /*count=*/1);
}
void push_constant_u(uint32_t val, int count = 1) {
this->push_constant_i(sk_bit_cast<int32_t>(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<const int8_t> 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<const int8_t> 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<const int8_t> 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) {
this->appendInstruction(BuilderOp::invoke_shader, {}, childIdx);
}
void invoke_color_filter(int childIdx) {
this->appendInstruction(BuilderOp::invoke_color_filter, {}, childIdx);
}
void invoke_blender(int childIdx) {
this->appendInstruction(BuilderOp::invoke_blender, {}, childIdx);
}
void invoke_to_linear_srgb() {
// The intrinsics accept a three-component value; add a fourth padding element (which
// will be ignored) since our RP ops deal in RGBA colors.
this->pad_stack(1);
this->appendInstruction(BuilderOp::invoke_to_linear_srgb, {});
this->discard_stack(1);
}
void invoke_from_linear_srgb() {
// The intrinsics accept a three-component value; add a fourth padding element (which
// will be ignored) since our RP ops deal in RGBA colors.
this->pad_stack(1);
this->appendInstruction(BuilderOp::invoke_from_linear_srgb, {});
this->discard_stack(1);
}
// 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<Instruction> fInstructions;
int fNumLabels = 0;
int fExecutionMaskWritesEnabled = 0;
int fCurrentStackID = 0;
};
} // namespace RP
} // namespace SkSL
#endif // SKSL_RASTERPIPELINEBUILDER