blob: dd0fc9f9f79d6549ea4c96f1407e5e192fcbe0f9 [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.
*/
#include "include/core/SkTypes.h"
#include "include/private/SkTArray.h"
#include "src/core/SkRasterPipeline.h"
#include "src/core/SkUtils.h"
#include <cstdint>
#include <initializer_list>
#include <memory>
class SkArenaAlloc;
class SkWStream;
namespace SkSL {
class SkRPDebugTrace;
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;
};
// Ops that the builder will contextually rewrite into different RasterPipeline stages.
enum class BuilderOp {
// We support all the native Raster Pipeline stages.
#define M(stage) stage,
SK_RASTER_PIPELINE_STAGES_ALL(M)
#undef M
// We also support Builder-specific ops; these are converted into real RP stages during
// `appendStages`.
push_literal_f,
push_slots,
copy_stack_to_slots,
copy_stack_to_slots_unmasked,
discard_stack,
duplicate,
push_condition_mask,
pop_condition_mask,
unsupported
};
// Represents a single raster-pipeline SkSL instruction.
struct Instruction {
Instruction(BuilderOp op, std::initializer_list<Slot> slots) : fOp(op), fImmA(0) {
auto iter = slots.begin();
if (iter != slots.end()) { fSlotA = *iter++; }
if (iter != slots.end()) { fSlotB = *iter++; }
if (iter != slots.end()) { fSlotC = *iter++; }
SkASSERT(iter == slots.end());
}
Instruction(BuilderOp op, std::initializer_list<Slot> slots, int i) : fOp(op), fImmA(i) {
auto iter = slots.begin();
if (iter != slots.end()) { fSlotA = *iter++; }
if (iter != slots.end()) { fSlotB = *iter++; }
if (iter != slots.end()) { fSlotC = *iter++; }
SkASSERT(iter == slots.end());
}
BuilderOp fOp;
Slot fSlotA = NA;
Slot fSlotB = NA;
Slot fSlotC = NA;
int fImmA = 0;
};
class Program {
public:
Program(SkTArray<Instruction> instrs, int numValueSlots, SkRPDebugTrace* debugTrace);
void appendStages(SkRasterPipeline* pipeline, SkArenaAlloc* alloc);
void dump(SkWStream* s);
private:
void optimize();
int numValueSlots();
int numTempStackSlots();
SkTArray<Instruction> fInstructions;
int fNumValueSlots = 0;
int fNumTempStackSlots = 0;
SkRPDebugTrace* fDebugTrace = nullptr;
};
class Builder {
public:
/** Finalizes and optimizes the program. */
std::unique_ptr<Program> finish(int numValueSlots, SkRPDebugTrace* debugTrace = nullptr);
/** Assemble a program from the Raster Pipeline instructions below. */
void init_lane_masks() {
fInstructions.push_back({BuilderOp::init_lane_masks, {}});
}
void store_src_rg(SlotRange slots) {
SkASSERT(slots.count == 2);
fInstructions.push_back({BuilderOp::store_src_rg, {slots.index}});
}
void store_src(SlotRange slots) {
SkASSERT(slots.count == 4);
fInstructions.push_back({BuilderOp::store_src, {slots.index}});
}
void store_dst(SlotRange slots) {
SkASSERT(slots.count == 4);
fInstructions.push_back({BuilderOp::store_dst, {slots.index}});
}
void load_src(SlotRange slots) {
SkASSERT(slots.count == 4);
fInstructions.push_back({BuilderOp::load_src, {slots.index}});
}
void load_dst(SlotRange slots) {
SkASSERT(slots.count == 4);
fInstructions.push_back({BuilderOp::load_dst, {slots.index}});
}
// Use the same SkRasterPipeline op regardless of the literal type.
void immediate_f(float val) {
fInstructions.push_back({BuilderOp::immediate_f, {}, sk_bit_cast<int32_t>(val)});
}
void immediate_i(int32_t val) {
fInstructions.push_back({BuilderOp::immediate_f, {}, val});
}
void immediate_u(uint32_t val) {
fInstructions.push_back({BuilderOp::immediate_f, {}, sk_bit_cast<int32_t>(val)});
}
void push_literal_f(float val) {
fInstructions.push_back({BuilderOp::push_literal_f, {}, sk_bit_cast<int32_t>(val)});
}
void push_literal_i(int32_t val) {
fInstructions.push_back({BuilderOp::push_literal_f, {}, val});
}
void push_literal_u(uint32_t val) {
fInstructions.push_back({BuilderOp::push_literal_f, {}, sk_bit_cast<int32_t>(val)});
}
void push_slots(SlotRange src) {
// Translates into copy_slots_unmasked (from values into temp stack) in Raster Pipeline.
fInstructions.push_back({BuilderOp::push_slots, {src.index}, src.count});
}
void copy_stack_to_slots(SlotRange dst) {
// Translates into copy_slots_masked (from temp stack to values) in Raster Pipeline.
// Does not discard any values on the temp stack.
fInstructions.push_back({BuilderOp::copy_stack_to_slots, {dst.index}, dst.count});
}
void copy_stack_to_slots_unmasked(SlotRange dst) {
// Translates into copy_slots_unmasked (from temp stack to values) in Raster Pipeline.
// Does not discard any values on the temp stack.
fInstructions.push_back({BuilderOp::copy_stack_to_slots_unmasked, {dst.index}, dst.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`. Both input values are consumed, and the result is pushed onto the stack.
void binary_op(BuilderOp op, int32_t slots);
void discard_stack(int32_t count = 1) {
// Shrinks the temp stack, discarding values on top.
fInstructions.push_back({BuilderOp::discard_stack, {}, count});
}
void pop_slots(SlotRange dst) {
// The opposite of push_slots; copies values from the temp stack into value slots, then
// shrinks the temp stack.
this->copy_stack_to_slots(dst);
this->discard_stack(dst.count);
}
void duplicate(int count) {
// Creates duplicates of the top item on the temp stack.
SkASSERT(count >= 0);
fInstructions.push_back({BuilderOp::duplicate, {}, count});
}
void pop_slots_unmasked(SlotRange dst) {
// The opposite of push_slots; copies values from the temp stack into value slots, then
// shrinks the temp stack.
this->copy_stack_to_slots_unmasked(dst);
this->discard_stack(dst.count);
}
void load_unmasked(Slot slot) {
fInstructions.push_back({BuilderOp::load_unmasked, {slot}});
}
void store_unmasked(Slot slot) {
fInstructions.push_back({BuilderOp::store_unmasked, {slot}});
}
void store_masked(Slot slot) {
fInstructions.push_back({BuilderOp::store_masked, {slot}});
}
void copy_slots_masked(SlotRange dst, SlotRange src) {
SkASSERT(dst.count == src.count);
fInstructions.push_back({BuilderOp::copy_slot_masked, {dst.index, src.index}, dst.count});
}
void copy_slots_unmasked(SlotRange dst, SlotRange src) {
SkASSERT(dst.count == src.count);
fInstructions.push_back({BuilderOp::copy_slot_unmasked, {dst.index, src.index}, dst.count});
}
void zero_slots_unmasked(SlotRange dst) {
fInstructions.push_back({BuilderOp::zero_slot_unmasked, {dst.index}, dst.count});
}
void push_condition_mask() {
fInstructions.push_back({BuilderOp::push_condition_mask, {}});
}
void pop_condition_mask() {
fInstructions.push_back({BuilderOp::pop_condition_mask, {}});
}
void update_return_mask() {
fInstructions.push_back({BuilderOp::update_return_mask, {}});
}
private:
SkTArray<Instruction> fInstructions;
};
} // namespace RP
} // namespace SkSL