blob: df04264cbd7ce29cf3a520e84795028aa1c3a46d [file] [log] [blame]
/*
* Copyright © 2022 Collabora, LTD
*
* 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.
*/
#include "vk_pipeline.h"
#include "vk_alloc.h"
#include "vk_common_entrypoints.h"
#include "vk_command_buffer.h"
#include "vk_descriptor_set_layout.h"
#include "vk_device.h"
#include "vk_graphics_state.h"
#include "vk_log.h"
#include "vk_nir.h"
#include "vk_physical_device.h"
#include "vk_physical_device_features.h"
#include "vk_pipeline_layout.h"
#include "vk_shader.h"
#include "vk_shader_module.h"
#include "vk_util.h"
#include "nir_serialize.h"
#include "nir.h"
#include "util/mesa-sha1.h"
bool
vk_pipeline_shader_stage_is_null(const VkPipelineShaderStageCreateInfo *info)
{
if (info->module != VK_NULL_HANDLE)
return false;
vk_foreach_struct_const(ext, info->pNext) {
if (ext->sType == VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO ||
ext->sType == VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_MODULE_IDENTIFIER_CREATE_INFO_EXT)
return false;
}
return true;
}
bool
vk_pipeline_shader_stage_has_identifier(const VkPipelineShaderStageCreateInfo *info)
{
const VkPipelineShaderStageModuleIdentifierCreateInfoEXT *id_info =
vk_find_struct_const(info->pNext, PIPELINE_SHADER_STAGE_MODULE_IDENTIFIER_CREATE_INFO_EXT);
return id_info && id_info->identifierSize != 0;
}
static nir_shader *
get_builtin_nir(const VkPipelineShaderStageCreateInfo *info)
{
VK_FROM_HANDLE(vk_shader_module, module, info->module);
nir_shader *nir = NULL;
if (module != NULL) {
nir = module->nir;
} else {
const VkPipelineShaderStageNirCreateInfoMESA *nir_info =
vk_find_struct_const(info->pNext, PIPELINE_SHADER_STAGE_NIR_CREATE_INFO_MESA);
if (nir_info != NULL)
nir = nir_info->nir;
}
if (nir == NULL)
return NULL;
assert(nir->info.stage == vk_to_mesa_shader_stage(info->stage));
ASSERTED nir_function_impl *entrypoint = nir_shader_get_entrypoint(nir);
assert(strcmp(entrypoint->function->name, info->pName) == 0);
assert(info->pSpecializationInfo == NULL);
return nir;
}
static uint32_t
get_required_subgroup_size(const void *info_pNext)
{
const VkPipelineShaderStageRequiredSubgroupSizeCreateInfo *rss_info =
vk_find_struct_const(info_pNext,
PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO);
return rss_info != NULL ? rss_info->requiredSubgroupSize : 0;
}
enum gl_subgroup_size
vk_get_subgroup_size(uint32_t spirv_version,
gl_shader_stage stage,
const void *info_pNext,
bool allow_varying,
bool require_full)
{
uint32_t req_subgroup_size = get_required_subgroup_size(info_pNext);
if (req_subgroup_size > 0) {
assert(util_is_power_of_two_nonzero(req_subgroup_size));
assert(req_subgroup_size >= 4 && req_subgroup_size <= 128);
return req_subgroup_size;
} else if (allow_varying || spirv_version >= 0x10600) {
/* Starting with SPIR-V 1.6, varying subgroup size the default */
return SUBGROUP_SIZE_VARYING;
} else if (require_full) {
assert(stage == MESA_SHADER_COMPUTE ||
stage == MESA_SHADER_MESH ||
stage == MESA_SHADER_TASK);
return SUBGROUP_SIZE_FULL_SUBGROUPS;
} else {
return SUBGROUP_SIZE_API_CONSTANT;
}
}
VkResult
vk_pipeline_shader_stage_to_nir(struct vk_device *device,
VkPipelineCreateFlags2KHR pipeline_flags,
const VkPipelineShaderStageCreateInfo *info,
const struct spirv_to_nir_options *spirv_options,
const struct nir_shader_compiler_options *nir_options,
void *mem_ctx, nir_shader **nir_out)
{
VK_FROM_HANDLE(vk_shader_module, module, info->module);
const gl_shader_stage stage = vk_to_mesa_shader_stage(info->stage);
assert(info->sType == VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO);
nir_shader *builtin_nir = get_builtin_nir(info);
if (builtin_nir != NULL) {
nir_validate_shader(builtin_nir, "internal shader");
nir_shader *clone = nir_shader_clone(mem_ctx, builtin_nir);
if (clone == NULL)
return vk_error(device, VK_ERROR_OUT_OF_HOST_MEMORY);
assert(clone->options == NULL || clone->options == nir_options);
clone->options = nir_options;
*nir_out = clone;
return VK_SUCCESS;
}
const uint32_t *spirv_data;
uint32_t spirv_size;
if (module != NULL) {
spirv_data = (uint32_t *)module->data;
spirv_size = module->size;
} else {
const VkShaderModuleCreateInfo *minfo =
vk_find_struct_const(info->pNext, SHADER_MODULE_CREATE_INFO);
if (unlikely(minfo == NULL)) {
return vk_errorf(device, VK_ERROR_UNKNOWN,
"No shader module provided");
}
spirv_data = minfo->pCode;
spirv_size = minfo->codeSize;
}
enum gl_subgroup_size subgroup_size = vk_get_subgroup_size(
vk_spirv_version(spirv_data, spirv_size),
stage, info->pNext,
info->flags & VK_PIPELINE_SHADER_STAGE_CREATE_ALLOW_VARYING_SUBGROUP_SIZE_BIT,
info->flags & VK_PIPELINE_SHADER_STAGE_CREATE_REQUIRE_FULL_SUBGROUPS_BIT);
nir_shader *nir = vk_spirv_to_nir(device, spirv_data, spirv_size, stage,
info->pName, subgroup_size,
info->pSpecializationInfo,
spirv_options, nir_options,
false /* internal */,
mem_ctx);
if (nir == NULL)
return vk_errorf(device, VK_ERROR_UNKNOWN, "spirv_to_nir failed");
if (pipeline_flags & VK_PIPELINE_CREATE_2_VIEW_INDEX_FROM_DEVICE_INDEX_BIT_KHR)
NIR_PASS(_, nir, nir_lower_view_index_to_device_index);
*nir_out = nir;
return VK_SUCCESS;
}
void
vk_pipeline_hash_shader_stage(VkPipelineCreateFlags2KHR pipeline_flags,
const VkPipelineShaderStageCreateInfo *info,
const struct vk_pipeline_robustness_state *rstate,
unsigned char *stage_sha1)
{
VK_FROM_HANDLE(vk_shader_module, module, info->module);
const nir_shader *builtin_nir = get_builtin_nir(info);
if (builtin_nir != NULL) {
/* Internal NIR module: serialize and hash the NIR shader.
* We don't need to hash other info fields since they should match the
* NIR data.
*/
struct blob blob;
blob_init(&blob);
nir_serialize(&blob, builtin_nir, false);
assert(!blob.out_of_memory);
_mesa_sha1_compute(blob.data, blob.size, stage_sha1);
blob_finish(&blob);
return;
}
const VkShaderModuleCreateInfo *minfo =
vk_find_struct_const(info->pNext, SHADER_MODULE_CREATE_INFO);
const VkPipelineShaderStageModuleIdentifierCreateInfoEXT *iinfo =
vk_find_struct_const(info->pNext, PIPELINE_SHADER_STAGE_MODULE_IDENTIFIER_CREATE_INFO_EXT);
struct mesa_sha1 ctx;
_mesa_sha1_init(&ctx);
/* We only care about one of the pipeline flags */
pipeline_flags &= VK_PIPELINE_CREATE_2_VIEW_INDEX_FROM_DEVICE_INDEX_BIT_KHR;
_mesa_sha1_update(&ctx, &pipeline_flags, sizeof(pipeline_flags));
_mesa_sha1_update(&ctx, &info->flags, sizeof(info->flags));
assert(util_bitcount(info->stage) == 1);
_mesa_sha1_update(&ctx, &info->stage, sizeof(info->stage));
if (module) {
_mesa_sha1_update(&ctx, module->hash, sizeof(module->hash));
} else if (minfo) {
blake3_hash spirv_hash;
_mesa_blake3_compute(minfo->pCode, minfo->codeSize, spirv_hash);
_mesa_sha1_update(&ctx, spirv_hash, sizeof(spirv_hash));
} else {
/* It is legal to pass in arbitrary identifiers as long as they don't exceed
* the limit. Shaders with bogus identifiers are more or less guaranteed to fail. */
assert(iinfo);
assert(iinfo->identifierSize <= VK_MAX_SHADER_MODULE_IDENTIFIER_SIZE_EXT);
_mesa_sha1_update(&ctx, iinfo->pIdentifier, iinfo->identifierSize);
}
if (rstate) {
_mesa_sha1_update(&ctx, &rstate->storage_buffers, sizeof(rstate->storage_buffers));
_mesa_sha1_update(&ctx, &rstate->uniform_buffers, sizeof(rstate->uniform_buffers));
_mesa_sha1_update(&ctx, &rstate->vertex_inputs, sizeof(rstate->vertex_inputs));
_mesa_sha1_update(&ctx, &rstate->images, sizeof(rstate->images));
_mesa_sha1_update(&ctx, &rstate->null_uniform_buffer_descriptor,
sizeof(rstate->null_uniform_buffer_descriptor));
_mesa_sha1_update(&ctx, &rstate->null_storage_buffer_descriptor,
sizeof(rstate->null_storage_buffer_descriptor));
}
_mesa_sha1_update(&ctx, info->pName, strlen(info->pName));
if (info->pSpecializationInfo) {
_mesa_sha1_update(&ctx, info->pSpecializationInfo->pMapEntries,
info->pSpecializationInfo->mapEntryCount *
sizeof(*info->pSpecializationInfo->pMapEntries));
_mesa_sha1_update(&ctx, info->pSpecializationInfo->pData,
info->pSpecializationInfo->dataSize);
}
uint32_t req_subgroup_size = get_required_subgroup_size(info);
_mesa_sha1_update(&ctx, &req_subgroup_size, sizeof(req_subgroup_size));
_mesa_sha1_final(&ctx, stage_sha1);
}
static VkPipelineRobustnessBufferBehaviorEXT
vk_device_default_robust_buffer_behavior(const struct vk_device *device)
{
if (device->enabled_features.robustBufferAccess2) {
return VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_ROBUST_BUFFER_ACCESS_2_EXT;
} else if (device->enabled_features.robustBufferAccess) {
return VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_ROBUST_BUFFER_ACCESS_EXT;
} else {
return VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_DISABLED_EXT;
}
}
static VkPipelineRobustnessImageBehaviorEXT
vk_device_default_robust_image_behavior(const struct vk_device *device)
{
if (device->enabled_features.robustImageAccess2) {
return VK_PIPELINE_ROBUSTNESS_IMAGE_BEHAVIOR_ROBUST_IMAGE_ACCESS_2_EXT;
} else if (device->enabled_features.robustImageAccess) {
return VK_PIPELINE_ROBUSTNESS_IMAGE_BEHAVIOR_ROBUST_IMAGE_ACCESS_EXT;
} else {
return VK_PIPELINE_ROBUSTNESS_IMAGE_BEHAVIOR_DISABLED_EXT;
}
}
void
vk_pipeline_robustness_state_fill(const struct vk_device *device,
struct vk_pipeline_robustness_state *rs,
const void *pipeline_pNext,
const void *shader_stage_pNext)
{
rs->uniform_buffers = VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_DEVICE_DEFAULT_EXT;
rs->storage_buffers = VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_DEVICE_DEFAULT_EXT;
rs->vertex_inputs = VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_DEVICE_DEFAULT_EXT;
rs->images = VK_PIPELINE_ROBUSTNESS_IMAGE_BEHAVIOR_DEVICE_DEFAULT_EXT;
rs->null_uniform_buffer_descriptor = device->enabled_features.nullDescriptor;
rs->null_storage_buffer_descriptor = device->enabled_features.nullDescriptor;
const VkPipelineRobustnessCreateInfoEXT *shader_info =
vk_find_struct_const(shader_stage_pNext,
PIPELINE_ROBUSTNESS_CREATE_INFO_EXT);
if (shader_info) {
rs->storage_buffers = shader_info->storageBuffers;
rs->uniform_buffers = shader_info->uniformBuffers;
rs->vertex_inputs = shader_info->vertexInputs;
rs->images = shader_info->images;
} else {
const VkPipelineRobustnessCreateInfoEXT *pipeline_info =
vk_find_struct_const(pipeline_pNext,
PIPELINE_ROBUSTNESS_CREATE_INFO_EXT);
if (pipeline_info) {
rs->storage_buffers = pipeline_info->storageBuffers;
rs->uniform_buffers = pipeline_info->uniformBuffers;
rs->vertex_inputs = pipeline_info->vertexInputs;
rs->images = pipeline_info->images;
}
}
if (rs->storage_buffers ==
VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_DEVICE_DEFAULT_EXT)
rs->storage_buffers = vk_device_default_robust_buffer_behavior(device);
if (rs->uniform_buffers ==
VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_DEVICE_DEFAULT_EXT)
rs->uniform_buffers = vk_device_default_robust_buffer_behavior(device);
if (rs->vertex_inputs ==
VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_DEVICE_DEFAULT_EXT)
rs->vertex_inputs = vk_device_default_robust_buffer_behavior(device);
if (rs->images == VK_PIPELINE_ROBUSTNESS_IMAGE_BEHAVIOR_DEVICE_DEFAULT_EXT)
rs->images = vk_device_default_robust_image_behavior(device);
}
static void
vk_pipeline_init(struct vk_pipeline *pipeline,
const struct vk_pipeline_ops *ops,
VkPipelineBindPoint bind_point,
VkPipelineCreateFlags2KHR flags)
{
pipeline->ops = ops;
pipeline->bind_point = bind_point;
pipeline->flags = flags;
}
void *
vk_pipeline_zalloc(struct vk_device *device,
const struct vk_pipeline_ops *ops,
VkPipelineBindPoint bind_point,
VkPipelineCreateFlags2KHR flags,
const VkAllocationCallbacks *alloc,
size_t size)
{
struct vk_pipeline *pipeline =
vk_object_zalloc(device, alloc, size, VK_OBJECT_TYPE_PIPELINE);
if (pipeline == NULL)
return NULL;
vk_pipeline_init(pipeline, ops, bind_point, flags);
return pipeline;
}
void *vk_pipeline_multizalloc(struct vk_device *device,
struct vk_multialloc *ma,
const struct vk_pipeline_ops *ops,
VkPipelineBindPoint bind_point,
VkPipelineCreateFlags2KHR flags,
const VkAllocationCallbacks *alloc)
{
struct vk_pipeline *pipeline =
vk_object_multizalloc(device, ma, alloc, VK_OBJECT_TYPE_PIPELINE);
if (!pipeline)
return NULL;
vk_pipeline_init(pipeline, ops, bind_point, flags);
return pipeline;
}
void
vk_pipeline_free(struct vk_device *device,
const VkAllocationCallbacks *alloc,
struct vk_pipeline *pipeline)
{
vk_object_free(device, alloc, &pipeline->base);
}
VKAPI_ATTR void VKAPI_CALL
vk_common_DestroyPipeline(VkDevice _device,
VkPipeline _pipeline,
const VkAllocationCallbacks *pAllocator)
{
VK_FROM_HANDLE(vk_device, device, _device);
VK_FROM_HANDLE(vk_pipeline, pipeline, _pipeline);
if (pipeline == NULL)
return;
pipeline->ops->destroy(device, pipeline, pAllocator);
}
VKAPI_ATTR VkResult VKAPI_CALL
vk_common_GetPipelineExecutablePropertiesKHR(
VkDevice _device,
const VkPipelineInfoKHR *pPipelineInfo,
uint32_t *pExecutableCount,
VkPipelineExecutablePropertiesKHR *pProperties)
{
VK_FROM_HANDLE(vk_device, device, _device);
VK_FROM_HANDLE(vk_pipeline, pipeline, pPipelineInfo->pipeline);
return pipeline->ops->get_executable_properties(device, pipeline,
pExecutableCount,
pProperties);
}
VKAPI_ATTR VkResult VKAPI_CALL
vk_common_GetPipelineExecutableStatisticsKHR(
VkDevice _device,
const VkPipelineExecutableInfoKHR *pExecutableInfo,
uint32_t *pStatisticCount,
VkPipelineExecutableStatisticKHR *pStatistics)
{
VK_FROM_HANDLE(vk_device, device, _device);
VK_FROM_HANDLE(vk_pipeline, pipeline, pExecutableInfo->pipeline);
return pipeline->ops->get_executable_statistics(device, pipeline,
pExecutableInfo->executableIndex,
pStatisticCount, pStatistics);
}
VKAPI_ATTR VkResult VKAPI_CALL
vk_common_GetPipelineExecutableInternalRepresentationsKHR(
VkDevice _device,
const VkPipelineExecutableInfoKHR *pExecutableInfo,
uint32_t *pInternalRepresentationCount,
VkPipelineExecutableInternalRepresentationKHR* pInternalRepresentations)
{
VK_FROM_HANDLE(vk_device, device, _device);
VK_FROM_HANDLE(vk_pipeline, pipeline, pExecutableInfo->pipeline);
return pipeline->ops->get_internal_representations(device, pipeline,
pExecutableInfo->executableIndex,
pInternalRepresentationCount,
pInternalRepresentations);
}
VKAPI_ATTR void VKAPI_CALL
vk_common_CmdBindPipeline(VkCommandBuffer commandBuffer,
VkPipelineBindPoint pipelineBindPoint,
VkPipeline _pipeline)
{
VK_FROM_HANDLE(vk_command_buffer, cmd_buffer, commandBuffer);
VK_FROM_HANDLE(vk_pipeline, pipeline, _pipeline);
assert(pipeline->bind_point == pipelineBindPoint);
pipeline->ops->cmd_bind(cmd_buffer, pipeline);
}
static const struct vk_pipeline_cache_object_ops pipeline_shader_cache_ops;
static struct vk_shader *
vk_shader_from_cache_obj(struct vk_pipeline_cache_object *object)
{
assert(object->ops == &pipeline_shader_cache_ops);
return container_of(object, struct vk_shader, pipeline.cache_obj);
}
static bool
vk_pipeline_shader_serialize(struct vk_pipeline_cache_object *object,
struct blob *blob)
{
struct vk_shader *shader = vk_shader_from_cache_obj(object);
struct vk_device *device = shader->base.device;
return shader->ops->serialize(device, shader, blob);
}
static void
vk_shader_init_cache_obj(struct vk_device *device, struct vk_shader *shader,
const void *key_data, size_t key_size)
{
assert(key_size == sizeof(shader->pipeline.cache_key));
memcpy(&shader->pipeline.cache_key, key_data,
sizeof(shader->pipeline.cache_key));
vk_pipeline_cache_object_init(device, &shader->pipeline.cache_obj,
&pipeline_shader_cache_ops,
&shader->pipeline.cache_key,
sizeof(shader->pipeline.cache_key));
}
static struct vk_pipeline_cache_object *
vk_pipeline_shader_deserialize(struct vk_pipeline_cache *cache,
const void *key_data, size_t key_size,
struct blob_reader *blob)
{
struct vk_device *device = cache->base.device;
const struct vk_device_shader_ops *ops = device->shader_ops;
/* TODO: Do we really want to always use the latest version? */
const uint32_t version = device->physical->properties.shaderBinaryVersion;
struct vk_shader *shader;
VkResult result = ops->deserialize(device, blob, version,
&device->alloc, &shader);
if (result != VK_SUCCESS) {
assert(result == VK_ERROR_OUT_OF_HOST_MEMORY);
return NULL;
}
vk_shader_init_cache_obj(device, shader, key_data, key_size);
return &shader->pipeline.cache_obj;
}
static void
vk_pipeline_shader_destroy(struct vk_device *device,
struct vk_pipeline_cache_object *object)
{
struct vk_shader *shader = vk_shader_from_cache_obj(object);
assert(shader->base.device == device);
vk_shader_destroy(device, shader, &device->alloc);
}
static const struct vk_pipeline_cache_object_ops pipeline_shader_cache_ops = {
.serialize = vk_pipeline_shader_serialize,
.deserialize = vk_pipeline_shader_deserialize,
.destroy = vk_pipeline_shader_destroy,
};
static struct vk_shader *
vk_shader_ref(struct vk_shader *shader)
{
vk_pipeline_cache_object_ref(&shader->pipeline.cache_obj);
return shader;
}
static void
vk_shader_unref(struct vk_device *device, struct vk_shader *shader)
{
vk_pipeline_cache_object_unref(device, &shader->pipeline.cache_obj);
}
PRAGMA_DIAGNOSTIC_PUSH
PRAGMA_DIAGNOSTIC_ERROR(-Wpadded)
struct vk_pipeline_tess_info {
unsigned tcs_vertices_out : 8;
unsigned primitive_mode : 2; /* tess_primitive_mode */
unsigned spacing : 2; /* gl_tess_spacing */
unsigned ccw : 1;
unsigned point_mode : 1;
unsigned _pad : 18;
};
PRAGMA_DIAGNOSTIC_POP
static_assert(sizeof(struct vk_pipeline_tess_info) == 4,
"This struct has no holes");
static void
vk_pipeline_gather_nir_tess_info(const nir_shader *nir,
struct vk_pipeline_tess_info *info)
{
info->tcs_vertices_out = nir->info.tess.tcs_vertices_out;
info->primitive_mode = nir->info.tess._primitive_mode;
info->spacing = nir->info.tess.spacing;
info->ccw = nir->info.tess.ccw;
info->point_mode = nir->info.tess.point_mode;
}
static void
vk_pipeline_replace_nir_tess_info(nir_shader *nir,
const struct vk_pipeline_tess_info *info)
{
nir->info.tess.tcs_vertices_out = info->tcs_vertices_out;
nir->info.tess._primitive_mode = info->primitive_mode;
nir->info.tess.spacing = info->spacing;
nir->info.tess.ccw = info->ccw;
nir->info.tess.point_mode = info->point_mode;
}
static void
vk_pipeline_tess_info_merge(struct vk_pipeline_tess_info *dst,
const struct vk_pipeline_tess_info *src)
{
/* The Vulkan 1.0.38 spec, section 21.1 Tessellator says:
*
* "PointMode. Controls generation of points rather than triangles
* or lines. This functionality defaults to disabled, and is
* enabled if either shader stage includes the execution mode.
*
* and about Triangles, Quads, IsoLines, VertexOrderCw, VertexOrderCcw,
* PointMode, SpacingEqual, SpacingFractionalEven, SpacingFractionalOdd,
* and OutputVertices, it says:
*
* "One mode must be set in at least one of the tessellation
* shader stages."
*
* So, the fields can be set in either the TCS or TES, but they must
* agree if set in both.
*/
assert(dst->tcs_vertices_out == 0 ||
src->tcs_vertices_out == 0 ||
dst->tcs_vertices_out == src->tcs_vertices_out);
dst->tcs_vertices_out |= src->tcs_vertices_out;
static_assert(TESS_SPACING_UNSPECIFIED == 0, "");
assert(dst->spacing == TESS_SPACING_UNSPECIFIED ||
src->spacing == TESS_SPACING_UNSPECIFIED ||
dst->spacing == src->spacing);
dst->spacing |= src->spacing;
static_assert(TESS_PRIMITIVE_UNSPECIFIED == 0, "");
assert(dst->primitive_mode == TESS_PRIMITIVE_UNSPECIFIED ||
src->primitive_mode == TESS_PRIMITIVE_UNSPECIFIED ||
dst->primitive_mode == src->primitive_mode);
dst->primitive_mode |= src->primitive_mode;
dst->ccw |= src->ccw;
dst->point_mode |= src->point_mode;
}
struct vk_pipeline_precomp_shader {
struct vk_pipeline_cache_object cache_obj;
/* Key for this cache_obj in the pipeline cache.
*
* This is always the output of vk_pipeline_hash_shader_stage() so it must
* be a SHA1 hash.
*/
uint8_t cache_key[SHA1_DIGEST_LENGTH];
gl_shader_stage stage;
struct vk_pipeline_robustness_state rs;
/* Tessellation info if the shader is a tessellation shader */
struct vk_pipeline_tess_info tess;
/* Hash of the vk_pipeline_precomp_shader
*
* This is the hash of the final compiled NIR together with tess info and
* robustness state. It's used as a key for final binary lookups. By
* having this as a separate key, we can de-duplicate cases where you have
* different SPIR-V or specialization constants but end up compiling the
* same NIR shader in the end anyway.
*/
blake3_hash blake3;
struct blob nir_blob;
};
static struct vk_pipeline_precomp_shader *
vk_pipeline_precomp_shader_ref(struct vk_pipeline_precomp_shader *shader)
{
vk_pipeline_cache_object_ref(&shader->cache_obj);
return shader;
}
static void
vk_pipeline_precomp_shader_unref(struct vk_device *device,
struct vk_pipeline_precomp_shader *shader)
{
vk_pipeline_cache_object_unref(device, &shader->cache_obj);
}
static const struct vk_pipeline_cache_object_ops pipeline_precomp_shader_cache_ops;
static struct vk_pipeline_precomp_shader *
vk_pipeline_precomp_shader_from_cache_obj(struct vk_pipeline_cache_object *obj)
{
assert(obj->ops == & pipeline_precomp_shader_cache_ops);
return container_of(obj, struct vk_pipeline_precomp_shader, cache_obj);
}
static struct vk_pipeline_precomp_shader *
vk_pipeline_precomp_shader_create(struct vk_device *device,
const void *key_data, size_t key_size,
const struct vk_pipeline_robustness_state *rs,
nir_shader *nir)
{
struct blob blob;
blob_init(&blob);
nir_serialize(&blob, nir, false);
if (blob.out_of_memory)
goto fail_blob;
struct vk_pipeline_precomp_shader *shader =
vk_zalloc(&device->alloc, sizeof(*shader), 8,
VK_SYSTEM_ALLOCATION_SCOPE_DEVICE);
if (shader == NULL)
goto fail_blob;
assert(sizeof(shader->cache_key) == key_size);
memcpy(shader->cache_key, key_data, sizeof(shader->cache_key));
vk_pipeline_cache_object_init(device, &shader->cache_obj,
&pipeline_precomp_shader_cache_ops,
shader->cache_key,
sizeof(shader->cache_key));
shader->stage = nir->info.stage;
shader->rs = *rs;
vk_pipeline_gather_nir_tess_info(nir, &shader->tess);
struct mesa_blake3 blake3_ctx;
_mesa_blake3_init(&blake3_ctx);
_mesa_blake3_update(&blake3_ctx, rs, sizeof(*rs));
_mesa_blake3_update(&blake3_ctx, blob.data, blob.size);
_mesa_blake3_final(&blake3_ctx, shader->blake3);
shader->nir_blob = blob;
return shader;
fail_blob:
blob_finish(&blob);
return NULL;
}
static bool
vk_pipeline_precomp_shader_serialize(struct vk_pipeline_cache_object *obj,
struct blob *blob)
{
struct vk_pipeline_precomp_shader *shader =
vk_pipeline_precomp_shader_from_cache_obj(obj);
blob_write_uint32(blob, shader->stage);
blob_write_bytes(blob, &shader->rs, sizeof(shader->rs));
blob_write_bytes(blob, &shader->tess, sizeof(shader->tess));
blob_write_bytes(blob, shader->blake3, sizeof(shader->blake3));
blob_write_uint64(blob, shader->nir_blob.size);
blob_write_bytes(blob, shader->nir_blob.data, shader->nir_blob.size);
return !blob->out_of_memory;
}
static struct vk_pipeline_cache_object *
vk_pipeline_precomp_shader_deserialize(struct vk_pipeline_cache *cache,
const void *key_data, size_t key_size,
struct blob_reader *blob)
{
struct vk_device *device = cache->base.device;
struct vk_pipeline_precomp_shader *shader =
vk_zalloc(&device->alloc, sizeof(*shader), 8,
VK_SYSTEM_ALLOCATION_SCOPE_DEVICE);
if (shader == NULL)
return NULL;
assert(sizeof(shader->cache_key) == key_size);
memcpy(shader->cache_key, key_data, sizeof(shader->cache_key));
vk_pipeline_cache_object_init(device, &shader->cache_obj,
&pipeline_precomp_shader_cache_ops,
shader->cache_key,
sizeof(shader->cache_key));
shader->stage = blob_read_uint32(blob);
blob_copy_bytes(blob, &shader->rs, sizeof(shader->rs));
blob_copy_bytes(blob, &shader->tess, sizeof(shader->tess));
blob_copy_bytes(blob, shader->blake3, sizeof(shader->blake3));
uint64_t nir_size = blob_read_uint64(blob);
if (blob->overrun || nir_size > SIZE_MAX)
goto fail_shader;
const void *nir_data = blob_read_bytes(blob, nir_size);
if (blob->overrun)
goto fail_shader;
blob_init(&shader->nir_blob);
blob_write_bytes(&shader->nir_blob, nir_data, nir_size);
if (shader->nir_blob.out_of_memory)
goto fail_nir_blob;
return &shader->cache_obj;
fail_nir_blob:
blob_finish(&shader->nir_blob);
fail_shader:
vk_pipeline_cache_object_finish(&shader->cache_obj);
vk_free(&device->alloc, shader);
return NULL;
}
static void
vk_pipeline_precomp_shader_destroy(struct vk_device *device,
struct vk_pipeline_cache_object *obj)
{
struct vk_pipeline_precomp_shader *shader =
vk_pipeline_precomp_shader_from_cache_obj(obj);
blob_finish(&shader->nir_blob);
vk_pipeline_cache_object_finish(&shader->cache_obj);
vk_free(&device->alloc, shader);
}
static nir_shader *
vk_pipeline_precomp_shader_get_nir(const struct vk_pipeline_precomp_shader *shader,
const struct nir_shader_compiler_options *nir_options)
{
struct blob_reader blob;
blob_reader_init(&blob, shader->nir_blob.data, shader->nir_blob.size);
nir_shader *nir = nir_deserialize(NULL, nir_options, &blob);
if (blob.overrun) {
ralloc_free(nir);
return NULL;
}
return nir;
}
static const struct vk_pipeline_cache_object_ops pipeline_precomp_shader_cache_ops = {
.serialize = vk_pipeline_precomp_shader_serialize,
.deserialize = vk_pipeline_precomp_shader_deserialize,
.destroy = vk_pipeline_precomp_shader_destroy,
};
static VkResult
vk_pipeline_precompile_shader(struct vk_device *device,
struct vk_pipeline_cache *cache,
VkPipelineCreateFlags2KHR pipeline_flags,
const void *pipeline_info_pNext,
const VkPipelineShaderStageCreateInfo *info,
struct vk_pipeline_precomp_shader **ps_out)
{
const struct vk_device_shader_ops *ops = device->shader_ops;
VkResult result;
struct vk_pipeline_robustness_state rs = { 0 };
vk_pipeline_robustness_state_fill(device, &rs,
pipeline_info_pNext,
info->pNext);
uint8_t stage_sha1[SHA1_DIGEST_LENGTH];
vk_pipeline_hash_shader_stage(pipeline_flags, info, &rs, stage_sha1);
if (cache != NULL) {
struct vk_pipeline_cache_object *cache_obj =
vk_pipeline_cache_lookup_object(cache, stage_sha1, sizeof(stage_sha1),
&pipeline_precomp_shader_cache_ops,
NULL /* cache_hit */);
if (cache_obj != NULL) {
*ps_out = vk_pipeline_precomp_shader_from_cache_obj(cache_obj);
return VK_SUCCESS;
}
}
if (pipeline_flags &
VK_PIPELINE_CREATE_2_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT_KHR)
return VK_PIPELINE_COMPILE_REQUIRED;
const gl_shader_stage stage = vk_to_mesa_shader_stage(info->stage);
const struct nir_shader_compiler_options *nir_options =
ops->get_nir_options(device->physical, stage, &rs);
const struct spirv_to_nir_options spirv_options =
ops->get_spirv_options(device->physical, stage, &rs);
nir_shader *nir;
result = vk_pipeline_shader_stage_to_nir(device, pipeline_flags, info,
&spirv_options, nir_options,
NULL, &nir);
if (result != VK_SUCCESS)
return result;
if (ops->preprocess_nir != NULL)
ops->preprocess_nir(device->physical, nir, &rs);
struct vk_pipeline_precomp_shader *shader =
vk_pipeline_precomp_shader_create(device, stage_sha1,
sizeof(stage_sha1),
&rs, nir);
ralloc_free(nir);
if (shader == NULL)
return vk_error(device, VK_ERROR_OUT_OF_HOST_MEMORY);
if (cache != NULL) {
struct vk_pipeline_cache_object *cache_obj = &shader->cache_obj;
cache_obj = vk_pipeline_cache_add_object(cache, cache_obj);
shader = vk_pipeline_precomp_shader_from_cache_obj(cache_obj);
}
*ps_out = shader;
return VK_SUCCESS;
}
struct vk_pipeline_stage {
gl_shader_stage stage;
struct vk_pipeline_precomp_shader *precomp;
struct vk_shader *shader;
};
static int
cmp_vk_pipeline_stages(const void *_a, const void *_b)
{
const struct vk_pipeline_stage *a = _a, *b = _b;
return vk_shader_cmp_graphics_stages(a->stage, b->stage);
}
static bool
vk_pipeline_stage_is_null(const struct vk_pipeline_stage *stage)
{
return stage->precomp == NULL && stage->shader == NULL;
}
static void
vk_pipeline_stage_finish(struct vk_device *device,
struct vk_pipeline_stage *stage)
{
if (stage->precomp != NULL)
vk_pipeline_precomp_shader_unref(device, stage->precomp);
if (stage->shader)
vk_shader_unref(device, stage->shader);
}
static struct vk_pipeline_stage
vk_pipeline_stage_clone(const struct vk_pipeline_stage *in)
{
struct vk_pipeline_stage out = {
.stage = in->stage,
};
if (in->precomp)
out.precomp = vk_pipeline_precomp_shader_ref(in->precomp);
if (in->shader)
out.shader = vk_shader_ref(in->shader);
return out;
}
struct vk_graphics_pipeline {
struct vk_pipeline base;
union {
struct {
struct vk_graphics_pipeline_all_state all_state;
struct vk_graphics_pipeline_state state;
} lib;
struct {
struct vk_vertex_input_state _dynamic_vi;
struct vk_sample_locations_state _dynamic_sl;
struct vk_dynamic_graphics_state dynamic;
} linked;
};
uint32_t set_layout_count;
struct vk_descriptor_set_layout *set_layouts[MESA_VK_MAX_DESCRIPTOR_SETS];
uint32_t stage_count;
struct vk_pipeline_stage stages[MESA_VK_MAX_GRAPHICS_PIPELINE_STAGES];
};
static void
vk_graphics_pipeline_destroy(struct vk_device *device,
struct vk_pipeline *pipeline,
const VkAllocationCallbacks *pAllocator)
{
struct vk_graphics_pipeline *gfx_pipeline =
container_of(pipeline, struct vk_graphics_pipeline, base);
for (uint32_t i = 0; i < gfx_pipeline->stage_count; i++)
vk_pipeline_stage_finish(device, &gfx_pipeline->stages[i]);
for (uint32_t i = 0; i < gfx_pipeline->set_layout_count; i++) {
if (gfx_pipeline->set_layouts[i] != NULL)
vk_descriptor_set_layout_unref(device, gfx_pipeline->set_layouts[i]);
}
vk_pipeline_free(device, pAllocator, pipeline);
}
static bool
vk_device_supports_stage(struct vk_device *device,
gl_shader_stage stage)
{
const struct vk_features *features = &device->physical->supported_features;
switch (stage) {
case MESA_SHADER_VERTEX:
case MESA_SHADER_FRAGMENT:
case MESA_SHADER_COMPUTE:
return true;
case MESA_SHADER_TESS_CTRL:
case MESA_SHADER_TESS_EVAL:
return features->tessellationShader;
case MESA_SHADER_GEOMETRY:
return features->geometryShader;
case MESA_SHADER_TASK:
return features->taskShader;
case MESA_SHADER_MESH:
return features->meshShader;
default:
return false;
}
}
static const gl_shader_stage all_gfx_stages[] = {
MESA_SHADER_VERTEX,
MESA_SHADER_TESS_CTRL,
MESA_SHADER_TESS_EVAL,
MESA_SHADER_GEOMETRY,
MESA_SHADER_TASK,
MESA_SHADER_MESH,
MESA_SHADER_FRAGMENT,
};
static void
vk_graphics_pipeline_cmd_bind(struct vk_command_buffer *cmd_buffer,
struct vk_pipeline *pipeline)
{
struct vk_device *device = cmd_buffer->base.device;
const struct vk_device_shader_ops *ops = device->shader_ops;
struct vk_graphics_pipeline *gfx_pipeline = NULL;
struct vk_shader *stage_shader[PIPE_SHADER_MESH_TYPES] = { NULL, };
if (pipeline != NULL) {
assert(pipeline->bind_point == VK_PIPELINE_BIND_POINT_GRAPHICS);
assert(!(pipeline->flags & VK_PIPELINE_CREATE_2_LIBRARY_BIT_KHR));
gfx_pipeline = container_of(pipeline, struct vk_graphics_pipeline, base);
for (uint32_t i = 0; i < gfx_pipeline->stage_count; i++) {
struct vk_shader *shader = gfx_pipeline->stages[i].shader;
stage_shader[shader->stage] = shader;
}
}
uint32_t stage_count = 0;
gl_shader_stage stages[ARRAY_SIZE(all_gfx_stages)];
struct vk_shader *shaders[ARRAY_SIZE(all_gfx_stages)];
VkShaderStageFlags vk_stages = 0;
for (uint32_t i = 0; i < ARRAY_SIZE(all_gfx_stages); i++) {
gl_shader_stage stage = all_gfx_stages[i];
if (!vk_device_supports_stage(device, stage)) {
assert(stage_shader[stage] == NULL);
continue;
}
vk_stages |= mesa_to_vk_shader_stage(stage);
stages[stage_count] = stage;
shaders[stage_count] = stage_shader[stage];
stage_count++;
}
ops->cmd_bind_shaders(cmd_buffer, stage_count, stages, shaders);
if (gfx_pipeline != NULL) {
cmd_buffer->pipeline_shader_stages |= vk_stages;
ops->cmd_set_dynamic_graphics_state(cmd_buffer,
&gfx_pipeline->linked.dynamic);
} else {
cmd_buffer->pipeline_shader_stages &= ~vk_stages;
}
}
static VkShaderCreateFlagsEXT
vk_pipeline_to_shader_flags(VkPipelineCreateFlags2KHR pipeline_flags,
gl_shader_stage stage)
{
VkShaderCreateFlagsEXT shader_flags = 0;
if (pipeline_flags & VK_PIPELINE_CREATE_2_CAPTURE_INTERNAL_REPRESENTATIONS_BIT_KHR)
shader_flags |= VK_SHADER_CREATE_CAPTURE_INTERNAL_REPRESENTATIONS_BIT_MESA;
if (pipeline_flags & VK_PIPELINE_CREATE_2_INDIRECT_BINDABLE_BIT_EXT)
shader_flags |= VK_SHADER_CREATE_INDIRECT_BINDABLE_BIT_EXT;
if (stage == MESA_SHADER_FRAGMENT) {
if (pipeline_flags & VK_PIPELINE_CREATE_2_RENDERING_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR)
shader_flags |= VK_SHADER_CREATE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_EXT;
if (pipeline_flags & VK_PIPELINE_CREATE_2_RENDERING_FRAGMENT_DENSITY_MAP_ATTACHMENT_BIT_EXT)
shader_flags |= VK_SHADER_CREATE_FRAGMENT_DENSITY_MAP_ATTACHMENT_BIT_EXT;
}
if (stage == MESA_SHADER_COMPUTE) {
if (pipeline_flags & VK_PIPELINE_CREATE_2_DISPATCH_BASE_BIT_KHR)
shader_flags |= VK_SHADER_CREATE_DISPATCH_BASE_BIT_EXT;
}
return shader_flags;
}
static VkResult
vk_graphics_pipeline_compile_shaders(struct vk_device *device,
struct vk_pipeline_cache *cache,
struct vk_graphics_pipeline *pipeline,
struct vk_pipeline_layout *pipeline_layout,
const struct vk_graphics_pipeline_state *state,
uint32_t stage_count,
struct vk_pipeline_stage *stages,
VkPipelineCreationFeedback *stage_feedbacks)
{
const struct vk_device_shader_ops *ops = device->shader_ops;
VkResult result;
if (stage_count == 0)
return VK_SUCCESS;
/* If we're linking, throw away any previously compiled shaders as they
* likely haven't been properly linked. We keep the precompiled shaders
* and we still look it up in the cache so it may still be fast.
*/
if (pipeline->base.flags & VK_PIPELINE_CREATE_2_LINK_TIME_OPTIMIZATION_BIT_EXT) {
for (uint32_t i = 0; i < stage_count; i++) {
if (stages[i].shader != NULL) {
vk_shader_unref(device, stages[i].shader);
stages[i].shader = NULL;
}
}
}
bool have_all_shaders = true;
VkShaderStageFlags all_stages = 0;
struct vk_pipeline_precomp_shader *tcs_precomp = NULL, *tes_precomp = NULL;
for (uint32_t i = 0; i < stage_count; i++) {
all_stages |= mesa_to_vk_shader_stage(stages[i].stage);
if (stages[i].shader == NULL)
have_all_shaders = false;
if (stages[i].stage == MESA_SHADER_TESS_CTRL)
tcs_precomp = stages[i].precomp;
if (stages[i].stage == MESA_SHADER_TESS_EVAL)
tes_precomp = stages[i].precomp;
}
/* If we already have a shader for each stage, there's nothing to do. */
if (have_all_shaders)
return VK_SUCCESS;
struct vk_pipeline_tess_info tess_info = { ._pad = 0 };
if (tcs_precomp != NULL && tes_precomp != NULL) {
tess_info = tcs_precomp->tess;
vk_pipeline_tess_info_merge(&tess_info, &tes_precomp->tess);
}
struct mesa_blake3 blake3_ctx;
_mesa_blake3_init(&blake3_ctx);
for (uint32_t i = 0; i < pipeline->set_layout_count; i++) {
if (pipeline->set_layouts[i] != NULL) {
_mesa_blake3_update(&blake3_ctx, pipeline->set_layouts[i]->blake3,
sizeof(pipeline->set_layouts[i]->blake3));
}
}
if (pipeline_layout != NULL) {
_mesa_blake3_update(&blake3_ctx, &pipeline_layout->push_ranges,
sizeof(pipeline_layout->push_ranges[0]) *
pipeline_layout->push_range_count);
}
blake3_hash layout_blake3;
_mesa_blake3_final(&blake3_ctx, layout_blake3);
/* Partition the shaders */
uint32_t part_count;
uint32_t partition[MESA_VK_MAX_GRAPHICS_PIPELINE_STAGES + 1] = { 0 };
if (pipeline->base.flags & VK_PIPELINE_CREATE_2_LINK_TIME_OPTIMIZATION_BIT_EXT) {
partition[1] = stage_count;
part_count = 1;
} else if (ops->link_geom_stages) {
if (stages[0].stage == MESA_SHADER_FRAGMENT) {
assert(stage_count == 1);
partition[1] = stage_count;
part_count = 1;
} else if (stages[stage_count - 1].stage == MESA_SHADER_FRAGMENT) {
/* In this case we have both */
assert(stage_count > 1);
partition[1] = stage_count - 1;
partition[2] = stage_count;
part_count = 2;
} else {
/* In this case we only have geometry */
partition[1] = stage_count;
part_count = 1;
}
} else {
/* Otherwise, we're don't want to link anything */
part_count = stage_count;
for (uint32_t i = 0; i < stage_count; i++)
partition[i + 1] = i + 1;
}
for (uint32_t p = 0; p < part_count; p++) {
const int64_t part_start = os_time_get_nano();
/* Don't try to re-compile any fast-link shaders */
if (!(pipeline->base.flags &
VK_PIPELINE_CREATE_2_LINK_TIME_OPTIMIZATION_BIT_EXT)) {
assert(ops->link_geom_stages || partition[p + 1] == partition[p] + 1);
if (stages[partition[p]].shader != NULL)
continue;
}
struct vk_shader_pipeline_cache_key shader_key = { 0 };
_mesa_blake3_init(&blake3_ctx);
VkShaderStageFlags part_stages = 0;
for (uint32_t i = partition[p]; i < partition[p + 1]; i++) {
const struct vk_pipeline_stage *stage = &stages[i];
part_stages |= mesa_to_vk_shader_stage(stage->stage);
_mesa_blake3_update(&blake3_ctx, stage->precomp->blake3,
sizeof(stage->precomp->blake3));
VkShaderCreateFlagsEXT shader_flags =
vk_pipeline_to_shader_flags(pipeline->base.flags, stage->stage);
_mesa_blake3_update(&blake3_ctx, &shader_flags, sizeof(shader_flags));
}
blake3_hash state_blake3;
ops->hash_state(device->physical, state, &device->enabled_features,
part_stages, state_blake3);
_mesa_blake3_update(&blake3_ctx, state_blake3, sizeof(state_blake3));
_mesa_blake3_update(&blake3_ctx, layout_blake3, sizeof(layout_blake3));
if (part_stages & (VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT |
VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT))
_mesa_blake3_update(&blake3_ctx, &tess_info, sizeof(tess_info));
/* The set of geometry stages used together is used to generate the
* nextStage mask as well as VK_SHADER_CREATE_NO_TASK_SHADER_BIT_EXT.
*/
const VkShaderStageFlags geom_stages =
all_stages & ~VK_SHADER_STAGE_FRAGMENT_BIT;
_mesa_blake3_update(&blake3_ctx, &geom_stages, sizeof(geom_stages));
_mesa_blake3_final(&blake3_ctx, shader_key.blake3);
if (cache != NULL) {
/* From the Vulkan 1.3.278 spec:
*
* "VK_PIPELINE_CREATION_FEEDBACK_APPLICATION_PIPELINE_CACHE_HIT_BIT
* indicates that a readily usable pipeline or pipeline stage was
* found in the pipelineCache specified by the application in the
* pipeline creation command.
*
* [...]
*
* Note
*
* Implementations are encouraged to provide a meaningful signal
* to applications using this bit. The intention is to communicate
* to the application that the pipeline or pipeline stage was
* created “as fast as it gets” using the pipeline cache provided
* by the application. If an implementation uses an internal
* cache, it is discouraged from setting this bit as the feedback
* would be unactionable."
*
* The cache_hit value returned by vk_pipeline_cache_lookup_object()
* is only set to true when the shader is found in the provided
* pipeline cache. It is left false if we fail to find it in the
* memory cache but find it in the disk cache even though that's
* still a cache hit from the perspective of the compile pipeline.
*/
bool all_shaders_found = true;
bool all_cache_hits = true;
for (uint32_t i = partition[p]; i < partition[p + 1]; i++) {
struct vk_pipeline_stage *stage = &stages[i];
shader_key.stage = stage->stage;
if (stage->shader) {
/* If we have a shader from some library pipeline and the key
* matches, just use that.
*/
if (memcmp(&stage->shader->pipeline.cache_key,
&shader_key, sizeof(shader_key)) == 0)
continue;
/* Otherwise, throw it away */
vk_shader_unref(device, stage->shader);
stage->shader = NULL;
}
bool cache_hit = false;
struct vk_pipeline_cache_object *cache_obj =
vk_pipeline_cache_lookup_object(cache, &shader_key,
sizeof(shader_key),
&pipeline_shader_cache_ops,
&cache_hit);
if (cache_obj != NULL) {
assert(stage->shader == NULL);
stage->shader = vk_shader_from_cache_obj(cache_obj);
} else {
all_shaders_found = false;
}
if (cache_obj == NULL && !cache_hit)
all_cache_hits = false;
}
if (all_cache_hits && cache != device->mem_cache) {
/* The pipeline cache only really helps if we hit for everything
* in the partition. Otherwise, we have to go re-compile it all
* anyway.
*/
for (uint32_t i = partition[p]; i < partition[p + 1]; i++) {
struct vk_pipeline_stage *stage = &stages[i];
stage_feedbacks[stage->stage].flags |=
VK_PIPELINE_CREATION_FEEDBACK_APPLICATION_PIPELINE_CACHE_HIT_BIT;
}
}
if (all_shaders_found) {
/* Update duration to take cache lookups into account */
const int64_t part_end = os_time_get_nano();
for (uint32_t i = partition[p]; i < partition[p + 1]; i++) {
struct vk_pipeline_stage *stage = &stages[i];
stage_feedbacks[stage->stage].duration += part_end - part_start;
}
continue;
}
}
if (pipeline->base.flags &
VK_PIPELINE_CREATE_2_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT_KHR)
return VK_PIPELINE_COMPILE_REQUIRED;
struct vk_shader_compile_info infos[MESA_VK_MAX_GRAPHICS_PIPELINE_STAGES];
for (uint32_t i = partition[p]; i < partition[p + 1]; i++) {
struct vk_pipeline_stage *stage = &stages[i];
VkShaderCreateFlagsEXT shader_flags =
vk_pipeline_to_shader_flags(pipeline->base.flags, stage->stage);
if (partition[p + 1] - partition[p] > 1)
shader_flags |= VK_SHADER_CREATE_LINK_STAGE_BIT_EXT;
if ((part_stages & VK_SHADER_STAGE_MESH_BIT_EXT) &&
!(geom_stages & VK_SHADER_STAGE_TASK_BIT_EXT))
shader_flags = VK_SHADER_CREATE_NO_TASK_SHADER_BIT_EXT;
VkShaderStageFlags next_stage;
if (stage->stage == MESA_SHADER_FRAGMENT) {
next_stage = 0;
} else if (i + 1 < stage_count) {
/* We hash geom_stages above so this is safe */
next_stage = mesa_to_vk_shader_stage(stages[i + 1].stage);
} else {
/* We're the last geometry stage */
next_stage = VK_SHADER_STAGE_FRAGMENT_BIT;
}
const struct nir_shader_compiler_options *nir_options =
ops->get_nir_options(device->physical, stage->stage,
&stage->precomp->rs);
nir_shader *nir =
vk_pipeline_precomp_shader_get_nir(stage->precomp, nir_options);
if (nir == NULL) {
for (uint32_t j = partition[p]; j < i; j++)
ralloc_free(infos[i].nir);
return vk_error(device, VK_ERROR_OUT_OF_HOST_MEMORY);
}
if (stage->stage == MESA_SHADER_TESS_CTRL ||
stage->stage == MESA_SHADER_TESS_EVAL)
vk_pipeline_replace_nir_tess_info(nir, &tess_info);
const VkPushConstantRange *push_range = NULL;
if (pipeline_layout != NULL) {
for (uint32_t r = 0; r < pipeline_layout->push_range_count; r++) {
if (pipeline_layout->push_ranges[r].stageFlags &
mesa_to_vk_shader_stage(stage->stage)) {
assert(push_range == NULL);
push_range = &pipeline_layout->push_ranges[r];
}
}
}
infos[i] = (struct vk_shader_compile_info) {
.stage = stage->stage,
.flags = shader_flags,
.next_stage_mask = next_stage,
.nir = nir,
.robustness = &stage->precomp->rs,
.set_layout_count = pipeline->set_layout_count,
.set_layouts = pipeline->set_layouts,
.push_constant_range_count = push_range != NULL,
.push_constant_ranges = push_range != NULL ? push_range : NULL,
};
}
/* vk_shader_ops::compile() consumes the NIR regardless of whether or
* not it succeeds and only generates shaders on success. Once this
* returns, we own the shaders but not the NIR in infos.
*/
struct vk_shader *shaders[MESA_VK_MAX_GRAPHICS_PIPELINE_STAGES];
result = ops->compile(device, partition[p + 1] - partition[p],
&infos[partition[p]],
state, &device->enabled_features,
&device->alloc,
&shaders[partition[p]]);
if (result != VK_SUCCESS)
return result;
const int64_t part_end = os_time_get_nano();
for (uint32_t i = partition[p]; i < partition[p + 1]; i++) {
struct vk_pipeline_stage *stage = &stages[i];
shader_key.stage = stage->stage;
vk_shader_init_cache_obj(device, shaders[i], &shader_key,
sizeof(shader_key));
if (stage->shader == NULL) {
struct vk_pipeline_cache_object *cache_obj =
&shaders[i]->pipeline.cache_obj;
if (cache != NULL)
cache_obj = vk_pipeline_cache_add_object(cache, cache_obj);
stage->shader = vk_shader_from_cache_obj(cache_obj);
} else {
/* This can fail to happen if only some of the shaders were found
* in the pipeline cache. In this case, we just throw away the
* shader as vk_pipeline_cache_add_object() would throw it away
* for us anyway.
*/
assert(memcmp(&stage->shader->pipeline.cache_key,
&shaders[i]->pipeline.cache_key,
sizeof(shaders[i]->pipeline.cache_key)) == 0);
vk_shader_unref(device, shaders[i]);
}
stage_feedbacks[stage->stage].duration += part_end - part_start;
}
}
return VK_SUCCESS;
}
static VkResult
vk_graphics_pipeline_get_executable_properties(
struct vk_device *device,
struct vk_pipeline *pipeline,
uint32_t *executable_count,
VkPipelineExecutablePropertiesKHR *properties)
{
struct vk_graphics_pipeline *gfx_pipeline =
container_of(pipeline, struct vk_graphics_pipeline, base);
VkResult result;
if (properties == NULL) {
*executable_count = 0;
for (uint32_t i = 0; i < gfx_pipeline->stage_count; i++) {
struct vk_shader *shader = gfx_pipeline->stages[i].shader;
uint32_t shader_exec_count = 0;
result = shader->ops->get_executable_properties(device, shader,
&shader_exec_count,
NULL);
assert(result == VK_SUCCESS);
*executable_count += shader_exec_count;
}
} else {
uint32_t arr_len = *executable_count;
*executable_count = 0;
for (uint32_t i = 0; i < gfx_pipeline->stage_count; i++) {
struct vk_shader *shader = gfx_pipeline->stages[i].shader;
uint32_t shader_exec_count = arr_len - *executable_count;
result = shader->ops->get_executable_properties(device, shader,
&shader_exec_count,
&properties[*executable_count]);
if (result != VK_SUCCESS)
return result;
*executable_count += shader_exec_count;
}
}
return VK_SUCCESS;
}
static inline struct vk_shader *
vk_graphics_pipeline_executable_shader(struct vk_device *device,
struct vk_graphics_pipeline *gfx_pipeline,
uint32_t *executable_index)
{
for (uint32_t i = 0; i < gfx_pipeline->stage_count; i++) {
struct vk_shader *shader = gfx_pipeline->stages[i].shader;
uint32_t shader_exec_count = 0;
shader->ops->get_executable_properties(device, shader,
&shader_exec_count, NULL);
if (*executable_index < shader_exec_count)
return shader;
else
*executable_index -= shader_exec_count;
}
return NULL;
}
static VkResult
vk_graphics_pipeline_get_executable_statistics(
struct vk_device *device,
struct vk_pipeline *pipeline,
uint32_t executable_index,
uint32_t *statistic_count,
VkPipelineExecutableStatisticKHR *statistics)
{
struct vk_graphics_pipeline *gfx_pipeline =
container_of(pipeline, struct vk_graphics_pipeline, base);
struct vk_shader *shader =
vk_graphics_pipeline_executable_shader(device, gfx_pipeline,
&executable_index);
if (shader == NULL) {
*statistic_count = 0;
return VK_SUCCESS;
}
return shader->ops->get_executable_statistics(device, shader,
executable_index,
statistic_count,
statistics);
}
static VkResult
vk_graphics_pipeline_get_internal_representations(
struct vk_device *device,
struct vk_pipeline *pipeline,
uint32_t executable_index,
uint32_t *internal_representation_count,
VkPipelineExecutableInternalRepresentationKHR* internal_representations)
{
struct vk_graphics_pipeline *gfx_pipeline =
container_of(pipeline, struct vk_graphics_pipeline, base);
struct vk_shader *shader =
vk_graphics_pipeline_executable_shader(device, gfx_pipeline,
&executable_index);
if (shader == NULL) {
*internal_representation_count = 0;
return VK_SUCCESS;
}
return shader->ops->get_executable_internal_representations(
device, shader, executable_index,
internal_representation_count, internal_representations);
}
static struct vk_shader *
vk_graphics_pipeline_get_shader(struct vk_pipeline *pipeline,
gl_shader_stage stage)
{
struct vk_graphics_pipeline *gfx_pipeline =
container_of(pipeline, struct vk_graphics_pipeline, base);
for (uint32_t i = 0; i < gfx_pipeline->stage_count; i++) {
if (gfx_pipeline->stages[i].stage == stage)
return gfx_pipeline->stages[i].shader;
}
return NULL;
}
static const struct vk_pipeline_ops vk_graphics_pipeline_ops = {
.destroy = vk_graphics_pipeline_destroy,
.get_executable_statistics = vk_graphics_pipeline_get_executable_statistics,
.get_executable_properties = vk_graphics_pipeline_get_executable_properties,
.get_internal_representations = vk_graphics_pipeline_get_internal_representations,
.cmd_bind = vk_graphics_pipeline_cmd_bind,
.get_shader = vk_graphics_pipeline_get_shader,
};
static VkResult
vk_create_graphics_pipeline(struct vk_device *device,
struct vk_pipeline_cache *cache,
const VkGraphicsPipelineCreateInfo *pCreateInfo,
const VkAllocationCallbacks *pAllocator,
VkPipeline *pPipeline)
{
VK_FROM_HANDLE(vk_pipeline_layout, pipeline_layout, pCreateInfo->layout);
const int64_t pipeline_start = os_time_get_nano();
VkResult result;
const VkPipelineCreateFlags2KHR pipeline_flags =
vk_graphics_pipeline_create_flags(pCreateInfo);
const VkPipelineCreationFeedbackCreateInfo *feedback_info =
vk_find_struct_const(pCreateInfo->pNext,
PIPELINE_CREATION_FEEDBACK_CREATE_INFO);
const VkPipelineLibraryCreateInfoKHR *libs_info =
vk_find_struct_const(pCreateInfo->pNext,
PIPELINE_LIBRARY_CREATE_INFO_KHR);
struct vk_graphics_pipeline *pipeline =
vk_pipeline_zalloc(device, &vk_graphics_pipeline_ops,
VK_PIPELINE_BIND_POINT_GRAPHICS,
pipeline_flags, pAllocator, sizeof(*pipeline));
if (pipeline == NULL)
return vk_error(device, VK_ERROR_OUT_OF_HOST_MEMORY);
struct vk_pipeline_stage stages[PIPE_SHADER_MESH_TYPES];
memset(stages, 0, sizeof(stages));
VkPipelineCreationFeedback stage_feedbacks[PIPE_SHADER_MESH_TYPES];
memset(stage_feedbacks, 0, sizeof(stage_feedbacks));
struct vk_graphics_pipeline_state state_tmp, *state;
struct vk_graphics_pipeline_all_state all_state_tmp, *all_state;
if (pipeline->base.flags & VK_PIPELINE_CREATE_2_LIBRARY_BIT_KHR) {
/* For pipeline libraries, the state is stored in the pipeline */
state = &pipeline->lib.state;
all_state = &pipeline->lib.all_state;
} else {
/* For linked pipelines, we throw the state away at the end of pipeline
* creation and only keep the dynamic state.
*/
memset(&state_tmp, 0, sizeof(state_tmp));
state = &state_tmp;
all_state = &all_state_tmp;
}
/* If we have libraries, import them first. */
if (libs_info) {
for (uint32_t i = 0; i < libs_info->libraryCount; i++) {
VK_FROM_HANDLE(vk_pipeline, lib_pipeline, libs_info->pLibraries[i]);
assert(lib_pipeline->bind_point == VK_PIPELINE_BIND_POINT_GRAPHICS);
assert(lib_pipeline->flags & VK_PIPELINE_CREATE_2_LIBRARY_BIT_KHR);
struct vk_graphics_pipeline *lib_gfx_pipeline =
container_of(lib_pipeline, struct vk_graphics_pipeline, base);
vk_graphics_pipeline_state_merge(state, &lib_gfx_pipeline->lib.state);
pipeline->set_layout_count = MAX2(pipeline->set_layout_count,
lib_gfx_pipeline->set_layout_count);
for (uint32_t i = 0; i < lib_gfx_pipeline->set_layout_count; i++) {
if (lib_gfx_pipeline->set_layouts[i] == NULL)
continue;
if (pipeline->set_layouts[i] == NULL) {
pipeline->set_layouts[i] =
vk_descriptor_set_layout_ref(lib_gfx_pipeline->set_layouts[i]);
}
}
for (uint32_t i = 0; i < lib_gfx_pipeline->stage_count; i++) {
const struct vk_pipeline_stage *lib_stage =
&lib_gfx_pipeline->stages[i];
/* We shouldn't have duplicated stages in the imported pipeline
* but it's cheap enough to protect against it so we may as well.
*/
assert(lib_stage->stage < ARRAY_SIZE(stages));
assert(vk_pipeline_stage_is_null(&stages[lib_stage->stage]));
if (!vk_pipeline_stage_is_null(&stages[lib_stage->stage]))
continue;
stages[lib_stage->stage] = vk_pipeline_stage_clone(lib_stage);
}
}
}
result = vk_graphics_pipeline_state_fill(device, state,
pCreateInfo,
NULL /* driver_rp */,
0 /* driver_rp_flags */,
all_state,
NULL, 0, NULL);
if (result != VK_SUCCESS)
goto fail_stages;
if (!(pipeline->base.flags & VK_PIPELINE_CREATE_2_LIBRARY_BIT_KHR)) {
pipeline->linked.dynamic.vi = &pipeline->linked._dynamic_vi;
pipeline->linked.dynamic.ms.sample_locations =
&pipeline->linked._dynamic_sl;
vk_dynamic_graphics_state_fill(&pipeline->linked.dynamic, &state_tmp);
}
if (pipeline_layout != NULL) {
pipeline->set_layout_count = MAX2(pipeline->set_layout_count,
pipeline_layout->set_count);
for (uint32_t i = 0; i < pipeline_layout->set_count; i++) {
if (pipeline_layout->set_layouts[i] == NULL)
continue;
if (pipeline->set_layouts[i] == NULL) {
pipeline->set_layouts[i] =
vk_descriptor_set_layout_ref(pipeline_layout->set_layouts[i]);
}
}
}
for (uint32_t i = 0; i < pCreateInfo->stageCount; i++) {
const VkPipelineShaderStageCreateInfo *stage_info =
&pCreateInfo->pStages[i];
const int64_t stage_start = os_time_get_nano();
assert(util_bitcount(stage_info->stage) == 1);
if (!(state->shader_stages & stage_info->stage))
continue;
gl_shader_stage stage = vk_to_mesa_shader_stage(stage_info->stage);
assert(vk_device_supports_stage(device, stage));
stage_feedbacks[stage].flags |=
VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT;
if (!vk_pipeline_stage_is_null(&stages[stage]))
continue;
struct vk_pipeline_precomp_shader *precomp;
result = vk_pipeline_precompile_shader(device, cache, pipeline_flags,
pCreateInfo->pNext,
stage_info,
&precomp);
if (result != VK_SUCCESS)
goto fail_stages;
stages[stage] = (struct vk_pipeline_stage) {
.stage = stage,
.precomp = precomp,
};
const int64_t stage_end = os_time_get_nano();
stage_feedbacks[stage].duration += stage_end - stage_start;
}
/* Compact the array of stages */
uint32_t stage_count = 0;
for (uint32_t s = 0; s < ARRAY_SIZE(stages); s++) {
assert(s >= stage_count);
if (!vk_pipeline_stage_is_null(&stages[s]))
stages[stage_count++] = stages[s];
}
for (uint32_t s = stage_count; s < ARRAY_SIZE(stages); s++)
memset(&stages[s], 0, sizeof(stages[s]));
/* Sort so we always give the driver shaders in order.
*
* This makes everything easier for everyone. This also helps stabilize
* shader keys so that we get a cache hit even if the client gives us
* the stages in a different order.
*/
qsort(stages, stage_count, sizeof(*stages), cmp_vk_pipeline_stages);
result = vk_graphics_pipeline_compile_shaders(device, cache, pipeline,
pipeline_layout, state,
stage_count, stages,
stage_feedbacks);
if (result != VK_SUCCESS)
goto fail_stages;
/* Throw away precompiled shaders unless the client explicitly asks us to
* keep them.
*/
if (!(pipeline_flags &
VK_PIPELINE_CREATE_2_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT)) {
for (uint32_t i = 0; i < stage_count; i++) {
if (stages[i].precomp != NULL) {
vk_pipeline_precomp_shader_unref(device, stages[i].precomp);
stages[i].precomp = NULL;
}
}
}
pipeline->stage_count = stage_count;
for (uint32_t i = 0; i < stage_count; i++) {
pipeline->base.stages |= mesa_to_vk_shader_stage(stages[i].stage);
pipeline->stages[i] = stages[i];
}
const int64_t pipeline_end = os_time_get_nano();
if (feedback_info != NULL) {
VkPipelineCreationFeedback pipeline_feedback = {
.flags = VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT,
.duration = pipeline_end - pipeline_start,
};
/* From the Vulkan 1.3.275 spec:
*
* "An implementation should set the
* VK_PIPELINE_CREATION_FEEDBACK_APPLICATION_PIPELINE_CACHE_HIT_BIT
* bit if it was able to avoid the large majority of pipeline or
* pipeline stage creation work by using the pipelineCache parameter"
*
* We really shouldn't set this bit unless all the shaders hit the
* cache.
*/
uint32_t cache_hit_count = 0;
for (uint32_t i = 0; i < stage_count; i++) {
const gl_shader_stage stage = stages[i].stage;
if (stage_feedbacks[stage].flags &
VK_PIPELINE_CREATION_FEEDBACK_APPLICATION_PIPELINE_CACHE_HIT_BIT)
cache_hit_count++;
}
if (cache_hit_count > 0 && cache_hit_count == stage_count) {
pipeline_feedback.flags |=
VK_PIPELINE_CREATION_FEEDBACK_APPLICATION_PIPELINE_CACHE_HIT_BIT;
}
*feedback_info->pPipelineCreationFeedback = pipeline_feedback;
/* VUID-VkGraphicsPipelineCreateInfo-pipelineStageCreationFeedbackCount-06594 */
assert(feedback_info->pipelineStageCreationFeedbackCount == 0 ||
feedback_info->pipelineStageCreationFeedbackCount ==
pCreateInfo->stageCount);
for (uint32_t i = 0;
i < feedback_info->pipelineStageCreationFeedbackCount; i++) {
const gl_shader_stage stage =
vk_to_mesa_shader_stage(pCreateInfo->pStages[i].stage);
feedback_info->pPipelineStageCreationFeedbacks[i] =
stage_feedbacks[stage];
}
}
*pPipeline = vk_pipeline_to_handle(&pipeline->base);
return VK_SUCCESS;
fail_stages:
for (uint32_t i = 0; i < ARRAY_SIZE(stages); i++)
vk_pipeline_stage_finish(device, &stages[i]);
vk_graphics_pipeline_destroy(device, &pipeline->base, pAllocator);
return result;
}
VKAPI_ATTR VkResult VKAPI_CALL
vk_common_CreateGraphicsPipelines(VkDevice _device,
VkPipelineCache pipelineCache,
uint32_t createInfoCount,
const VkGraphicsPipelineCreateInfo *pCreateInfos,
const VkAllocationCallbacks *pAllocator,
VkPipeline *pPipelines)
{
VK_FROM_HANDLE(vk_device, device, _device);
VK_FROM_HANDLE(vk_pipeline_cache, cache, pipelineCache);
VkResult first_error_or_success = VK_SUCCESS;
/* Use implicit pipeline cache if there's no cache set */
if (!cache && device->mem_cache)
cache = device->mem_cache;
/* From the Vulkan 1.3.274 spec:
*
* "When attempting to create many pipelines in a single command, it is
* possible that creation may fail for a subset of them. In this case,
* the corresponding elements of pPipelines will be set to
* VK_NULL_HANDLE.
*/
memset(pPipelines, 0, createInfoCount * sizeof(*pPipelines));
unsigned i = 0;
for (; i < createInfoCount; i++) {
VkResult result = vk_create_graphics_pipeline(device, cache,
&pCreateInfos[i],
pAllocator,
&pPipelines[i]);
if (result == VK_SUCCESS)
continue;
if (first_error_or_success == VK_SUCCESS)
first_error_or_success = result;
/* Bail out on the first error != VK_PIPELINE_COMPILE_REQUIRED as it
* is not obvious what error should be report upon 2 different failures.
*/
if (result != VK_PIPELINE_COMPILE_REQUIRED)
return result;
const VkPipelineCreateFlags2KHR flags =
vk_graphics_pipeline_create_flags(&pCreateInfos[i]);
if (flags & VK_PIPELINE_CREATE_2_EARLY_RETURN_ON_FAILURE_BIT_KHR)
return result;
}
return first_error_or_success;
}
struct vk_compute_pipeline {
struct vk_pipeline base;
struct vk_shader *shader;
};
static void
vk_compute_pipeline_destroy(struct vk_device *device,
struct vk_pipeline *pipeline,
const VkAllocationCallbacks *pAllocator)
{
struct vk_compute_pipeline *comp_pipeline =
container_of(pipeline, struct vk_compute_pipeline, base);
vk_shader_unref(device, comp_pipeline->shader);
vk_pipeline_free(device, pAllocator, pipeline);
}
static void
vk_compute_pipeline_cmd_bind(struct vk_command_buffer *cmd_buffer,
struct vk_pipeline *pipeline)
{
struct vk_device *device = cmd_buffer->base.device;
const struct vk_device_shader_ops *ops = device->shader_ops;
struct vk_shader *shader = NULL;
if (pipeline != NULL) {
assert(pipeline->bind_point == VK_PIPELINE_BIND_POINT_COMPUTE);
struct vk_compute_pipeline *comp_pipeline =
container_of(pipeline, struct vk_compute_pipeline, base);
shader = comp_pipeline->shader;
cmd_buffer->pipeline_shader_stages |= VK_SHADER_STAGE_COMPUTE_BIT;
} else {
cmd_buffer->pipeline_shader_stages &= ~VK_SHADER_STAGE_COMPUTE_BIT;
}
gl_shader_stage stage = MESA_SHADER_COMPUTE;
ops->cmd_bind_shaders(cmd_buffer, 1, &stage, &shader);
}
static VkResult
vk_pipeline_compile_compute_stage(struct vk_device *device,
struct vk_pipeline_cache *cache,
struct vk_compute_pipeline *pipeline,
struct vk_pipeline_layout *pipeline_layout,
struct vk_pipeline_stage *stage,
bool *cache_hit)
{
const struct vk_device_shader_ops *ops = device->shader_ops;
VkResult result;
const VkPushConstantRange *push_range = NULL;
if (pipeline_layout != NULL) {
for (uint32_t r = 0; r < pipeline_layout->push_range_count; r++) {
if (pipeline_layout->push_ranges[r].stageFlags &
VK_SHADER_STAGE_COMPUTE_BIT) {
assert(push_range == NULL);
push_range = &pipeline_layout->push_ranges[r];
}
}
}
VkShaderCreateFlagsEXT shader_flags =
vk_pipeline_to_shader_flags(pipeline->base.flags, MESA_SHADER_COMPUTE);
struct mesa_blake3 blake3_ctx;
_mesa_blake3_init(&blake3_ctx);
_mesa_blake3_update(&blake3_ctx, stage->precomp->blake3,
sizeof(stage->precomp->blake3));
_mesa_blake3_update(&blake3_ctx, &shader_flags, sizeof(shader_flags));
blake3_hash features_blake3;
ops->hash_state(device->physical, NULL /* state */,
&device->enabled_features, VK_SHADER_STAGE_COMPUTE_BIT,
features_blake3);
_mesa_blake3_update(&blake3_ctx, features_blake3, sizeof(features_blake3));
for (uint32_t i = 0; i < pipeline_layout->set_count; i++) {
if (pipeline_layout->set_layouts[i] != NULL) {
_mesa_blake3_update(&blake3_ctx,
pipeline_layout->set_layouts[i]->blake3,
sizeof(pipeline_layout->set_layouts[i]->blake3));
}
}
if (push_range != NULL)
_mesa_blake3_update(&blake3_ctx, push_range, sizeof(*push_range));
struct vk_shader_pipeline_cache_key shader_key = {
.stage = MESA_SHADER_COMPUTE,
};
_mesa_blake3_final(&blake3_ctx, shader_key.blake3);
if (cache != NULL) {
struct vk_pipeline_cache_object *cache_obj =
vk_pipeline_cache_lookup_object(cache, &shader_key,
sizeof(shader_key),
&pipeline_shader_cache_ops,
cache_hit);
if (cache_obj != NULL) {
stage->shader = vk_shader_from_cache_obj(cache_obj);
return VK_SUCCESS;
}
}
if (pipeline->base.flags &
VK_PIPELINE_CREATE_2_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT_KHR)
return VK_PIPELINE_COMPILE_REQUIRED;
const struct nir_shader_compiler_options *nir_options =
ops->get_nir_options(device->physical, stage->stage,
&stage->precomp->rs);
nir_shader *nir = vk_pipeline_precomp_shader_get_nir(stage->precomp,
nir_options);
if (nir == NULL)
return vk_error(device, VK_ERROR_OUT_OF_HOST_MEMORY);
/* vk_device_shader_ops::compile() consumes the NIR regardless of whether
* or not it succeeds and only generates shaders on success. Once compile()
* returns, we own the shaders but not the NIR in infos.
*/
struct vk_shader_compile_info compile_info = {
.stage = stage->stage,
.flags = shader_flags,
.next_stage_mask = 0,
.nir = nir,
.robustness = &stage->precomp->rs,
.set_layout_count = pipeline_layout->set_count,
.set_layouts = pipeline_layout->set_layouts,
.push_constant_range_count = push_range != NULL,
.push_constant_ranges = push_range != NULL ? push_range : NULL,
};
struct vk_shader *shader;
result = ops->compile(device, 1, &compile_info, NULL,
&device->enabled_features, &device->alloc, &shader);
if (result != VK_SUCCESS)
return result;
vk_shader_init_cache_obj(device, shader, &shader_key, sizeof(shader_key));
struct vk_pipeline_cache_object *cache_obj = &shader->pipeline.cache_obj;
if (cache != NULL)
cache_obj = vk_pipeline_cache_add_object(cache, cache_obj);
stage->shader = vk_shader_from_cache_obj(cache_obj);
return VK_SUCCESS;
}
static VkResult
vk_compute_pipeline_get_executable_properties(
struct vk_device *device,
struct vk_pipeline *pipeline,
uint32_t *executable_count,
VkPipelineExecutablePropertiesKHR *properties)
{
struct vk_compute_pipeline *comp_pipeline =
container_of(pipeline, struct vk_compute_pipeline, base);
struct vk_shader *shader = comp_pipeline->shader;
return shader->ops->get_executable_properties(device, shader,
executable_count,
properties);
}
static VkResult
vk_compute_pipeline_get_executable_statistics(
struct vk_device *device,
struct vk_pipeline *pipeline,
uint32_t executable_index,
uint32_t *statistic_count,
VkPipelineExecutableStatisticKHR *statistics)
{
struct vk_compute_pipeline *comp_pipeline =
container_of(pipeline, struct vk_compute_pipeline, base);
struct vk_shader *shader = comp_pipeline->shader;
return shader->ops->get_executable_statistics(device, shader,
executable_index,
statistic_count,
statistics);
}
static VkResult
vk_compute_pipeline_get_internal_representations(
struct vk_device *device,
struct vk_pipeline *pipeline,
uint32_t executable_index,
uint32_t *internal_representation_count,
VkPipelineExecutableInternalRepresentationKHR* internal_representations)
{
struct vk_compute_pipeline *comp_pipeline =
container_of(pipeline, struct vk_compute_pipeline, base);
struct vk_shader *shader = comp_pipeline->shader;
return shader->ops->get_executable_internal_representations(
device, shader, executable_index,
internal_representation_count, internal_representations);
}
static struct vk_shader *
vk_compute_pipeline_get_shader(struct vk_pipeline *pipeline,
gl_shader_stage stage)
{
struct vk_compute_pipeline *comp_pipeline =
container_of(pipeline, struct vk_compute_pipeline, base);
assert(stage == MESA_SHADER_COMPUTE);
return comp_pipeline->shader;
}
static const struct vk_pipeline_ops vk_compute_pipeline_ops = {
.destroy = vk_compute_pipeline_destroy,
.get_executable_statistics = vk_compute_pipeline_get_executable_statistics,
.get_executable_properties = vk_compute_pipeline_get_executable_properties,
.get_internal_representations = vk_compute_pipeline_get_internal_representations,
.cmd_bind = vk_compute_pipeline_cmd_bind,
.get_shader = vk_compute_pipeline_get_shader,
};
static VkResult
vk_create_compute_pipeline(struct vk_device *device,
struct vk_pipeline_cache *cache,
const VkComputePipelineCreateInfo *pCreateInfo,
const VkAllocationCallbacks *pAllocator,
VkPipeline *pPipeline)
{
VK_FROM_HANDLE(vk_pipeline_layout, pipeline_layout, pCreateInfo->layout);
int64_t pipeline_start = os_time_get_nano();
VkResult result;
const VkPipelineCreateFlags2KHR pipeline_flags =
vk_compute_pipeline_create_flags(pCreateInfo);
const VkPipelineCreationFeedbackCreateInfo *feedback_info =
vk_find_struct_const(pCreateInfo->pNext,
PIPELINE_CREATION_FEEDBACK_CREATE_INFO);
struct vk_compute_pipeline *pipeline =
vk_pipeline_zalloc(device, &vk_compute_pipeline_ops,
VK_PIPELINE_BIND_POINT_COMPUTE,
pipeline_flags, pAllocator, sizeof(*pipeline));
if (pipeline == NULL)
return vk_error(device, VK_ERROR_OUT_OF_HOST_MEMORY);
pipeline->base.stages = VK_SHADER_STAGE_COMPUTE_BIT;
struct vk_pipeline_stage stage = {
.stage = MESA_SHADER_COMPUTE,
};
result = vk_pipeline_precompile_shader(device, cache, pipeline_flags,
pCreateInfo->pNext,
&pCreateInfo->stage,
&stage.precomp);
if (result != VK_SUCCESS)
goto fail_pipeline;
bool cache_hit;
result = vk_pipeline_compile_compute_stage(device, cache, pipeline,
pipeline_layout, &stage,
&cache_hit);
if (result != VK_SUCCESS)
goto fail_stage;
if (stage.precomp != NULL)
vk_pipeline_precomp_shader_unref(device, stage.precomp);
pipeline->shader = stage.shader;
const int64_t pipeline_end = os_time_get_nano();
if (feedback_info != NULL) {
VkPipelineCreationFeedback pipeline_feedback = {
.flags = VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT,
.duration = pipeline_end - pipeline_start,
};
if (cache_hit && cache != device->mem_cache) {
pipeline_feedback.flags |=
VK_PIPELINE_CREATION_FEEDBACK_APPLICATION_PIPELINE_CACHE_HIT_BIT;
}
*feedback_info->pPipelineCreationFeedback = pipeline_feedback;
if (feedback_info->pipelineStageCreationFeedbackCount > 0) {
feedback_info->pPipelineStageCreationFeedbacks[0] =
pipeline_feedback;
}
}
*pPipeline = vk_pipeline_to_handle(&pipeline->base);
return VK_SUCCESS;
fail_stage:
vk_pipeline_stage_finish(device, &stage);
fail_pipeline:
vk_pipeline_free(device, pAllocator, &pipeline->base);
return result;
}
VKAPI_ATTR VkResult VKAPI_CALL
vk_common_CreateComputePipelines(VkDevice _device,
VkPipelineCache pipelineCache,
uint32_t createInfoCount,
const VkComputePipelineCreateInfo *pCreateInfos,
const VkAllocationCallbacks *pAllocator,
VkPipeline *pPipelines)
{
VK_FROM_HANDLE(vk_device, device, _device);
VK_FROM_HANDLE(vk_pipeline_cache, cache, pipelineCache);
VkResult first_error_or_success = VK_SUCCESS;
/* Use implicit pipeline cache if there's no cache set */
if (!cache && device->mem_cache)
cache = device->mem_cache;
/* From the Vulkan 1.3.274 spec:
*
* "When attempting to create many pipelines in a single command, it is
* possible that creation may fail for a subset of them. In this case,
* the corresponding elements of pPipelines will be set to
* VK_NULL_HANDLE.
*/
memset(pPipelines, 0, createInfoCount * sizeof(*pPipelines));
unsigned i = 0;
for (; i < createInfoCount; i++) {
VkResult result = vk_create_compute_pipeline(device, cache,
&pCreateInfos[i],
pAllocator,
&pPipelines[i]);
if (result == VK_SUCCESS)
continue;
if (first_error_or_success == VK_SUCCESS)
first_error_or_success = result;
/* Bail out on the first error != VK_PIPELINE_COMPILE_REQUIRED as it
* is not obvious what error should be report upon 2 different failures.
*/
if (result != VK_PIPELINE_COMPILE_REQUIRED)
return result;
const VkPipelineCreateFlags2KHR flags =
vk_compute_pipeline_create_flags(&pCreateInfos[i]);
if (flags & VK_PIPELINE_CREATE_2_EARLY_RETURN_ON_FAILURE_BIT_KHR)
return result;
}
return first_error_or_success;
}
void
vk_cmd_unbind_pipelines_for_stages(struct vk_command_buffer *cmd_buffer,
VkShaderStageFlags stages)
{
stages &= cmd_buffer->pipeline_shader_stages;
if (stages & ~VK_SHADER_STAGE_COMPUTE_BIT)
vk_graphics_pipeline_cmd_bind(cmd_buffer, NULL);
if (stages & VK_SHADER_STAGE_COMPUTE_BIT)
vk_compute_pipeline_cmd_bind(cmd_buffer, NULL);
}