enum direction { F, R };
/**
- * Marks a dependency between two intructions, that @after must appear after
- * @before.
+ * Marks a dependency between two intructions, that \p after must appear after
+ * \p before.
*
* Our dependencies are tracked as a DAG. Since we're scheduling bottom-up,
* the latest instructions with nothing left to schedule are the DAG heads,
struct schedule_node *last_tex_coord;
struct schedule_node *last_tex_result;
struct schedule_node *last_tlb;
+ struct schedule_node *last_uniforms_reset;
enum direction dir;
/**
* ignore uniforms accesses, because qir_reorder_uniforms() happens
* after this.
*/
- for (int i = 0; i < qir_get_op_nsrc(inst->op); i++) {
+ for (int i = 0; i < qir_get_nsrc(inst); i++) {
switch (inst->src[i].file) {
case QFILE_TEMP:
add_dep(dir,
add_dep(dir, state->last_vary_read, n);
break;
- case QOP_TEX_S:
- case QOP_TEX_T:
- case QOP_TEX_R:
- case QOP_TEX_B:
- case QOP_TEX_DIRECT:
- /* Texturing setup gets scheduled in order, because
- * the uniforms referenced by them have to land in a
- * specific order.
- */
- add_write_dep(dir, &state->last_tex_coord, n);
- break;
-
case QOP_TEX_RESULT:
/* Results have to be fetched in order. */
add_write_dep(dir, &state->last_tex_result, n);
break;
- case QOP_TLB_COLOR_WRITE:
- case QOP_TLB_COLOR_READ:
- case QOP_TLB_Z_WRITE:
- case QOP_TLB_STENCIL_SETUP:
- case QOP_MS_MASK:
+ case QOP_THRSW:
+ /* After a new THRSW, one must collect all texture samples
+ * queued since the previous THRSW/program start. For now, we
+ * have one THRSW in between each texture setup and its
+ * results collection as our input, and we just make sure that
+ * that ordering is maintained.
+ */
+ add_write_dep(dir, &state->last_tex_coord, n);
+ add_write_dep(dir, &state->last_tex_result, n);
+
+ /* accumulators and flags are lost across thread switches. */
+ add_write_dep(dir, &state->last_sf, n);
+
+ /* Setup, like the varyings, will need to be drained before we
+ * thread switch.
+ */
+ add_write_dep(dir, &state->last_vary_read, n);
+
+ /* The TLB-locking operations have to stay after the last
+ * thread switch.
+ */
add_write_dep(dir, &state->last_tlb, n);
break;
- case QOP_TLB_DISCARD_SETUP:
- add_write_dep(dir, &state->last_sf, n);
+ case QOP_TLB_COLOR_READ:
+ case QOP_MS_MASK:
add_write_dep(dir, &state->last_tlb, n);
break;
break;
}
- if (inst->dst.file == QFILE_VPM)
+ switch (inst->dst.file) {
+ case QFILE_VPM:
add_write_dep(dir, &state->last_vpm_write, n);
- else if (inst->dst.file == QFILE_TEMP)
+ break;
+
+ case QFILE_TEMP:
add_write_dep(dir, &state->last_temp_write[inst->dst.index], n);
+ break;
- if (inst->sf)
- add_write_dep(dir, &state->last_sf, n);
+ case QFILE_TLB_COLOR_WRITE:
+ case QFILE_TLB_COLOR_WRITE_MS:
+ case QFILE_TLB_Z_WRITE:
+ case QFILE_TLB_STENCIL_SETUP:
+ add_write_dep(dir, &state->last_tlb, n);
+ break;
- if (qir_depends_on_flags(inst)) {
- add_dep(dir, state->last_sf, n);
+ case QFILE_TEX_S_DIRECT:
+ case QFILE_TEX_S:
+ case QFILE_TEX_T:
+ case QFILE_TEX_R:
+ case QFILE_TEX_B:
+ /* Texturing setup gets scheduled in order, because
+ * the uniforms referenced by them have to land in a
+ * specific order.
+ */
+ add_write_dep(dir, &state->last_tex_coord, n);
+ break;
+
+ default:
+ break;
}
+
+ if (qir_depends_on_flags(inst))
+ add_dep(dir, state->last_sf, n);
+
+ if (inst->sf)
+ add_write_dep(dir, &state->last_sf, n);
}
static void
calculate_deps(&state, n);
- switch (inst->op) {
- case QOP_TEX_S:
- case QOP_TEX_T:
- case QOP_TEX_R:
- case QOP_TEX_B:
- case QOP_TEX_DIRECT:
- /* If the texture coordinate fifo is full,
- * block this on the last QOP_TEX_RESULT.
+ for (int i = 0; i < qir_get_nsrc(inst); i++) {
+ switch (inst->src[i].file) {
+ case QFILE_UNIF:
+ add_dep(state.dir, state.last_uniforms_reset, n);
+ break;
+ default:
+ break;
+ }
+ }
+
+ switch (inst->dst.file) {
+ case QFILE_TEX_S_DIRECT:
+ case QFILE_TEX_S:
+ case QFILE_TEX_T:
+ case QFILE_TEX_R:
+ case QFILE_TEX_B:
+ /* From the VC4 spec:
+ *
+ * "The TFREQ input FIFO holds two full lots of s,
+ * t, r, b data, plus associated setup data, per
+ * QPU, that is, there are eight data slots. For
+ * each texture request, slots are only consumed
+ * for the components of s, t, r, and b actually
+ * written. Thus the FIFO can hold four requests
+ * of just (s, t) data, or eight requests of just
+ * s data (for direct addressed data lookups).
+ *
+ * Note that there is one FIFO per QPU, and the
+ * FIFO has no concept of threads - that is,
+ * multi-threaded shaders must be careful to use
+ * only 1/2 the FIFO depth before reading
+ * back. Multi-threaded programs must also
+ * therefore always thread switch on texture
+ * fetch as the other thread may have data
+ * waiting in the FIFO."
+ *
+ * If the texture coordinate fifo is full, block this
+ * on the last QOP_TEX_RESULT.
*/
- if (state.tfreq_count == 8) {
+ if (state.tfreq_count == (c->fs_threaded ? 4 : 8)) {
block_until_tex_result(&state, n);
}
- /* If the texture result fifo is full, block
- * adding any more to it until the last
- * QOP_TEX_RESULT.
+ /* From the VC4 spec:
+ *
+ * "Since the maximum number of texture requests
+ * in the input (TFREQ) FIFO is four lots of (s,
+ * t) data, the output (TFRCV) FIFO is sized to
+ * holds four lots of max-size color data per
+ * QPU. For non-float color, reads are packed
+ * RGBA8888 data (one read per pixel). For 16-bit
+ * float color, two reads are necessary per
+ * pixel, with reads packed as RG1616 then
+ * BA1616. So per QPU there are eight color slots
+ * in the TFRCV FIFO."
+ *
+ * If the texture result fifo is full, block adding
+ * any more to it until the last QOP_TEX_RESULT.
*/
- if (inst->op == QOP_TEX_S ||
- inst->op == QOP_TEX_DIRECT) {
- if (state.tfrcv_count == 4)
+ if (inst->dst.file == QFILE_TEX_S ||
+ inst->dst.file == QFILE_TEX_S_DIRECT) {
+ if (state.tfrcv_count ==
+ (c->fs_threaded ? 2 : 4))
block_until_tex_result(&state, n);
state.tfrcv_count++;
}
state.tfreq_count++;
break;
+ default:
+ break;
+ }
+
+ switch (inst->op) {
case QOP_TEX_RESULT:
/* Results have to be fetched after the
* coordinate setup. Note that we're assuming
memset(&state.tex_fifo[state.tex_fifo_pos], 0,
sizeof(state.tex_fifo[0]));
break;
+
+ case QOP_UNIFORMS_RESET:
+ add_write_dep(state.dir, &state.last_uniforms_reset, n);
+ break;
+
default:
- assert(!qir_is_tex(inst));
break;
}
}
state->temp_writes[inst->dst.index] == 1)
cost--;
- for (int i = 0; i < qir_get_op_nsrc(inst->op); i++) {
- if (inst->src[i].file == QFILE_TEMP &&
- !BITSET_TEST(state->temp_live, inst->src[i].index)) {
- cost++;
+ for (int i = 0; i < qir_get_nsrc(inst); i++) {
+ if (inst->src[i].file != QFILE_TEMP ||
+ BITSET_TEST(state->temp_live, inst->src[i].index)) {
+ continue;
+ }
+
+ bool already_counted = false;
+ for (int j = 0; j < i; j++) {
+ if (inst->src[i].file == inst->src[j].file &&
+ inst->src[i].index == inst->src[j].index) {
+ already_counted = true;
+ }
}
+ if (!already_counted)
+ cost++;
}
return cost;
static bool
locks_scoreboard(struct qinst *inst)
{
- switch (inst->op) {
- case QOP_TLB_Z_WRITE:
- case QOP_TLB_COLOR_WRITE:
- case QOP_TLB_COLOR_WRITE_MS:
- case QOP_TLB_COLOR_READ:
+ if (inst->op == QOP_TLB_COLOR_READ)
+ return true;
+
+ switch (inst->dst.file) {
+ case QFILE_TLB_Z_WRITE:
+ case QFILE_TLB_COLOR_WRITE:
+ case QFILE_TLB_COLOR_WRITE_MS:
return true;
default:
return false;
struct schedule_node *chosen = NULL;
list_for_each_entry(struct schedule_node, n, &state->worklist, link) {
+ /* The branches aren't being tracked as dependencies. Make
+ * sure that they stay scheduled as the last instruction of
+ * the block, which is to say the first one we choose to
+ * schedule.
+ */
+ if (n->inst->op == QOP_BRANCH)
+ return n;
+
if (!chosen) {
chosen = n;
continue;
}
/* If we would block on the previously chosen node, but would
- * block less on this one, then then prefer it.
+ * block less on this one, then prefer it.
*/
if (chosen->unblocked_time > state->time &&
n->unblocked_time < chosen->unblocked_time) {
static uint32_t
latency_between(struct schedule_node *before, struct schedule_node *after)
{
- if ((before->inst->op == QOP_TEX_S ||
- before->inst->op == QOP_TEX_DIRECT) &&
+ if ((before->inst->dst.file == QFILE_TEX_S ||
+ before->inst->dst.file == QFILE_TEX_S_DIRECT) &&
after->inst->op == QOP_TEX_RESULT)
return 100;
+ switch (before->inst->op) {
+ case QOP_RCP:
+ case QOP_RSQ:
+ case QOP_EXP2:
+ case QOP_LOG2:
+ for (int i = 0; i < qir_get_nsrc(after->inst); i++) {
+ if (after->inst->src[i].file ==
+ before->inst->dst.file &&
+ after->inst->src[i].index ==
+ before->inst->dst.index) {
+ /* There are two QPU delay slots before we can
+ * read a math result, which could be up to 4
+ * QIR instructions if they packed well.
+ */
+ return 4;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
return 1;
}
compute_delay(n->children[i]);
n->delay = MAX2(n->delay,
n->children[i]->delay +
- latency_between(n, n->children[i]));
+ latency_between(n->children[i], n));
}
}
}
static void
-schedule_instructions(struct vc4_compile *c, struct schedule_state *state)
+schedule_instructions(struct vc4_compile *c,
+ struct qblock *block, struct schedule_state *state)
{
if (debug) {
fprintf(stderr, "initial deps:\n");
/* Schedule this instruction back onto the QIR list. */
list_del(&chosen->link);
- list_add(&inst->link, &c->instructions);
+ list_add(&inst->link, &block->instructions);
/* Now that we've scheduled a new instruction, some of its
* children can be promoted to the list of instructions ready to
child->unblocked_time = MAX2(child->unblocked_time,
state->time +
- latency_between(chosen,
- child));
+ latency_between(child,
+ chosen));
child->parent_count--;
if (child->parent_count == 0)
list_add(&child->link, &state->worklist);
}
/* Update our tracking of register pressure. */
- for (int i = 0; i < qir_get_op_nsrc(inst->op); i++) {
+ for (int i = 0; i < qir_get_nsrc(inst); i++) {
if (inst->src[i].file == QFILE_TEMP)
BITSET_SET(state->temp_live, inst->src[i].index);
}
}
}
-void
-qir_schedule_instructions(struct vc4_compile *c)
+static void
+qir_schedule_instructions_block(struct vc4_compile *c,
+ struct qblock *block)
{
void *mem_ctx = ralloc_context(NULL);
- struct schedule_state state = { 0 };
-
- if (debug) {
- fprintf(stderr, "Pre-schedule instructions\n");
- qir_dump(c);
- }
+ struct schedule_state state = { { 0 } };
state.temp_writes = rzalloc_array(mem_ctx, uint32_t, c->num_temps);
state.temp_live = rzalloc_array(mem_ctx, BITSET_WORD,
list_inithead(&state.worklist);
/* Wrap each instruction in a scheduler structure. */
- list_for_each_entry_safe(struct qinst, inst, &c->instructions, link) {
+ qir_for_each_inst_safe(inst, block) {
struct schedule_node *n = rzalloc(mem_ctx, struct schedule_node);
n->inst = inst;
list_for_each_entry(struct schedule_node, n, &state.worklist, link)
compute_delay(n);
- schedule_instructions(c, &state);
+ schedule_instructions(c, block, &state);
+
+ ralloc_free(mem_ctx);
+}
+
+void
+qir_schedule_instructions(struct vc4_compile *c)
+{
if (debug) {
- fprintf(stderr, "Post-schedule instructions\n");
+ fprintf(stderr, "Pre-schedule instructions\n");
qir_dump(c);
}
- ralloc_free(mem_ctx);
+ qir_for_each_block(block, c)
+ qir_schedule_instructions_block(c, block);
+
+ if (debug) {
+ fprintf(stderr, "Post-schedule instructions\n");
+ qir_dump(c);
+ }
}