diff --git a/src/video_core/engines/draw_manager.cpp b/src/video_core/engines/draw_manager.cpp index 177d83700b..0d3558a8f3 100644 --- a/src/video_core/engines/draw_manager.cpp +++ b/src/video_core/engines/draw_manager.cpp @@ -282,28 +282,68 @@ void Maxwell3D::DrawManager::DrawTexture(Maxwell3D& maxwell3d) { } void Maxwell3D::DrawManager::UpdateTopology(Maxwell3D& maxwell3d) { + using Topology = Maxwell3D::Regs::PrimitiveTopology; + using Override = Maxwell3D::Regs::PrimitiveTopologyOverride; switch (maxwell3d.regs.primitive_topology_control) { case Maxwell3D::Regs::PrimitiveTopologyControl::UseInBeginMethods: break; case Maxwell3D::Regs::PrimitiveTopologyControl::UseSeparateState: switch (maxwell3d.regs.topology_override) { - case Maxwell3D::Regs::PrimitiveTopologyOverride::None: + case Override::None: break; - case Maxwell3D::Regs::PrimitiveTopologyOverride::Points: - draw_state.topology = Maxwell3D::Regs::PrimitiveTopology::Points; + case Override::Points: + case Override::LegacyPoints: + draw_state.topology = Topology::Points; break; - case Maxwell3D::Regs::PrimitiveTopologyOverride::Lines: - draw_state.topology = Maxwell3D::Regs::PrimitiveTopology::Lines; + case Override::Lines: + case Override::LegacyLines: + case Override::LegacyLinesImm: + case Override::LegacyIndexedLines: + case Override::LegacyIndexedLines2: + draw_state.topology = Topology::Lines; break; - case Maxwell3D::Regs::PrimitiveTopologyOverride::LineStrip: - draw_state.topology = Maxwell3D::Regs::PrimitiveTopology::LineStrip; + case Override::LineStrip: + case Override::LegacyLineStrip: + case Override::LegacyIndexedLineStrip: + draw_state.topology = Topology::LineStrip; break; - default: - draw_state.topology = Maxwell3D::Regs::PrimitiveTopology(maxwell3d.regs.topology_override); + case Override::Triangles: + case Override::LegacyTriangles: + case Override::LegacyIndexedTriangles: + case Override::LegacyIndexedTriangles2: + draw_state.topology = Topology::Triangles; + break; + case Override::TriangleStrip: + case Override::LegacyTriangleStrip: + case Override::LegacyIndexedTriangleStrip: + draw_state.topology = Topology::TriangleStrip; + break; + case Override::LegacyTriangleFan: + case Override::LegacyTriangleFanImm: + case Override::LegacyIndexedTriangleFan: + draw_state.topology = Topology::TriangleFan; + break; + case Override::LinesAdjacency: + draw_state.topology = Topology::LinesAdjacency; + break; + case Override::LineStripAdjacency: + draw_state.topology = Topology::LineStripAdjacency; + break; + case Override::TrianglesAdjacency: + draw_state.topology = Topology::TrianglesAdjacency; + break; + case Override::TriangleStripAdjacency: + draw_state.topology = Topology::TriangleStripAdjacency; + break; + case Override::Patches: + draw_state.topology = Topology::Patches; break; } break; } + if (u32(draw_state.topology) > u32(Topology::Patches)) { + draw_state.topology = Topology::Triangles; + } } void Maxwell3D::DrawManager::ProcessDraw(Maxwell3D& maxwell3d, bool draw_indexed, u32 instance_count) { diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index a115bc7b64..468bdc19ec 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt @@ -78,6 +78,7 @@ set(SHADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/vulkan_fidelityfx_fsr_easu_fp32.frag ${CMAKE_CURRENT_SOURCE_DIR}/vulkan_fidelityfx_fsr_rcas_fp16.frag ${CMAKE_CURRENT_SOURCE_DIR}/vulkan_fidelityfx_fsr_rcas_fp32.frag + ${CMAKE_CURRENT_SOURCE_DIR}/vulkan_indirect_quads.comp ${CMAKE_CURRENT_SOURCE_DIR}/vulkan_present.frag ${CMAKE_CURRENT_SOURCE_DIR}/vulkan_present.vert ${CMAKE_CURRENT_SOURCE_DIR}/vulkan_present_scaleforce_fp16.frag diff --git a/src/video_core/host_shaders/vulkan_indirect_quads.comp b/src/video_core/host_shaders/vulkan_indirect_quads.comp new file mode 100644 index 0000000000..01bc2f961f --- /dev/null +++ b/src/video_core/host_shaders/vulkan_indirect_quads.comp @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#version 460 core + +layout (local_size_x = 32) in; + +layout (std430, set = 0, binding = 0) readonly buffer InputBuffer { + uint input_commands[]; +}; + +layout (std430, set = 0, binding = 1) writeonly buffer OutputBuffer { + uint output_commands[]; +}; + +layout (push_constant) uniform PushConstants { + uint num_draws; + uint src_stride; +}; + +void main() { + uint draw = gl_GlobalInvocationID.x; + if (draw >= num_draws) { + return; + } + uint src = draw * src_stride; + uint dst = draw * 5; + output_commands[dst + 0] = input_commands[src + 0] / 4 * 6; + output_commands[dst + 1] = input_commands[src + 1]; + output_commands[dst + 2] = input_commands[src + 2] / 4 * 6; + output_commands[dst + 3] = input_commands[src + 3]; + output_commands[dst + 4] = input_commands[src + 4]; +} diff --git a/src/video_core/macro.cpp b/src/video_core/macro.cpp index 86f528af15..ac658f7534 100644 --- a/src/video_core/macro.cpp +++ b/src/video_core/macro.cpp @@ -62,6 +62,10 @@ bool IsTopologySafe(Maxwell3D::Regs::PrimitiveTopology topology) { } } +bool IsTopologySafeIndexedIndirect(Maxwell3D::Regs::PrimitiveTopology topology) { + return IsTopologySafe(topology) || topology == Maxwell3D::Regs::PrimitiveTopology::Quads; +} + } // Anonymous namespace void HLE_DrawArraysIndirect::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { @@ -125,7 +129,7 @@ void HLE_DrawArraysIndirect::Fallback(Core::System& system, Engines::Maxwell3D& void HLE_DrawIndexedIndirect::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { auto topology = static_cast(parameters[0]); - if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) { + if (!maxwell3d.AnyParametersDirty() || !IsTopologySafeIndexedIndirect(topology)) { Fallback(system, maxwell3d, parameters); return; } @@ -198,7 +202,7 @@ void HLE_MultiLayerClear::Execute(Core::System& system, Engines::Maxwell3D& maxw } void HLE_MultiDrawIndexedIndirectCount::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { const auto topology = Maxwell3D::Regs::PrimitiveTopology(parameters[2]); - if (IsTopologySafe(topology)) { + if (IsTopologySafeIndexedIndirect(topology)) { const u32 start_indirect = parameters[0]; const u32 end_indirect = parameters[1]; if (start_indirect >= end_indirect) { diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index 5aeef5ad3c..a92155881d 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -20,6 +20,7 @@ #include "video_core/host_shaders/queries_prefix_scan_sum_comp_spv.h" #include "video_core/host_shaders/queries_prefix_scan_sum_nosubgroups_comp_spv.h" #include "video_core/host_shaders/resolve_conditional_render_comp_spv.h" +#include "video_core/host_shaders/vulkan_indirect_quads_comp_spv.h" #include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h" #include "video_core/host_shaders/vulkan_uint8_comp_spv.h" #include "video_core/renderer_vulkan/vk_compute_pass.h" @@ -375,6 +376,56 @@ std::pair QuadIndexedPass::Assemble( return {staging.buffer, staging.offset}; } +IndirectQuadsPass::IndirectQuadsPass(const Device& device_, Scheduler& scheduler_, + DescriptorPool& descriptor_pool_, + StagingBufferPool& staging_buffer_pool_, + ComputePassDescriptorQueue& compute_pass_descriptor_queue_) + : ComputePass(device_, scheduler_, descriptor_pool_, INPUT_OUTPUT_DESCRIPTOR_SET_BINDINGS, + INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE, INPUT_OUTPUT_BANK_INFO, + COMPUTE_PUSH_CONSTANT_RANGE, VULKAN_INDIRECT_QUADS_COMP_SPV), + scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_}, + compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {} + +IndirectQuadsPass::~IndirectQuadsPass() = default; + +std::pair IndirectQuadsPass::Assemble(u32 num_draws, u32 stride, + VkBuffer src_buffer, u32 src_offset) { + u32 src_stride = stride / static_cast(sizeof(u32)); + if (src_stride < COMMAND_WORDS) { + src_stride = COMMAND_WORDS; + } + const u32 input_size = num_draws * src_stride * static_cast(sizeof(u32)); + const std::size_t staging_size = std::size_t(num_draws) * COMMAND_WORDS * sizeof(u32); + const auto staging = staging_buffer_pool.Request(staging_size, MemoryUsage::DeviceLocal); + + compute_pass_descriptor_queue.Acquire(scheduler, 2); + compute_pass_descriptor_queue.AddBuffer(src_buffer, src_offset, input_size); + compute_pass_descriptor_queue.AddBuffer(staging.buffer, staging.offset, staging_size); + const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; + + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([this, descriptor_data, num_draws, src_stride](vk::CommandBuffer cmdbuf) { + static constexpr u32 DISPATCH_SIZE = 32; + static constexpr VkMemoryBarrier WRITE_BARRIER{ + .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_INDIRECT_COMMAND_READ_BIT, + }; + const std::array push_constants{num_draws, src_stride}; + const VkDescriptorSet set = descriptor_allocator.Commit(); + device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); + cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(push_constants), + &push_constants); + cmdbuf.Dispatch(Common::DivCeil(num_draws, DISPATCH_SIZE), 1, 1); + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT, 0, WRITE_BARRIER); + }); + return {staging.buffer, staging.offset}; +} + ConditionalRenderingResolvePass::ConditionalRenderingResolvePass( const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, ComputePassDescriptorQueue& compute_pass_descriptor_queue_) diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h index 12723099ca..3fdd2c6f01 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.h +++ b/src/video_core/renderer_vulkan/vk_compute_pass.h @@ -88,6 +88,25 @@ private: ComputePassDescriptorQueue& compute_pass_descriptor_queue; }; +class IndirectQuadsPass final : public ComputePass { +public: + explicit IndirectQuadsPass(const Device& device_, Scheduler& scheduler_, + DescriptorPool& descriptor_pool_, + StagingBufferPool& staging_buffer_pool_, + ComputePassDescriptorQueue& compute_pass_descriptor_queue_); + ~IndirectQuadsPass(); + + static constexpr u32 COMMAND_WORDS = 5; + + std::pair Assemble(u32 num_draws, u32 stride, VkBuffer src_buffer, + u32 src_offset); + +private: + Scheduler& scheduler; + StagingBufferPool& staging_buffer_pool; + ComputePassDescriptorQueue& compute_pass_descriptor_queue; +}; + class ConditionalRenderingResolvePass final : public ComputePass { public: explicit ConditionalRenderingResolvePass( diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 7346c5c269..7fa889e22f 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -212,6 +212,8 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra compute_pass_descriptor_queue(device, UpdateDescriptorQueue::COMPUTE_FRAME_PAYLOAD_SIZE), descriptor_buffer_ring(device, memory_allocator), blit_image(device, scheduler, state_tracker, descriptor_pool), render_pass_cache(device), + indirect_quads_pass(device, scheduler, descriptor_pool, staging_pool, + compute_pass_descriptor_queue), texture_cache_runtime{ device, scheduler, memory_allocator, staging_pool, blit_image, render_pass_cache, descriptor_pool, compute_pass_descriptor_queue}, @@ -314,6 +316,18 @@ void RasterizerVulkan::DrawIndirect() { const auto indirect_buffer = buffer_cache.GetDrawIndirectBuffer(); const auto& buffer = indirect_buffer.first; const auto& offset = indirect_buffer.second; + VkBuffer command_buffer = buffer->Handle(); + VkDeviceSize command_offset = offset; + u32 command_stride = static_cast(params.stride); + if (params.is_indexed && + maxwell3d->draw_manager.draw_state.topology == Maxwell::PrimitiveTopology::Quads) { + const auto patched = indirect_quads_pass.Assemble( + static_cast(params.max_draw_counts), command_stride, command_buffer, + static_cast(offset)); + command_buffer = patched.first; + command_offset = patched.second; + command_stride = IndirectQuadsPass::COMMAND_WORDS * static_cast(sizeof(u32)); + } if (params.is_byte_count) { scheduler.Record([buffer_obj = buffer->Handle(), offset, stride = params.stride](vk::CommandBuffer cmdbuf) { @@ -326,29 +340,32 @@ void RasterizerVulkan::DrawIndirect() { const auto count = buffer_cache.GetDrawIndirectCount(); const auto& draw_buffer = count.first; const auto& offset_base = count.second; - scheduler.Record([draw_buffer_obj = draw_buffer->Handle(), - buffer_obj = buffer->Handle(), offset_base, offset, + scheduler.Record([draw_buffer_obj = draw_buffer->Handle(), command_buffer, + offset_base, command_offset, command_stride, params](vk::CommandBuffer cmdbuf) { if (params.is_indexed) { - cmdbuf.DrawIndexedIndirectCount( - buffer_obj, offset, draw_buffer_obj, offset_base, - static_cast(params.max_draw_counts), static_cast(params.stride)); + cmdbuf.DrawIndexedIndirectCount(command_buffer, command_offset, draw_buffer_obj, + offset_base, + static_cast(params.max_draw_counts), + command_stride); } else { - cmdbuf.DrawIndirectCount(buffer_obj, offset, draw_buffer_obj, offset_base, + cmdbuf.DrawIndirectCount(command_buffer, command_offset, draw_buffer_obj, + offset_base, static_cast(params.max_draw_counts), - static_cast(params.stride)); + command_stride); } }); return; } - scheduler.Record([buffer_obj = buffer->Handle(), offset, params](vk::CommandBuffer cmdbuf) { + scheduler.Record([command_buffer, command_offset, command_stride, + params](vk::CommandBuffer cmdbuf) { if (params.is_indexed) { - cmdbuf.DrawIndexedIndirect(buffer_obj, offset, + cmdbuf.DrawIndexedIndirect(command_buffer, command_offset, static_cast(params.max_draw_counts), - static_cast(params.stride)); + command_stride); } else { - cmdbuf.DrawIndirect(buffer_obj, offset, static_cast(params.max_draw_counts), - static_cast(params.stride)); + cmdbuf.DrawIndirect(command_buffer, command_offset, + static_cast(params.max_draw_counts), command_stride); } }); diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index bb7c5ff09e..52ab0742a7 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -17,6 +17,7 @@ #include "video_core/rasterizer_interface.h" #include "video_core/renderer_vulkan/blit_image.h" #include "video_core/renderer_vulkan/vk_buffer_cache.h" +#include "video_core/renderer_vulkan/vk_compute_pass.h" #include "video_core/renderer_vulkan/vk_descriptor_buffer.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" #include "video_core/renderer_vulkan/vk_fence_manager.h" @@ -211,6 +212,7 @@ private: DescriptorBufferRing descriptor_buffer_ring; BlitImageHelper blit_image; RenderPassCache render_pass_cache; + IndirectQuadsPass indirect_quads_pass; TextureCacheRuntime texture_cache_runtime; TextureCache texture_cache; diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp index 7b6571d33d..b847b66e7c 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp @@ -211,7 +211,8 @@ StagingBufferRef StagingBufferPool::CreateStagingBuffer(size_t size, MemoryUsage .size = 1ULL << log2_size, .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | - VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | + VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .queueFamilyIndexCount = 0, .pQueueFamilyIndices = nullptr,