Comparer les révisions

...

5 Révisions

Auteur SHA1 Message Date
Samuliak 4d700ac01c
metal: create pipeline cache at startup 2024-04-07 17:58:30 +02:00
Samuliak 58ac31e2be
metal: support buffer to buffer copy 2024-04-07 16:03:20 +02:00
Samuliak 081ad83490
metal: add basic compute pipeline 2024-04-07 15:51:11 +02:00
Samuliak a5e7672de5
metal: blit rendering result to drawable 2024-04-07 13:08:14 +02:00
Samuliak c8a717651c
metal: create basic graphics pipeline 2024-04-07 11:58:35 +02:00
13 fichiers modifiés avec 931 ajouts et 11 suppressions

Voir le fichier

@ -378,7 +378,10 @@ if (APPLE)
renderer_metal/mtl_buffer_cache.cpp
renderer_metal/mtl_buffer_cache_base.cpp
renderer_metal/mtl_command_recorder.cpp
renderer_metal/mtl_compute_pipeline.cpp
renderer_metal/mtl_device.cpp
renderer_metal/mtl_graphics_pipeline.cpp
renderer_metal/mtl_pipeline_cache.cpp
renderer_metal/mtl_rasterizer.cpp
renderer_metal/mtl_staging_buffer_pool.cpp
renderer_metal/mtl_swap_chain.cpp

Voir le fichier

