From 1c8c1ad39f8129a51812138c5446b30c2ea4a350 Mon Sep 17 00:00:00 2001 From: pineappleEA Date: Mon, 5 Jul 2021 18:23:20 +0200 Subject: [PATCH] early-access version 1845 --- README.md | 2 +- src/tests/video_core/buffer_base.cpp | 2 +- src/video_core/buffer_cache/buffer_base.h | 14 +- src/video_core/buffer_cache/buffer_cache.h | 134 +++++++----------- src/video_core/engines/fermi_2d.cpp | 4 +- src/video_core/fence_manager.h | 34 ++--- src/video_core/gpu_thread.cpp | 15 -- src/video_core/gpu_thread.h | 5 - .../renderer_vulkan/vk_fence_manager.cpp | 4 - .../renderer_vulkan/vk_rasterizer.cpp | 2 +- src/video_core/renderer_vulkan/vk_scheduler.h | 4 - src/video_core/texture_cache/texture_cache.h | 16 +-- src/video_core/texture_cache/types.h | 4 +- src/yuzu/debugger/profiler.cpp | 9 +- 14 files changed, 97 insertions(+), 152 deletions(-) diff --git a/README.md b/README.md index 145a6540d..a6fdca6c8 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ yuzu emulator early access ============= -This is the source code for early-access 1844. +This is the source code for early-access 1845. ## Legal Notice diff --git a/src/tests/video_core/buffer_base.cpp b/src/tests/video_core/buffer_base.cpp index cfcdc2253..edced69bb 100755 --- a/src/tests/video_core/buffer_base.cpp +++ b/src/tests/video_core/buffer_base.cpp @@ -536,7 +536,7 @@ TEST_CASE("BufferBase: Cached write downloads") { REQUIRE(rasterizer.Count() == 63); buffer.MarkRegionAsGpuModified(c + PAGE, PAGE); int num = 0; - buffer.ForEachDownloadRange(c, WORD, true, [&](u64 offset, u64 size) { ++num; }); + buffer.ForEachDownloadRange(c, WORD, [&](u64 offset, u64 size) { ++num; }); buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { ++num; }); REQUIRE(num == 0); REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE)); diff --git a/src/video_core/buffer_cache/buffer_base.h b/src/video_core/buffer_cache/buffer_base.h index 9e39858c8..b121d36a3 100755 --- a/src/video_core/buffer_cache/buffer_base.h +++ b/src/video_core/buffer_cache/buffer_base.h @@ -226,19 +226,19 @@ public: /// Call 'func' for each CPU modified range and unmark those pages as CPU modified template void ForEachUploadRange(VAddr query_cpu_range, u64 size, Func&& func) { - ForEachModifiedRange(query_cpu_range, size, true, func); + ForEachModifiedRange(query_cpu_range, size, func); } /// Call 'func' for each GPU modified range and unmark those pages as GPU modified template - void ForEachDownloadRange(VAddr query_cpu_range, u64 size, bool clear, Func&& func) { - ForEachModifiedRange(query_cpu_range, size, clear, func); + void ForEachDownloadRange(VAddr query_cpu_range, u64 size, Func&& func) { + ForEachModifiedRange(query_cpu_range, size, func); } /// Call 'func' for each GPU modified range and unmark those pages as GPU modified template void ForEachDownloadRange(Func&& func) { - ForEachModifiedRange(cpu_addr, SizeBytes(), true, func); + ForEachModifiedRange(cpu_addr, SizeBytes(), func); } /// Mark buffer as picked @@ -415,7 +415,7 @@ private: * @param func Function to call for each turned off region */ template - void ForEachModifiedRange(VAddr query_cpu_range, s64 size, bool clear, Func&& func) { + void ForEachModifiedRange(VAddr query_cpu_range, s64 size, Func&& func) { static_assert(type != Type::Untracked); const s64 difference = query_cpu_range - cpu_addr; @@ -467,9 +467,7 @@ private: bits = (bits << left_offset) >> left_offset; const u64 current_word = state_words[word_index] & bits; - if (clear) { - state_words[word_index] &= ~bits; - } + state_words[word_index] &= ~bits; if constexpr (type == Type::CPU) { const u64 current_bits = untracked_words[word_index] & bits; diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index dc2b1f447..cad7f902d 100755 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -15,7 +15,6 @@ #include #include -#include #include "common/common_types.h" #include "common/div_ceil.h" @@ -78,9 +77,6 @@ class BufferCache { using Runtime = typename P::Runtime; using Buffer = typename P::Buffer; - using IntervalSet = boost::icl::interval_set; - using IntervalType = typename IntervalSet::interval_type; - struct Empty {}; struct OverlapResult { @@ -157,7 +153,6 @@ public: /// Commit asynchronous downloads void CommitAsyncFlushes(); - void CommitAsyncFlushesHigh(); /// Pop asynchronous downloads void PopAsyncFlushes(); @@ -165,9 +160,6 @@ public: /// Return true when a CPU region is modified from the GPU [[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size); - /// Return true when a CPU region is modified from the GPU - [[nodiscard]] bool IsRegionCpuModified(VAddr addr, size_t size); - std::mutex mutex; private: @@ -280,6 +272,8 @@ private: void DeleteBuffer(BufferId buffer_id); + void ReplaceBufferDownloads(BufferId old_buffer_id, BufferId new_buffer_id); + void NotifyBufferDeletion(); [[nodiscard]] Binding StorageBufferBinding(GPUVAddr ssbo_addr) const; @@ -333,7 +327,9 @@ private: std::vector cached_write_buffer_ids; - IntervalSet uncommitted_ranges; + // TODO: This data structure is not optimal and it should be reworked + std::vector uncommitted_downloads; + std::deque> committed_downloads; size_t immediate_buffer_capacity = 0; std::unique_ptr immediate_buffer_alloc; @@ -551,18 +547,29 @@ void BufferCache

