util_dynarray_init(&batch->scissor, ctx);
util_dynarray_init(&batch->depth_bias, ctx);
+ util_dynarray_init(&batch->occlusion_queries, ctx);
batch->clear = 0;
batch->draw = 0;
if (ctx->batch == batch)
ctx->batch = NULL;
+ agx_finish_batch_occlusion_queries(batch);
+ batch->occlusion_buffer.cpu = NULL;
+ batch->occlusion_buffer.gpu = 0;
+
/* There is no more writer for anything we wrote recorded on this context */
hash_table_foreach(ctx->writer, ent) {
if (ent->data == batch)
util_dynarray_fini(&batch->scissor);
util_dynarray_fini(&batch->depth_bias);
+ util_dynarray_fini(&batch->occlusion_queries);
util_unreference_framebuffer_state(&batch->key);
unsigned batch_idx = agx_batch_idx(batch);
assert(!_mesa_hash_table_search(ctx->writer, rsrc));
_mesa_hash_table_insert(ctx->writer, rsrc, batch);
}
+
+/*
+ * The OpenGL specification says that
+ *
+ * It must always be true that if any query object returns a result
+ * available of TRUE, all queries of the same type issued prior to that
+ * query must also return TRUE.
+ *
+ * To implement this, we need to be able to flush all batches writing occlusion
+ * queries so we ensure ordering.
+ */
+void
+agx_flush_occlusion_queries(struct agx_context *ctx)
+{
+ unsigned i;
+ foreach_batch(ctx, i) {
+ struct agx_batch *other = &ctx->batches.slots[i];
+
+ if (other->occlusion_queries.size != 0)
+ agx_flush_batch_for_reason(ctx, other, "Occlusion query ordering");
+ }
+}
*/
agx_batch_add_bo(batch, batch->encoder);
+ /* Occlusion queries are allocated as a contiguous pool */
+ unsigned oq_count = util_dynarray_num_elements(&batch->occlusion_queries,
+ struct agx_query *);
+ size_t oq_size = oq_count * sizeof(uint64_t);
+
+ if (oq_size) {
+ batch->occlusion_buffer = agx_pool_alloc_aligned(&batch->pool, oq_size, 64);
+ memset(batch->occlusion_buffer.cpu, 0, oq_size);
+ } else {
+ batch->occlusion_buffer.gpu = 0;
+ }
+
unsigned handle_count =
agx_batch_num_bo(batch) +
agx_pool_num_bos(&batch->pool) +
encoder_id,
scissor,
zbias,
+ batch->occlusion_buffer.gpu,
pipeline_background,
pipeline_background_partial,
pipeline_store,
{
struct agx_query *query = calloc(1, sizeof(struct agx_query));
+ query->type = query_type;
+ query->index = index;
+
return (struct pipe_query *)query;
}
}
static bool
-agx_begin_query(struct pipe_context *ctx, struct pipe_query *query)
+agx_begin_query(struct pipe_context *pctx, struct pipe_query *pquery)
{
- return true;
+ struct agx_context *ctx = agx_context(pctx);
+ struct agx_query *query = (struct agx_query *) pquery;
+
+ switch (query->type) {
+ case PIPE_QUERY_OCCLUSION_COUNTER:
+ case PIPE_QUERY_OCCLUSION_PREDICATE:
+ case PIPE_QUERY_OCCLUSION_PREDICATE_CONSERVATIVE:
+ ctx->occlusion_query = query;
+ ctx->dirty |= AGX_DIRTY_QUERY;
+
+ /* begin_query zeroes, flush so we can do that write. If anything (i.e.
+ * other than piglit) actually hits this, we could shadow the query to
+ * avoid the flush.
+ */
+ if (query->writer)
+ agx_flush_batch_for_reason(ctx, query->writer, "Occlusion overwritten");
+
+ assert(query->writer == NULL);
+
+ query->value = 0;
+ return true;
+
+ default:
+ return false;
+ }
}
static bool
-agx_end_query(struct pipe_context *ctx, struct pipe_query *query)
+agx_end_query(struct pipe_context *pctx, struct pipe_query *pquery)
{
- return true;
+ struct agx_context *ctx = agx_context(pctx);
+ struct agx_query *query = (struct agx_query *) pquery;
+
+ switch (query->type) {
+ case PIPE_QUERY_OCCLUSION_COUNTER:
+ case PIPE_QUERY_OCCLUSION_PREDICATE:
+ case PIPE_QUERY_OCCLUSION_PREDICATE_CONSERVATIVE:
+ ctx->occlusion_query = NULL;
+ ctx->dirty |= AGX_DIRTY_QUERY;
+ return true;
+
+ default:
+ return false;
+ }
}
static bool
-agx_get_query_result(struct pipe_context *ctx,
- struct pipe_query *query,
+agx_get_query_result(struct pipe_context *pctx,
+ struct pipe_query *pquery,
bool wait,
union pipe_query_result *vresult)
{
- uint64_t *result = (uint64_t*)vresult;
+ struct agx_query *query = (struct agx_query *) pquery;
+ struct agx_context *ctx = agx_context(pctx);
- *result = 0;
- return true;
+ switch (query->type) {
+ case PIPE_QUERY_OCCLUSION_COUNTER:
+ case PIPE_QUERY_OCCLUSION_PREDICATE:
+ case PIPE_QUERY_OCCLUSION_PREDICATE_CONSERVATIVE:
+ if (query->writer != NULL) {
+ assert(query->writer->occlusion_queries.size != 0);
+
+ /* Querying the result forces a query to finish in finite time, so we
+ * need to flush regardless. Furthermore, we need all earlier queries
+ * to finish before this query, so we flush all batches writing queries
+ * now. Yes, this sucks for tilers.
+ */
+ agx_flush_occlusion_queries(ctx);
+
+ /* TODO: Respect wait when we have real sync */
+ }
+
+ assert(query->writer == NULL && "cleared when cleaning up batch");
+
+ if (query->type == PIPE_QUERY_OCCLUSION_COUNTER)
+ vresult->u64 = query->value;
+ else
+ vresult->b = query->value;
+
+ return true;
+
+ default:
+ unreachable("Other queries not yet supported");
+ }
}
static void
agx_set_active_query_state(struct pipe_context *pipe, bool enable)
{
+ struct agx_context *ctx = agx_context(pipe);
+
+ ctx->active_queries = enable;
+ ctx->dirty |= AGX_DIRTY_QUERY;
+}
+
+uint16_t
+agx_get_oq_index(struct agx_batch *batch, struct agx_query *query)
+{
+ /* If written by another batch, flush it now. If this affects real apps, we
+ * could avoid this flush by merging query results.
+ */
+ if (query->writer && query->writer != batch) {
+ agx_flush_batch_for_reason(batch->ctx, query->writer,
+ "Multiple occlusion query writers");
+ }
+
+ /* Allocate if needed */
+ if (query->writer == NULL) {
+ query->writer = batch;
+ query->writer_index = util_dynarray_num_elements(&batch->occlusion_queries,
+ struct agx_query *);
+
+ util_dynarray_append(&batch->occlusion_queries, struct agx_query *, query);
+ }
+
+ assert(query->writer == batch);
+ assert(*util_dynarray_element(&batch->occlusion_queries, struct agx_query *,
+ query->writer_index) == query);
+
+ return query->writer_index;
+}
+
+void
+agx_finish_batch_occlusion_queries(struct agx_batch *batch)
+{
+ uint64_t *results = (uint64_t *) batch->occlusion_buffer.cpu;
+
+ util_dynarray_foreach(&batch->occlusion_queries, struct agx_query *, it) {
+ struct agx_query *query = *it;
+ assert(query->writer == batch);
+
+ /* Get the result for this batch. If results is NULL, it means that no
+ * draws actually enabled any occlusion queries, so there's no change.
+ */
+ if (results != NULL) {
+ uint64_t result = *(results++);
+
+ /* Accumulate with the previous result (e.g. in case we split a frame
+ * into multiple batches so an API-level query spans multiple batches).
+ */
+ if (query->type == PIPE_QUERY_OCCLUSION_COUNTER)
+ query->value += result;
+ else
+ query->value |= (!!result);
+ }
+
+ query->writer = NULL;
+ query->writer_index = 0;
+ }
}
void
pctx->end_query = agx_end_query;
pctx->get_query_result = agx_get_query_result;
pctx->set_active_query_state = agx_set_active_query_state;
+
+ /* By default queries are active */
+ agx_context(pctx)->active_queries = true;
}
.w_clamp = true,
.varying_word_1 = true,
.cull_2 = true,
- .occlusion_query = true,
.occlusion_query_2 = true,
.output_unknown = true,
.varying_word_2 = true,
agx_ppp_push(&ppp, W_CLAMP, cfg) cfg.w_clamp = 1e-10;
agx_ppp_push(&ppp, VARYING_1, cfg);
agx_ppp_push(&ppp, CULL_2, cfg);
- agx_ppp_push(&ppp, FRAGMENT_OCCLUSION_QUERY, cfg);
agx_ppp_push(&ppp, FRAGMENT_OCCLUSION_QUERY_2, cfg);
agx_ppp_push(&ppp, OUTPUT_UNKNOWN, cfg);
agx_ppp_push(&ppp, VARYING_2, cfg);
(is_points && IS_DIRTY(SPRITE_COORD_MODE));
bool fragment_control_dirty = IS_DIRTY(ZS) || IS_DIRTY(RS) ||
- IS_DIRTY(PRIM);
+ IS_DIRTY(PRIM) || IS_DIRTY(QUERY);
bool fragment_face_dirty = IS_DIRTY(ZS) || IS_DIRTY(STENCIL_REF) ||
IS_DIRTY(RS);
.varying_word_0 = IS_DIRTY(VS_PROG),
.cull = IS_DIRTY(RS),
.fragment_shader = IS_DIRTY(FS) || varyings_dirty,
+ .occlusion_query = IS_DIRTY(QUERY),
.output_size = IS_DIRTY(VS_PROG),
});
if (fragment_control_dirty) {
agx_ppp_push(&ppp, FRAGMENT_CONTROL, cfg) {
+ if (ctx->active_queries && ctx->occlusion_query) {
+ if (ctx->occlusion_query->type == PIPE_QUERY_OCCLUSION_COUNTER)
+ cfg.visibility_mode = AGX_VISIBILITY_MODE_COUNTING;
+ else
+ cfg.visibility_mode = AGX_VISIBILITY_MODE_BOOLEAN;
+ }
+
cfg.stencil_test_enable = ctx->zs->base.stencil[0].enabled;
cfg.two_sided_stencil = ctx->zs->base.stencil[1].enabled;
cfg.depth_bias_enable = rast->base.offset_tri;
}
}
+ if (IS_DIRTY(QUERY)) {
+ agx_ppp_push(&ppp, FRAGMENT_OCCLUSION_QUERY, cfg) {
+ if (ctx->active_queries && ctx->occlusion_query) {
+ cfg.index = agx_get_oq_index(batch, ctx->occlusion_query);
+ } else {
+ cfg.index = 0;
+ }
+ }
+ }
+
if (IS_DIRTY(VS_PROG)) {
agx_ppp_push(&ppp, OUTPUT_SIZE, cfg)
cfg.count = vs->info.varyings.vs.nr_index;
/* Scissor and depth-bias descriptors, uploaded at GPU time */
struct util_dynarray scissor, depth_bias;
+
+ /* Indexed occlusion queries within the occlusion buffer, and the occlusion
+ * buffer itself which is allocated at submit time.
+ */
+ struct util_dynarray occlusion_queries;
+ struct agx_ptr occlusion_buffer;
};
struct agx_zsa {
AGX_DIRTY_FS_PROG = BITFIELD_BIT(11),
AGX_DIRTY_BLEND = BITFIELD_BIT(12),
+ AGX_DIRTY_QUERY = BITFIELD_BIT(13),
};
#define AGX_MAX_BATCHES (2)
bool cond_cond;
enum pipe_render_cond_flag cond_mode;
+ struct agx_query *occlusion_query;
+ bool active_queries;
+
struct util_debug_callback debug;
bool is_noop;
};
struct agx_query {
- unsigned query;
+ unsigned type;
+ unsigned index;
+
+ /* Invariant for occlusion queries:
+ *
+ * writer != NULL => writer->occlusion_queries[writer_index] == this, and
+ * writer == NULL => no batch such that this in batch->occlusion_queries
+ */
+ struct agx_batch *writer;
+ unsigned writer_index;
+
+ /* For occlusion queries, which use some CPU work */
+ uint64_t value;
};
struct agx_sampler_state {
void agx_flush_all(struct agx_context *ctx, const char *reason);
void agx_flush_readers(struct agx_context *ctx, struct agx_resource *rsrc, const char *reason);
void agx_flush_writer(struct agx_context *ctx, struct agx_resource *rsrc, const char *reason);
+void agx_flush_batches_writing_occlusion_queries(struct agx_context *ctx);
+void agx_flush_occlusion_queries(struct agx_context *ctx);
/* Use these instead of batch_add_bo for proper resource tracking */
void agx_batch_reads(struct agx_batch *batch, struct agx_resource *rsrc);
uint64_t
agx_build_meta(struct agx_batch *batch, bool store, bool partial_render);
+/* Query management */
+uint16_t
+agx_get_oq_index(struct agx_batch *batch, struct agx_query *query);
+
+void
+agx_finish_batch_occlusion_queries(struct agx_batch *batch);
+
#endif
uint64_t encoder_id,
uint64_t scissor_ptr,
uint64_t depth_bias_ptr,
+ uint64_t occlusion_ptr,
uint32_t pipeline_clear,
uint32_t pipeline_load,
uint32_t pipeline_store,
cfg.store_pipeline = pipeline_store;
cfg.scissor_array = scissor_ptr;
cfg.depth_bias_array = depth_bias_ptr;
+ cfg.visibility_result_buffer = occlusion_ptr;
if (framebuffer->zsbuf) {
struct pipe_surface *zsbuf = framebuffer->zsbuf;
uint64_t encoder_id,
uint64_t scissor_ptr,
uint64_t depth_bias_ptr,
+ uint64_t occlusion_ptr,
uint32_t pipeline_clear,
uint32_t pipeline_load,
uint32_t pipeline_store,