#include "common/assert.h" #include "common/common.h" #include "common/emulatorConfig.h" #include "common/file.h" #include "common/logging/log.h" #include "common/profiler.h" #include "common/stringUtils.h" #include "common/threads.h" #include "graphics/guest_gpu/gpu_defs.h" #include "graphics/guest_gpu/graphicsRun.h" #include "graphics/guest_gpu/hardwareContext.h" #include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/renderer/image/imageInfo.h" #include "graphics/host_gpu/renderer/pipeline/descriptors.h" #include "graphics/host_gpu/renderer/pipeline/pipelineCache.h" #include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h" #include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/renderContext.h" #include "graphics/host_gpu/vulkanCommon.h" #include "graphics/shader/recompiler/ir/ShaderIR.h" #include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h" #include "graphics/shader/shader.h" #include "kernel/eventQueue.h" #include "kernel/pthread.h" #include "libs/errno.h" #include #include #include #include #include #include #include #include #include #include namespace Libs::Graphics { static uint64_t BufferDescriptorSize(const ShaderBufferResource& descriptor) { const uint64_t records = descriptor.NumRecords(); const uint64_t stride = descriptor.Stride(); if (stride != 0 && records > UINT64_MAX / stride) { EXIT("compute buffer descriptor footprint overflow\n"); } return stride == 0 ? records : records * stride; } static bool FillSourcesDisjoint(std::span sources, GuestRange destination, uint32_t output_buffer = UINT32_MAX) { for (uint32_t i = 0; i < sources.size(); ++i) { if (i == output_buffer) continue; const auto source = DecodeNativeDescriptor(sources[i]); const auto bytes = BufferDescriptorSize(source); if (source.Base48() > UINT64_MAX - bytes || (source.Base48() < destination.End() && destination.address < source.Base48() + bytes)) return false; } return true; } bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input, const CommandBuffer& buffer) { const auto& program = *input.stage.program; const auto& resources = input.stage.resources; if (resources.buffers.size() != program.info.buffers.size()) { EXIT("compute runtime buffer count does not match shader metadata\n"); } auto& cache = buffer.GetContext().GetTextureCache(); for (uint32_t i = 0; i < program.info.buffers.size(); i++) { const auto& resource = program.info.buffers[i]; const auto descriptor = DecodeNativeDescriptor(resources.buffers[i]); // A metadata resource that is also read is not proven to be a full overwrite. Execute it // conservatively instead of replacing the dispatch with a coarse full-surface clear. if (cache.IsMeta(descriptor.Base48()) && (!resource.written || resource.read)) { return false; } } if (!program.info.has_bitwise_xor) { for (uint32_t i = 0; i < program.info.buffers.size(); i++) { const auto& resource = program.info.buffers[i]; if (resource.written) { const auto descriptor = DecodeNativeDescriptor(resources.buffers[i]); if (cache.ClearMeta(descriptor.Base48())) { return true; } } } } return false; } bool ResolveComputeImageClear(const ShaderComputeInputInfo& input, uint32_t group_x, uint32_t group_y, uint32_t group_z, uint32_t mode, ShaderBufferResource& resolved_descriptor, uint32_t& resolved_clear, uint64_t& resolved_size) { const auto& program = *input.stage.program; const auto& resources = input.stage.resources; const auto& fill = resources.uniform_fill; if (fill.kind != ShaderRecompiler::IR::UniformFillKind::Buffer || fill.words == 0 || fill.words > 4 || fill.resource >= resources.buffers.size() || resources.buffers.size() != program.info.buffers.size()) { return false; } const auto element_size = fill.words * sizeof(uint32_t); const auto& resource = program.info.buffers[fill.resource]; const auto& raw = resources.buffers[fill.resource]; const auto descriptor = DecodeNativeDescriptor(raw); constexpr std::array formats { Prospero::BufferFormat::k32UInt, Prospero::BufferFormat::k32_32UInt, Prospero::BufferFormat::k32_32_32UInt, Prospero::BufferFormat::k32_32_32_32UInt}; if (!resource.formatted || !resource.written || resource.read || resource.atomic || resource.scalar || descriptor.Stride() != element_size || descriptor.Format() != formats[fill.words - 1] || descriptor.SwizzleEnabled() || descriptor.IndexStride() != 0 || descriptor.AddTid() || resource.packed_stride != descriptor.PackedStride() || raw.dword_count != 4 || descriptor.Base48() == 0) { return false; } if (input.threads_num[0] == 0 || input.threads_num[0] != fill.group_stride[0] || input.threads_num[1] != 1 || input.threads_num[2] != 1 || group_x == 0 || group_y != 1 || group_z != 1 || mode != (input.dispatch_thread_dimensions ? 0x61u : 0x41u)) { return false; } const uint64_t invocations = input.dispatch_thread_dimensions ? group_x : static_cast(group_x) * input.threads_num[0]; const auto size = BufferDescriptorSize(descriptor); if (invocations != descriptor.NumRecords() || size == 0 || size > UINT32_MAX || descriptor.Base48() > UINT64_MAX - size || (input.dispatch_thread_dimensions && (group_x % input.threads_num[0] != 0 || input.dispatch_threads_num[0] != group_x || input.dispatch_threads_num[1] != 1 || input.dispatch_threads_num[2] != 1))) { return false; } if (!FillSourcesDisjoint(resources.buffers, {descriptor.Base48(), size}, fill.resource)) return false; resolved_descriptor = descriptor; resolved_clear = fill.value; resolved_size = size; return true; } bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& input, CommandBuffer& command, uint32_t group_x, uint32_t group_y, uint32_t group_z, uint32_t mode) { const auto& program = *input.stage.program; const auto& resources = input.stage.resources; const auto& fill = resources.uniform_fill; auto& cache = command.GetContext().GetTextureCache(); if (fill.kind == ShaderRecompiler::IR::UniformFillKind::Image) { if (mode != 0x41u || input.dispatch_thread_dimensions || fill.words != 1 || fill.value > 255 || resources.images.size() != 1 || program.info.images.size() != 1 || fill.resource != 0 || resources.images[0].dword_count != 8 || input.threads_num[2] != 1 || fill.group_stride[2] != 1) return false; const auto descriptor = DecodeNativeDescriptor(resources.images[0]); const auto& resource = program.info.images[0]; if (descriptor.IsNull() || descriptor.Format() != Prospero::BufferFormat::k8UInt || descriptor.Type() != Prospero::ImageType::kColor2DArray || descriptor.MetaCompress() || descriptor.WriteCompress() || descriptor.BaseLevel() > descriptor.LastLevel() || descriptor.BaseLevel() > descriptor.MaxMip() || descriptor.BaseArray5() > descriptor.Depth() || descriptor.DstSelX() != 4 || (resource.shader_swizzle & 7u) != 4 || resource.numeric_class != Prospero::TextureNumericClass::Uint || resource.dimension != ShaderRecompiler::Decoder::ImageDimension::Dim2DArray || resource.read || resource.atomic || !resource.written || resource.mip_mode != ShaderRecompiler::IR::ImageMipMode::None) return false; const std::array extents { std::max(1u, (descriptor.Width5() + 1u) >> descriptor.BaseLevel()), std::max(1u, (descriptor.Height5() + 1u) >> descriptor.BaseLevel()), descriptor.Depth() - descriptor.BaseArray5() + 1u}; const std::array groups {group_x, group_y, group_z}; for (uint32_t axis = 0; axis < 3; ++axis) { const uint64_t threads = input.threads_num[axis]; // Guest image writes outside the descriptor dimensions are discarded. Only the // final workgroup may extend beyond the selected image view. if (threads == 0 || threads != fill.group_stride[axis] || groups[axis] != (extents[axis] + threads - 1) / threads || groups[axis] * threads > UINT32_MAX) return false; } const auto binding = ResolveTexture(resource, resources.images[0]); const auto& destination = binding.desc.info.data; if (!FillSourcesDisjoint(resources.buffers, destination)) return false; std::scoped_lock lock {cache.m_lock}; const auto& image = cache.GetImage(binding.image_id); const auto& view = binding.desc.view_info; if (image.backing.format != vk::Format::eD32SfloatS8Uint || image.info.samples != 1 || image.info.stencil != destination || view.base_level >= image.backing.mip_levels || view.base_layer >= image.backing.layers || view.layer_count != extents[2] || view.layer_count > image.backing.layers - view.base_layer || std::max(1u, image.info.extent.width >> view.base_level) != extents[0] || std::max(1u, image.info.extent.height >> view.base_level) != extents[1]) return false; const vk::ImageSubresourceRange range {vk::ImageAspectFlagBits::eStencil, view.base_level, 1, view.base_layer, view.layer_count}; vk::ClearValue clear {}; clear.depthStencil = vk::ClearDepthStencilValue {0.0f, fill.value}; cache.ClearImage(command, binding.image_id, range, clear); return true; } ShaderBufferResource descriptor; uint32_t packed_clear = 0; uint64_t size = 0; if (!ResolveComputeImageClear(input, group_x, group_y, group_z, mode, descriptor, packed_clear, size)) { return false; } if (!cache.ClearImageFromBuffer(command, descriptor.Base48(), size, packed_clear)) { // Recognized metadata-fill shaders access DCC as an ordinary storage buffer and may run // before the render target is bound. TryConsumeDccFill either consumes registered state // or retains a PendingDcc fill while allowing the dispatch to run. const bool registered_metadata = cache.TryConsumeDccFill(descriptor.Base48(), size, packed_clear); static std::atomic logged_metadata_clears {0}; if (logged_metadata_clears.fetch_add(1, std::memory_order_relaxed) < 32) { LOGF("GraphicsRenderDispatchDirect: %s metadata clear shader=0x%016" PRIx64 " addr=0x%016" PRIx64 " size=0x%016" PRIx64 " value=0x%08" PRIx32 "\n", registered_metadata ? "tracked" : "deferred", input.stage.program->shader_hash, descriptor.Base48(), size, packed_clear); } if (registered_metadata) { return true; } return false; } static std::atomic logged_clears {0}; if (logged_clears.fetch_add(1, std::memory_order_relaxed) < 32) { LOGF("GraphicsRenderDispatchDirect: compute image clear shader=0x%016" PRIx64 " addr=0x%016" PRIx64 " size=0x%016" PRIx64 " value=0x%08" PRIx32 "\n", input.stage.program->shader_hash, descriptor.Base48(), size, packed_clear); } return true; } void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer, uint32_t thread_group_x, uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode) { EXIT_IF(buffer.IsInvalid()); m_context.GetCommandScheduler().PopPendingOperations(); auto& ctx = buffer.GetRegisters(); auto& sh_ctx = buffer.GetShaders(); buffer.SetDebugInfo(static_cast(CommandBufferDebugOp::DispatchDirect), submit_id, thread_group_x, thread_group_y, thread_group_z, mode, sh_ctx.GetCs().cs_regs.data_addr); Common::LockGuard lock(m_context.GetMutex()); if (sh_ctx.GetCs().cs_regs.data_addr == 0) { LOGF("GraphicsRenderDispatchDirect: temporary: ignoring dispatch with null CS shader, " "groups=%ux%ux%u mode=%u\n", thread_group_x, thread_group_y, thread_group_z, mode); return; } if (!ShaderAddressValid(sh_ctx.GetCs().cs_regs.data_addr)) { return; } constexpr uint32_t DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS = 1u << 5u; constexpr uint32_t DISPATCH_INITIATOR_BASE_BITS = 0x41u; constexpr uint32_t DISPATCH_INITIATOR_MODIFIER_BITS = 0xa038u; constexpr uint32_t DISPATCH_INITIATOR_KNOWN_MASK = DISPATCH_INITIATOR_BASE_BITS | DISPATCH_INITIATOR_MODIFIER_BITS; const uint32_t unknown_mode_bits = mode & ~DISPATCH_INITIATOR_KNOWN_MASK; if (unknown_mode_bits != 0) { static std::atomic log_count {0}; if (log_count.fetch_add(1, std::memory_order_relaxed) < 32) { LOGF("GraphicsRenderDispatchDirect: unknown dispatch initiator bits " "mode=0x%08" PRIx32 " unknown=0x%08" PRIx32 " shader=0x%016" PRIx64 " groups=%ux%ux%u\n", mode, unknown_mode_bits, sh_ctx.GetCs().cs_regs.data_addr, thread_group_x, thread_group_y, thread_group_z); } } const auto& cs_regs = sh_ctx.GetCs(); const auto& sh_regs = ctx.GetShaderRegisters(); ShaderComputeInputInfo input_info {}; const bool use_thread_dimensions = (mode & DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS) != 0; input_info.dispatch_thread_dimensions = use_thread_dimensions; const auto compute_program = m_context.GetPipelineCache().GetComputeProgram(cs_regs, sh_regs, input_info); if (use_thread_dimensions) { input_info.dispatch_threads_num[0] = thread_group_x; input_info.dispatch_threads_num[1] = thread_group_y; input_info.dispatch_threads_num[2] = thread_group_z; } const uint32_t frame_num = static_cast(m_context.GetGpu().GetFrameNum()); const bool large_workgroup = (input_info.threads_num[0] * input_info.threads_num[1] * input_info.threads_num[2] >= 512); const auto& program = *input_info.stage.program; const auto& resources = input_info.stage.resources; if (TryConsumeComputeMetaClear(input_info, buffer)) { ResetBindings(); return; } if (TryConsumeComputeImageClear(input_info, buffer, thread_group_x, thread_group_y, thread_group_z, mode)) { ResetBindings(); return; } const auto sampled_images = std::count_if( program.info.images.begin(), program.info.images.end(), [](const auto& image) { return image.resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled; }); const bool has_sampler = !program.info.samplers.empty(); static std::atomic dispatch_log_count {0}; if ((large_workgroup || has_sampler) && dispatch_log_count.fetch_add(1, std::memory_order_relaxed) < 512) { LOGF("GraphicsRenderDispatchDirect: frame=%u shader=0x%016" PRIx64 " groups=%ux%ux%u mode=0x%08" PRIx32 " local=%ux%ux%u " "buffers=%zu textures=%zu sampled=%zu storage=%zu samplers=%zu push=%u\n", frame_num, sh_ctx.GetCs().cs_regs.data_addr, thread_group_x, thread_group_y, thread_group_z, mode, input_info.threads_num[0], input_info.threads_num[1], input_info.threads_num[2], program.info.buffers.size(), program.info.images.size(), sampled_images, program.info.images.size() - sampled_images, program.info.samplers.size(), program.bindings.UsesPushData() ? static_cast(sizeof(ShaderRecompiler::IR::PushData)) : 0u); for (uint32_t i = 0; i < program.info.buffers.size(); i++) { const auto& buffer = program.info.buffers[i]; const auto r = DecodeNativeDescriptor(resources.buffers[i]); LOGF(" CS buffer[%u]: source=%u usage=%s addr=0x%012" PRIx64 " stride=%u records=%u format=%u\n", i, buffer.source, buffer.written ? "read-write" : "read-only", r.Base48(), r.Stride(), r.NumRecords(), r.RawFormat()); } for (uint32_t i = 0; i < program.info.images.size(); i++) { const auto& image = program.info.images[i]; const auto r = DecodeNativeDescriptor(resources.images[i]); LOGF(" CS texture[%u]: source=%u usage=%s sampled=%s addr=0x%010" PRIx64 " type=%u fmt=%u extent=%ux%u depth=%u levels=%u tile=%u\n", i, image.source, image.written ? "read-write" : "read-only", image.resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled ? "true" : "false", r.Base40(), static_cast(r.Type()), static_cast(r.Format()), static_cast(r.Width5()) + 1u, static_cast(r.Height5()) + 1u, static_cast(r.Depth()) + 1u, r.Type() == Prospero::ImageType::kColor2DMsaa || r.Type() == Prospero::ImageType::kColor2DMsaaArray ? 1u : static_cast(image.r128 ? r.LastLevel() : r.MaxMip()) + 1u, static_cast(r.TileMode())); } for (uint32_t i = 0; i < program.info.samplers.size(); i++) { const auto r = DecodeNativeDescriptor(resources.samplers[i]); LOGF(" CS sampler[%u]: source=%u clamp=%u/%u/%u filter=%u/%u/%u mip=%u " "lod=%u-%u bias=%d\n", i, program.info.samplers[i].source, static_cast(r.ClampX()), static_cast(r.ClampY()), static_cast(r.ClampZ()), static_cast(r.XyMagFilter()), static_cast(r.XyMinFilter()), static_cast(r.ZFilter()), static_cast(r.MipFilter()), static_cast(r.MinLod()), static_cast(r.MaxLod()), static_cast(r.LodBias())); } } if (use_thread_dimensions) { auto groups_from_threads = [](uint32_t threads, uint32_t group_size) { return (threads == 0 ? 0u : (threads + std::max(group_size, 1u) - 1u) / std::max(group_size, 1u)); }; const uint32_t old_x = thread_group_x; const uint32_t old_y = thread_group_y; const uint32_t old_z = thread_group_z; thread_group_x = groups_from_threads(thread_group_x, cs_regs.cs_regs.num_thread_x); thread_group_y = groups_from_threads(thread_group_y, cs_regs.cs_regs.num_thread_y); thread_group_z = groups_from_threads(thread_group_z, cs_regs.cs_regs.num_thread_z); static std::atomic log_count {0}; if (log_count.fetch_add(1, std::memory_order_relaxed) < 32) { LOGF("GraphicsRenderDispatchDirect: use-thread-dimensions %ux%ux%u / %ux%ux%u -> " "groups %ux%ux%u\n", old_x, old_y, old_z, std::max(cs_regs.cs_regs.num_thread_x, 1u), std::max(cs_regs.cs_regs.num_thread_y, 1u), std::max(cs_regs.cs_regs.num_thread_z, 1u), thread_group_x, thread_group_y, thread_group_z); } } if (thread_group_x == 0 || thread_group_y == 0 || thread_group_z == 0) { static std::atomic log_count {0}; if (log_count.fetch_add(1, std::memory_order_relaxed) < 32) { LOGF("GraphicsRenderDispatchDirect: skipping zero-sized dispatch groups=%ux%ux%u " "mode=0x%08" PRIx32 " shader=0x%016" PRIx64 "\n", thread_group_x, thread_group_y, thread_group_z, mode, sh_ctx.GetCs().cs_regs.data_addr); } return; } buffer.EndRendering(); auto& pipeline = m_context.GetPipelineCache().CreateComputePipeline(input_info, compute_program); auto bindings = PrepareBindings(input_info.stage); FindBuffers(bindings); if (program.info.uses_dma) { m_context.GetGpuResources().PrepareBda(); } RebindBuffers(bindings); RebindImages(bindings); auto vk_buffer = buffer.Handle(); PreparedBindings* descriptor_stage = &bindings; CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline, std::span {&descriptor_stage, 1u}); bool has_storage_writes = HasShaderBufferWrites(input_info.stage); has_storage_writes = std::any_of(program.info.images.begin(), program.info.images.end(), [](const auto& image) { return image.written && image.resource_class == ShaderRecompiler::IR::ImageResourceClass::Storage; }) || has_storage_writes; if (has_storage_writes) { // A host fence used to serialize every dispatch. Preserve its read-before-write ordering // while allowing the queue to execute asynchronously. ShaderWriteHazardBarrier(vk_buffer, vk::PipelineStageFlagBits::eComputeShader); } vk_buffer.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline.pipeline); vk_buffer.dispatch(thread_group_x, thread_group_y, thread_group_z); // The removed host fence also ordered read-only dispatches before later writers. ShaderAccessBarrier(vk_buffer, vk::PipelineStageFlagBits::eComputeShader); ResetBindings(); } } // namespace Libs::Graphics