diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 3306fe69ee..034952801e 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -374,6 +374,8 @@ if (APPLE) ) list(APPEND sources + renderer_metal/mtl_buffer_cache.mm + renderer_metal/mtl_buffer_cache_base.cpp renderer_metal/mtl_command_recorder.mm renderer_metal/mtl_device.mm renderer_metal/mtl_rasterizer.mm diff --git a/src/video_core/renderer_metal/mtl_buffer_cache.h b/src/video_core/renderer_metal/mtl_buffer_cache.h new file mode 100644 index 0000000000..96a33db35f --- /dev/null +++ b/src/video_core/renderer_metal/mtl_buffer_cache.h @@ -0,0 +1,201 @@ +// SPDX-FileCopyrightText: Copyright 2024 suyu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "video_core/buffer_cache/buffer_cache_base.h" +#include "video_core/buffer_cache/memory_tracker_base.h" +#include "video_core/buffer_cache/usage_tracker.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/renderer_metal/mtl_staging_buffer_pool.h" +#include "video_core/surface.h" + +namespace Metal { + +class Device; +class CommandRecorder; + +class BufferCacheRuntime; + +struct BoundBuffer { + BoundBuffer() = default; + BoundBuffer(MTLBuffer_t buffer_, size_t offset_, size_t size_); + + ~BoundBuffer(); + + MTLBuffer_t buffer = nil; + size_t offset{}; + size_t size{}; +}; + +struct BufferView { + BufferView(MTLBuffer_t buffer_, size_t offset_, size_t size_, + VideoCore::Surface::PixelFormat format_ = VideoCore::Surface::PixelFormat::Invalid); + ~BufferView(); + + MTLBuffer_t buffer = nil; + size_t offset{}; + size_t size{}; + VideoCore::Surface::PixelFormat format{}; +}; + +class Buffer : public VideoCommon::BufferBase { +public: + explicit Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params); + explicit Buffer(BufferCacheRuntime& runtime, VAddr cpu_addr_, u64 size_bytes_); + + [[nodiscard]] BufferView View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format); + + void MarkUsage(u64 offset, u64 size) noexcept { + // TODO: track usage + } + + [[nodiscard]] MTLBuffer_t Handle() const noexcept { + return buffer; + } + + operator MTLBuffer_t() const noexcept { + return buffer; + } + +private: + MTLBuffer_t buffer = nil; + bool is_null{}; + + BufferView view; +}; + +class BufferCacheRuntime { + friend Buffer; + + using PrimitiveTopology = Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology; + using IndexFormat = Tegra::Engines::Maxwell3D::Regs::IndexFormat; + +public: + static constexpr size_t NULL_BUFFER_SIZE = 4; + static constexpr size_t MAX_METAL_BUFFERS = 31; + + explicit BufferCacheRuntime(const Device& device_, CommandRecorder& command_recorder_, + StagingBufferPool& staging_pool_); + + void TickFrame(Common::SlotVector& slot_buffers) noexcept; + + void Finish(); + + u64 GetDeviceLocalMemory() const { + return 0; + } + + u64 GetDeviceMemoryUsage() const { + return 0; + } + + bool CanReportMemoryUsage() const { + return false; + } + + u32 GetStorageBufferAlignment() const; + + [[nodiscard]] StagingBufferRef UploadStagingBuffer(size_t size); + + [[nodiscard]] StagingBufferRef DownloadStagingBuffer(size_t size, bool deferred = false); + + bool CanReorderUpload(const Buffer& buffer, std::span copies) { + return false; + } + + void FreeDeferredStagingBuffer(StagingBufferRef& ref); + + void PreCopyBarrier() {} + + void CopyBuffer(MTLBuffer_t src_buffer, MTLBuffer_t dst_buffer, + std::span copies, bool barrier, + bool can_reorder_upload = false); + + void PostCopyBarrier() {} + + void ClearBuffer(MTLBuffer_t dest_buffer, u32 offset, size_t size, u32 value); + + void BindIndexBuffer(PrimitiveTopology topology, IndexFormat index_format, u32 num_indices, + u32 base_vertex, MTLBuffer_t buffer, u32 offset, u32 size); + + void BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count); + + void BindVertexBuffer(u32 index, MTLBuffer_t buffer, u32 offset, u32 size, u32 stride); + + void BindVertexBuffers(VideoCommon::HostBindings& bindings); + + // TODO: implement + void BindTransformFeedbackBuffer(u32 index, MTLBuffer_t buffer, u32 offset, u32 size) {} + + // TODO: implement + void BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings) {} + + std::span BindMappedUniformBuffer([[maybe_unused]] size_t stage, + [[maybe_unused]] u32 binding_index, u32 size) { + const StagingBufferRef ref = staging_pool.Request(size, MemoryUsage::Upload); + BindBuffer(ref.buffer, static_cast(ref.offset), size); + return ref.mapped_span; + } + + void BindUniformBuffer(MTLBuffer_t buffer, u32 offset, u32 size) { + BindBuffer(buffer, offset, size); + } + + void BindStorageBuffer(MTLBuffer_t buffer, u32 offset, u32 size, + [[maybe_unused]] bool is_written) { + BindBuffer(buffer, offset, size); + } + + // TODO: implement + void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size, + VideoCore::Surface::PixelFormat format) {} + +private: + void BindBuffer(MTLBuffer_t buffer, u32 offset, u32 size) { + // FIXME: what should be the index? + bound_buffers[0] = BoundBuffer(buffer, offset, size); + } + + void ReserveNullBuffer(); + MTLBuffer_t CreateNullBuffer(); + + const Device& device; + CommandRecorder& command_recorder; + StagingBufferPool& staging_pool; + + // Common buffers + MTLBuffer_t null_buffer = nil; + MTLBuffer_t quad_index_buffer = nil; + + // TODO: probably move this into a separate class + // Bound state + // Vertex buffers are bound to MAX_METAL_BUFFERS - index - 1, while regular buffers are bound to + // index + BoundBuffer bound_vertex_buffers[MAX_METAL_BUFFERS] = {{}}; + struct { + BoundBuffer buffer; + // TODO: include index type and primitive topology + } bound_index_buffer = {}; + BoundBuffer bound_buffers[MAX_METAL_BUFFERS] = {{}}; +}; + +struct BufferCacheParams { + using Runtime = Metal::BufferCacheRuntime; + using Buffer = Metal::Buffer; + using Async_Buffer = Metal::StagingBufferRef; + using MemoryTracker = VideoCommon::MemoryTrackerBase; + + static constexpr bool IS_OPENGL = false; + static constexpr bool HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS = false; + static constexpr bool HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT = false; + static constexpr bool NEEDS_BIND_UNIFORM_INDEX = false; + static constexpr bool NEEDS_BIND_STORAGE_INDEX = false; + static constexpr bool USE_MEMORY_MAPS = true; + static constexpr bool SEPARATE_IMAGE_BUFFER_BINDINGS = false; + static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = true; +}; + +using BufferCache = VideoCommon::BufferCache; + +} // namespace Metal diff --git a/src/video_core/renderer_metal/mtl_buffer_cache.mm b/src/video_core/renderer_metal/mtl_buffer_cache.mm new file mode 100644 index 0000000000..ff6daf3bf7 --- /dev/null +++ b/src/video_core/renderer_metal/mtl_buffer_cache.mm @@ -0,0 +1,121 @@ +// SPDX-FileCopyrightText: Copyright 2024 suyu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include +#include + +#include "video_core/renderer_metal/mtl_buffer_cache.h" + +#include "video_core/renderer_metal/mtl_device.h" + +namespace Metal { + +namespace { + +MTLBuffer_t CreatePrivateBuffer(const Device& device, size_t size) { + return [device.GetDevice() newBufferWithLength:size options:MTLResourceStorageModePrivate]; +} + +} // Anonymous namespace + +BoundBuffer::BoundBuffer(MTLBuffer_t buffer_, size_t offset_, size_t size_) + : buffer{[buffer_ retain]}, offset{offset_}, size{size_} {} + +BoundBuffer::~BoundBuffer() { + [buffer release]; +} + +BufferView::BufferView(MTLBuffer_t buffer_, size_t offset_, size_t size_, + VideoCore::Surface::PixelFormat format_) + : buffer{[buffer_ retain]}, offset{offset_}, size{size_}, format{format_} {} + +BufferView::~BufferView() { + [buffer release]; +} + +Buffer::Buffer(BufferCacheRuntime& runtime, VideoCommon::NullBufferParams null_params) + : VideoCommon::BufferBase(null_params), buffer{runtime.CreateNullBuffer()}, + is_null{true}, view(buffer, 0, BufferCacheRuntime::NULL_BUFFER_SIZE) {} + +Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_) + : VideoCommon::BufferBase(cpu_addr_, size_bytes_), + buffer{CreatePrivateBuffer(runtime.device, size_bytes_)}, + view(buffer, 0, size_bytes_) {} + +BufferView Buffer::View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format) { + return BufferView(buffer, offset, size, format); +} + +BufferCacheRuntime::BufferCacheRuntime(const Device& device_, CommandRecorder& command_recorder_, + StagingBufferPool& staging_pool_) + : device{device_}, command_recorder{command_recorder_}, staging_pool{staging_pool_} { + // TODO: create quad index buffer +} + +StagingBufferRef BufferCacheRuntime::UploadStagingBuffer(size_t size) { + return staging_pool.Request(size, MemoryUsage::Upload); +} + +StagingBufferRef BufferCacheRuntime::DownloadStagingBuffer(size_t size, bool deferred) { + return staging_pool.Request(size, MemoryUsage::Download, deferred); +} + +void BufferCacheRuntime::FreeDeferredStagingBuffer(StagingBufferRef& ref) { + staging_pool.FreeDeferred(ref); +} + +u32 BufferCacheRuntime::GetStorageBufferAlignment() const { + // TODO: do not hardcode this + return 4; +} + +void BufferCacheRuntime::TickFrame(Common::SlotVector& slot_buffers) noexcept {} + +void BufferCacheRuntime::Finish() {} + +void BufferCacheRuntime::CopyBuffer(MTLBuffer_t dst_buffer, MTLBuffer_t src_buffer, + std::span copies, bool barrier, + bool can_reorder_upload) { + // TODO: copy buffer +} + +void BufferCacheRuntime::ClearBuffer(MTLBuffer_t dest_buffer, u32 offset, size_t size, u32 value) { + // TODO: clear buffer +} + +void BufferCacheRuntime::BindIndexBuffer(PrimitiveTopology topology, IndexFormat index_format, + u32 base_vertex, u32 num_indices, MTLBuffer_t buffer, + u32 offset, [[maybe_unused]] u32 size) { + // TODO: convert parameters to Metal enums + bound_index_buffer = {BoundBuffer(buffer, offset, size)}; +} + +void BufferCacheRuntime::BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count) { + // TODO: bind quad index buffer +} + +void BufferCacheRuntime::BindVertexBuffer(u32 index, MTLBuffer_t buffer, u32 offset, u32 size, + u32 stride) { + // TODO: use stride + bound_vertex_buffers[MAX_METAL_BUFFERS - index - 1] = {BoundBuffer(buffer, offset, size)}; +} + +void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings) { + // TODO: implement +} + +void BufferCacheRuntime::ReserveNullBuffer() { + if (!null_buffer) { + null_buffer = CreateNullBuffer(); + } +} + +MTLBuffer_t BufferCacheRuntime::CreateNullBuffer() { + return [device.GetDevice() newBufferWithLength:NULL_BUFFER_SIZE + options:MTLResourceStorageModePrivate]; +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_metal/mtl_buffer_cache_base.cpp b/src/video_core/renderer_metal/mtl_buffer_cache_base.cpp new file mode 100644 index 0000000000..db6bf7431a --- /dev/null +++ b/src/video_core/renderer_metal/mtl_buffer_cache_base.cpp @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: Copyright 2024 suyu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "video_core/buffer_cache/buffer_cache.h" +#include "video_core/renderer_metal/mtl_buffer_cache.h" + +namespace VideoCommon { +template class VideoCommon::BufferCache; +}