diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 206c41e884..4d40b93c18 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -146,7 +146,8 @@ Shader::AttributeType AttributeType(const FixedPipelineState& state, size_t inde Shader::RuntimeInfo MakeRuntimeInfo(std::span programs, const GraphicsPipelineCacheKey& key, const Shader::IR::Program& program, - const Shader::IR::Program* previous_program) { + const Shader::IR::Program* previous_program, + const Vulkan::Device& device) { Shader::RuntimeInfo info; if (previous_program) { info.previous_stage_stores = previous_program->info.stores; @@ -168,10 +169,14 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span program info.fixed_state_point_size = point_size; } if (key.state.xfb_enabled) { - auto [varyings, count] = - VideoCommon::MakeTransformFeedbackVaryings(key.state.xfb_state); - info.xfb_varyings = varyings; - info.xfb_count = count; + if (device.IsExtTransformFeedbackSupported()) { + auto [varyings, count] = + VideoCommon::MakeTransformFeedbackVaryings(key.state.xfb_state); + info.xfb_varyings = varyings; + info.xfb_count = count; + } else { + LOG_WARNING(Render_Vulkan, "XFB requested in pipeline key but device lacks VK_EXT_transform_feedback; ignoring XFB decorations"); + } } info.convert_depth_mode = gl_ndc; } @@ -218,10 +223,14 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span program info.fixed_state_point_size = point_size; } if (key.state.xfb_enabled != 0) { - auto [varyings, count] = - VideoCommon::MakeTransformFeedbackVaryings(key.state.xfb_state); - info.xfb_varyings = varyings; - info.xfb_count = count; + if (device.IsExtTransformFeedbackSupported()) { + auto [varyings, count] = + VideoCommon::MakeTransformFeedbackVaryings(key.state.xfb_state); + info.xfb_varyings = varyings; + info.xfb_count = count; + } else { + LOG_WARNING(Render_Vulkan, "XFB requested in pipeline key but device lacks VK_EXT_transform_feedback; ignoring XFB decorations"); + } } info.convert_depth_mode = gl_ndc; break; @@ -692,7 +701,7 @@ std::unique_ptr PipelineCache::CreateGraphicsPipeline( const size_t stage_index{index - 1}; infos[stage_index] = &program.info; - const auto runtime_info{MakeRuntimeInfo(programs, key, program, previous_stage)}; + const auto runtime_info{MakeRuntimeInfo(programs, key, program, previous_stage, device)}; ConvertLegacyToGeneric(program, runtime_info); const std::vector code{EmitSPIRV(profile, runtime_info, program, binding, this->optimize_spirv_output)}; device.SaveShader(code); diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp index 2b47ea7dd7..49fa6b5f40 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp @@ -872,17 +872,18 @@ private: return; } has_flushed_end_pending = true; - if (!has_started || buffers_count == 0) { + // Refresh buffers state before beginning transform feedback so counters are up-to-date + UpdateBuffers(); + if (buffers_count == 0) { + // No counter buffers available: begin without counters scheduler.Record([](vk::CommandBuffer cmdbuf) { cmdbuf.BeginTransformFeedbackEXT(0, 0, nullptr, nullptr); }); - UpdateBuffers(); return; } scheduler.Record([this, total = static_cast(buffers_count)](vk::CommandBuffer cmdbuf) { cmdbuf.BeginTransformFeedbackEXT(0, total, counter_buffers.data(), offsets.data()); }); - UpdateBuffers(); } void FlushEndTFB() { @@ -892,11 +893,15 @@ private: } has_flushed_end_pending = false; + // Refresh buffer state before ending transform feedback to ensure counters_count is up-to-date. + UpdateBuffers(); if (buffers_count == 0) { + LOG_DEBUG(Render_Vulkan, "EndTransformFeedbackEXT called with no counters (buffers_count=0)"); scheduler.Record([](vk::CommandBuffer cmdbuf) { cmdbuf.EndTransformFeedbackEXT(0, 0, nullptr, nullptr); }); } else { + LOG_DEBUG(Render_Vulkan, "EndTransformFeedbackEXT called with counters (buffers_count={})", buffers_count); scheduler.Record([this, total = static_cast(buffers_count)](vk::CommandBuffer cmdbuf) { cmdbuf.EndTransformFeedbackEXT(0, total, counter_buffers.data(), offsets.data()); @@ -907,6 +912,7 @@ private: void UpdateBuffers() { last_queries.fill(0); last_queries_stride.fill(1); + streams_mask = 0; // reset previously recorded streams runtime.View3DRegs([this](Maxwell3D& maxwell3d) { buffers_count = 0; out_topology = maxwell3d.draw_manager->GetDrawState().topology; @@ -916,6 +922,10 @@ private: continue; } const size_t stream = tf.controls[i].stream; + if (stream >= last_queries_stride.size()) { + LOG_WARNING(Render_Vulkan, "TransformFeedback stream {} out of range", stream); + continue; + } last_queries_stride[stream] = tf.controls[i].stride; streams_mask |= 1ULL << stream; buffers_count = std::max(buffers_count, stream + 1); @@ -1116,16 +1126,21 @@ public: query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced; u64 num_vertices = 0; + // Protect against stride == 0 (avoid divide-by-zero). Use fallback stride=1 and warn. + u64 safe_stride = query->stride == 0 ? 1 : query->stride; + if (query->stride == 0) { + LOG_WARNING(Render_Vulkan, "TransformFeedback query has stride 0; using 1 to avoid div-by-zero (addr=0x{:x})", query->dependant_address); + } if (query->dependant_manage) { auto* dependant_query = tfb_streamer.GetQuery(query->dependant_index); - num_vertices = dependant_query->value / query->stride; + num_vertices = dependant_query->value / safe_stride; tfb_streamer.Free(query->dependant_index); } else { u8* pointer = device_memory.GetPointer(query->dependant_address); if (pointer != nullptr) { u32 result; std::memcpy(&result, pointer, sizeof(u32)); - num_vertices = static_cast(result) / query->stride; + num_vertices = static_cast(result) / safe_stride; } } query->value = [&]() -> u64 { diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index b99bcfa293..a89bca84db 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1066,9 +1066,16 @@ void RasterizerVulkan::HandleTransformFeedback() { const auto& regs = maxwell3d->regs; if (!device.IsExtTransformFeedbackSupported()) { - std::call_once(warn_unsupported, [&] { - LOG_ERROR(Render_Vulkan, "Transform feedbacks used but not supported"); - }); + // If the guest enabled transform feedback, warn once that the device lacks support. + if (regs.transform_feedback_enabled != 0) { + std::call_once(warn_unsupported, [&] { + LOG_WARNING(Render_Vulkan, "Transform feedback requested by guest but VK_EXT_transform_feedback is unavailable; queries disabled"); + }); + } else { + std::call_once(warn_unsupported, [&] { + LOG_INFO(Render_Vulkan, "VK_EXT_transform_feedback not available on device"); + }); + } return; } query_cache.CounterEnable(VideoCommon::QueryType::StreamingByteCount, @@ -1077,7 +1084,7 @@ void RasterizerVulkan::HandleTransformFeedback() { UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderType::TessellationInit) || regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation)); } -} +} void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& regs) { if (!state_tracker.TouchViewports()) { diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index ed5ff91fbc..6913d300c4 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -1365,15 +1365,21 @@ void Device::RemoveUnsuitableExtensions() { VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME); // VK_EXT_transform_feedback + // We only require the basic transformFeedback feature and at least + // one transform feedback buffer. We keep transformFeedbackQueries as it's used by + // the streaming byte count implementation. GeometryStreams and multiple streams + // are not strictly required since we currently support only stream 0. extensions.transform_feedback = features.transform_feedback.transformFeedback && - features.transform_feedback.geometryStreams && - properties.transform_feedback.maxTransformFeedbackStreams >= 4 && properties.transform_feedback.maxTransformFeedbackBuffers > 0 && - properties.transform_feedback.transformFeedbackQueries && - properties.transform_feedback.transformFeedbackDraw; + properties.transform_feedback.transformFeedbackQueries; RemoveExtensionFeatureIfUnsuitable(extensions.transform_feedback, features.transform_feedback, VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME); + if (extensions.transform_feedback) { + LOG_INFO(Render_Vulkan, "VK_EXT_transform_feedback enabled (buffers={}, queries={})", + properties.transform_feedback.maxTransformFeedbackBuffers, + properties.transform_feedback.transformFeedbackQueries); + } // VK_EXT_vertex_input_dynamic_state extensions.vertex_input_dynamic_state =