+/**
+ * Create a staging texture and blit the requested region to it.
+ */
+static struct pipe_resource *
+blit_to_staging(struct st_context *st, struct st_renderbuffer *strb,
+ bool invert_y,
+ GLint x, GLint y, GLsizei width, GLsizei height,
+ GLenum format,
+ enum pipe_format src_format, enum pipe_format dst_format)
+{
+ struct pipe_context *pipe = st->pipe;
+ struct pipe_screen *screen = pipe->screen;
+ struct pipe_resource dst_templ;
+ struct pipe_resource *dst;
+ struct pipe_blit_info blit;
+
+ /* We are creating a texture of the size of the region being read back.
+ * Need to check for NPOT texture support. */
+ if (!screen->get_param(screen, PIPE_CAP_NPOT_TEXTURES) &&
+ (!util_is_power_of_two_or_zero(width) ||
+ !util_is_power_of_two_or_zero(height)))
+ return NULL;
+
+ /* create the destination texture */
+ memset(&dst_templ, 0, sizeof(dst_templ));
+ dst_templ.target = PIPE_TEXTURE_2D;
+ dst_templ.format = dst_format;
+ if (util_format_is_depth_or_stencil(dst_format))
+ dst_templ.bind |= PIPE_BIND_DEPTH_STENCIL;
+ else
+ dst_templ.bind |= PIPE_BIND_RENDER_TARGET;
+ dst_templ.usage = PIPE_USAGE_STAGING;
+
+ st_gl_texture_dims_to_pipe_dims(GL_TEXTURE_2D, width, height, 1,
+ &dst_templ.width0, &dst_templ.height0,
+ &dst_templ.depth0, &dst_templ.array_size);
+
+ dst = screen->resource_create(screen, &dst_templ);
+ if (!dst)
+ return NULL;
+
+ memset(&blit, 0, sizeof(blit));
+ blit.src.resource = strb->texture;
+ blit.src.level = strb->surface->u.tex.level;
+ blit.src.format = src_format;
+ blit.dst.resource = dst;
+ blit.dst.level = 0;
+ blit.dst.format = dst->format;
+ blit.src.box.x = x;
+ blit.dst.box.x = 0;
+ blit.src.box.y = y;
+ blit.dst.box.y = 0;
+ blit.src.box.z = strb->surface->u.tex.first_layer;
+ blit.dst.box.z = 0;
+ blit.src.box.width = blit.dst.box.width = width;
+ blit.src.box.height = blit.dst.box.height = height;
+ blit.src.box.depth = blit.dst.box.depth = 1;
+ blit.mask = st_get_blit_mask(strb->Base._BaseFormat, format);
+ blit.filter = PIPE_TEX_FILTER_NEAREST;
+ blit.scissor_enable = FALSE;
+
+ if (invert_y) {
+ blit.src.box.y = strb->Base.Height - blit.src.box.y;
+ blit.src.box.height = -blit.src.box.height;
+ }
+
+ /* blit */
+ st->pipe->blit(st->pipe, &blit);
+
+ return dst;
+}
+
+static struct pipe_resource *
+try_cached_readpixels(struct st_context *st, struct st_renderbuffer *strb,
+ bool invert_y,
+ GLsizei width, GLsizei height,
+ GLenum format,
+ enum pipe_format src_format, enum pipe_format dst_format)
+{
+ struct pipe_resource *src = strb->texture;
+ struct pipe_resource *dst = NULL;
+
+ if (ST_DEBUG & DEBUG_NOREADPIXCACHE)
+ return NULL;
+
+ /* Reset cache after invalidation or switch of parameters. */
+ if (st->readpix_cache.src != src ||
+ st->readpix_cache.dst_format != dst_format ||
+ st->readpix_cache.level != strb->surface->u.tex.level ||
+ st->readpix_cache.layer != strb->surface->u.tex.first_layer) {
+ pipe_resource_reference(&st->readpix_cache.src, src);
+ pipe_resource_reference(&st->readpix_cache.cache, NULL);
+ st->readpix_cache.dst_format = dst_format;
+ st->readpix_cache.level = strb->surface->u.tex.level;
+ st->readpix_cache.layer = strb->surface->u.tex.first_layer;
+ st->readpix_cache.hits = 0;
+ }
+
+ /* Decide whether to trigger the cache. */
+ if (!st->readpix_cache.cache) {
+ if (!strb->use_readpix_cache && !ALWAYS_READPIXELS_CACHE) {
+ /* Heuristic: If previous successive calls read at least a fraction
+ * of the surface _and_ we read again, trigger the cache.
+ */
+ unsigned threshold = MAX2(1, strb->Base.Width * strb->Base.Height / 8);
+
+ if (st->readpix_cache.hits < threshold) {
+ st->readpix_cache.hits += width * height;
+ return NULL;
+ }
+
+ strb->use_readpix_cache = true;
+ }
+
+ /* Fill the cache */
+ st->readpix_cache.cache = blit_to_staging(st, strb, invert_y,
+ 0, 0,
+ strb->Base.Width,
+ strb->Base.Height, format,
+ src_format, dst_format);
+ }
+
+ /* Return an owning reference to stay consistent with the non-cached path */
+ pipe_resource_reference(&dst, st->readpix_cache.cache);
+
+ return dst;
+}
+