#include <math.h>
#include <new>
#ifndef INFINITY
#define INFINITY __builtin_huge_valf()
#endif
#include <Foundation/Foundation.hpp>
#include <Metal/Metal.hpp>
#include <QuartzCore/QuartzCore.hpp>
#include "backend/metal/mt_api.h"
#include "backend/metal/mt_internal.h"
#include "core/logger.h"
namespace mt {
static CA::MetalDrawable *g_current_drawable = nullptr;
static MTL4::CommandBuffer *g_last_committed = nullptr;
void mt_set_current_drawable(CA::MetalDrawable *drawable) {
if (g_current_drawable) {
g_current_drawable->release();
}
g_current_drawable = drawable;
if (g_current_drawable) {
g_current_drawable->retain();
}
}
CA::MetalDrawable *mt_current_drawable() {
return g_current_drawable;
}
void mt_clear_current_drawable() {
if (g_current_drawable) {
g_current_drawable->release();
g_current_drawable = nullptr;
}
}
void mt_wait_idle() {
(void)g_last_committed;
}
} // namespace mt
namespace rhi {
using namespace mt;
bool create_cmd_pool(Device &device, const rhi::CmdPoolDesc &desc, CmdPool &pool) {
(void)device;
(void)desc;
// no pools on metal
pool.handle = (void *)1;
return true;
}
void destroy_cmd_pool(Device &device, CmdPool &pool) {
(void)device;
pool.handle = nullptr;
}
void reset_cmd_pool(Device &device, CmdPool &pool, u32 flags) {
(void)device;
(void)pool;
(void)flags;
}
bool create_cmd_buffer(Device &device, const CmdBufferDesc &desc, CmdBuffer &cmd) {
(void)desc;
cmd.queue = device.graphics.handle;
if (!cmd.queue || !device.heap) {
return false;
}
cmd.table = device.heap->table;
cmd.residency = device.heap->residency;
cmd.allocator = cmd.queue->device()->newCommandAllocator();
return cmd.table && cmd.residency && cmd.allocator;
}
void destroy_cmd_buffer(Device &device, CmdPool &pool, CmdBuffer &cmd) {
(void)device;
(void)pool;
for (MTL::Buffer *t : cmd.transients) {
if (t) {
t->release();
}
}
cmd.transients.clear();
if (cmd.handle) {
cmd.handle->release();
cmd.handle = nullptr;
}
if (cmd.allocator) {
cmd.allocator->release();
cmd.allocator = nullptr;
}
cmd.encoder = nullptr;
cmd.queue = nullptr;
cmd.table = nullptr;
cmd.residency = nullptr;
cmd.is_rendering = false;
}
bool begin_cmd_buffer(CmdBuffer &cmd, const rhi::CmdBufferBeginDesc &desc) {
(void)desc;
for (MTL::Buffer *t : cmd.transients) {
if (t) {
t->release();
}
}
cmd.transients.clear();
if (cmd.handle) {
cmd.handle->release();
cmd.handle = nullptr;
}
if (!cmd.queue || !cmd.allocator || !cmd.residency) {
return false;
}
cmd.allocator->reset();
MTL::Device *dev = cmd.queue->device();
cmd.handle = dev->newCommandBuffer();
if (!cmd.handle) {
return false;
}
cmd.handle->beginCommandBuffer(cmd.allocator);
cmd.handle->useResidencySet(cmd.residency);
return true;
}
bool end_cmd_buffer(CmdBuffer &cmd) {
if (cmd.handle) {
cmd.handle->endCommandBuffer();
}
return true;
}
bool reset_cmd_buffer(CmdBuffer &cmd) {
(void)cmd;
return true;
}
void begin_rendering(CmdBuffer &cmd, RenderingInfo &ri) {
MTL4::RenderPassDescriptor *rpd = MTL4::RenderPassDescriptor::alloc()->init();
MTL::RenderPassColorAttachmentDescriptor *att = rpd->colorAttachments()->object(0);
att->setTexture(mt_current_drawable()->texture());
att->setLoadAction(MTL::LoadActionClear);
att->setStoreAction(MTL::StoreActionStore);
if (ri.colorAttachmentCount > 0) {
auto &ca = ri.colorAttachments[0];
if (ca.loadOp == rhi::LoadOp::LOAD) {
att->setLoadAction(MTL::LoadActionLoad);
} else {
att->setLoadAction(MTL::LoadActionClear);
att->setClearColor(
MTL::ClearColor(
ca.clearValue.color.float32[0],
ca.clearValue.color.float32[1],
ca.clearValue.color.float32[2],
ca.clearValue.color.float32[3]
)
);
}
}
rpd->setRenderTargetWidth(ri.width);
rpd->setRenderTargetHeight(ri.height);
// depth opt-in. pipeline must match pass.
if (ri.depthAttachment.img && ri.depthAttachment.img->handle) {
MTL::RenderPassDepthAttachmentDescriptor *datt = rpd->depthAttachment();
datt->setTexture(ri.depthAttachment.img->handle);
if (ri.depthAttachment.loadOp == rhi::LoadOp::LOAD) {
datt->setLoadAction(MTL::LoadActionLoad);
} else {
datt->setLoadAction(MTL::LoadActionClear);
datt->setClearDepth(ri.depthAttachment.clearValue.depthStencil.depth);
}
datt->setStoreAction(
ri.depthAttachment.storeOp == rhi::StoreOp::STORE ? MTL::StoreActionStore : MTL::StoreActionDontCare
);
}
cmd.encoder = cmd.handle->renderCommandEncoder(rpd);
// retain. outlives pool.
cmd.encoder->retain();
cmd.is_rendering = true;
rpd->release();
}
void end_rendering(CmdBuffer &cmd) {
if (!cmd.encoder) {
return;
}
cmd.encoder->endEncoding();
cmd.encoder->release();
cmd.encoder = nullptr;
cmd.is_rendering = false;
}
static void submit_cmds(const CmdBuffer *cmds, u32 cmd_count) {
for (u32 i = 0; i < cmd_count; ++i) {
const CmdBuffer &c = cmds[i];
MTL4::CommandBuffer *cb = c.handle;
if (!cb || !c.queue) {
continue;
}
MTL4::CommandBuffer *arr[1] = {cb};
c.queue->commit(arr, 1);
if (g_current_drawable) {
c.queue->signalDrawable(g_current_drawable);
g_current_drawable->present();
}
if (g_last_committed) {
g_last_committed->release();
}
g_last_committed = cb;
g_last_committed->retain();
}
}
void queue_submit(Device &device, Queue queue, const QueueSubmitDesc &desc) {
(void)queue;
// no waits/signals
(void)desc.waits;
(void)desc.signals;
// page in late adds before commit
if (device.heap && device.heap->residency) {
device.heap->residency->requestResidency();
device.heap->residency->commit();
}
submit_cmds(desc.cmds, desc.cmd_count);
mt_clear_current_drawable();
}
Sync *sync_create(Device &device, u64 initial_value, const char *name) {
(void)name; // Metal has no timeline-semaphore naming hook here; kept for API parity.
(void)device;
(void)initial_value;
Sync *sync = new (std::nothrow) Sync{};
if (sync == nullptr) {
return nullptr;
}
sync->timeline = (void *)1;
return sync;
}
void sync_destroy(Sync *sync) {
delete sync;
}
u64 sync_get_completed_value(Sync *sync) {
if (sync == nullptr || sync->timeline == nullptr) {
return 0;
}
return UINT64_MAX;
}
bool sync_host_wait(Sync *sync, u64 value, u64 timeout_ns) {
(void)value;
(void)timeout_ns;
if (sync == nullptr || sync->timeline == nullptr) {
return false;
}
return true;
}
void set_pipeline(CmdBuffer &cmd, RenderPipeline &pipeline) {
if (cmd.encoder && pipeline.handle && cmd.table) {
cmd.encoder->setRenderPipelineState(pipeline.handle);
if (pipeline.depth_stencil) {
cmd.encoder->setDepthStencilState(pipeline.depth_stencil);
}
cmd.encoder->setArgumentTable(cmd.table, MTL::RenderStageVertex | MTL::RenderStageFragment);
}
}
void set_pipeline(CmdBuffer &cmd, ComputePipeline &pipeline) {
(void)cmd;
(void)pipeline;
VEL_ERROR("set_pipeline(compute) is not implemented on Metal");
}
void destroy_pipeline(Device &device, RenderPipeline &p) {
(void)device;
p.const_blocks.clear();
if (p.depth_stencil) {
p.depth_stencil->release();
p.depth_stencil = nullptr;
}
if (p.handle) {
p.handle->release();
p.handle = nullptr;
}
}
void destroy_pipeline(Device &device, ComputePipeline &p) {
(void)device;
(void)p;
VEL_ERROR("destroy_pipeline(compute) is not implemented on Metal");
}
void set_vertex_buffer(CmdBuffer &cmd, Buffer &buffer, u32 binding, u64 offset) {
// binding b -> index 2+b. matches pipeline.
(void)offset;
if (!cmd.encoder || !buffer.handle || !cmd.table) {
return;
}
if (binding >= kMaxVertexBindings) {
VEL_ERROR("set_vertex_buffer: binding {} exceeds max {}", binding, kMaxVertexBindings);
return;
}
cmd.table->setAddress(buffer.handle->gpuAddress(), kVertexBaseIndex + binding);
}
void set_index_buffer(CmdBuffer &cmd, Buffer &buffer, u32 offset, IndexType type) {
cmd.index_buffer = buffer.handle;
cmd.index_type = type;
cmd.index_offset = offset;
}
void set_constants(CmdBuffer &cmd, RenderPipeline &pipeline, rhi::ShaderStage stage, u32 size, const void *data) {
(void)stage;
if (!cmd.encoder || !cmd.table || size == 0 || !data) {
return;
}
u32 table_index = 0;
if (!pipeline.const_blocks.empty()) {
u32 matches = 0;
for (auto &b : pipeline.const_blocks) {
if (b.byte_size == size) {
table_index = b.table_index;
++matches;
}
}
if (matches != 1) {
VEL_ERROR("set_constants: size {} matches {} constant blocks (need exactly 1)", size, matches);
return;
}
}
MTL::Buffer *tmp = mt::push_bump_alloc(cmd, data, size);
if (tmp) {
cmd.table->setAddress(tmp->gpuAddress(), table_index);
}
}
void draw(CmdBuffer &cmd, u32 vertexCount, u32 instanceCount, u32 firstVertex, u32 firstInstance) {
if (cmd.encoder) {
cmd.encoder->drawPrimitives(MTL::PrimitiveTypeTriangle, firstVertex, vertexCount, instanceCount, firstInstance);
}
}
void draw_indexed(CmdBuffer &cmd, u32 indexCount, u32 instanceCount, u32 firstIndex, u32 vtxOff, u32 firstInstance) {
if (!cmd.encoder) {
return;
}
if (!cmd.index_buffer) {
VEL_ERROR("draw_indexed: no index buffer bound (call set_index_buffer first)");
return;
}
MTL::IndexType type = (cmd.index_type == IndexType::UINT16) ? MTL::IndexTypeUInt16 : MTL::IndexTypeUInt32;
NS::UInteger index_size = (cmd.index_type == IndexType::UINT16) ? sizeof(u16) : sizeof(u32);
MTL::GPUAddress addr = cmd.index_buffer->gpuAddress();
addr += cmd.index_offset + (u64)firstIndex * index_size;
cmd.encoder->drawIndexedPrimitives(
MTL::PrimitiveTypeTriangle,
indexCount,
type,
addr,
(NS::UInteger)indexCount * index_size,
instanceCount,
(NS::Integer)vtxOff,
firstInstance
);
}
void set_viewports(CmdBuffer &cmd, Viewport *viewports, u32 viewportCount) {
if (!cmd.encoder || !viewports || viewportCount == 0) {
return;
}
const Viewport &vp = viewports[0];
MTL::Viewport mvp;
mvp.originX = vp.x;
mvp.originY = vp.y;
mvp.width = vp.width;
mvp.height = vp.height;
mvp.znear = vp.depth_min;
mvp.zfar = vp.depth_max;
cmd.encoder->setViewport(mvp);
}
void set_scissors(CmdBuffer &cmd, const Rect *rects, u32 rectCount) {
if (!cmd.encoder || !rects || rectCount == 0) {
return;
}
const Rect &r = rects[0];
MTL::ScissorRect sr;
sr.x = r.x > 0 ? (NS::UInteger)r.x : 0;
sr.y = r.y > 0 ? (NS::UInteger)r.y : 0;
sr.width = r.width > 0 ? (NS::UInteger)r.width : 0;
sr.height = r.height > 0 ? (NS::UInteger)r.height : 0;
cmd.encoder->setScissorRect(sr);
}
bool valid(const CmdBuffer &cmd) {
return cmd.handle != nullptr;
}
bool valid(const CmdPool &pool) {
return pool.handle != nullptr;
}
bool valid(const Swapchain &swapchain) {
return swapchain.layer != nullptr;
}
bool valid(const Sync *sync) {
return sync != nullptr && sync->timeline != nullptr;
}
bool valid(const Buffer &buffer) {
return buffer.handle != nullptr;
}
bool valid(const RenderPipeline &pipeline) {
return pipeline.handle != nullptr;
}
bool valid(const ComputePipeline &pipeline) {
return pipeline.handle != nullptr;
}
void set_constants(CmdBuffer &cmd, ComputePipeline &pipeline, rhi::ShaderStage stage, u32 size, const void *data) {
(void)cmd;
(void)pipeline;
(void)stage;
(void)size;
(void)data;
VEL_ERROR("set_constants(compute) is not implemented on Metal");
}
void barrier(CmdBuffer &cmd, ResourceState before, ResourceState after) {
(void)cmd;
(void)before;
(void)after;
}
void barrier(CmdBuffer &cmd, Buffer &buffer, ResourceState before, ResourceState after) {
(void)cmd;
(void)buffer;
(void)before;
(void)after;
}
void barrier(CmdBuffer &cmd, Image &image, const ImageRange &range, ResourceState before, ResourceState after) {
(void)cmd;
(void)image;
(void)range;
(void)before;
(void)after;
}
void blit_image(CmdBuffer &cmd, Image &image, const ImageBlit *regions, u32 region_count) {
(void)cmd;
(void)image;
(void)regions;
(void)region_count;
VEL_ERROR("blit_image is not implemented on Metal");
}
void copy_buffer_to_texture(
CmdBuffer &cmd, Buffer &srcBuffer, ImageView &dstView, const BufferTextureCopyRegion ®ion
) {
(void)cmd;
if (!srcBuffer.handle || !dstView.desc.image || !dstView.desc.image->handle) {
VEL_ERROR("copy_buffer_to_texture: null source buffer or destination image");
return;
}
if (region.mip_level != 0 || region.base_array_layer != 0 || region.layer_count != 1 ||
region.texture_offset_z != 0 || region.texture_extent_depth != 1) {
VEL_ERROR("copy_buffer_to_texture: only mip-0/layer-0/depth-1 copies are supported on Metal");
return;
}
MTL::Texture *tex = dstView.desc.image->handle;
u32 bytes_per_pixel = 0;
switch (tex->pixelFormat()) {
case MTL::PixelFormatR8Unorm:
bytes_per_pixel = 1;
break;
case MTL::PixelFormatRGBA8Unorm:
case MTL::PixelFormatRGBA8Unorm_sRGB:
case MTL::PixelFormatBGRA8Unorm:
case MTL::PixelFormatR32Float:
bytes_per_pixel = 4;
break;
case MTL::PixelFormatRGBA16Float:
bytes_per_pixel = 8;
break;
default:
break;
}
if (bytes_per_pixel == 0) {
VEL_ERROR("copy_buffer_to_texture: unsupported Metal pixel format for upload");
return;
}
const u8 *src = (const u8 *)srcBuffer.handle->contents();
if (!src) {
VEL_ERROR("copy_buffer_to_texture: staging buffer is not CPU-visible");
return;
}
src += region.buffer_offset;
u32 row_pixels = region.buffer_row_length != 0 ? region.buffer_row_length : region.texture_extent_width;
NS::UInteger src_bytes_per_row = (NS::UInteger)row_pixels * bytes_per_pixel;
MTL::Region dst = MTL::Region(
region.texture_offset_x, region.texture_offset_y, region.texture_extent_width, region.texture_extent_height
);
tex->replaceRegion(dst, 0, src, src_bytes_per_row);
}
} // namespace rhi