diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-30 05:02:52 -0400 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-11-01 11:29:30 -0500 |
| commit | 02504aeb37cb3ee07dc421acc0570ad5ba25d53f (patch) | |
| tree | ee0a820addc89f4609e7d8ef5dc8eb93f324e7eb /src | |
| parent | 310dd596d0f6d48de3517cfb1bdc90a366817405 (diff) | |
video_core: draw Switch 2 task/mesh pipelines with VK_EXT_mesh_shader
While SET_MESH_CONTROL is on, a native draw binds a task program in the
vertex slot, its mesh program in the tessellation slot and a header-only
pass-through geometry program naming the per-primitive outputs. Translate
the first two as task and mesh programs with the mesh registers
(SET_MESH_SHADER_A/B, SET_MESH_INIT_SHADER) and that header, key their
pipelines by them, and build pipelines with task/mesh stages and no
vertex input, input assembly or tessellation state. The draw's vertex
count is the number of task workgroups: record vkCmdDrawMeshTasksEXT.
Enable VK_EXT_mesh_shader (task and mesh only) when the host has it; draws
are skipped without it, as they were before.
Diffstat (limited to 'src')
| -rw-r--r-- | src/video_core/dirty_flags.cpp | 2 | ||||
| -rw-r--r-- | src/video_core/engines/maxwell_3d.h | 13 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/maxwell_to_vk.cpp | 4 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp | 35 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_graphics_pipeline.h | 5 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | 15 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_rasterizer.cpp | 20 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_rasterizer.h | 1 | ||||
| -rw-r--r-- | src/video_core/shader_cache.cpp | 27 | ||||
| -rw-r--r-- | src/video_core/shader_cache.h | 2 | ||||
| -rw-r--r-- | src/video_core/shader_environment.cpp | 25 | ||||
| -rw-r--r-- | src/video_core/vulkan_common/vulkan_device.cpp | 9 | ||||
| -rw-r--r-- | src/video_core/vulkan_common/vulkan_device.h | 7 | ||||
| -rw-r--r-- | src/video_core/vulkan_common/vulkan_wrapper.cpp | 1 | ||||
| -rw-r--r-- | src/video_core/vulkan_common/vulkan_wrapper.h | 5 |
15 files changed, 163 insertions, 8 deletions
diff --git a/src/video_core/dirty_flags.cpp b/src/video_core/dirty_flags.cpp index e6856aaca3..2f359749aa 100644 --- a/src/video_core/dirty_flags.cpp +++ b/src/video_core/dirty_flags.cpp @@ -64,6 +64,8 @@ void SetupDirtyRenderTargets(Maxwell3D::DirtyState::Tables& tables) { void SetupDirtyShaders(Maxwell3D::DirtyState::Tables& tables) { FillBlock(tables[0], OFF(pipelines), NUM(pipelines[0]) * Maxwell3D::Regs::MaxShaderProgram, Shaders); + // Task and mesh programs are translated with the mesh registers. + FillBlock(tables[1], Maxwell3D::Regs::MeshControl, 4, Shaders); } } // Anonymous namespace diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 0121f1a01c..3e946db1fe 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -2541,6 +2541,19 @@ public: return IsShaderConfigEnabled(static_cast<std::size_t>(type)); } + // Switch 2 (NVN) task and mesh pipelines (clc597.h): SET_MESH_CONTROL, SET_MESH_SHADER_A + // and _B, SET_MESH_INIT_SHADER. While mesh control is on, the vertex slot holds the task + // program (or the mesh program when there is no task), the tessellation slot the mesh + // program and the geometry slot a pass-through header naming the per-primitive outputs. + static constexpr std::size_t MeshControl = 0x114c / 4; + static constexpr std::size_t MeshShaderA = 0x1150 / 4; + static constexpr std::size_t MeshShaderB = 0x1154 / 4; + static constexpr std::size_t MeshInitShader = 0x1158 / 4; + + bool IsMeshEnabled() const { + return (reg_array[MeshControl] & 1) != 0; + } + struct ConstantBuffer { u32 size; u32 address_high; diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp index c8e43d0650..e03e5786fe 100644 --- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp +++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp @@ -333,6 +333,10 @@ VkShaderStageFlagBits ShaderStage(Shader::Stage stage) { return VK_SHADER_STAGE_FRAGMENT_BIT; case Shader::Stage::Compute: return VK_SHADER_STAGE_COMPUTE_BIT; + case Shader::Stage::Task: + return VK_SHADER_STAGE_TASK_BIT_EXT; + case Shader::Stage::Mesh: + return VK_SHADER_STAGE_MESH_BIT_EXT; } UNIMPLEMENTED_MSG("Unimplemented shader stage={}", stage); return {}; diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 94bace432c..86b6e5a983 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -54,6 +54,17 @@ using VideoCore::Surface::PixelFormatFromRenderTargetFormat; constexpr size_t NUM_STAGES = Maxwell::MaxShaderStage; +// The stage of a program: task and mesh programs sit in the vertex and tessellation slots. +VkShaderStageFlagBits StageFlag(const Shader::Info& info, VkShaderStageFlagBits slot_stage) { + if (info.is_task) { + return VK_SHADER_STAGE_TASK_BIT_EXT; + } + if (info.is_mesh) { + return VK_SHADER_STAGE_MESH_BIT_EXT; + } + return slot_stage; +} + DescriptorLayoutBuilder MakeBuilder(const Device& device, std::span<const Shader::Info> infos) { DescriptorLayoutBuilder builder{device}; for (size_t index = 0; index < infos.size(); ++index) { @@ -64,7 +75,7 @@ DescriptorLayoutBuilder MakeBuilder(const Device& device, std::span<const Shader VK_SHADER_STAGE_GEOMETRY_BIT, VK_SHADER_STAGE_FRAGMENT_BIT, }; - builder.Add(infos[index], stages.at(index)); + builder.Add(infos[index], StageFlag(infos[index], stages.at(index))); } return builder; } @@ -1073,6 +1084,19 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { } } + const bool is_mesh{IsMeshPipeline()}; + if (is_mesh) { + // Mesh pipelines have no vertex input or input assembly. + dynamic_states.erase(std::remove_if(dynamic_states.begin(), dynamic_states.end(), + [](VkDynamicState state) { + return state == VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT || + state == VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE_EXT || + state == VK_DYNAMIC_STATE_VERTEX_INPUT_EXT || + state == VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT || + state == VK_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT; + }), + dynamic_states.end()); + } const VkPipelineDynamicStateCreateInfo dynamic_state_ci{ .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, .pNext = nullptr, @@ -1095,7 +1119,8 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .pNext = nullptr, .flags = 0, - .stage = MaxwellToVK::ShaderStage(Shader::StageFromIndex(stage)), + .stage = StageFlag(stage_infos[stage], + MaxwellToVK::ShaderStage(Shader::StageFromIndex(stage))), .module = *spv_modules[stage], .pName = "main", .pSpecializationInfo = nullptr, @@ -1115,9 +1140,9 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { .flags = flags, .stageCount = static_cast<u32>(shader_stages.size()), .pStages = shader_stages.data(), - .pVertexInputState = &vertex_input_ci, - .pInputAssemblyState = &input_assembly_ci, - .pTessellationState = &tessellation_ci, + .pVertexInputState = is_mesh ? nullptr : &vertex_input_ci, + .pInputAssemblyState = is_mesh ? nullptr : &input_assembly_ci, + .pTessellationState = is_mesh ? nullptr : &tessellation_ci, .pViewportState = &viewport_ci, .pRasterizationState = &rasterization_ci, .pMultisampleState = &multisample_ci, diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index b1ac5a3fcc..daee7910c1 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -98,6 +98,11 @@ public: bool UsesExtendedDynamicState() const noexcept { return key.state.extended_dynamic_state != 0; } + + /// Switch 2 task/mesh pipeline: drawn with vkCmdDrawMeshTasksEXT. + [[nodiscard]] bool IsMeshPipeline() const noexcept { + return stage_infos[0].is_task || stage_infos[0].is_mesh; + } GraphicsPipeline& operator=(GraphicsPipeline&&) noexcept = delete; GraphicsPipeline(GraphicsPipeline&&) noexcept = delete; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 2c64f3375b..03f390d272 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -170,6 +170,14 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program } else { info.previous_stage_stores.mask.set(); } + if (previous_program && previous_program->stage == Shader::Stage::Mesh) { + for (const u32 line : previous_program->mesh.primitive_lines) { + const auto attribute{static_cast<Shader::IR::Attribute>(line)}; + if (Shader::IR::IsGeneric(attribute)) { + info.per_primitive_generics.set(Shader::IR::GenericAttributeIndex(attribute)); + } + } + } const Shader::Stage stage{program.stage}; const bool has_geometry{key.unique_hashes[4] != 0 && !programs[4].is_geometry_passthrough}; const bool gl_ndc{key.state.ndc_minus_one_to_one != 0}; @@ -230,6 +238,9 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program return Shader::TessSpacing::Equal; }(); break; + case Shader::Stage::Mesh: + info.convert_depth_mode = gl_ndc; + break; case Shader::Stage::Geometry: if (program.output_topology == Shader::OutputTopology::PointList) { info.fixed_state_point_size = point_size; @@ -374,7 +385,9 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, Shader::Stage::TessellationEval) | subgroup_stage_bit(VK_SHADER_STAGE_GEOMETRY_BIT, Shader::Stage::Geometry) | subgroup_stage_bit(VK_SHADER_STAGE_FRAGMENT_BIT, Shader::Stage::Fragment) | - subgroup_stage_bit(VK_SHADER_STAGE_COMPUTE_BIT, Shader::Stage::Compute)}; + subgroup_stage_bit(VK_SHADER_STAGE_COMPUTE_BIT, Shader::Stage::Compute) | + subgroup_stage_bit(VK_SHADER_STAGE_TASK_BIT_EXT, Shader::Stage::Task) | + subgroup_stage_bit(VK_SHADER_STAGE_MESH_BIT_EXT, Shader::Stage::Mesh)}; if (driver_id == VK_DRIVER_ID_MESA_TURNIP) { supported_subgroup_stages &= ~(1u << static_cast<u32>(Shader::Stage::Geometry)); } diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index cb31a0e7e4..0109117344 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -279,6 +279,12 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) { report_failure("no pipeline"); return; } + // Task/mesh pipelines: the draw's vertex count is the number of task workgroups. + mesh_draw = pipeline->IsMeshPipeline(); + if (mesh_draw && !device.IsExtMeshShaderSupported()) { + report_failure("no VK_EXT_mesh_shader"); + return; + } std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; // update engine as channel may be different. pipeline->SetEngine(maxwell3d, gpu_memory); @@ -541,6 +547,12 @@ void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) { const u32 num_instances{instance_count}; const DrawParams draw_params{MakeDrawParams(draw_state, num_instances, is_indexed)}; + if (mesh_draw) { + scheduler.Record([groups = draw_params.num_vertices](vk::CommandBuffer cmdbuf) { + cmdbuf.DrawMeshTasksEXT(groups, 1, 1); + }); + return; + } scheduler.Record([draw_params](vk::CommandBuffer cmdbuf) { if (draw_params.is_indexed) { cmdbuf.DrawIndexed(draw_params.num_vertices, draw_params.num_instances, @@ -573,6 +585,14 @@ void RasterizerVulkan::DrawIndirect() { const auto& params = maxwell3d->draw_manager.indirect_state; buffer_cache.SetDrawIndirect(¶ms); PrepareDraw(params.is_indexed, [this, ¶ms] { + if (mesh_draw) { + // Indirect task/mesh draws would need vkCmdDrawMeshTasksIndirectEXT's layout. + static bool logged{}; + if (!std::exchange(logged, true)) { + LOG_WARNING(Render_Vulkan, "Indirect task/mesh draw skipped"); + } + return; + } const auto indirect_buffer = buffer_cache.GetDrawIndirectBuffer(); const auto& buffer = indirect_buffer.first; const auto& offset = indirect_buffer.second; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index ae9929ca0b..e8989dcdfc 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -230,6 +230,7 @@ private: boost::container::static_vector<VkSampler, MAX_TEXTURES> sampler_handles; u32 draw_counter = 0; + bool mesh_draw{}; ///< the draw being prepared uses a task/mesh pipeline }; } // namespace Vulkan diff --git a/src/video_core/shader_cache.cpp b/src/video_core/shader_cache.cpp index dfe80abac3..f03df6bac5 100644 --- a/src/video_core/shader_cache.cpp +++ b/src/video_core/shader_cache.cpp @@ -14,6 +14,7 @@ #include <boost/container/small_vector.hpp> #include "common/assert.h" +#include "common/cityhash.h" #include "shader_recompiler/frontend/maxwell/control_flow.h" #include "shader_recompiler/exception.h" #include "shader_recompiler/object_pool.h" @@ -48,6 +49,22 @@ void ShaderCache::SyncGuestHost() { ShaderCache::ShaderCache(Tegra::MaxwellDeviceMemoryManager& device_memory_) : device_memory{device_memory_} {} +// Task and mesh programs are translated with the mesh registers and the header of the pass-through +// geometry program (per-primitive outputs), so pipelines are keyed by them too. +u64 ShaderCache::MeshStateHash() const { + using Regs = Tegra::Engines::Maxwell3D::Regs; + const auto& regs{maxwell3d->regs}; + std::array<u32, 3 + 20> state{regs.reg_array[Regs::MeshShaderA], regs.reg_array[Regs::MeshShaderB], + regs.reg_array[Regs::MeshInitShader]}; + if (regs.IsShaderConfigEnabled(Regs::ShaderType::Geometry)) { + const auto& geometry{regs.pipelines[static_cast<size_t>(Regs::ShaderType::Geometry)]}; + const GPUVAddr address{(static_cast<u64>(geometry.reserved[0] & 0xff) << 32) | + geometry.reserved[1]}; + gpu_memory->ReadBlockUnsafe(address, state.data() + 3, 20 * sizeof(u32)); + } + return Common::CityHash64(reinterpret_cast<const char*>(state.data()), sizeof(state)) | 1; +} + bool ShaderCache::RefreshStages(std::array<u64, 6>& unique_hashes) try { auto& dirty{maxwell3d->dirty.flags}; if (!dirty[VideoCommon::Dirty::Shaders]) { @@ -57,6 +74,7 @@ bool ShaderCache::RefreshStages(std::array<u64, 6>& unique_hashes) try { const GPUVAddr base_addr{maxwell3d->regs.program_region.Address()}; const bool native = maxwell3d->GetNativeMacroEngine().native_turing; + const bool mesh{native && maxwell3d->regs.IsMeshEnabled()}; for (size_t index = 0; index < Tegra::Engines::Maxwell3D::Regs::MaxShaderProgram; ++index) { if (!maxwell3d->regs.IsShaderConfigEnabled(index)) { unique_hashes[index] = 0; @@ -69,6 +87,11 @@ bool ShaderCache::RefreshStages(std::array<u64, 6>& unique_hashes) try { unique_hashes[index] = 0; continue; } + if (mesh && program == Tegra::Engines::Maxwell3D::Regs::ShaderType::Geometry) { + // A header without code: the mesh program's environment reads its output map. + unique_hashes[index] = 0; + continue; + } const GPUVAddr shader_addr{native ? (static_cast<u64>(shader_config.reserved[0] & 0xff) << 32) | shader_config.reserved[1] : base_addr + shader_config.offset}; @@ -111,6 +134,10 @@ bool ShaderCache::RefreshStages(std::array<u64, 6>& unique_hashes) try { } shader_infos[index] = shader_info; unique_hashes[index] = shader_info->unique_hash; + if (mesh && (program == Tegra::Engines::Maxwell3D::Regs::ShaderType::VertexB || + program == Tegra::Engines::Maxwell3D::Regs::ShaderType::Tessellation)) { + unique_hashes[index] ^= MeshStateHash(); + } } last_shaders_valid = true; return true; diff --git a/src/video_core/shader_cache.h b/src/video_core/shader_cache.h index f4179dd435..80e375a38b 100644 --- a/src/video_core/shader_cache.h +++ b/src/video_core/shader_cache.h @@ -151,6 +151,8 @@ private: bool IsRejected(VAddr addr); /// Remembers the exception being handled as this address's rejection. void RememberRejection(VAddr addr); + /// Hash of the state task and mesh programs are translated with. + u64 MeshStateHash() const; Tegra::MaxwellDeviceMemoryManager& device_memory; diff --git a/src/video_core/shader_environment.cpp b/src/video_core/shader_environment.cpp index 9d189ad4f0..78438769f5 100644 --- a/src/video_core/shader_environment.cpp +++ b/src/video_core/shader_environment.cpp @@ -470,8 +470,16 @@ GraphicsEnvironment::GraphicsEnvironment(Tegra::Engines::Maxwell3D& maxwell3d_, gpu_memory->ReadBlock(program_base + start_address, &sph, sizeof(sph)); initial_offset = sizeof(sph); texture_bound = maxwell3d->regs.bindless_texture_const_buffer_slot; - if (maxwell3d->GetNativeMacroEngine().native_turing) { - if (program != Maxwell::ShaderType::VertexB && program != Maxwell::ShaderType::Pixel) { + const auto& regs{maxwell3d->regs}; + const bool native{maxwell3d->GetNativeMacroEngine().native_turing}; + // Task and mesh programs: the task in the vertex slot, its mesh program in the tessellation + // slot (without a task, the mesh program is in the vertex slot). + const bool mesh_slot{native && regs.IsMeshEnabled() && + (program == Maxwell::ShaderType::VertexB || + program == Maxwell::ShaderType::Tessellation)}; + if (native) { + if (program != Maxwell::ShaderType::VertexB && program != Maxwell::ShaderType::Pixel && + !mesh_slot) { throw Shader::RuntimeError("Unsupported native graphics shader stage {}", static_cast<u32>(program)); } if (std::getenv("EDEN_NATIVE_DRAW_DIAGNOSTICS")) { @@ -516,6 +524,19 @@ GraphicsEnvironment::GraphicsEnvironment(Tegra::Engines::Maxwell3D& maxwell3d_, ASSERT_MSG(false, "Invalid program={}", program); break; } + if (mesh_slot) { + const bool has_task{regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation)}; + stage = program == Maxwell::ShaderType::VertexB && has_task ? Shader::Stage::Task + : Shader::Stage::Mesh; + mesh_registers = {regs.reg_array[Maxwell::MeshShaderA], regs.reg_array[Maxwell::MeshShaderB], + regs.reg_array[Maxwell::MeshInitShader]}; + if (stage == Shader::Stage::Mesh && regs.IsShaderConfigEnabled(Maxwell::ShaderType::Geometry)) { + const auto& geometry{regs.pipelines[static_cast<size_t>(Maxwell::ShaderType::Geometry)]}; + const GPUVAddr address{(static_cast<u64>(geometry.reserved[0] & 0xff) << 32) | + geometry.reserved[1]}; + gpu_memory->ReadBlock(address, &mesh_primitive_header, sizeof(mesh_primitive_header)); + } + } const u64 local_size{sph.LocalMemorySize()}; ASSERT(local_size <= (std::numeric_limits<u32>::max)()); local_memory_size = static_cast<u32>(local_size) + sph.common3.shader_local_memory_crs_size; diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 623ab7c30d..29b0c816fb 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -1511,6 +1511,15 @@ void Device::RemoveUnsuitableExtensions() { VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME); } + // VK_EXT_mesh_shader: Switch 2 titles draw some geometry with task and mesh programs. Only + // the two stages are used; the multiview, shading rate and query features need more. + features.mesh_shader.multiviewMeshShader = VK_FALSE; + features.mesh_shader.primitiveFragmentShadingRateMeshShader = VK_FALSE; + features.mesh_shader.meshShaderQueries = VK_FALSE; + extensions.mesh_shader = features.mesh_shader.taskShader && features.mesh_shader.meshShader; + RemoveExtensionFeatureIfUnsuitable(extensions.mesh_shader, features.mesh_shader, + VK_EXT_MESH_SHADER_EXTENSION_NAME); + // VK_KHR_shader_quad_control extensions.shader_quad_control = features.shader_quad_control.shaderQuadControl; RemoveExtensionFeatureIfUnsuitable(extensions.shader_quad_control, features.shader_quad_control, diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index d8524f6575..0d256c1dd9 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -65,6 +65,7 @@ VK_DEFINE_HANDLE(VmaAllocator) FEATURE(EXT, 4444Formats, 4444_FORMATS, format_a4b4g4r4) \ FEATURE(EXT, IndexTypeUint8, INDEX_TYPE_UINT8, index_type_uint8) \ FEATURE(EXT, LineRasterization, LINE_RASTERIZATION, line_rasterization) \ + FEATURE(EXT, MeshShader, MESH_SHADER, mesh_shader) \ FEATURE(EXT, PrimitiveTopologyListRestart, PRIMITIVE_TOPOLOGY_LIST_RESTART, \ primitive_topology_list_restart) \ FEATURE(EXT, ProvokingVertex, PROVOKING_VERTEX, provoking_vertex) \ @@ -610,6 +611,12 @@ FN_MAX_LIMIT_LIST return extensions.shader_quad_control && features.shader_quad_control.shaderQuadControl; } + /// Returns true if the device supports task and mesh shaders (VK_EXT_mesh_shader). + bool IsExtMeshShaderSupported() const { + return extensions.mesh_shader && features.mesh_shader.taskShader && + features.mesh_shader.meshShader; + } + /// Returns true if the device supports VK_KHR_image_format_list. bool IsKhrImageFormatListSupported() const { return extensions.image_format_list || instance_version >= VK_API_VERSION_1_2; diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp index dbaaf450a0..ac67b95707 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.cpp +++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp @@ -116,6 +116,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkCmdDrawIndirectCount); X(vkCmdDrawIndexedIndirectCount); X(vkCmdDrawIndirectByteCountEXT); + X(vkCmdDrawMeshTasksEXT); X(vkCmdEndConditionalRenderingEXT); X(vkCmdEndQuery); X(vkCmdEndRenderPass); diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h index 3010368973..f9b3cc15df 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.h +++ b/src/video_core/vulkan_common/vulkan_wrapper.h @@ -232,6 +232,7 @@ struct DeviceDispatch : InstanceDispatch { PFN_vkCmdDrawIndirectCount vkCmdDrawIndirectCount{}; PFN_vkCmdDrawIndexedIndirectCount vkCmdDrawIndexedIndirectCount{}; PFN_vkCmdDrawIndirectByteCountEXT vkCmdDrawIndirectByteCountEXT{}; + PFN_vkCmdDrawMeshTasksEXT vkCmdDrawMeshTasksEXT{}; PFN_vkCmdEndConditionalRenderingEXT vkCmdEndConditionalRenderingEXT{}; PFN_vkCmdEndDebugUtilsLabelEXT vkCmdEndDebugUtilsLabelEXT{}; PFN_vkCmdEndQuery vkCmdEndQuery{}; @@ -1319,6 +1320,10 @@ public: dld->vkCmdDrawIndexedIndirect(handle, src_buffer, src_offset, draw_count, stride); } + void DrawMeshTasksEXT(u32 group_count_x, u32 group_count_y, u32 group_count_z) const noexcept { + dld->vkCmdDrawMeshTasksEXT(handle, group_count_x, group_count_y, group_count_z); + } + void DrawIndirectCount(VkBuffer src_buffer, VkDeviceSize src_offset, VkBuffer count_buffer, VkDeviceSize count_offset, u32 draw_count, u32 stride) const noexcept { dld->vkCmdDrawIndirectCount(handle, src_buffer, src_offset, count_buffer, count_offset, |
