| /* |
| * Copyright (c) 2022 Amazon.com, Inc. or its affiliates. |
| * Copyright (C) 2019-2022 Collabora, Ltd. |
| * Copyright (C) 2019 Red Hat Inc. |
| * Copyright (C) 2018 Alyssa Rosenzweig |
| * Copyright © 2017 Intel Corporation |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a |
| * copy of this software and associated documentation files (the "Software"), |
| * to deal in the Software without restriction, including without limitation |
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| * and/or sell copies of the Software, and to permit persons to whom the |
| * Software is furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice (including the next |
| * paragraph) shall be included in all copies or substantial portions of the |
| * Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. |
| * |
| * Authors (Collabora): |
| * Alyssa Rosenzweig <alyssa.rosenzweig@collabora.com> |
| * |
| */ |
| |
| #include "pan_context.h" |
| #include "pan_bo.h" |
| #include "pan_shader.h" |
| #include "util/u_memory.h" |
| #include "nir/tgsi_to_nir.h" |
| #include "nir_serialize.h" |
| |
| static struct panfrost_uncompiled_shader * |
| panfrost_alloc_shader(const nir_shader *nir) |
| { |
| struct panfrost_uncompiled_shader *so = |
| rzalloc(NULL, struct panfrost_uncompiled_shader); |
| |
| simple_mtx_init(&so->lock, mtx_plain); |
| util_dynarray_init(&so->variants, so); |
| |
| so->nir = nir; |
| |
| /* Serialize the NIR to a binary blob that we can hash for the disk |
| * cache. Drop unnecessary information (like variable names) so the |
| * serialized NIR is smaller, and also to let us detect more isomorphic |
| * shaders when hashing, increasing cache hits. |
| */ |
| struct blob blob; |
| blob_init(&blob); |
| nir_serialize(&blob, nir, true); |
| _mesa_sha1_compute(blob.data, blob.size, so->nir_sha1); |
| blob_finish(&blob); |
| |
| return so; |
| } |
| |
| static struct panfrost_compiled_shader * |
| panfrost_alloc_variant(struct panfrost_uncompiled_shader *so) |
| { |
| return util_dynarray_grow(&so->variants, struct panfrost_compiled_shader, 1); |
| } |
| |
| static void |
| panfrost_shader_compile(struct panfrost_screen *screen, |
| const nir_shader *ir, |
| struct util_debug_callback *dbg, |
| struct panfrost_shader_key *key, |
| unsigned req_local_mem, |
| unsigned fixed_varying_mask, |
| struct panfrost_shader_binary *out) |
| { |
| struct panfrost_device *dev = pan_device(&screen->base); |
| |
| nir_shader *s = nir_shader_clone(NULL, ir); |
| |
| struct panfrost_compile_inputs inputs = { |
| .debug = dbg, |
| .gpu_id = dev->gpu_id, |
| .fixed_sysval_ubo = -1, |
| }; |
| |
| /* Lower this early so the backends don't have to worry about it */ |
| if (s->info.stage == MESA_SHADER_FRAGMENT) { |
| inputs.fixed_varying_mask = key->fs.fixed_varying_mask; |
| |
| if (s->info.outputs_written & BITFIELD_BIT(FRAG_RESULT_COLOR)) { |
| NIR_PASS_V(s, nir_lower_fragcolor, |
| key->fs.nr_cbufs_for_fragcolor); |
| } |
| |
| if (key->fs.sprite_coord_enable) { |
| NIR_PASS_V(s, nir_lower_texcoord_replace, |
| key->fs.sprite_coord_enable, |
| true /* point coord is sysval */, |
| false /* Y-invert */); |
| } |
| |
| if (key->fs.clip_plane_enable) { |
| NIR_PASS_V(s, nir_lower_clip_fs, |
| key->fs.clip_plane_enable, |
| false); |
| } |
| |
| memcpy(inputs.rt_formats, key->fs.rt_formats, sizeof(inputs.rt_formats)); |
| } else if (s->info.stage == MESA_SHADER_VERTEX) { |
| inputs.fixed_varying_mask = fixed_varying_mask; |
| |
| /* No IDVS for internal XFB shaders */ |
| inputs.no_idvs = s->info.has_transform_feedback_varyings; |
| } |
| |
| util_dynarray_init(&out->binary, NULL); |
| screen->vtbl.compile_shader(s, &inputs, &out->binary, &out->info); |
| |
| assert(req_local_mem >= out->info.wls_size); |
| out->info.wls_size = req_local_mem; |
| |
| /* In both clone and tgsi_to_nir paths, the shader is ralloc'd against |
| * a NULL context |
| */ |
| ralloc_free(s); |
| } |
| |
| static void |
| panfrost_shader_get(struct pipe_screen *pscreen, |
| struct panfrost_pool *shader_pool, |
| struct panfrost_pool *desc_pool, |
| struct panfrost_uncompiled_shader *uncompiled, |
| struct util_debug_callback *dbg, |
| struct panfrost_compiled_shader *state, |
| unsigned req_local_mem) |
| { |
| struct panfrost_screen *screen = pan_screen(pscreen); |
| struct panfrost_device *dev = pan_device(pscreen); |
| |
| struct panfrost_shader_binary res = { 0 }; |
| |
| /* Try to retrieve the variant from the disk cache. If that fails, |
| * compile a new variant and store in the disk cache for later reuse. |
| */ |
| if (!panfrost_disk_cache_retrieve(screen->disk_cache, uncompiled, &state->key, &res)) { |
| panfrost_shader_compile(screen, uncompiled->nir, dbg, &state->key, |
| req_local_mem, |
| uncompiled->fixed_varying_mask, &res); |
| |
| panfrost_disk_cache_store(screen->disk_cache, uncompiled, &state->key, &res); |
| } |
| |
| state->info = res.info; |
| |
| if (res.binary.size) { |
| state->bin = panfrost_pool_take_ref(shader_pool, |
| pan_pool_upload_aligned(&shader_pool->base, |
| res.binary.data, res.binary.size, 128)); |
| } |
| |
| util_dynarray_fini(&res.binary); |
| |
| /* Don't upload RSD for fragment shaders since they need draw-time |
| * merging for e.g. depth/stencil/alpha. RSDs are replaced by simpler |
| * shader program descriptors on Valhall, which can be preuploaded even |
| * for fragment shaders. */ |
| bool upload = !(uncompiled->nir->info.stage == MESA_SHADER_FRAGMENT && dev->arch <= 7); |
| screen->vtbl.prepare_shader(state, desc_pool, upload); |
| |
| panfrost_analyze_sysvals(state); |
| } |
| |
| static void |
| panfrost_build_key(struct panfrost_context *ctx, |
| struct panfrost_shader_key *key, |
| const nir_shader *nir) |
| { |
| /* We don't currently have vertex shader variants */ |
| if (nir->info.stage != MESA_SHADER_FRAGMENT) |
| return; |
| |
| struct panfrost_device *dev = pan_device(ctx->base.screen); |
| struct pipe_framebuffer_state *fb = &ctx->pipe_framebuffer; |
| struct pipe_rasterizer_state *rast = (void *) ctx->rasterizer; |
| struct panfrost_uncompiled_shader *vs = ctx->uncompiled[MESA_SHADER_VERTEX]; |
| |
| /* gl_FragColor lowering needs the number of colour buffers */ |
| if (nir->info.outputs_written & BITFIELD_BIT(FRAG_RESULT_COLOR)) { |
| key->fs.nr_cbufs_for_fragcolor = fb->nr_cbufs; |
| } |
| |
| /* Point sprite lowering needed on Bifrost and newer */ |
| if (dev->arch >= 6 && rast && ctx->active_prim == PIPE_PRIM_POINTS) { |
| key->fs.sprite_coord_enable = rast->sprite_coord_enable; |
| } |
| |
| /* User clip plane lowering needed everywhere */ |
| if (rast) { |
| key->fs.clip_plane_enable = rast->clip_plane_enable; |
| } |
| |
| if (dev->arch <= 5) { |
| u_foreach_bit(i, (nir->info.outputs_read >> FRAG_RESULT_DATA0)) { |
| enum pipe_format fmt = PIPE_FORMAT_R8G8B8A8_UNORM; |
| |
| if ((fb->nr_cbufs > i) && fb->cbufs[i]) |
| fmt = fb->cbufs[i]->format; |
| |
| if (panfrost_blendable_formats_v6[fmt].internal) |
| fmt = PIPE_FORMAT_NONE; |
| |
| key->fs.rt_formats[i] = fmt; |
| } |
| } |
| |
| /* Funny desktop GL varying lowering on Valhall */ |
| if (dev->arch >= 9) { |
| assert(vs != NULL && "too early"); |
| key->fs.fixed_varying_mask = vs->fixed_varying_mask; |
| } |
| } |
| |
| /** |
| * Fix an uncompiled shader's stream output info, and produce a bitmask |
| * of which VARYING_SLOT_* are captured for stream output. |
| * |
| * Core Gallium stores output->register_index as a "slot" number, where |
| * slots are assigned consecutively to all outputs in info->outputs_written. |
| * This naive packing of outputs doesn't work for us - we too have slots, |
| * but the layout is defined by the VUE map, which we won't have until we |
| * compile a specific shader variant. So, we remap these and simply store |
| * VARYING_SLOT_* in our copy's output->register_index fields. |
| * |
| * We then produce a bitmask of outputs which are used for SO. |
| * |
| * Implementation from iris. |
| */ |
| |
| static uint64_t |
| update_so_info(struct pipe_stream_output_info *so_info, |
| uint64_t outputs_written) |
| { |
| uint64_t so_outputs = 0; |
| uint8_t reverse_map[64] = {0}; |
| unsigned slot = 0; |
| |
| while (outputs_written) |
| reverse_map[slot++] = u_bit_scan64(&outputs_written); |
| |
| for (unsigned i = 0; i < so_info->num_outputs; i++) { |
| struct pipe_stream_output *output = &so_info->output[i]; |
| |
| /* Map Gallium's condensed "slots" back to real VARYING_SLOT_* enums */ |
| output->register_index = reverse_map[output->register_index]; |
| |
| so_outputs |= 1ull << output->register_index; |
| } |
| |
| return so_outputs; |
| } |
| |
| static struct panfrost_compiled_shader * |
| panfrost_new_variant_locked( |
| struct panfrost_context *ctx, |
| struct panfrost_uncompiled_shader *uncompiled, |
| struct panfrost_shader_key *key) |
| { |
| struct panfrost_compiled_shader *prog = panfrost_alloc_variant(uncompiled); |
| |
| *prog = (struct panfrost_compiled_shader) { |
| .key = *key, |
| .stream_output = uncompiled->stream_output, |
| }; |
| |
| panfrost_shader_get(ctx->base.screen, &ctx->shaders, &ctx->descs, |
| uncompiled, &ctx->base.debug, prog, 0); |
| |
| /* Fixup the stream out information */ |
| prog->so_mask = |
| update_so_info(&prog->stream_output, |
| prog->info.outputs_written); |
| |
| prog->earlyzs = pan_earlyzs_analyze(&prog->info); |
| |
| return prog; |
| } |
| |
| static void |
| panfrost_bind_shader_state( |
| struct pipe_context *pctx, |
| void *hwcso, |
| enum pipe_shader_type type) |
| { |
| struct panfrost_context *ctx = pan_context(pctx); |
| ctx->uncompiled[type] = hwcso; |
| ctx->prog[type] = NULL; |
| |
| ctx->dirty |= PAN_DIRTY_TLS_SIZE; |
| ctx->dirty_shader[type] |= PAN_DIRTY_STAGE_SHADER; |
| |
| if (hwcso) |
| panfrost_update_shader_variant(ctx, type); |
| } |
| |
| void |
| panfrost_update_shader_variant(struct panfrost_context *ctx, |
| enum pipe_shader_type type) |
| { |
| /* No shader variants for compute */ |
| if (type == PIPE_SHADER_COMPUTE) |
| return; |
| |
| /* We need linking information, defer this */ |
| if (type == PIPE_SHADER_FRAGMENT && !ctx->uncompiled[PIPE_SHADER_VERTEX]) |
| return; |
| |
| /* Also defer, happens with GALLIUM_HUD */ |
| if (!ctx->uncompiled[type]) |
| return; |
| |
| /* Match the appropriate variant */ |
| struct panfrost_uncompiled_shader *uncompiled = ctx->uncompiled[type]; |
| struct panfrost_compiled_shader *compiled = NULL; |
| |
| simple_mtx_lock(&uncompiled->lock); |
| |
| struct panfrost_shader_key key = { 0 }; |
| panfrost_build_key(ctx, &key, uncompiled->nir); |
| |
| util_dynarray_foreach(&uncompiled->variants, struct panfrost_compiled_shader, so) { |
| if (memcmp(&key, &so->key, sizeof(key)) == 0) { |
| compiled = so; |
| break; |
| } |
| } |
| |
| if (compiled == NULL) |
| compiled = panfrost_new_variant_locked(ctx, uncompiled, &key); |
| |
| ctx->prog[type] = compiled; |
| |
| /* TODO: it would be more efficient to release the lock before |
| * compiling instead of after, but that can race if thread A compiles a |
| * variant while thread B searches for that same variant */ |
| simple_mtx_unlock(&uncompiled->lock); |
| } |
| |
| static void |
| panfrost_bind_vs_state(struct pipe_context *pctx, void *hwcso) |
| { |
| panfrost_bind_shader_state(pctx, hwcso, PIPE_SHADER_VERTEX); |
| |
| /* Fragment shaders are linked with vertex shaders */ |
| struct panfrost_context *ctx = pan_context(pctx); |
| panfrost_update_shader_variant(ctx, PIPE_SHADER_FRAGMENT); |
| } |
| |
| static void |
| panfrost_bind_fs_state(struct pipe_context *pctx, void *hwcso) |
| { |
| panfrost_bind_shader_state(pctx, hwcso, PIPE_SHADER_FRAGMENT); |
| } |
| |
| static void * |
| panfrost_create_shader_state( |
| struct pipe_context *pctx, |
| const struct pipe_shader_state *cso) |
| { |
| nir_shader *nir = (cso->type == PIPE_SHADER_IR_TGSI) ? |
| tgsi_to_nir(cso->tokens, pctx->screen, false) : |
| cso->ir.nir; |
| |
| struct panfrost_uncompiled_shader *so = panfrost_alloc_shader(nir); |
| |
| /* The driver gets ownership of the nir_shader for graphics. The NIR is |
| * ralloc'd. Free the NIR when we free the uncompiled shader. |
| */ |
| ralloc_steal(so, nir); |
| |
| so->stream_output = cso->stream_output; |
| so->nir = nir; |
| |
| /* Fix linkage early */ |
| if (so->nir->info.stage == MESA_SHADER_VERTEX) { |
| so->fixed_varying_mask = |
| (so->nir->info.outputs_written & BITFIELD_MASK(VARYING_SLOT_VAR0)) & |
| ~VARYING_BIT_POS & ~VARYING_BIT_PSIZ; |
| } |
| |
| /* If this shader uses transform feedback, compile the transform |
| * feedback program. This is a special shader variant. |
| */ |
| struct panfrost_context *ctx = pan_context(pctx); |
| |
| if (so->nir->xfb_info) { |
| nir_shader *xfb = nir_shader_clone(NULL, so->nir); |
| xfb->info.name = ralloc_asprintf(xfb, "%s@xfb", xfb->info.name); |
| xfb->info.internal = true; |
| |
| so->xfb = calloc(1, sizeof(struct panfrost_compiled_shader)); |
| so->xfb->key.vs_is_xfb = true; |
| |
| panfrost_shader_get(ctx->base.screen, &ctx->shaders, &ctx->descs, |
| so, &ctx->base.debug, so->xfb, 0); |
| |
| /* Since transform feedback is handled via the transform |
| * feedback program, the original program no longer uses XFB |
| */ |
| nir->info.has_transform_feedback_varyings = false; |
| } |
| |
| /* Compile the program. We don't use vertex shader keys, so there will |
| * be no further vertex shader variants. We do have fragment shader |
| * keys, but we can still compile with a default key that will work most |
| * of the time. |
| */ |
| struct panfrost_shader_key key = { 0 }; |
| |
| /* gl_FragColor lowering needs the number of colour buffers on desktop |
| * GL, where it acts as an implicit broadcast to all colour buffers. |
| * |
| * However, gl_FragColor is a legacy feature, so assume that if |
| * gl_FragColor is used, there is only a single render target. The |
| * implicit broadcast is neither especially useful nor required by GLES. |
| */ |
| if (so->nir->info.stage == MESA_SHADER_FRAGMENT && |
| so->nir->info.outputs_written & BITFIELD_BIT(FRAG_RESULT_COLOR)) { |
| |
| key.fs.nr_cbufs_for_fragcolor = 1; |
| } |
| |
| /* Creating a CSO is single-threaded, so it's ok to use the |
| * locked function without explicitly taking the lock. Creating a |
| * default variant acts as a precompile. |
| */ |
| panfrost_new_variant_locked(ctx, so, &key); |
| |
| return so; |
| } |
| |
| static void |
| panfrost_delete_shader_state(struct pipe_context *pctx, void *so) |
| { |
| struct panfrost_uncompiled_shader *cso = (struct panfrost_uncompiled_shader *) so; |
| |
| util_dynarray_foreach(&cso->variants, struct panfrost_compiled_shader, so) { |
| panfrost_bo_unreference(so->bin.bo); |
| panfrost_bo_unreference(so->state.bo); |
| panfrost_bo_unreference(so->linkage.bo); |
| } |
| |
| if (cso->xfb) { |
| panfrost_bo_unreference(cso->xfb->bin.bo); |
| panfrost_bo_unreference(cso->xfb->state.bo); |
| panfrost_bo_unreference(cso->xfb->linkage.bo); |
| free(cso->xfb); |
| } |
| |
| simple_mtx_destroy(&cso->lock); |
| |
| ralloc_free(so); |
| } |
| |
| /* |
| * Create a compute CSO. As compute kernels do not require variants, they are |
| * precompiled, creating both the uncompiled and compiled shaders now. |
| */ |
| static void * |
| panfrost_create_compute_state( |
| struct pipe_context *pctx, |
| const struct pipe_compute_state *cso) |
| { |
| struct panfrost_context *ctx = pan_context(pctx); |
| struct panfrost_uncompiled_shader *so = panfrost_alloc_shader(cso->prog); |
| struct panfrost_compiled_shader *v = panfrost_alloc_variant(so); |
| memset(v, 0, sizeof *v); |
| |
| assert(cso->ir_type == PIPE_SHADER_IR_NIR && "TGSI kernels unsupported"); |
| |
| panfrost_shader_get(pctx->screen, &ctx->shaders, &ctx->descs, |
| so, &ctx->base.debug, v, cso->req_local_mem); |
| |
| /* The NIR becomes invalid after this. For compute kernels, we never |
| * need to access it again. Don't keep a dangling pointer around. |
| */ |
| so->nir = NULL; |
| |
| return so; |
| } |
| |
| static void |
| panfrost_bind_compute_state(struct pipe_context *pipe, void *cso) |
| { |
| struct panfrost_context *ctx = pan_context(pipe); |
| struct panfrost_uncompiled_shader *uncompiled = cso; |
| |
| ctx->uncompiled[PIPE_SHADER_COMPUTE] = uncompiled; |
| |
| ctx->prog[PIPE_SHADER_COMPUTE] = |
| uncompiled ? util_dynarray_begin(&uncompiled->variants) : NULL; |
| } |
| |
| void |
| panfrost_shader_context_init(struct pipe_context *pctx) |
| { |
| pctx->create_vs_state = panfrost_create_shader_state; |
| pctx->delete_vs_state = panfrost_delete_shader_state; |
| pctx->bind_vs_state = panfrost_bind_vs_state; |
| |
| pctx->create_fs_state = panfrost_create_shader_state; |
| pctx->delete_fs_state = panfrost_delete_shader_state; |
| pctx->bind_fs_state = panfrost_bind_fs_state; |
| |
| pctx->create_compute_state = panfrost_create_compute_state; |
| pctx->bind_compute_state = panfrost_bind_compute_state; |
| pctx->delete_compute_state = panfrost_delete_shader_state; |
| } |