::FlushCachedWrites() { template bool BufferCache

::HasUncommittedFlushes() const noexcept { - return !uncommitted_ranges.empty(); + return !uncommitted_downloads.empty(); } template bool BufferCache

::ShouldWaitAsyncFlushes() const noexcept { - return false; + return !committed_downloads.empty() && !committed_downloads.front().empty(); } template -void BufferCache

::CommitAsyncFlushesHigh() { - const IntervalSet& intervals = uncommitted_ranges; - if (intervals.empty()) { +void BufferCache

::CommitAsyncFlushes() { + // This is intentionally passing the value by copy + committed_downloads.push_front(uncommitted_downloads); + uncommitted_downloads.clear(); +} + +template +void BufferCache

::PopAsyncFlushes() { + if (committed_downloads.empty()) { + return; + } + auto scope_exit_pop_download = detail::ScopeExit([this] { committed_downloads.pop_back(); }); + const std::span download_ids = committed_downloads.back(); + if (download_ids.empty()) { return; } MICROPROFILE_SCOPE(GPU_DownloadMemory); @@ -570,35 +577,18 @@ void BufferCache

::CommitAsyncFlushesHigh() { boost::container::small_vector, 1> downloads; u64 total_size_bytes = 0; u64 largest_copy = 0; - for (auto& interval : intervals) { - const std::size_t size = interval.upper() - interval.lower(); - const VAddr cpu_addr = interval.lower(); - const VAddr cpu_addr_end = interval.upper(); - ForEachBufferInRange(cpu_addr, size, [&](BufferId buffer_id, Buffer& buffer) { - boost::container::small_vector copies; - buffer.ForEachDownloadRange( - cpu_addr, size, false, [&](u64 range_offset, u64 range_size) { - VAddr cpu_addr_base = buffer.CpuAddr() + range_offset; - VAddr cpu_addr_end2 = cpu_addr_base + range_size; - const s64 difference = s64(cpu_addr_end2 - cpu_addr_end); - cpu_addr_end2 -= u64(std::max(difference, 0)); - const s64 difference2 = s64(cpu_addr - cpu_addr_base); - cpu_addr_base += u64(std::max(difference2, 0)); - const u64 new_size = cpu_addr_end2 - cpu_addr_base; - const u64 new_offset = cpu_addr_base - buffer.CpuAddr(); - ASSERT(!IsRegionCpuModified(cpu_addr_base, new_size)); - downloads.push_back({ - BufferCopy{ - .src_offset = new_offset, - .dst_offset = total_size_bytes, - .size = new_size, - }, - buffer_id, - }); - total_size_bytes += new_size; - buffer.UnmarkRegionAsGpuModified(cpu_addr_base, new_size); - largest_copy = std::max(largest_copy, new_size); - }); + for (const BufferId buffer_id : download_ids) { + slot_buffers[buffer_id].ForEachDownloadRange([&](u64 range_offset, u64 range_size) { + downloads.push_back({ + BufferCopy{ + .src_offset = range_offset, + .dst_offset = total_size_bytes, + .size = range_size, + }, + buffer_id, + }); + total_size_bytes += range_size; + largest_copy = std::max(largest_copy, range_size); }); } if (downloads.empty()) { @@ -632,18 +622,6 @@ void BufferCache

::CommitAsyncFlushesHigh() { } } -template -void BufferCache

::CommitAsyncFlushes() { - if (Settings::values.gpu_accuracy.GetValue() == Settings::GPUAccuracy::High) { - CommitAsyncFlushesHigh(); - } else { - uncommitted_ranges.clear(); - } -} - -template -void BufferCache

::PopAsyncFlushes() {} - template bool BufferCache

::IsRegionGpuModified(VAddr addr, size_t size) { const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE); @@ -663,25 +641,6 @@ bool BufferCache

::IsRegionGpuModified(VAddr addr, size_t size) { return false; } -template -bool BufferCache

::IsRegionCpuModified(VAddr addr, size_t size) { - const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE); - for (u64 page = addr >> PAGE_BITS; page < page_end;) { - const BufferId image_id = page_table[page]; - if (!image_id) { - ++page; - continue; - } - Buffer& buffer = slot_buffers[image_id]; - if (buffer.IsRegionCpuModified(addr, size)) { - return true; - } - const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes(); - page = Common::DivCeil(end_addr, PAGE_SIZE); - } - return false; -} - template void BufferCache

::BindHostIndexBuffer() { Buffer& buffer = slot_buffers[index_buffer.buffer_id]; @@ -1051,14 +1010,16 @@ void BufferCache

::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 s Buffer& buffer = slot_buffers[buffer_id]; buffer.MarkRegionAsGpuModified(cpu_addr, size); - const bool is_accuracy_high = - Settings::values.gpu_accuracy.GetValue() == Settings::GPUAccuracy::High; + const bool is_accuracy_high = Settings::IsGPULevelHigh(); const bool is_async = Settings::values.use_asynchronous_gpu_emulation.GetValue(); - if (!is_async && !is_accuracy_high) { + if (!is_accuracy_high || !is_async) { return; } - const IntervalType base_interval{cpu_addr, cpu_addr + size}; - uncommitted_ranges.add(base_interval); + if (std::ranges::find(uncommitted_downloads, buffer_id) != uncommitted_downloads.end()) { + // Already inserted + return; + } + uncommitted_downloads.push_back(buffer_id); } template @@ -1142,6 +1103,7 @@ void BufferCache

::JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, if (!copies.empty()) { runtime.CopyBuffer(slot_buffers[new_buffer_id], overlap, copies); } + ReplaceBufferDownloads(overlap_id, new_buffer_id); DeleteBuffer(overlap_id); } @@ -1282,7 +1244,7 @@ void BufferCache

::DownloadBufferMemory(Buffer& buffer, VAddr cpu_addr, u64 si boost::container::small_vector copies; u64 total_size_bytes = 0; u64 largest_copy = 0; - buffer.ForEachDownloadRange(cpu_addr, size, true, [&](u64 range_offset, u64 range_size) { + buffer.ForEachDownloadRange(cpu_addr, size, [&](u64 range_offset, u64 range_size) { copies.push_back(BufferCopy{ .src_offset = range_offset, .dst_offset = total_size_bytes, @@ -1353,6 +1315,18 @@ void BufferCache

::DeleteBuffer(BufferId buffer_id) { NotifyBufferDeletion(); } +template +void BufferCache

::ReplaceBufferDownloads(BufferId old_buffer_id, BufferId new_buffer_id) { + const auto replace = [old_buffer_id, new_buffer_id](std::vector& buffers) { + std::ranges::replace(buffers, old_buffer_id, new_buffer_id); + if (auto it = std::ranges::find(buffers, new_buffer_id); it != buffers.end()) { + buffers.erase(std::remove(it + 1, buffers.end(), new_buffer_id), buffers.end()); + } + }; + replace(uncommitted_downloads); + std::ranges::for_each(committed_downloads, replace); +} + template void BufferCache

::NotifyBufferDeletion() { if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp index ec5cbc11f..b95750fd3 100755 --- a/src/video_core/engines/fermi_2d.cpp +++ b/src/video_core/engines/fermi_2d.cpp @@ -67,10 +67,10 @@ void Fermi2D::Blit() { }; Surface src = regs.src; const auto bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(src.format)); - const auto is_copy_out_of_bound = + const auto need_align_to_pitch = src.linear == Tegra::Engines::Fermi2D::MemoryLayout::Pitch && src.width == config.src_x1 && config.src_x1 > static_cast(src.pitch / bytes_per_pixel) && config.src_x0 > 0; - if (is_copy_out_of_bound) { + if (need_align_to_pitch) { auto address = src.Address() + config.src_x0 * bytes_per_pixel; src.addr_upper = static_cast(address >> 32); src.addr_lower = static_cast(address); diff --git a/src/video_core/fence_manager.h b/src/video_core/fence_manager.h index 696ea6dd9..f055b61e9 100755 --- a/src/video_core/fence_manager.h +++ b/src/video_core/fence_manager.h @@ -96,23 +96,6 @@ public: } } - void TryReleasePendingFences() { - while (!fences.empty()) { - TFence& current_fence = fences.front(); - if (ShouldWait() && !IsFenceSignaled(current_fence)) { - return; - } - PopAsyncFlushes(); - if (current_fence->IsSemaphore()) { - gpu_memory.template Write(current_fence->GetAddress(), - current_fence->GetPayload()); - } else { - gpu.IncrementSyncPoint(current_fence->GetPayload()); - } - PopFence(); - } - } - protected: explicit FenceManager(VideoCore::RasterizerInterface& rasterizer_, Tegra::GPU& gpu_, TTextureCache& texture_cache_, TTBufferCache& buffer_cache_, @@ -142,6 +125,23 @@ protected: TQueryCache& query_cache; private: + void TryReleasePendingFences() { + while (!fences.empty()) { + TFence& current_fence = fences.front(); + if (ShouldWait() && !IsFenceSignaled(current_fence)) { + return; + } + PopAsyncFlushes(); + if (current_fence->IsSemaphore()) { + gpu_memory.template Write(current_fence->GetAddress(), + current_fence->GetPayload()); + } else { + gpu.IncrementSyncPoint(current_fence->GetPayload()); + } + PopFence(); + } + } + bool ShouldWait() const { std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; return texture_cache.ShouldWaitAsyncFlushes() || buffer_cache.ShouldWaitAsyncFlushes() || diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp index 25c0d30dd..46f642b19 100755 --- a/src/video_core/gpu_thread.cpp +++ b/src/video_core/gpu_thread.cpp @@ -8,7 +8,6 @@ #include "common/settings.h" #include "common/thread.h" #include "core/core.h" -#include "core/core_timing.h" #include "core/frontend/emu_window.h" #include "video_core/dma_pusher.h" #include "video_core/gpu.h" @@ -84,17 +83,6 @@ void ThreadManager::StartThread(VideoCore::RendererBase& renderer, rasterizer = renderer.ReadRasterizer(); thread = std::thread(RunThread, std::ref(system), std::ref(renderer), std::ref(context), std::ref(dma_pusher), std::ref(state)); - gpu_sync_event = Core::Timing::CreateEvent( - "GPUHostSyncCallback", [this](std::uintptr_t, std::chrono::nanoseconds) { - if (!state.is_running) { - return; - } - - OnCommandListEnd(); - const auto time_interval = std::chrono::nanoseconds{500 * 1000}; - system.CoreTiming().ScheduleEvent(time_interval, gpu_sync_event); - }); - system.CoreTiming().ScheduleEvent(std::chrono::nanoseconds{500 * 1000}, gpu_sync_event); } void ThreadManager::SubmitList(Tegra::CommandList&& entries) { @@ -140,9 +128,6 @@ void ThreadManager::ShutDown() { state.cv.notify_all(); } - system.CoreTiming().UnscheduleEvent(gpu_sync_event, 0); - system.CoreTiming().RemoveEvent(gpu_sync_event); - if (!thread.joinable()) { return; } diff --git a/src/video_core/gpu_thread.h b/src/video_core/gpu_thread.h index ea98df3b1..11a648f38 100755 --- a/src/video_core/gpu_thread.h +++ b/src/video_core/gpu_thread.h @@ -20,10 +20,6 @@ class DmaPusher; } // namespace Tegra namespace Core { -namespace Timing { -class CoreTiming; -struct EventType; -} // namespace Timing namespace Frontend { class GraphicsContext; } @@ -154,7 +150,6 @@ private: SynchState state; std::thread thread; - std::shared_ptr gpu_sync_event; }; } // namespace VideoCommon::GPUThread diff --git a/src/video_core/renderer_vulkan/vk_fence_manager.cpp b/src/video_core/renderer_vulkan/vk_fence_manager.cpp index c2d6676e7..3bec48d14 100755 --- a/src/video_core/renderer_vulkan/vk_fence_manager.cpp +++ b/src/video_core/renderer_vulkan/vk_fence_manager.cpp @@ -34,10 +34,6 @@ bool InnerFence::IsSignaled() const { if (is_stubbed) { return true; } - if (scheduler.IsFree(wait_tick)) { - return true; - } - scheduler.Refresh(); return scheduler.IsFree(wait_tick); } diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 2ce1be4af..8ae2202bd 100755 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -604,7 +604,7 @@ void RasterizerVulkan::ReleaseFences() { if (!gpu.IsAsync()) { return; } - fence_manager.TryReleasePendingFences(); + fence_manager.WaitPendingFences(); } void RasterizerVulkan::FlushAndInvalidateRegion(VAddr addr, u64 size) { diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index 9e0a1d4e6..3ce48e9d2 100755 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -83,10 +83,6 @@ public: return master_semaphore->IsFree(tick); } - void Refresh() const noexcept { - return master_semaphore->Refresh(); - } - /// Waits for the given tick to trigger on the GPU. void Wait(u64 tick) { master_semaphore->Wait(tick); diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 39a5347fa..01de2d498 100755 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -769,20 +769,20 @@ void TextureCache

::BlitImage(const Tegra::Engines::Fermi2D::Surface& dst, const ImageBase& src_image = slot_images[src_id]; // TODO: Deduplicate - const std::optional dst_base = dst_image.TryFindBase(dst.Address()); - const SubresourceRange dst_range{.base = dst_base.value(), .extent = {1, 1}}; - const ImageViewInfo dst_view_info(ImageViewType::e2D, images.dst_format, dst_range); - const auto [dst_framebuffer_id, dst_view_id] = RenderTargetFromImage(dst_id, dst_view_info); + const std::optional src_base = src_image.TryFindBase(src.Address()); + const SubresourceRange src_range{.base = src_base.value(), .extent = {1, 1}}; + const ImageViewInfo src_view_info(ImageViewType::e2D, images.src_format, src_range); + const auto [src_framebuffer_id, src_view_id] = RenderTargetFromImage(src_id, src_view_info); const auto [src_samples_x, src_samples_y] = SamplesLog2(src_image.info.num_samples); const Region2D src_region{ Offset2D{.x = copy.src_x0 >> src_samples_x, .y = copy.src_y0 >> src_samples_y}, Offset2D{.x = copy.src_x1 >> src_samples_x, .y = copy.src_y1 >> src_samples_y}, }; - const std::optional src_base = src_image.TryFindBase(src.Address()); - const SubresourceRange src_range{.base = src_base.value(), .extent = {1, 1}}; - const ImageViewInfo src_view_info(ImageViewType::e2D, images.src_format, src_range); - const auto [src_framebuffer_id, src_view_id] = RenderTargetFromImage(src_id, src_view_info); + const std::optional dst_base = dst_image.TryFindBase(dst.Address()); + const SubresourceRange dst_range{.base = dst_base.value(), .extent = {1, 1}}; + const ImageViewInfo dst_view_info(ImageViewType::e2D, images.dst_format, dst_range); + const auto [dst_framebuffer_id, dst_view_id] = RenderTargetFromImage(dst_id, dst_view_info); const auto [dst_samples_x, dst_samples_y] = SamplesLog2(dst_image.info.num_samples); const Region2D dst_region{ Offset2D{.x = copy.dst_x0 >> dst_samples_x, .y = copy.dst_y0 >> dst_samples_y}, diff --git a/src/video_core/texture_cache/types.h b/src/video_core/texture_cache/types.h index 47a11cb2f..9fbdc1ac6 100755 --- a/src/video_core/texture_cache/types.h +++ b/src/video_core/texture_cache/types.h @@ -133,8 +133,8 @@ struct BufferImageCopy { }; struct BufferCopy { - u64 src_offset; - u64 dst_offset; + size_t src_offset; + size_t dst_offset; size_t size; }; diff --git a/src/yuzu/debugger/profiler.cpp b/src/yuzu/debugger/profiler.cpp index efdc6aa50..7a6f84d96 100755 --- a/src/yuzu/debugger/profiler.cpp +++ b/src/yuzu/debugger/profiler.cpp @@ -143,24 +143,25 @@ void MicroProfileWidget::hideEvent(QHideEvent* ev) { } void MicroProfileWidget::mouseMoveEvent(QMouseEvent* ev) { - MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, 0); + MicroProfileMousePosition(ev->pos().x() / x_scale, ev->pos().y() / y_scale, 0); ev->accept(); } void MicroProfileWidget::mousePressEvent(QMouseEvent* ev) { - MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, 0); + MicroProfileMousePosition(ev->pos().x() / x_scale, ev->pos().y() / y_scale, 0); MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton); ev->accept(); } void MicroProfileWidget::mouseReleaseEvent(QMouseEvent* ev) { - MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, 0); + MicroProfileMousePosition(ev->pos().x() / x_scale, ev->pos().y() / y_scale, 0); MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton); ev->accept(); } void MicroProfileWidget::wheelEvent(QWheelEvent* ev) { - MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, ev->delta() / 120); + MicroProfileMousePosition(ev->pos().x() / x_scale, ev->pos().y() / y_scale, + ev->angleDelta().y() / 120); ev->accept(); }