@ -9,6 +9,7 @@
#include "video_core/renderer_metal/mtl_buffer_cache.h"
#include "video_core/renderer_metal/mtl_command_recorder.h"
#include "video_core/renderer_metal/mtl_device.h"
namespace Metal {
@ -80,7 +81,10 @@ void BufferCacheRuntime::Finish() {}
void BufferCacheRuntime::CopyBuffer(MTL::Buffer* dst_buffer, MTL::Buffer* src_buffer,
std::span<const VideoCommon::BufferCopy> copies, bool barrier,
bool can_reorder_upload) {
// TODO: copy buffer
for (const VideoCommon::BufferCopy& copy : copies) {
command_recorder.GetBlitCommandEncoder()->copyFromBuffer(
src_buffer, copy.src_offset, dst_buffer, copy.dst_offset, copy.size);
}
}
void BufferCacheRuntime::ClearBuffer(MTL::Buffer* dest_buffer, u32 offset, size_t size, u32 value) {

Voir le fichier

@ -40,8 +40,22 @@ public:
return command_buffer;
}
MTL::CommandEncoder* GetCommandEncoder() {
return encoder;
MTL::RenderCommandEncoder* GetRenderCommandEncoder() {
CheckIfRenderPassIsActive();
return static_cast<MTL::RenderCommandEncoder*>(encoder);
}
MTL::ComputeCommandEncoder* GetComputeCommandEncoder() {
RequireComputeEncoder();
return static_cast<MTL::ComputeCommandEncoder*>(encoder);
}
MTL::BlitCommandEncoder* GetBlitCommandEncoder() {
RequireBlitEncoder();
return static_cast<MTL::BlitCommandEncoder*>(encoder);
}
private:

Voir le fichier

@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: Copyright 2024 suyu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <vector>
#include <boost/container/small_vector.hpp>
#include "video_core/buffer_cache/buffer_cache_base.h"
#include "video_core/renderer_metal/mtl_compute_pipeline.h"
#include "video_core/renderer_metal/mtl_device.h"
#include "video_core/shader_notify.h"
#include "video_core/texture_cache/texture_cache_base.h"
namespace Metal {
ComputePipeline::ComputePipeline(const Device& device_, VideoCore::ShaderNotify* shader_notify,
const Shader::Info& info_, MTL::Function* function_)
: device{device_}, info{info_}, function{function_} {
if (shader_notify) {
shader_notify->MarkShaderBuilding();
}
MTL::ComputePipelineDescriptor* pipeline_descriptor =
MTL::ComputePipelineDescriptor::alloc()->init();
pipeline_descriptor->setComputeFunction(function);
// TODO: set other properties
NS::Error* error = nullptr;
pipeline_state =
device.GetDevice()->newComputePipelineState(pipeline_descriptor, 0, nullptr, &error);
if (error) {
LOG_ERROR(Render_Metal, "failed to create compute pipeline: {}",
error->description()->cString(NS::ASCIIStringEncoding));
}
}
void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
Tegra::MemoryManager& gpu_memory, CommandRecorder& scheduler,
BufferCache& buffer_cache, TextureCache& texture_cache) {
// TODO: bind resources
}
} // namespace Metal

Voir le fichier

@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: Copyright 2024 suyu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <condition_variable>
#include <mutex>
#include "common/common_types.h"
#include "common/thread_worker.h"
#include "shader_recompiler/shader_info.h"
#include "video_core/renderer_metal/mtl_buffer_cache.h"
#include "video_core/renderer_metal/mtl_texture_cache.h"
namespace VideoCore {
class ShaderNotify;
}
namespace Metal {
class Device;
class PipelineStatistics;
class Scheduler;
class ComputePipeline {
public:
explicit ComputePipeline(const Device& device_, VideoCore::ShaderNotify* shader_notify,
const Shader::Info& info_, MTL::Function* function_);
ComputePipeline& operator=(ComputePipeline&&) noexcept = delete;
ComputePipeline(ComputePipeline&&) noexcept = delete;
ComputePipeline& operator=(const ComputePipeline&) = delete;
ComputePipeline(const ComputePipeline&) = delete;
void Configure(Tegra::Engines::KeplerCompute& kepler_compute, Tegra::MemoryManager& gpu_memory,
CommandRecorder& scheduler, BufferCache& buffer_cache,
TextureCache& texture_cache);
private:
const Device& device;
Shader::Info info;
MTL::Function* function;
MTL::ComputePipelineState* pipeline_state;
};
} // namespace Metal

Voir le fichier

@ -0,0 +1,105 @@
// SPDX-FileCopyrightText: Copyright 2024 suyu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <span>
#include <boost/container/small_vector.hpp>
#include <boost/container/static_vector.hpp>
#include "video_core/renderer_metal/mtl_graphics_pipeline.h"
#include "common/bit_field.h"
#include "video_core/buffer_cache/buffer_cache_base.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_metal/mtl_command_recorder.h"
#include "video_core/renderer_metal/mtl_device.h"
#include "video_core/shader_notify.h"
#include "video_core/texture_cache/texture_cache_base.h"
namespace Metal {
namespace {
using Tegra::Texture::TexturePair;
using VideoCommon::NUM_RT;
using VideoCore::Surface::PixelFormat;
using VideoCore::Surface::PixelFormatFromDepthFormat;
using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
} // Anonymous namespace
GraphicsPipeline::GraphicsPipeline(const Device& device_, CommandRecorder& command_recorder_,
const GraphicsPipelineCacheKey& key_, BufferCache& buffer_cache_,
TextureCache& texture_cache_,
VideoCore::ShaderNotify* shader_notify,
std::array<MTL::Function*, NUM_STAGES> functions_,
const std::array<const Shader::Info*, NUM_STAGES>& infos)
: device{device_}, command_recorder{command_recorder_}, key{key_}, buffer_cache{buffer_cache_},
texture_cache{texture_cache_}, functions{functions_} {
if (shader_notify) {
shader_notify->MarkShaderBuilding();
}
for (size_t stage = 0; stage < NUM_STAGES; ++stage) {
const Shader::Info* const info{infos[stage]};
if (!info) {
continue;
}
stage_infos[stage] = *info;
}
Validate();
// TODO: is the framebuffer available by this time?
Framebuffer* framebuffer = texture_cache.GetFramebuffer();
if (!framebuffer) {
LOG_DEBUG(Render_Metal, "framebuffer not available");
return;
}
MakePipeline(framebuffer->GetHandle());
// configure_func = ConfigureFunc(functions, stage_infos);
}
template <typename Spec>
void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
// TODO: implement
}
void GraphicsPipeline::ConfigureDraw() {
Framebuffer* framebuffer = texture_cache.GetFramebuffer();
if (!framebuffer) {
return;
}
// MTL::RenderPassDescriptor* render_pass = framebuffer->GetHandle();
// TODO: bind resources
}
void GraphicsPipeline::MakePipeline(MTL::RenderPassDescriptor* render_pass) {
MTL::RenderPipelineDescriptor* pipeline_descriptor =
MTL::RenderPipelineDescriptor::alloc()->init();
pipeline_descriptor->setVertexFunction(functions[0]);
pipeline_descriptor->setFragmentFunction(functions[1]);
// pipeline_descriptor->setVertexDescriptor(vertex_descriptor);
// TODO: get the attachment count from render pass descriptor
for (u32 index = 0; index < NUM_RT; index++) {
auto* render_pass_attachment = render_pass->colorAttachments()->object(index);
// TODO: is this the correct way to check if the attachment is valid?
if (!render_pass_attachment->texture()) {
continue;
}
auto* color_attachment = pipeline_descriptor->colorAttachments()->object(index);
color_attachment->setPixelFormat(render_pass_attachment->texture()->pixelFormat());
// TODO: provide blend information
}
NS::Error* error = nullptr;
pipeline_state = device.GetDevice()->newRenderPipelineState(pipeline_descriptor, &error);
if (error) {
LOG_ERROR(Render_Metal, "failed to create pipeline state: {}",
error->description()->cString(NS::ASCIIStringEncoding));
}
}
void GraphicsPipeline::Validate() {
// TODO: validate pipeline
}
} // namespace Metal

