rework the binding system

This commit is contained in:
Samuliak 2024-05-05 11:43:27 +02:00
parent 49f60adeb2
commit 3a51a8de2e
7 changed files with 320 additions and 184 deletions

View file

@ -22,15 +22,6 @@ MTL::Buffer* CreatePrivateBuffer(const Device& device, size_t size) {
} // Anonymous namespace
BoundBuffer::BoundBuffer(MTL::Buffer* buffer_, size_t offset_, size_t size_)
: buffer{buffer_->retain()}, offset{offset_}, size{size_} {}
BoundBuffer::~BoundBuffer() {
if (buffer) {
buffer->release();
}
}
BufferView::BufferView(MTL::Buffer* buffer_, size_t offset_, size_t size_,
VideoCore::Surface::PixelFormat format_)
: buffer{buffer_->retain()}, offset{offset_}, size{size_}, format{format_} {}
@ -94,24 +85,29 @@ void BufferCacheRuntime::ClearBuffer(MTL::Buffer* dest_buffer, u32 offset, size_
void BufferCacheRuntime::BindIndexBuffer(PrimitiveTopology topology, IndexFormat index_format,
u32 base_vertex, u32 num_indices, MTL::Buffer* buffer,
u32 offset, [[maybe_unused]] u32 size) {
// TODO: convert parameters to Metal enums
bound_index_buffer = {BoundBuffer(buffer, offset, size)};
command_recorder.SetIndexBuffer(buffer, offset, index_format, topology, num_indices,
base_vertex);
}
void BufferCacheRuntime::BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count) {
// TODO: bind quad index buffer
}
void BufferCacheRuntime::BindVertexBuffer(u32 index, MTL::Buffer* buffer, u32 offset, u32 size,
u32 stride) {
void BufferCacheRuntime::BindVertexBuffer(size_t stage, u32 index, MTL::Buffer* buffer, u32 offset,
u32 size, u32 stride) {
// TODO: use stride
bound_vertex_buffers[MAX_METAL_BUFFERS - index - 1] = {BoundBuffer(buffer, offset, size)};
BindBuffer(stage, MAX_METAL_BUFFERS - index - 1, buffer, offset, size);
}
void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings) {
// TODO: implement
}
void BufferCacheRuntime::BindBuffer(size_t stage, u32 binding_index, MTL::Buffer* buffer,
u32 offset, u32 size) {
command_recorder.SetBuffer(stage, buffer, binding_index, offset);
}
void BufferCacheRuntime::ReserveNullBuffer() {
if (!null_buffer) {
null_buffer = CreateNullBuffer();

View file

@ -17,17 +17,6 @@ class CommandRecorder;
class BufferCacheRuntime;
struct BoundBuffer {
BoundBuffer() = default;
BoundBuffer(MTL::Buffer* buffer_, size_t offset_, size_t size_);
~BoundBuffer();
MTL::Buffer* buffer = nil;
size_t offset{};
size_t size{};
};
struct BufferView {
BufferView(MTL::Buffer* buffer_, size_t offset_, size_t size_,
VideoCore::Surface::PixelFormat format_ = VideoCore::Surface::PixelFormat::Invalid);
@ -121,7 +110,8 @@ public:
void BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count);
void BindVertexBuffer(u32 index, MTL::Buffer* buffer, u32 offset, u32 size, u32 stride);
void BindVertexBuffer(size_t stage, u32 index, MTL::Buffer* buffer, u32 offset, u32 size,
u32 stride);
void BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings);
@ -131,31 +121,35 @@ public:
// TODO: implement
void BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings) {}
std::span<u8> BindMappedUniformBuffer([[maybe_unused]] size_t stage,
[[maybe_unused]] u32 binding_index, u32 size) {
std::span<u8> BindMappedUniformBuffer(size_t stage, u32 binding_index, u32 size) {
const StagingBufferRef ref = staging_pool.Request(size, MemoryUsage::Upload);
BindBuffer(ref.buffer, static_cast<u32>(ref.offset), size);
BindBuffer(stage, binding_index, ref.buffer, static_cast<u32>(ref.offset), size);
return ref.mapped_span;
}
void BindUniformBuffer(MTL::Buffer* buffer, u32 offset, u32 size) {
BindBuffer(buffer, offset, size);
void BindUniformBuffer(size_t stage, u32 binding_index, MTL::Buffer* buffer, u32 offset,
u32 size) {
BindBuffer(stage, binding_index, buffer, offset, size);
}
void BindStorageBuffer(MTL::Buffer* buffer, u32 offset, u32 size,
[[maybe_unused]] bool is_written) {
BindBuffer(buffer, offset, size);
// TODO: implement
void BindComputeUniformBuffer(u32 binding_index, MTL::Buffer* buffer, u32 offset, u32 size) {}
void BindStorageBuffer(size_t stage, u32 binding_index, MTL::Buffer* buffer, u32 offset,
u32 size, [[maybe_unused]] bool is_written) {
BindBuffer(stage, binding_index, buffer, offset, size);
}
// TODO: implement
void BindComputeStorageBuffer(u32 binding_index, Buffer& buffer, u32 offset, u32 size,
bool is_written) {}
// TODO: implement
void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size,
VideoCore::Surface::PixelFormat format) {}
private:
void BindBuffer(MTL::Buffer* buffer, u32 offset, u32 size) {
// FIXME: what should be the index?
bound_buffers[0] = BoundBuffer(buffer, offset, size);
}
void BindBuffer(size_t stage, u32 binding_index, MTL::Buffer* buffer, u32 offset, u32 size);
void ReserveNullBuffer();
MTL::Buffer* CreateNullBuffer();
@ -167,17 +161,6 @@ private:
// Common buffers
MTL::Buffer* null_buffer = nil;
MTL::Buffer* 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 {
@ -189,8 +172,8 @@ struct BufferCacheParams {
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 NEEDS_BIND_UNIFORM_INDEX = true;
static constexpr bool NEEDS_BIND_STORAGE_INDEX = true;
static constexpr bool USE_MEMORY_MAPS = true;
static constexpr bool SEPARATE_IMAGE_BUFFER_BINDINGS = false;
static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = true;

View file

@ -11,12 +11,86 @@ CommandRecorder::CommandRecorder(const Device& device_) : device(device_) {}
CommandRecorder::~CommandRecorder() = default;
void CommandRecorder::BeginOrContinueRenderPass(MTL::RenderPassDescriptor* render_pass) {
bool should_reset_bound_resources = false;
if (render_pass != render_state.render_pass) {
RequireCommandBuffer();
EndEncoding();
RequireCommandBuffer();
encoder = command_buffer->renderCommandEncoder(render_pass);
encoder_type = EncoderType::Render;
render_state.render_pass = render_pass;
should_reset_bound_resources = true;
}
const auto bind_resources{[&](size_t stage) {
// Buffers
for (u8 i = 0; i < MAX_BUFFERS; i++) {
auto& bound_buffer = render_state.buffers[stage][i];
if (bound_buffer.buffer &&
(bound_buffer.needs_update || should_reset_bound_resources)) {
switch (stage) {
case 0:
GetRenderCommandEncoderUnchecked()->setVertexBuffer(bound_buffer.buffer, i,
bound_buffer.offset);
break;
case 4:
GetRenderCommandEncoderUnchecked()->setFragmentBuffer(bound_buffer.buffer, i,
bound_buffer.offset);
break;
}
bound_buffer.needs_update = false;
}
}
// Textures
for (u8 i = 0; i < MAX_TEXTURES; i++) {
auto& bound_texture = render_state.textures[stage][i];
if (bound_texture.texture &&
(bound_texture.needs_update || should_reset_bound_resources)) {
switch (stage) {
case 0:
GetRenderCommandEncoderUnchecked()->setVertexTexture(bound_texture.texture, i);
break;
case 4:
GetRenderCommandEncoderUnchecked()->setFragmentTexture(bound_texture.texture,
i);
break;
}
bound_texture.needs_update = false;
}
}
// Sampler states
for (u8 i = 0; i < MAX_SAMPLERS; i++) {
auto& bound_sampler_state = render_state.sampler_states[stage][i];
if (bound_sampler_state.sampler_state &&
(bound_sampler_state.needs_update || should_reset_bound_resources)) {
switch (stage) {
case 0:
GetRenderCommandEncoderUnchecked()->setVertexSamplerState(
bound_sampler_state.sampler_state, i);
break;
case 4:
GetRenderCommandEncoderUnchecked()->setFragmentSamplerState(
bound_sampler_state.sampler_state, i);
break;
}
bound_sampler_state.needs_update = false;
}
}
}};
bind_resources(0);
bind_resources(4);
if (should_reset_bound_resources) {
for (size_t stage = 0; stage < 5; stage++) {
for (u8 i = 0; i < MAX_BUFFERS; i++) {
render_state.buffers[stage][i].buffer = nullptr;
}
for (u8 i = 0; i < MAX_TEXTURES; i++) {
render_state.textures[stage][i].texture = nullptr;
}
for (u8 i = 0; i < MAX_SAMPLERS; i++) {
render_state.sampler_states[stage][i].sampler_state = nullptr;
}
}
}
}
@ -44,7 +118,8 @@ void CommandRecorder::EndEncoding() {
//[encoder release];
encoder = nullptr;
if (encoder_type == EncoderType::Render) {
render_state = {};
render_state.render_pass = nullptr;
render_state.pipeline_state = nullptr;
}
}
}

View file

@ -6,6 +6,8 @@
#include <Metal/Metal.hpp>
#include <QuartzCore/QuartzCore.hpp>
#include "video_core/engines/maxwell_3d.h"
namespace Metal {
class Device;
@ -16,27 +18,48 @@ constexpr size_t MAX_BUFFERS = 31;
constexpr size_t MAX_TEXTURES = 31;
constexpr size_t MAX_SAMPLERS = 31;
struct BoundBuffer {
bool needs_update{true};
MTL::Buffer* buffer{nullptr};
size_t offset{0};
};
struct BoundTexture {
bool needs_update{true};
MTL::Texture* texture{nullptr};
};
struct BoundSamplerState {
bool needs_update{true};
MTL::SamplerState* sampler_state{nullptr};
};
struct BoundIndexBuffer {
MTL::Buffer* buffer{nullptr};
size_t offset{0};
MTL::IndexType index_format;
MTL::PrimitiveType primitive_topology;
u32 num_indices;
u32 base_vertex;
};
struct RenderState {
MTL::RenderPassDescriptor* render_pass{nullptr};
MTL::RenderPipelineState* pipeline_state{nullptr};
MTL::Buffer* vertex_buffers[MAX_BUFFERS] = {nullptr};
MTL::Buffer* fragment_buffers[MAX_BUFFERS] = {nullptr};
MTL::Buffer* compute_buffers[MAX_BUFFERS] = {nullptr};
MTL::Texture* vertex_textures[MAX_TEXTURES] = {nullptr};
MTL::Texture* fragment_textures[MAX_TEXTURES] = {nullptr};
MTL::Texture* compute_textures[MAX_TEXTURES] = {nullptr};
MTL::SamplerState* vertex_sampler_states[MAX_SAMPLERS] = {nullptr};
MTL::SamplerState* fragment_sampler_states[MAX_SAMPLERS] = {nullptr};
MTL::SamplerState* compute_sampler_states[MAX_SAMPLERS] = {nullptr};
BoundBuffer buffers[5][MAX_BUFFERS] = {{}};
BoundTexture textures[5][MAX_TEXTURES] = {{}};
BoundSamplerState sampler_states[5][MAX_SAMPLERS] = {{}};
BoundIndexBuffer bound_index_buffer;
};
// TODO: whenever a render pass gets interrupted by either a compute or blit command and application
// then tries to perform a render command, begin the same render pass, but with all load actions set
// to "load"
class CommandRecorder {
using PrimitiveTopology = Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology;
using IndexFormat = Tegra::Engines::Maxwell3D::Regs::IndexFormat;
public:
CommandRecorder(const Device& device_);
~CommandRecorder();
@ -64,10 +87,14 @@ public:
return command_buffer;
}
MTL::RenderCommandEncoder* GetRenderCommandEncoderUnchecked() {
return static_cast<MTL::RenderCommandEncoder*>(encoder);
}
MTL::RenderCommandEncoder* GetRenderCommandEncoder() {
CheckIfRenderPassIsActive();
return static_cast<MTL::RenderCommandEncoder*>(encoder);
return GetRenderCommandEncoderUnchecked();
}
MTL::ComputeCommandEncoder* GetComputeCommandEncoder() {
@ -90,67 +117,34 @@ public:
}
}
inline void SetVertexBuffer(MTL::Buffer* buffer, size_t index) {
if (buffer != render_state.vertex_buffers[index]) {
GetRenderCommandEncoder()->setVertexBuffer(buffer, index, 0);
render_state.vertex_buffers[index] = buffer;
inline void SetBuffer(size_t stage, MTL::Buffer* buffer, size_t index, size_t offset) {
auto& bound_buffer = render_state.buffers[stage][index];
if (buffer != bound_buffer.buffer) {
bound_buffer = {true, buffer, offset};
}
}
inline void SetFragmentBuffer(MTL::Buffer* buffer, size_t index) {
if (buffer != render_state.fragment_buffers[index]) {
GetRenderCommandEncoder()->setFragmentBuffer(buffer, index, 0);
render_state.fragment_buffers[index] = buffer;
inline void SetTexture(size_t stage, MTL::Texture* texture, size_t index) {
auto& bound_texture = render_state.textures[stage][index];
if (texture != bound_texture.texture) {
bound_texture = {true, texture};
}
}
inline void SetComputeBuffer(MTL::Buffer* buffer, size_t index) {
if (buffer != render_state.compute_buffers[index]) {
GetComputeCommandEncoder()->setBuffer(buffer, index, 0);
render_state.compute_buffers[index] = buffer;
inline void SetSamplerState(size_t stage, MTL::SamplerState* sampler_state, size_t index) {
auto& bound_sampler_state = render_state.sampler_states[stage][index];
if (sampler_state != bound_sampler_state.sampler_state) {
bound_sampler_state = {true, sampler_state};
}
}
inline void SetVertexTexture(MTL::Texture* texture, size_t index) {
if (texture != render_state.vertex_textures[index]) {
GetRenderCommandEncoder()->setVertexTexture(texture, index);
render_state.vertex_textures[index] = texture;
}
}
inline void SetFragmentTexture(MTL::Texture* texture, size_t index) {
if (texture != render_state.fragment_textures[index]) {
GetRenderCommandEncoder()->setFragmentTexture(texture, index);
render_state.fragment_textures[index] = texture;
}
}
inline void SetComputeTexture(MTL::Texture* texture, size_t index) {
if (texture != render_state.compute_textures[index]) {
GetComputeCommandEncoder()->setTexture(texture, index);
render_state.compute_textures[index] = texture;
}
}
inline void SetVertexSamplerState(MTL::SamplerState* sampler_state, size_t index) {
if (sampler_state != render_state.vertex_sampler_states[index]) {
GetRenderCommandEncoder()->setVertexSamplerState(sampler_state, index);
render_state.vertex_sampler_states[index] = sampler_state;
}
}
inline void SetFragmentSamplerState(MTL::SamplerState* sampler_state, size_t index) {
if (sampler_state != render_state.fragment_sampler_states[index]) {
GetRenderCommandEncoder()->setFragmentSamplerState(sampler_state, index);
render_state.fragment_sampler_states[index] = sampler_state;
}
}
inline void SetComputeSamplerState(MTL::SamplerState* sampler_state, size_t index) {
if (sampler_state != render_state.compute_sampler_states[index]) {
GetComputeCommandEncoder()->setSamplerState(sampler_state, index);
render_state.compute_sampler_states[index] = sampler_state;
}
inline void SetIndexBuffer(MTL::Buffer* buffer, size_t offset, IndexFormat index_format,
PrimitiveTopology primitive_topology, u32 num_indices,
u32 base_vertex) {
// TODO: convert parameters to Metal enums
render_state.bound_index_buffer = {
buffer, offset, MTL::IndexTypeUInt32, MTL::PrimitiveTypeTriangle,
num_indices, base_vertex};
}
private:

View file

@ -58,13 +58,90 @@ GraphicsPipeline::GraphicsPipeline(const Device& device_, CommandRecorder& comma
}
void GraphicsPipeline::Configure(bool is_indexed) {
buffer_cache.UpdateGraphicsBuffers(is_indexed);
buffer_cache.BindHostGeometryBuffers(is_indexed);
texture_cache.SynchronizeGraphicsDescriptors();
std::array<VideoCommon::ImageViewInOut, 32> views;
std::array<VideoCommon::SamplerId, 32> samplers;
size_t view_index{};
size_t sampler_index{};
// Find resources
size_t stage = 4;
const auto& regs{maxwell3d->regs};
const bool via_header_index{regs.sampler_binding == Maxwell::SamplerBinding::ViaHeaderBinding};
const auto configure_stage{[&](u32 stage) {
const Shader::Info& info{stage_infos[stage]};
buffer_cache.UnbindGraphicsStorageBuffers(stage);
size_t ssbo_index{};
for (const auto& desc : info.storage_buffers_descriptors) {
ASSERT(desc.count == 1);
buffer_cache.BindGraphicsStorageBuffer(stage, ssbo_index, desc.cbuf_index,
desc.cbuf_offset, desc.is_written);
++ssbo_index;
}
const auto& cbufs{maxwell3d->state.shader_stages[stage].const_buffers};
const auto read_handle{[&](const auto& desc, u32 index) {
ASSERT(cbufs[desc.cbuf_index].enabled);
const u32 index_offset{index << desc.size_shift};
const u32 offset{desc.cbuf_offset + index_offset};
const GPUVAddr addr{cbufs[desc.cbuf_index].address + offset};
if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&> ||
std::is_same_v<decltype(desc), const Shader::TextureBufferDescriptor&>) {
if (desc.has_secondary) {
ASSERT(cbufs[desc.secondary_cbuf_index].enabled);
const u32 second_offset{desc.secondary_cbuf_offset + index_offset};
const GPUVAddr separate_addr{cbufs[desc.secondary_cbuf_index].address +
second_offset};
const u32 lhs_raw{gpu_memory->Read<u32>(addr) << desc.shift_left};
const u32 rhs_raw{gpu_memory->Read<u32>(separate_addr)
<< desc.secondary_shift_left};
const u32 raw{lhs_raw | rhs_raw};
return TexturePair(raw, via_header_index);
}
}
auto a = gpu_memory->Read<u32>(addr);
// HACK: this particular texture breaks SMO
if (a == 310378931)
a = 310378932;
return TexturePair(a, via_header_index);
}};
const auto add_image{[&](const auto& desc, bool blacklist) {
for (u32 index = 0; index < desc.count; ++index) {
const auto handle{read_handle(desc, index)};
views[view_index++] = {
.index = handle.first,
.blacklist = blacklist,
.id = {},
};
}
}};
/*
if constexpr (Spec::has_texture_buffers) {
for (const auto& desc : info.texture_buffer_descriptors) {
add_image(desc, false);
}
}
*/
/*
if constexpr (Spec::has_image_buffers) {
for (const auto& desc : info.image_buffer_descriptors) {
add_image(desc, false);
}
}
*/
for (const auto& desc : info.texture_descriptors) {
for (u32 index = 0; index < desc.count; ++index) {
const auto handle{read_handle(desc, index)};
views[view_index++] = {handle.first};
VideoCommon::SamplerId sampler{texture_cache.GetGraphicsSamplerId(handle.second)};
samplers[sampler_index++] = sampler;
}
}
for (const auto& desc : info.image_descriptors) {
add_image(desc, desc.is_written);
}
/*
const auto& cbufs{maxwell3d->state.shader_stages[stage].const_buffers};
const auto read_handle{[&](const auto& desc, u32 index) {
ASSERT(cbufs[desc.cbuf_index].enabled);
@ -110,18 +187,17 @@ void GraphicsPipeline::Configure(bool is_indexed) {
samplers[sampler_index++] = sampler;
}
}
*/
}};
configure_stage(0);
configure_stage(4);
buffer_cache.UpdateGraphicsBuffers(is_indexed);
buffer_cache.BindHostGeometryBuffers(is_indexed);
texture_cache.FillGraphicsImageViews<true>(std::span(views.data(), view_index));
// Begin render pass
texture_cache.UpdateRenderTargets(false);
const Framebuffer* const framebuffer = texture_cache.GetFramebuffer();
if (!framebuffer) {
return;
}
command_recorder.BeginOrContinueRenderPass(framebuffer->GetHandle());
command_recorder.SetRenderPipelineState(pipeline_state);
// Bind resources
// HACK: try to find a texture that we can bind
@ -131,8 +207,18 @@ void GraphicsPipeline::Configure(bool is_indexed) {
ImageView& image_view{texture_cache.GetImageView(views_it->id)};
Sampler& sampler{texture_cache.GetSampler(*samplers_it)};
command_recorder.SetFragmentTexture(image_view.GetHandle(), 0);
command_recorder.SetFragmentSamplerState(sampler.GetHandle(), 0);
command_recorder.SetTexture(4, image_view.GetHandle(), 0);
command_recorder.SetSamplerState(4, sampler.GetHandle(), 0);
// Begin render pass
texture_cache.UpdateRenderTargets(false);
const Framebuffer* const framebuffer = texture_cache.GetFramebuffer();
if (!framebuffer) {
return;
}
command_recorder.BeginOrContinueRenderPass(framebuffer->GetHandle());
command_recorder.SetRenderPipelineState(pipeline_state);
}
void GraphicsPipeline::MakePipeline(MTL::RenderPassDescriptor* render_pass) {

View file

@ -272,7 +272,7 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
std::cout << code << std::endl;
MTL::CompileOptions* compile_options = MTL::CompileOptions::alloc()->init();
NS::Error* error = nullptr;
MTL::Library* library = device.GetDevice()->newLibrary(
[[maybe_unused]] MTL::Library* library = device.GetDevice()->newLibrary(
NS::String::string(code.c_str(), NS::ASCIIStringEncoding), compile_options, &error);
if (error) {
LOG_ERROR(Render_Metal, "failed to create library: {}",
@ -280,11 +280,11 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
// HACK
std::cout << error->description()->cString(NS::ASCIIStringEncoding) << std::endl;
// HACK
throw;
// throw;
}
functions[index - 1] =
library->newFunction(NS::String::string("main_", NS::ASCIIStringEncoding));
// functions[stage_index] =
// library->newFunction(NS::String::string("main_", NS::ASCIIStringEncoding));
previous_stage = &program;
}
@ -316,8 +316,8 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
return out;
}
fragment float4 fragmentMain(VertexOut in [[stage_in]], texture2d<float> tex [[texture(0)]], sampler samplr [[sampler(0)]]) {
return tex.sample(samplr, in.texCoord);
fragment float4 fragmentMain(VertexOut in [[stage_in]], texture2d<float> tex [[texture(0)]],
sampler samplr [[sampler(0)]]) { return tex.sample(samplr, in.texCoord);
}
)",
NS::ASCIIStringEncoding),

View file

@ -42,18 +42,20 @@ void RendererMetal::Composite(std::span<const Tegra::FramebufferConfig> framebuf
render_pass_descriptor->colorAttachments()->object(0)->setTexture(
swap_chain.GetDrawableTexture());
command_recorder.BeginOrContinueRenderPass(render_pass_descriptor);
// Blit the framebuffer to the drawable texture
// Bind the source 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.SetTexture(4, src_texture, 0);
command_recorder.SetSamplerState(4, blit_sampler_state, 0);
command_recorder.BeginOrContinueRenderPass(render_pass_descriptor);
// Blit the framebuffer to the drawable texture
command_recorder.SetRenderPipelineState(blit_pipeline_state);
command_recorder.SetFragmentTexture(src_texture, 0);
command_recorder.SetFragmentSamplerState(blit_sampler_state, 0);
// Draw a full screen triangle which will get clipped to a rectangle
command_recorder.GetRenderCommandEncoder()->drawPrimitives(MTL::PrimitiveTypeTriangle,