Voir le fichier

@ -0,0 +1,135 @@
// SPDX-FileCopyrightText: Copyright 2024 suyu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <algorithm>
#include <array>
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <type_traits>
#include <Metal/Metal.hpp>
#include "common/thread_worker.h"
#include "shader_recompiler/shader_info.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_metal/mtl_buffer_cache.h"
#include "video_core/renderer_metal/mtl_texture_cache.h"
namespace VideoCore {
class ShaderNotify;
}
namespace Metal {
struct GraphicsPipelineCacheKey {
std::array<u64, 6> unique_hashes;
// TODO: include fixed state
size_t Hash() const noexcept;
bool operator==(const GraphicsPipelineCacheKey& rhs) const noexcept;
bool operator!=(const GraphicsPipelineCacheKey& rhs) const noexcept {
return !operator==(rhs);
}
size_t Size() const noexcept {
return sizeof(unique_hashes);
}
};
static_assert(std::has_unique_object_representations_v<GraphicsPipelineCacheKey>);
static_assert(std::is_trivially_copyable_v<GraphicsPipelineCacheKey>);
static_assert(std::is_trivially_constructible_v<GraphicsPipelineCacheKey>);
} // namespace Metal
namespace std {
template <>
struct hash<Metal::GraphicsPipelineCacheKey> {
size_t operator()(const Metal::GraphicsPipelineCacheKey& k) const noexcept {
return k.Hash();
}
};
} // namespace std
namespace Metal {
class Device;
class CommandRecorder;
class GraphicsPipeline {
static constexpr size_t NUM_STAGES = Tegra::Engines::Maxwell3D::Regs::MaxShaderStage;
public:
// TODO: accept render pass cache as an argument to provide info on color and depth attachments
explicit GraphicsPipeline(const Device& device_, CommandRecorder& command_recorder_,
const GraphicsPipelineCacheKey& key_, BufferCache& buffer_cache_,
TextureCache& texture_cache_, VideoCore::ShaderNotify* shader_notify,
std::array<MTL::Function*, NUM_STAGES> functions_,
const std::array<const Shader::Info*, NUM_STAGES>& infos);
GraphicsPipeline& operator=(GraphicsPipeline&&) noexcept = delete;
GraphicsPipeline(GraphicsPipeline&&) noexcept = delete;
GraphicsPipeline& operator=(const GraphicsPipeline&) = delete;
GraphicsPipeline(const GraphicsPipeline&) = delete;
// TODO: implement
void AddTransition(GraphicsPipeline* transition) {}
void Configure(bool is_indexed) {
// configure_func(this, is_indexed);
}
[[nodiscard]] GraphicsPipeline* Next(const GraphicsPipelineCacheKey& current_key) noexcept {
// TODO: implement
return nullptr;
}
[[nodiscard]] bool IsBuilt() const noexcept {
return true;
}
template <typename Spec>
static auto MakeConfigureSpecFunc() {
return [](GraphicsPipeline* pl, bool is_indexed) { pl->ConfigureImpl<Spec>(is_indexed); };
}
void SetEngine(Tegra::Engines::Maxwell3D* maxwell3d_, Tegra::MemoryManager* gpu_memory_) {
maxwell3d = maxwell3d_;
gpu_memory = gpu_memory_;
}
private:
template <typename Spec>
void ConfigureImpl(bool is_indexed);
void ConfigureDraw();
void MakePipeline(MTL::RenderPassDescriptor* render_pass);
void Validate();
const Device& device;
CommandRecorder& command_recorder;
const GraphicsPipelineCacheKey key;
Tegra::Engines::Maxwell3D* maxwell3d;
Tegra::MemoryManager* gpu_memory;
BufferCache& buffer_cache;
TextureCache& texture_cache;
void (*configure_func)(GraphicsPipeline*, bool){};
std::array<MTL::Function*, NUM_STAGES> functions;
std::array<Shader::Info, NUM_STAGES> stage_infos;
// VideoCommon::UniformBufferSizes uniform_buffer_sizes{};
// u32 num_textures{};
MTL::RenderPipelineState* pipeline_state{nullptr};
};
} // namespace Metal

Voir le fichier

@ -0,0 +1,314 @@
// SPDX-FileCopyrightText: Copyright 2024 suyu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstddef>
#include <fstream>
#include <memory>
#include <thread>
#include <vector>
#include "common/bit_cast.h"
#include "common/cityhash.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/microprofile.h"
#include "common/thread_worker.h"
#include "core/core.h"
#include "shader_recompiler/backend/spirv/emit_spirv.h"
#include "shader_recompiler/environment.h"
#include "shader_recompiler/frontend/maxwell/control_flow.h"
#include "shader_recompiler/frontend/maxwell/translate_program.h"
#include "shader_recompiler/program_header.h"
#include "video_core/engines/kepler_compute.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/memory_manager.h"
#include "video_core/renderer_metal/mtl_compute_pipeline.h"
#include "video_core/renderer_metal/mtl_device.h"
#include "video_core/renderer_metal/mtl_pipeline_cache.h"
#include "video_core/shader_cache.h"
#include "video_core/shader_environment.h"
#include "video_core/shader_notify.h"
namespace Metal {
namespace {
using Shader::Backend::SPIRV::EmitSPIRV;
using Shader::Maxwell::ConvertLegacyToGeneric;
using Shader::Maxwell::GenerateGeometryPassthrough;
using Shader::Maxwell::MergeDualVertexPrograms;
using Shader::Maxwell::TranslateProgram;
using VideoCommon::ComputeEnvironment;
using VideoCommon::FileEnvironment;
using VideoCommon::GenericEnvironment;
using VideoCommon::GraphicsEnvironment;
// constexpr u32 CACHE_VERSION = 1;
// constexpr std::array<char, 8> METAL_CACHE_MAGIC_NUMBER{'s', 'u', 'y', 'u', 'm', 'l', 'c', 'h'};
template <typename Container>
auto MakeSpan(Container& container) {
return std::span(container.data(), container.size());
}
} // Anonymous namespace
size_t ComputePipelineCacheKey::Hash() const noexcept {
const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), sizeof *this);
return static_cast<size_t>(hash);
}
bool ComputePipelineCacheKey::operator==(const ComputePipelineCacheKey& rhs) const noexcept {
return std::memcmp(&rhs, this, sizeof *this) == 0;
}
size_t GraphicsPipelineCacheKey::Hash() const noexcept {
const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), Size());
return static_cast<size_t>(hash);
}
bool GraphicsPipelineCacheKey::operator==(const GraphicsPipelineCacheKey& rhs) const noexcept {
return std::memcmp(&rhs, this, Size()) == 0;
}
PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
const Device& device_, CommandRecorder& command_recorder_,
BufferCache& buffer_cache_, TextureCache& texture_cache_,
VideoCore::ShaderNotify& shader_notify_)
: VideoCommon::ShaderCache{device_memory_}, device{device_},
command_recorder{command_recorder_}, buffer_cache{buffer_cache_},
texture_cache{texture_cache_}, shader_notify{shader_notify_} {
// TODO: query for some of these parameters
profile = Shader::Profile{
.supported_spirv = 0x00010300U, // HACK
.unified_descriptor_binding = false,
.support_descriptor_aliasing = false,
.support_int8 = true,
.support_int16 = true,
.support_int64 = true,
.support_vertex_instance_id = false,
.support_float_controls = false,
.support_separate_denorm_behavior = false,
.support_separate_rounding_mode = false,
.support_fp16_denorm_preserve = false,
.support_fp32_denorm_preserve = false,
.support_fp16_denorm_flush = false,
.support_fp32_denorm_flush = false,
.support_fp16_signed_zero_nan_preserve = false,
.support_fp32_signed_zero_nan_preserve = false,
.support_fp64_signed_zero_nan_preserve = false,
.support_explicit_workgroup_layout = false,
.support_vote = false,
.support_viewport_index_layer_non_geometry = false,
.support_viewport_mask = false,
.support_typeless_image_loads = true,
.support_demote_to_helper_invocation = false,
.support_int64_atomics = false,
.support_derivative_control = true,
.support_geometry_shader_passthrough = false,
.support_native_ndc = false,
.support_scaled_attributes = false,
.support_multi_viewport = false,
.support_geometry_streams = false,
.warp_size_potentially_larger_than_guest = false,
.lower_left_origin_mode = false,
.need_declared_frag_colors = false,
.need_gather_subpixel_offset = false,
.has_broken_spirv_clamp = false,
.has_broken_spirv_position_input = false,
.has_broken_unsigned_image_offsets = false,
.has_broken_signed_operations = false,
.has_broken_fp16_float_controls = false,
.ignore_nan_fp_comparisons = false,
.has_broken_spirv_subgroup_mask_vector_extract_dynamic = false,
.has_broken_robust = false,
.min_ssbo_alignment = 4,
.max_user_clip_distances = 8,
};
host_info = Shader::HostTranslateInfo{
.support_float64 = false,
.support_float16 = true,
.support_int64 = false,
.needs_demote_reorder = false,
.support_snorm_render_buffer = true,
.support_viewport_index_layer = true,
.min_ssbo_alignment = 4,
.support_geometry_shader_passthrough = false,
.support_conditional_barrier = false,
};
}
PipelineCache::~PipelineCache() = default;
GraphicsPipeline* PipelineCache::CurrentGraphicsPipeline() {
if (!RefreshStages(graphics_key.unique_hashes)) {
current_pipeline = nullptr;
return nullptr;
}
if (current_pipeline) {
GraphicsPipeline* const next{current_pipeline->Next(graphics_key)};
if (next) {
current_pipeline = next;
return BuiltPipeline(current_pipeline);
}
}
return CurrentGraphicsPipelineSlowPath();
}
ComputePipeline* PipelineCache::CurrentComputePipeline() {
const ShaderInfo* const shader{ComputeShader()};
if (!shader) {
return nullptr;
}
const auto& qmd{kepler_compute->launch_description};
const ComputePipelineCacheKey key{
.unique_hash = shader->unique_hash,
.shared_memory_size = qmd.shared_alloc,
.threadgroup_size{qmd.block_dim_x, qmd.block_dim_y, qmd.block_dim_z},
};
const auto [pair, is_new]{compute_cache.try_emplace(key)};
auto& pipeline{pair->second};
if (!is_new) {
return pipeline.get();
}
pipeline = CreateComputePipeline(key, shader);
return pipeline.get();
}
void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) {
// TODO: implement
}
GraphicsPipeline* PipelineCache::CurrentGraphicsPipelineSlowPath() {
const auto [pair, is_new]{graphics_cache.try_emplace(graphics_key)};
auto& pipeline{pair->second};
if (is_new) {
pipeline = CreateGraphicsPipeline();
}
if (!pipeline) {
return nullptr;
}
current_pipeline = pipeline.get();
return BuiltPipeline(current_pipeline);
}
GraphicsPipeline* PipelineCache::BuiltPipeline(GraphicsPipeline* pipeline) const noexcept {
if (pipeline->IsBuilt()) {
return pipeline;
}
const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
if (draw_state.index_buffer.count <= 6 || draw_state.vertex_buffer.count <= 6) {
return pipeline;
}
return nullptr;
}
std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
ShaderPools& pools, const GraphicsPipelineCacheKey& key,
std::span<Shader::Environment* const> envs) try {
auto hash = key.Hash();
LOG_INFO(Render_Metal, "0x{:016x}", hash);
// HACK: create hardcoded shaders
MTL::CompileOptions* compile_options = MTL::CompileOptions::alloc()->init();
NS::Error* error = nullptr;
MTL::Library* library = device.GetDevice()->newLibrary(NS::String::string(
R"(
#include <metal_stdlib>
using namespace metal;
constant float2 texCoords[] = {
float2(0.0, -1.0),
float2(0.0, 1.0),
float2(2.0, 1.0),
};
struct VertexOut {
float4 position [[position]];
float2 texCoord;
};
vertex VertexOut vertexMain(uint vid [[vertex_id]]) {
VertexOut out;
out.position = float4(texCoords[vid] * 2.0 - 1.0, 0.0, 1.0);
out.texCoord = texCoords[vid];
return out;
}
fragment float4 fragmentMain(VertexOut in [[stage_in]]) {
return float4(in.texCoord, 0.0, 1.0);
}
)",
NS::ASCIIStringEncoding),
compile_options, &error);
if (error) {
LOG_ERROR(Render_Metal, "failed to create blit library: {}",
error->description()->cString(NS::ASCIIStringEncoding));
}
std::array<MTL::Function*, VideoCommon::NUM_STAGES> functions;
functions[0] = library->newFunction(NS::String::string("vertexMain", NS::ASCIIStringEncoding));
functions[1] =
library->newFunction(NS::String::string("fragmentMain", NS::ASCIIStringEncoding));
// HACK: dummy info
std::array<const Shader::Info*, VideoCommon::NUM_STAGES> infos;
infos[0] = new Shader::Info{};
infos[1] = new Shader::Info{};
return std::make_unique<GraphicsPipeline>(device, command_recorder, key, buffer_cache,
texture_cache, &shader_notify, functions, infos);
} catch (const std::exception& e) {
LOG_ERROR(Render_Metal, "failed to create graphics pipeline: {}", e.what());
return nullptr;
}
std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline() {
GraphicsEnvironments environments;
GetGraphicsEnvironments(environments, graphics_key.unique_hashes);
main_pools.ReleaseContents();
return CreateGraphicsPipeline(main_pools, graphics_key, environments.Span());
}
std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
const ComputePipelineCacheKey& key, const ShaderInfo* shader) {
const GPUVAddr program_base{kepler_compute->regs.code_loc.Address()};
const auto& qmd{kepler_compute->launch_description};
ComputeEnvironment env{*kepler_compute, *gpu_memory, program_base, qmd.program_start};
env.SetCachedSize(shader->size_bytes);
main_pools.ReleaseContents();
return CreateComputePipeline(main_pools, key, env);
}
std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
ShaderPools& pools, const ComputePipelineCacheKey& key, Shader::Environment& env) try {
auto hash = key.Hash();
LOG_INFO(Render_Metal, "0x{:016x}", hash);
MTL::Function* function = nullptr;
// TODO: create compute function
throw std::runtime_error("Compute shaders are not implemented");
return std::make_unique<ComputePipeline>(device, &shader_notify, Shader::Info{}, function);
} catch (const std::exception& e) {
LOG_ERROR(Render_Metal, "failed to create compute pipeline: {}", e.what());
return nullptr;
}
} // namespace Metal

Voir le fichier

@ -0,0 +1,148 @@
// SPDX-FileCopyrightText: Copyright 2024 suyu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <cstddef>
#include <filesystem>
#include <memory>
#include <type_traits>
#include <unordered_map>
#include <vector>
#include "common/common_types.h"
#include "common/thread_worker.h"
#include "shader_recompiler/frontend/ir/basic_block.h"
#include "shader_recompiler/frontend/ir/value.h"
#include "shader_recompiler/frontend/maxwell/control_flow.h"
#include "shader_recompiler/host_translate_info.h"
#include "shader_recompiler/object_pool.h"
#include "shader_recompiler/profile.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/host1x/gpu_device_memory_manager.h"
#include "video_core/renderer_metal/mtl_buffer_cache.h"
#include "video_core/renderer_metal/mtl_compute_pipeline.h"
#include "video_core/renderer_metal/mtl_graphics_pipeline.h"
#include "video_core/renderer_metal/mtl_texture_cache.h"
#include "video_core/shader_cache.h"
namespace Core {
class System;
}
namespace Shader::IR {
struct Program;
}
namespace VideoCore {
class ShaderNotify;
}
namespace Metal {
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
struct ComputePipelineCacheKey {
u64 unique_hash;
u32 shared_memory_size;
std::array<u32, 3> threadgroup_size;
size_t Hash() const noexcept;
bool operator==(const ComputePipelineCacheKey& rhs) const noexcept;
bool operator!=(const ComputePipelineCacheKey& rhs) const noexcept {
return !operator==(rhs);
}
};
static_assert(std::has_unique_object_representations_v<ComputePipelineCacheKey>);
static_assert(std::is_trivially_copyable_v<ComputePipelineCacheKey>);
static_assert(std::is_trivially_constructible_v<ComputePipelineCacheKey>);
} // namespace Metal
namespace std {
template <>
struct hash<Metal::ComputePipelineCacheKey> {
size_t operator()(const Metal::ComputePipelineCacheKey& k) const noexcept {
return k.Hash();
}
};
} // namespace std
namespace Metal {
class ComputePipeline;
class Device;
class CommandRecorder;
using VideoCommon::ShaderInfo;
struct ShaderPools {
void ReleaseContents() {
flow_block.ReleaseContents();
block.ReleaseContents();
inst.ReleaseContents();
}
Shader::ObjectPool<Shader::IR::Inst> inst{8192};
Shader::ObjectPool<Shader::IR::Block> block{32};
Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block{32};
};
class PipelineCache : public VideoCommon::ShaderCache {
public:
explicit PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, const Device& device_,
CommandRecorder& command_recorder_, BufferCache& buffer_cache_,
TextureCache& texture_cache_, VideoCore::ShaderNotify& shader_notify_);
~PipelineCache();
[[nodiscard]] GraphicsPipeline* CurrentGraphicsPipeline();
[[nodiscard]] ComputePipeline* CurrentComputePipeline();
void LoadDiskResources(u64 title_id, std::stop_token stop_loading,
const VideoCore::DiskResourceLoadCallback& callback);
private:
[[nodiscard]] GraphicsPipeline* CurrentGraphicsPipelineSlowPath();
[[nodiscard]] GraphicsPipeline* BuiltPipeline(GraphicsPipeline* pipeline) const noexcept;
std::unique_ptr<GraphicsPipeline> CreateGraphicsPipeline();
std::unique_ptr<GraphicsPipeline> CreateGraphicsPipeline(
ShaderPools& pools, const GraphicsPipelineCacheKey& key,
std::span<Shader::Environment* const> envs);
std::unique_ptr<ComputePipeline> CreateComputePipeline(const ComputePipelineCacheKey& key,
const ShaderInfo* shader);
std::unique_ptr<ComputePipeline> CreateComputePipeline(ShaderPools& pools,
const ComputePipelineCacheKey& key,
Shader::Environment& env);
const Device& device;
CommandRecorder& command_recorder;
BufferCache& buffer_cache;
TextureCache& texture_cache;
VideoCore::ShaderNotify& shader_notify;
GraphicsPipelineCacheKey graphics_key{};
GraphicsPipeline* current_pipeline{};
std::unordered_map<ComputePipelineCacheKey, std::unique_ptr<ComputePipeline>> compute_cache;
std::unordered_map<GraphicsPipelineCacheKey, std::unique_ptr<GraphicsPipeline>> graphics_cache;
ShaderPools main_pools;
Shader::Profile profile;
Shader::HostTranslateInfo host_info;
std::filesystem::path pipeline_cache_filename;
};
} // namespace Metal

Voir le fichier

@ -35,7 +35,9 @@ RasterizerMetal::RasterizerMetal(Tegra::GPU& gpu_,
buffer_cache_runtime(device, command_recorder, staging_buffer_pool),
buffer_cache(device_memory, buffer_cache_runtime),
texture_cache_runtime(device, command_recorder, staging_buffer_pool),
texture_cache(texture_cache_runtime, device_memory) {}
texture_cache(texture_cache_runtime, device_memory),
pipeline_cache(device_memory, device, command_recorder, buffer_cache, texture_cache,
gpu.ShaderNotify()) {}
RasterizerMetal::~RasterizerMetal() = default;
void RasterizerMetal::Draw(bool is_indexed, u32 instance_count) {

Voir le fichier

@ -9,6 +9,7 @@
#include "video_core/engines/maxwell_dma.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_metal/mtl_buffer_cache.h"
#include "video_core/renderer_metal/mtl_pipeline_cache.h"
#include "video_core/renderer_metal/mtl_texture_cache.h"
namespace Core {
@ -106,7 +107,13 @@ private:
BufferCacheRuntime buffer_cache_runtime;
BufferCache buffer_cache;
TextureCacheRuntime texture_cache_runtime;
// HACK: make the texture cache public so that renderer can access it
public:
TextureCache texture_cache;
private:
PipelineCache pipeline_cache;
};
} // namespace Metal

Voir le fichier

@ -14,13 +14,18 @@ namespace Metal {
RendererMetal::RendererMetal(Core::Frontend::EmuWindow& emu_window,
Tegra::MaxwellDeviceMemoryManager& device_memory_, Tegra::GPU& gpu_,
std::unique_ptr<Core::Frontend::GraphicsContext> context_)
: RendererBase(emu_window, std::move(context_)),
device_memory{device_memory_}, gpu{gpu_}, device{}, command_recorder(device),
: RendererBase(emu_window, std::move(context_)), device_memory{device_memory_}, gpu{gpu_},
device{}, command_recorder(device),
swap_chain(device, command_recorder,
static_cast<CA::MetalLayer*>(render_window.GetWindowInfo().render_surface)),
rasterizer(gpu_, device_memory, device, command_recorder, swap_chain) {}
rasterizer(gpu_, device_memory, device, command_recorder, swap_chain) {
CreateBlitPipelineState();
}
RendererMetal::~RendererMetal() = default;
RendererMetal::~RendererMetal() {
blit_pipeline_state->release();
blit_sampler_state->release();
}
void RendererMetal::Composite(std::span<const Tegra::FramebufferConfig> framebuffers) {
if (framebuffers.empty()) {
@ -32,15 +37,28 @@ void RendererMetal::Composite(std::span<const Tegra::FramebufferConfig> framebuf
// TODO: copy the framebuffer to the drawable texture instead of this dummy render pass
MTL::RenderPassDescriptor* render_pass_descriptor = MTL::RenderPassDescriptor::alloc()->init();
render_pass_descriptor->colorAttachments()->object(0)->setClearColor(
MTL::ClearColor::Make(1.0, 0.5, 0.0, 1.0));
render_pass_descriptor->colorAttachments()->object(0)->setLoadAction(MTL::LoadActionClear);
render_pass_descriptor->colorAttachments()->object(0)->setLoadAction(MTL::LoadActionDontCare);
render_pass_descriptor->colorAttachments()->object(0)->setStoreAction(MTL::StoreActionStore);
render_pass_descriptor->colorAttachments()->object(0)->setTexture(
swap_chain.GetDrawableTexture());
command_recorder.BeginRenderPass(render_pass_descriptor);
// Blit the framebuffer to the drawable texture
// TODO: acquire the texture from @ref framebuffers
const Framebuffer* const framebuffer = rasterizer.texture_cache.GetFramebuffer();
if (!framebuffer) {
return;
}
MTL::Texture* src_texture = framebuffer->GetHandle()->colorAttachments()->object(0)->texture();
command_recorder.GetRenderCommandEncoder()->setRenderPipelineState(blit_pipeline_state);
command_recorder.GetRenderCommandEncoder()->setFragmentTexture(src_texture, 0);
command_recorder.GetRenderCommandEncoder()->setFragmentSamplerState(blit_sampler_state, 0);
// Draw a full screen triangle which will get clipped to a rectangle
command_recorder.GetRenderCommandEncoder()->drawPrimitives(MTL::PrimitiveTypeTriangle,
NS::UInteger(0), NS::UInteger(3));
swap_chain.Present();
command_recorder.Submit();
@ -54,4 +72,76 @@ std::vector<u8> RendererMetal::GetAppletCaptureBuffer() {
return std::vector<u8>(VideoCore::Capture::TiledSize);
}
void RendererMetal::CreateBlitPipelineState() {
MTL::CompileOptions* compile_options = MTL::CompileOptions::alloc()->init();
NS::Error* error = nullptr;
MTL::Library* library = device.GetDevice()->newLibrary(NS::String::string(
R"(
#include <metal_stdlib>
using namespace metal;
constant float2 texCoords[] = {
float2(0.0, -1.0),
float2(0.0, 1.0),
float2(2.0, 1.0),
};
struct VertexOut {
float4 position [[position]];
float2 texCoord;
};
vertex VertexOut vertexMain(uint vid [[vertex_id]]) {
VertexOut out;
out.position = float4(texCoords[vid] * 2.0 - 1.0, 0.0, 1.0);
out.texCoord = texCoords[vid];
return out;
}
fragment float4 fragmentMain(VertexOut in [[stage_in]],
texture2d<float> texture [[texture(0)]],
sampler sampler [[sampler(0)]]) {
return texture.sample(sampler, in.texCoord);
}
)",
NS::ASCIIStringEncoding),
compile_options, &error);
if (error) {
LOG_ERROR(Render_Metal, "failed to create blit library: {}",
error->description()->cString(NS::ASCIIStringEncoding));
}
MTL::Function* vertex_function =
library->newFunction(NS::String::string("vertexMain", NS::ASCIIStringEncoding));
MTL::Function* fragment_function =
library->newFunction(NS::String::string("fragmentMain", NS::ASCIIStringEncoding));
MTL::RenderPipelineDescriptor* pipeline_descriptor =
MTL::RenderPipelineDescriptor::alloc()->init();
pipeline_descriptor->setVertexFunction(vertex_function);
pipeline_descriptor->setFragmentFunction(fragment_function);
// TODO: get the pixel format from metal layer
pipeline_descriptor->colorAttachments()->object(0)->setPixelFormat(MTL::PixelFormatRGBA8Unorm);
error = nullptr;
blit_pipeline_state = device.GetDevice()->newRenderPipelineState(pipeline_descriptor, &error);
if (error) {
LOG_ERROR(Render_Metal, "failed to create blit pipeline state: {}",
error->description()->cString(NS::ASCIIStringEncoding));
}
// Create sampler state
MTL::SamplerDescriptor* sampler_descriptor = MTL::SamplerDescriptor::alloc()->init();
sampler_descriptor->setMinFilter(MTL::SamplerMinMagFilterLinear);
sampler_descriptor->setMagFilter(MTL::SamplerMinMagFilterLinear);
blit_sampler_state = device.GetDevice()->newSamplerState(sampler_descriptor);
// Deallocate unnecessary objects
fragment_function->release();
vertex_function->release();
library->release();
}
} // namespace Metal

Voir le fichier

@ -47,6 +47,8 @@ public:
}
private:
void CreateBlitPipelineState();
Tegra::MaxwellDeviceMemoryManager& device_memory;
Tegra::GPU& gpu;
@ -54,6 +56,9 @@ private:
CommandRecorder command_recorder;
SwapChain swap_chain;
MTL::RenderPipelineState* blit_pipeline_state{nullptr};
MTL::SamplerState* blit_sampler_state{nullptr};
RasterizerMetal rasterizer;
};