Archived
1
Fork 0
This repository has been archived on 2025-04-12. You can view files and clone it, but cannot push or open issues or pull requests.
prism/engine/gfx/metal/src/gfx_metal.cpp

1112 lines
44 KiB
C++
Executable file

#include "gfx_metal.hpp"
#include "gfx_metal_buffer.hpp"
#include "gfx_metal_pipeline.hpp"
#include "gfx_commandbuffer.hpp"
#include "gfx_metal_texture.hpp"
#include "gfx_metal_renderpass.hpp"
#include "gfx_metal_framebuffer.hpp"
#include "gfx_metal_sampler.hpp"
#include "file.hpp"
#include "log.hpp"
#include "utility.hpp"
#include "string_utils.hpp"
static inline bool debug_enabled = false;
static inline std::array<GFXCommandBuffer*, 15> command_buffers;
static inline std::array<bool, 15> free_command_buffers;
MTL::PixelFormat toPixelFormat(GFXPixelFormat format) {
switch(format) {
case GFXPixelFormat::R_32F:
return MTL::PixelFormatR32Float;
case GFXPixelFormat::R_16F:
return MTL::PixelFormatR16Float;
case GFXPixelFormat::RGBA_32F:
return MTL::PixelFormatRGBA32Float;
case GFXPixelFormat::RGBA8_UNORM:
return MTL::PixelFormatRGBA8Unorm;
case GFXPixelFormat::R8_UNORM:
return MTL::PixelFormatR8Unorm;
case GFXPixelFormat::R8G8_UNORM:
return MTL::PixelFormatRG8Unorm;
case GFXPixelFormat::R8G8_SFLOAT:
return MTL::PixelFormatRG16Float;
case GFXPixelFormat::R8G8B8A8_UNORM:
return MTL::PixelFormatRGBA8Unorm;
case GFXPixelFormat::R16G16B16A16_SFLOAT:
return MTL::PixelFormatRGBA16Float;
case GFXPixelFormat::DEPTH_32F:
return MTL::PixelFormatDepth32Float;
}
}
MTL::BlendFactor toBlendFactor(GFXBlendFactor factor) {
switch(factor) {
case GFXBlendFactor::One:
return MTL::BlendFactorOne;
case GFXBlendFactor::Zero:
return MTL::BlendFactorZero;
case GFXBlendFactor::SrcColor:
return MTL::BlendFactorSourceColor;
case GFXBlendFactor::DstColor:
return MTL::BlendFactorDestinationColor;
case GFXBlendFactor::SrcAlpha:
return MTL::BlendFactorSourceAlpha;
case GFXBlendFactor::DstAlpha:
return MTL::BlendFactorDestinationAlpha;
case GFXBlendFactor::OneMinusSrcAlpha:
return MTL::BlendFactorOneMinusSourceAlpha;
case GFXBlendFactor::OneMinusSrcColor:
return MTL::BlendFactorOneMinusSourceColor;
}
}
MTL::SamplerAddressMode toSamplingMode(SamplingMode mode) {
switch(mode) {
case SamplingMode::Repeat:
return MTL::SamplerAddressModeRepeat;
case SamplingMode::ClampToEdge:
return MTL::SamplerAddressModeClampToEdge;
case SamplingMode::ClampToBorder:
{
#if defined(PLATFORM_IOS) || defined(PLATFORM_TVOS)
return MTL::SamplerAddressModeRepeat;
#else
return MTL::SamplerAddressModeClampToBorderColor;
#endif
}
}
}
#if !defined(PLATFORM_IOS) && !defined(PLATFORM_TVOS)
MTL::SamplerBorderColor toBorderColor(GFXBorderColor color) {
switch(color) {
case GFXBorderColor::OpaqueWhite:
return MTL::SamplerBorderColorOpaqueWhite;
case GFXBorderColor::OpaqueBlack:
return MTL::SamplerBorderColorOpaqueBlack;
}
}
#endif
MTL::CompareFunction toCompare(GFXCompareFunction function) {
switch(function) {
case GFXCompareFunction::Never:
return MTL::CompareFunctionNever;
case GFXCompareFunction::Less:
return MTL::CompareFunctionLess;
case GFXCompareFunction::Equal:
return MTL::CompareFunctionEqual;
case GFXCompareFunction::LessOrEqual:
return MTL::CompareFunctionLessEqual;
case GFXCompareFunction::Greater:
return MTL::CompareFunctionGreater;
case GFXCompareFunction::NotEqual:
return MTL::CompareFunctionNotEqual;
case GFXCompareFunction::GreaterOrEqual:
return MTL::CompareFunctionGreaterEqual;
case GFXCompareFunction::Always:
return MTL::CompareFunctionAlways;
}
}
MTL::SamplerMinMagFilter toFilter(GFXFilter filter) {
switch(filter) {
case GFXFilter::Nearest:
return MTL::SamplerMinMagFilterNearest;
case GFXFilter::Linear:
return MTL::SamplerMinMagFilterLinear;
}
}
MTL::Winding toWinding(GFXWindingMode mode) {
switch(mode) {
case GFXWindingMode::Clockwise:
return MTL::WindingClockwise;
case GFXWindingMode::CounterClockwise:
return MTL::WindingCounterClockwise;
}
}
bool GFXMetal::is_supported() {
return true;
}
bool GFXMetal::initialize(const GFXCreateInfo& createInfo) {
debug_enabled = createInfo.api_validation_enabled;
device = MTL::CreateSystemDefaultDevice();
if(device) {
command_queue = device->newCommandQueue();
for(int i = 0; i < 15; i++) {
command_buffers[i] = new GFXCommandBuffer();
free_command_buffers[i] = true;
}
return true;
} else {
return false;
}
}
const char* GFXMetal::get_name() {
return "Metal";
}
bool GFXMetal::supports_feature(const GFXFeature feature) {
if(feature == GFXFeature::CubemapArray) {
#if defined(PLATFORM_TVOS)
return false;
#else
return true;
#endif
}
return false;
}
void GFXMetal::initialize_view(void* native_handle, const platform::window_ptr identifier, const uint32_t, const uint32_t) {
auto native = new NativeMTLView();
native->identifier = identifier;
native->format = (MTL::PixelFormat)platform::initialize_metal_layer(identifier, device);
nativeViews.push_back(native);
}
void GFXMetal::remove_view(const platform::window_ptr identifier) {
utility::erase_if(nativeViews, [identifier](NativeMTLView* view) {
return view->identifier == identifier;
});
}
GFXBuffer* GFXMetal::create_buffer(void* data, const GFXSize size, const bool dynamicData, const GFXBufferUsage) {
auto buffer = new GFXMetalBuffer();
buffer->dynamicData = dynamicData;
if(buffer->dynamicData) {
for(int i = 0; i < 3; i++) {
if(data == nullptr) {
buffer->handles[i] = device->newBuffer(size, MTL::ResourceOptionCPUCacheModeDefault);
} else {
buffer->handles[i] = device->newBuffer(data, size, MTL::ResourceOptionCPUCacheModeDefault);
}
}
} else {
if(data == nullptr) {
buffer->handles[0] = device->newBuffer(size, MTL::ResourceOptionCPUCacheModeDefault);
} else {
buffer->handles[0] = device->newBuffer(data, size, MTL::ResourceOptionCPUCacheModeDefault);
}
}
return buffer;
}
int currentFrameIndex = 0;
void GFXMetal::copy_buffer(GFXBuffer* buffer, void* data, const GFXSize offset, const GFXSize size) {
auto metalBuffer = (GFXMetalBuffer*)buffer;
auto src = reinterpret_cast<const unsigned char*>(data);
auto dest = reinterpret_cast<unsigned char *>(metalBuffer->get(currentFrameIndex)->contents());
if(dest != nullptr)
memcpy(dest + offset, src, size);
}
void* GFXMetal::get_buffer_contents(GFXBuffer* buffer) {
auto metalBuffer = (GFXMetalBuffer*)buffer;
return reinterpret_cast<unsigned char *>(metalBuffer->get(currentFrameIndex)->contents());
}
GFXTexture* GFXMetal::create_texture(const GFXTextureCreateInfo& info) {
auto texture = new GFXMetalTexture();
MTL::TextureDescriptor* textureDescriptor = MTL::TextureDescriptor::alloc()->init();
MTL::PixelFormat mtlFormat = toPixelFormat(info.format);
switch(info.type) {
case GFXTextureType::Single2D:
textureDescriptor->setTextureType(MTL::TextureType2D);
break;
case GFXTextureType::Array2D:
textureDescriptor->setTextureType(MTL::TextureType2DArray);
break;
case GFXTextureType::Cubemap:
{
textureDescriptor->setTextureType(MTL::TextureTypeCube);
texture->is_cubemap = true;
}
break;
case GFXTextureType::CubemapArray:
{
textureDescriptor->setTextureType(MTL::TextureTypeCubeArray);
texture->is_cubemap = true;
}
break;
}
if((info.usage & GFXTextureUsage::Attachment) == GFXTextureUsage::Attachment) {
textureDescriptor->setStorageMode(MTL::StorageModePrivate);
textureDescriptor->setUsage(textureDescriptor->usage() | MTL::TextureUsageRenderTarget);
} else {
#if defined(PLATFORM_IOS) || defined(PLATFORM_TVOS)
textureDescriptor->setStorageMode(MTL::StorageModeShared);
#else
if(info.format == GFXPixelFormat::DEPTH_32F) {
textureDescriptor->setStorageMode(MTL::StorageModePrivate);
} else {
textureDescriptor->setStorageMode(MTL::StorageModeManaged);
}
#endif
}
if((info.usage & GFXTextureUsage::Sampled) == GFXTextureUsage::Sampled) {
textureDescriptor->setUsage(textureDescriptor->usage() | MTL::TextureUsageShaderRead);
}
if((info.usage & GFXTextureUsage::ShaderWrite) == GFXTextureUsage::ShaderWrite) {
textureDescriptor->setUsage(textureDescriptor->usage() | MTL::TextureUsageShaderWrite);
}
textureDescriptor->setPixelFormat(mtlFormat);
textureDescriptor->setWidth(info.width);
textureDescriptor->setHeight(info.height);
textureDescriptor->setDepth(1);
textureDescriptor->setArrayLength(info.array_length);
texture->array_length = info.array_length;
textureDescriptor->setMipmapLevelCount(info.mip_count);
texture->format = mtlFormat;
texture->handle = device->newTexture(textureDescriptor);
texture->width = info.width;
texture->height = info.height;
MTL::SamplerDescriptor* samplerDescriptor = MTL::SamplerDescriptor::alloc()->init();
samplerDescriptor->setMinFilter(MTL::SamplerMinMagFilterLinear);
samplerDescriptor->setMagFilter(MTL::SamplerMinMagFilterLinear);
samplerDescriptor->setSAddressMode(toSamplingMode(info.samplingMode));
samplerDescriptor->setTAddressMode(toSamplingMode(info.samplingMode));
samplerDescriptor->setMipFilter(MTL::SamplerMipFilterLinear);
samplerDescriptor->setMaxAnisotropy(16);
#if !defined(PLATFORM_IOS) && !defined(PLATFORM_TVOS)
samplerDescriptor->setBorderColor(toBorderColor(info.border_color));
#endif
if(info.compare_enabled)
samplerDescriptor->setCompareFunction(toCompare(info.compare_function));
texture->sampler = device->newSamplerState(samplerDescriptor);
return texture;
}
void GFXMetal::copy_texture(GFXTexture* texture, void* data, const GFXSize) {
auto metalTexture = (GFXMetalTexture*)texture;
MTL::Region region = {};
region.size.width = texture->width;
region.size.height = texture->height;
region.size.depth = 1;
int byteSize = 1;
if(metalTexture->format == MTL::PixelFormatRGBA8Unorm)
byteSize = 4;
else if(metalTexture->format == MTL::PixelFormatRG8Unorm)
byteSize = 2;
metalTexture->handle->replaceRegion(region, 0, data, texture->width * byteSize);
}
void GFXMetal::copy_texture(GFXTexture* from, GFXTexture* to) {
auto metalFromTexture = (GFXMetalTexture*)from;
auto metalToTexture = (GFXMetalTexture*)to;
MTL::CommandBuffer* commandBuffer = command_queue->commandBuffer();
MTL::BlitCommandEncoder* commandEncoder = commandBuffer->blitCommandEncoder();
commandEncoder->copyFromTexture(metalFromTexture->handle, metalToTexture->handle);
commandEncoder->endEncoding();
commandBuffer->commit();
commandBuffer->waitUntilCompleted();
}
void GFXMetal::copy_texture(GFXTexture* from, GFXBuffer* to) {
auto metalFromTexture = (GFXMetalTexture*)from;
auto metalToBuffer = (GFXMetalBuffer*)to;
MTL::Origin origin = {};
origin.x = 0;
origin.y = 0;
origin.z = 0;
MTL::Size size = {};
size.width = from->width;
size.height = from->height;
size.depth = 1;
int byteSize = 1;
if(metalFromTexture->format == MTL::PixelFormatRGBA8Unorm)
byteSize = 4;
MTL::CommandBuffer* commandBuffer = command_queue->commandBuffer();
MTL::BlitCommandEncoder* commandEncoder = commandBuffer->blitCommandEncoder();
commandEncoder->copyFromTexture(metalFromTexture->handle, 0, 0, origin, size, metalToBuffer->get(currentFrameIndex), 0, metalFromTexture->width * byteSize, 0);
commandEncoder->endEncoding();
commandBuffer->commit();
commandBuffer->waitUntilCompleted();
}
GFXSampler* GFXMetal::create_sampler(const GFXSamplerCreateInfo& info) {
auto sampler = new GFXMetalSampler();
MTL::SamplerDescriptor* samplerDescriptor = MTL::SamplerDescriptor::alloc()->init();
samplerDescriptor->setMinFilter(toFilter(info.min_filter));
samplerDescriptor->setMagFilter(toFilter(info.mag_filter));
samplerDescriptor->setSAddressMode(toSamplingMode(info.samplingMode));
samplerDescriptor->setTAddressMode(toSamplingMode(info.samplingMode));
samplerDescriptor->setMipFilter(MTL::SamplerMipFilterLinear);
samplerDescriptor->setMaxAnisotropy(16);
#if !defined(PLATFORM_IOS) && !defined(PLATFORM_TVOS)
samplerDescriptor->setBorderColor(toBorderColor(info.border_color));
#endif
if(info.compare_enabled)
samplerDescriptor->setCompareFunction(toCompare(info.compare_function));
sampler->handle = device->newSamplerState(samplerDescriptor);
return sampler;
}
GFXFramebuffer* GFXMetal::create_framebuffer(const GFXFramebufferCreateInfo& info) {
auto framebuffer = new GFXMetalFramebuffer();
for(auto& attachment : info.attachments)
framebuffer->attachments.push_back((GFXMetalTexture*)attachment);
return framebuffer;
}
GFXRenderPass* GFXMetal::create_render_pass(const GFXRenderPassCreateInfo& info) {
auto renderPass = new GFXMetalRenderPass();
for(const auto& attachment : info.attachments)
renderPass->attachments.push_back(toPixelFormat(attachment));
return renderPass;
}
MTL::FunctionConstantValues* get_constant_values(GFXShaderConstants constants) {
MTL::FunctionConstantValues* constantValues = MTL::FunctionConstantValues::alloc()->init();
for(auto& constant : constants) {
switch(constant.type) {
case GFXShaderConstant::Type::Integer:
constantValues->setConstantValue(&constant.value, MTL::DataTypeInt, constant.index);
break;
}
}
return constantValues;
}
GFXPipeline* GFXMetal::create_graphics_pipeline(const GFXGraphicsPipelineCreateInfo& info) {
auto pipeline = new GFXMetalPipeline();
pipeline->label = info.label;
NS::Error* error = nullptr;
MTL::RenderPipelineDescriptor* pipelineDescriptor = MTL::RenderPipelineDescriptor::alloc()->init();
const bool has_vertex_stage = !info.shaders.vertex_src.empty();
const bool has_fragment_stage = !info.shaders.fragment_src.empty();
MTL::Function* vertexFunc;
if(has_vertex_stage) {
MTL::Library* vertexLibrary;
{
std::string vertex_src;
if(info.shaders.vertex_src.is_string()) {
vertex_src = info.shaders.vertex_src.as_string();
} else {
const auto vertex_path = info.shaders.vertex_src.as_path().string() + ".msl";
auto file = prism::open_file(prism::internal_domain / vertex_path);
if(file != std::nullopt) {
vertex_src = file->read_as_string();
} else {
prism::log("Failed to load vertex shader from {}!", vertex_path.data());
}
}
vertexLibrary = device->newLibrary(NS::String::string(vertex_src.c_str(), NS::ASCIIStringEncoding), nullptr,&error);
if(vertexLibrary == nullptr)
prism::log("Metal shader compiler error: {}", error->debugDescription()->cString(NS::ASCIIStringEncoding));
auto vertex_constants = get_constant_values(info.shaders.vertex_constants);
vertexFunc = vertexLibrary->newFunction(NS::String::string("main0", NS::ASCIIStringEncoding), vertex_constants, (NS::Error**)nullptr);
if(debug_enabled && info.shaders.vertex_src.is_path())
vertexFunc->setLabel(NS::String::string(info.shaders.vertex_src.as_path().string().data(), NS::ASCIIStringEncoding));
pipelineDescriptor->setVertexFunction(vertexFunc);
}
}
MTL::Function* fragmentFunc;
if(has_fragment_stage) {
MTL::Library* fragmentLibrary;
{
std::string fragment_src;
if(info.shaders.fragment_src.is_string()) {
fragment_src = info.shaders.fragment_src.as_string();
} else {
const auto fragment_path = info.shaders.fragment_src.as_path().string() + ".msl";
auto file = prism::open_file(prism::internal_domain / fragment_path);
if(file != std::nullopt) {
fragment_src = file->read_as_string();
} else {
prism::log("Failed to load fragment shader from {}!", fragment_path.data());
}
}
fragmentLibrary = device->newLibrary(NS::String::string(fragment_src.c_str(), NS::ASCIIStringEncoding), nullptr,&error);
if(fragmentLibrary == nullptr)
prism::log("Metal shader compiler error: {}", error->debugDescription()->cString(NS::ASCIIStringEncoding));
}
auto fragment_constants = get_constant_values(info.shaders.fragment_constants);
fragmentFunc = fragmentLibrary->newFunction(NS::String::string("main0", NS::ASCIIStringEncoding), fragment_constants, (NS::Error**)nullptr);
if(debug_enabled && info.shaders.fragment_src.is_path())
fragmentFunc->setLabel(NS::String::string(info.shaders.fragment_src.as_path().string().data(), NS::ASCIIStringEncoding));
pipelineDescriptor->setFragmentFunction(fragmentFunc);
}
MTL::VertexDescriptor* descriptor = MTL::VertexDescriptor::alloc()->init();
for(auto input : info.vertex_input.inputs) {
MTL::VertexBufferLayoutDescriptor* inputDescriptor = descriptor->layouts()->object(input.location);
inputDescriptor->setStride(input.stride);
inputDescriptor->setStepFunction(MTL::VertexStepFunctionPerVertex);
inputDescriptor->setStepRate(1);
GFXMetalPipeline::VertexStride vs = {};
vs.location = input.location;
vs.stride = input.stride;
pipeline->vertexStrides.push_back(vs);
}
for(auto attribute : info.vertex_input.attributes) {
MTL::VertexFormat format = MTL::VertexFormatFloat3;
switch(attribute.format) {
case GFXVertexFormat::FLOAT2:
format = MTL::VertexFormatFloat2;
break;
case GFXVertexFormat::FLOAT3:
format = MTL::VertexFormatFloat3;
break;
case GFXVertexFormat::FLOAT4:
format = MTL::VertexFormatFloat4;
break;
case GFXVertexFormat::INT:
format = MTL::VertexFormatInt;
break;
case GFXVertexFormat::UNORM4:
format = MTL::VertexFormatUChar4Normalized;
break;
case GFXVertexFormat::INT4:
format = MTL::VertexFormatInt4;
break;
}
MTL::VertexAttributeDescriptor* attributeDescriptor = descriptor->attributes()->object(attribute.location);
attributeDescriptor->setFormat(format);
attributeDescriptor->setBufferIndex(attribute.binding);
attributeDescriptor->setOffset(attribute.offset);
}
pipelineDescriptor->setVertexDescriptor(descriptor);
if(info.render_pass != nullptr) {
auto metalRenderPass = (GFXMetalRenderPass*)info.render_pass;
unsigned int i = 0;
for(const auto& attachment : metalRenderPass->attachments) {
if(attachment != MTL::PixelFormatDepth32Float) {
MTL::RenderPipelineColorAttachmentDescriptor* colorAttachmentDescriptor = pipelineDescriptor->colorAttachments()->object(i++);
colorAttachmentDescriptor->setPixelFormat(attachment);
colorAttachmentDescriptor->setBlendingEnabled(info.blending.enable_blending);
colorAttachmentDescriptor->setSourceRGBBlendFactor(toBlendFactor(info.blending.src_rgb));
colorAttachmentDescriptor->setDestinationRGBBlendFactor(toBlendFactor(info.blending.dst_rgb));
colorAttachmentDescriptor->setSourceAlphaBlendFactor(toBlendFactor(info.blending.src_alpha));
colorAttachmentDescriptor->setDestinationAlphaBlendFactor(toBlendFactor(info.blending.dst_alpha));
} else {
pipelineDescriptor->setDepthAttachmentPixelFormat(MTL::PixelFormatDepth32Float);
}
}
} else {
MTL::RenderPipelineColorAttachmentDescriptor* colorAttachmentDescriptor = pipelineDescriptor->colorAttachments()->object(0);
colorAttachmentDescriptor->setPixelFormat(nativeViews[0]->format);
colorAttachmentDescriptor->setBlendingEnabled(info.blending.enable_blending);
colorAttachmentDescriptor->setSourceRGBBlendFactor(toBlendFactor(info.blending.src_rgb));
colorAttachmentDescriptor->setDestinationRGBBlendFactor(toBlendFactor(info.blending.dst_rgb));
colorAttachmentDescriptor->setSourceAlphaBlendFactor(toBlendFactor(info.blending.src_alpha));
colorAttachmentDescriptor->setDestinationAlphaBlendFactor(toBlendFactor(info.blending.dst_alpha));
}
if(debug_enabled) {
pipelineDescriptor->setLabel(NS::String::string(info.label.data(), NS::ASCIIStringEncoding));
pipeline->label = info.label;
}
pipeline->handle = device->newRenderPipelineState(pipelineDescriptor, &error);
if(!pipeline->handle)
prism::log("Metal render pipeline creation error: {}", error->debugDescription()->cString(NS::ASCIIStringEncoding));
switch(info.rasterization.primitive_type) {
case GFXPrimitiveType::Triangle:
pipeline->primitiveType = MTL::PrimitiveTypeTriangle;
break;
case GFXPrimitiveType::TriangleStrip:
pipeline->primitiveType = MTL::PrimitiveTypeTriangleStrip;
break;
}
pipeline->winding_mode = info.rasterization.winding_mode;
MTL::DepthStencilDescriptor* depthStencil = MTL::DepthStencilDescriptor::alloc()->init();
if(info.depth.depth_mode != GFXDepthMode::None) {
switch(info.depth.depth_mode) {
case GFXDepthMode::Less:
depthStencil->setDepthCompareFunction(MTL::CompareFunctionLess);
break;
case GFXDepthMode::LessOrEqual:
depthStencil->setDepthCompareFunction(MTL::CompareFunctionLessEqual);
break;
case GFXDepthMode::Greater:
depthStencil->setDepthCompareFunction(MTL::CompareFunctionGreater);
break;
}
depthStencil->setDepthWriteEnabled(true);
}
pipeline->depthStencil = device->newDepthStencilState(depthStencil);
switch(info.rasterization.culling_mode) {
case GFXCullingMode::Frontface:
pipeline->cullMode = MTL::CullModeFront;
break;
case GFXCullingMode::Backface:
pipeline->cullMode = MTL::CullModeBack;
break;
case GFXCullingMode::None:
pipeline->cullMode = MTL::CullModeNone;
break;
}
if(info.rasterization.polygon_type == GFXPolygonType::Line)
pipeline->renderWire = true;
return pipeline;
}
GFXPipeline* GFXMetal::create_compute_pipeline(const GFXComputePipelineCreateInfo& info) {
auto pipeline = new GFXMetalPipeline();
NS::Error* error = nullptr;
MTL::Library* computeLibrary;
{
std::string compute_src;
if(info.compute_src.is_string()) {
compute_src = info.compute_src.as_string();
} else {
const auto compute_path = info.compute_src.as_path().string() + ".msl";
auto file = prism::open_file(prism::internal_domain / compute_path);
if(file != std::nullopt) {
compute_src = file->read_as_string();
} else {
prism::log("Failed to load compute shader from {}!", compute_path.data());
}
}
computeLibrary = device->newLibrary(NS::String::string(compute_src.c_str(), NS::ASCIIStringEncoding), nullptr, &error);
if(!computeLibrary)
prism::log("Compute library compilation error: {}", error->debugDescription()->cString(NS::ASCIIStringEncoding));
}
MTL::Function* computeFunc = computeLibrary->newFunction(NS::String::string("main0", NS::ASCIIStringEncoding));
MTL::ComputePipelineDescriptor* pipelineDescriptor = MTL::ComputePipelineDescriptor::alloc()->init();
pipelineDescriptor->setComputeFunction(computeFunc);
pipeline->threadGroupSize = MTL::Size(info.workgroup_size_x, info.workgroup_size_y, info.workgroup_size_z);
if(debug_enabled) {
pipelineDescriptor->setLabel(NS::String::string(info.label.c_str(), NS::ASCIIStringEncoding));
pipeline->label = info.label;
}
pipeline->compute_handle = device->newComputePipelineState(pipelineDescriptor, MTL::PipelineOptionNone, nullptr, &error);
if(!pipeline->compute_handle)
prism::log("Compute pipeline error: {}", error->debugDescription()->cString(NS::ASCIIStringEncoding));
computeLibrary->release();
return pipeline;
}
GFXSize GFXMetal::get_alignment(GFXSize size) {
#ifdef PLATFORM_MACOS
return (size + 32 / 2) & ~int(32 - 1);
#else
return size;
#endif
}
GFXCommandBuffer* GFXMetal::acquire_command_buffer(bool for_presentation_use) {
GFXCommandBuffer* cmdbuf = nullptr;
while(cmdbuf == nullptr) {
for(const auto [i, buffer_status] : utility::enumerate(free_command_buffers)) {
if(buffer_status) {
GFXCommandBuffer* buffer = command_buffers[i];
free_command_buffers[i] = false;
buffer->commands.clear();
return buffer;
}
}
}
return cmdbuf;
}
void GFXMetal::submit(GFXCommandBuffer* command_buffer, const platform::window_ptr window) {
NS::AutoreleasePool* pPool = NS::AutoreleasePool::alloc()->init();
NativeMTLView* native = getNativeView(window);
CA::MetalDrawable* drawable = nullptr;
if(native != nullptr)
drawable = ((CA::MetalDrawable*)platform::get_next_drawable(window));
MTL::CommandBuffer* commandBuffer = command_queue->commandBuffer();
MTL::RenderCommandEncoder* renderEncoder = nullptr;
MTL::ComputeCommandEncoder* computeEncoder = nullptr;
MTL::BlitCommandEncoder* blitEncoder = nullptr;
GFXMetalRenderPass* currentRenderPass = nullptr;
GFXMetalFramebuffer* currentFramebuffer = nullptr;
GFXMetalPipeline* currentPipeline = nullptr;
GFXMetalBuffer* currentIndexBuffer = nullptr;
IndexType currentIndextype = IndexType::UINT32;
MTL::Viewport currentViewport = MTL::Viewport();
MTL::ClearColor currentClearColor = {};
enum class CurrentEncoder {
None,
Render,
Compute,
Blit
} current_encoder = CurrentEncoder::None;
const auto needEncoder = [&](CurrentEncoder encoder, bool needs_reset = false) {
if(encoder != current_encoder || needs_reset) {
if(renderEncoder != nullptr)
renderEncoder->endEncoding();
if(computeEncoder != nullptr)
computeEncoder->endEncoding();
if(blitEncoder != nullptr)
blitEncoder->endEncoding();
renderEncoder = nullptr;
computeEncoder = nullptr;
blitEncoder = nullptr;
}
if(current_encoder == encoder && !needs_reset)
return;
switch(encoder) {
case CurrentEncoder::None:
break;
case CurrentEncoder::Render:
{
MTL::RenderPassDescriptor* descriptor = MTL::RenderPassDescriptor::alloc()->init();
if(currentRenderPass != nullptr && currentFramebuffer != nullptr) {
unsigned int i = 0;
for(const auto& attachment : currentFramebuffer->attachments) {
if(attachment->format == MTL::PixelFormatDepth32Float) {
MTL::RenderPassDepthAttachmentDescriptor* depthAttachment = descriptor->depthAttachment();
depthAttachment->setTexture(attachment->handle);
depthAttachment->setLoadAction(MTL::LoadActionClear);
depthAttachment->setStoreAction(MTL::StoreActionStore);
} else {
MTL::RenderPassColorAttachmentDescriptor* colorAttachment = descriptor->colorAttachments()->object(i);
colorAttachment->setTexture(attachment->handle);
colorAttachment->setLoadAction(MTL::LoadActionClear);
colorAttachment->setStoreAction(MTL::StoreActionStore);
colorAttachment->setClearColor(currentClearColor);
}
}
} else {
MTL::RenderPassColorAttachmentDescriptor* colorAttachment = descriptor->colorAttachments()->object(0);
colorAttachment->setTexture(drawable->texture());
colorAttachment->setLoadAction(MTL::LoadActionClear);
colorAttachment->setStoreAction(MTL::StoreActionStore);
colorAttachment->setClearColor(currentClearColor);
}
renderEncoder = commandBuffer->renderCommandEncoder(descriptor);
if(currentViewport.width != 0.0f && currentViewport.height != 0.0f)
renderEncoder->setViewport(currentViewport);
descriptor->release();
}
break;
case CurrentEncoder::Compute:
{
computeEncoder = commandBuffer->computeCommandEncoder();
}
break;
case CurrentEncoder::Blit:
{
blitEncoder = commandBuffer->blitCommandEncoder();
}
break;
}
current_encoder = encoder;
};
for(auto command : command_buffer->commands) {
switch(command.type) {
case GFXCommandType::Invalid:
break;
case GFXCommandType::SetRenderPass:
{
currentClearColor = MTL::ClearColor(command.data.set_render_pass.clear_color.r,
command.data.set_render_pass.clear_color.g,
command.data.set_render_pass.clear_color.b,
command.data.set_render_pass.clear_color.a );
currentFramebuffer = (GFXMetalFramebuffer*)command.data.set_render_pass.framebuffer;
currentRenderPass = (GFXMetalRenderPass*)command.data.set_render_pass.render_pass;
currentViewport = MTL::Viewport();
needEncoder(CurrentEncoder::Render, true);
}
break;
case GFXCommandType::SetGraphicsPipeline:
{
needEncoder(CurrentEncoder::Render);
currentPipeline = (GFXMetalPipeline*)command.data.set_graphics_pipeline.pipeline;
renderEncoder->setRenderPipelineState(((GFXMetalPipeline*)command.data.set_graphics_pipeline.pipeline)->handle);
renderEncoder->setDepthStencilState(currentPipeline->depthStencil);
renderEncoder->setCullMode(((GFXMetalPipeline*)command.data.set_graphics_pipeline.pipeline)->cullMode);
renderEncoder->setFrontFacingWinding(toWinding(((GFXMetalPipeline*)command.data.set_graphics_pipeline.pipeline)->winding_mode));
if(currentPipeline->renderWire)
renderEncoder->setTriangleFillMode(MTL::TriangleFillModeLines);
else
renderEncoder->setTriangleFillMode(MTL::TriangleFillModeFill);
}
break;
case GFXCommandType::SetComputePipeline:
{
needEncoder(CurrentEncoder::Compute);
currentPipeline = (GFXMetalPipeline*)command.data.set_compute_pipeline.pipeline;
computeEncoder->setComputePipelineState(((GFXMetalPipeline*)command.data.set_compute_pipeline.pipeline)->compute_handle);
}
break;
case GFXCommandType::SetVertexBuffer:
{
needEncoder(CurrentEncoder::Render);
renderEncoder->setVertexBuffer(((GFXMetalBuffer*)command.data.set_vertex_buffer.buffer)->get(currentFrameIndex), command.data.set_vertex_buffer.offset, command.data.set_vertex_buffer.index);
}
break;
case GFXCommandType::SetIndexBuffer:
{
currentIndexBuffer = (GFXMetalBuffer*)command.data.set_index_buffer.buffer;
currentIndextype = command.data.set_index_buffer.index_type;
}
break;
case GFXCommandType::SetPushConstant:
{
if(currentPipeline == nullptr)
continue;
if(current_encoder == CurrentEncoder::Render) {
renderEncoder->setVertexBytes(command.data.set_push_constant.bytes.data(), command.data.set_push_constant.size, currentPipeline->pushConstantIndex);
renderEncoder->setFragmentBytes(command.data.set_push_constant.bytes.data(), command.data.set_push_constant.size, currentPipeline->pushConstantIndex);
} else if(current_encoder == CurrentEncoder::Compute) {
computeEncoder->setBytes(command.data.set_push_constant.bytes.data(), command.data.set_push_constant.size, currentPipeline->pushConstantIndex);
}
}
break;
case GFXCommandType::BindShaderBuffer:
{
if(current_encoder == CurrentEncoder::Render) {
renderEncoder->setVertexBuffer(((GFXMetalBuffer*)command.data.bind_shader_buffer.buffer)->get(currentFrameIndex), command.data.bind_shader_buffer.offset, command.data.bind_shader_buffer.index);
renderEncoder->setFragmentBuffer(((GFXMetalBuffer*)command.data.bind_shader_buffer.buffer)->get(currentFrameIndex), command.data.bind_shader_buffer.offset, command.data.bind_shader_buffer.index);
} else if(current_encoder == CurrentEncoder::Compute) {
computeEncoder->setBuffer(((GFXMetalBuffer*)command.data.bind_shader_buffer.buffer)->get(currentFrameIndex), command.data.bind_shader_buffer.offset, command.data.bind_shader_buffer.index);
}
}
break;
case GFXCommandType::BindTexture:
{
if(current_encoder == CurrentEncoder::Render) {
if(command.data.bind_texture.texture != nullptr) {
renderEncoder->setVertexSamplerState(((GFXMetalTexture*)command.data.bind_texture.texture)->sampler, command.data.bind_texture.index);
renderEncoder->setVertexTexture(((GFXMetalTexture*)command.data.bind_texture.texture)->handle, command.data.bind_texture.index);
renderEncoder->setFragmentSamplerState(((GFXMetalTexture*)command.data.bind_texture.texture)->sampler, command.data.bind_texture.index);
renderEncoder->setFragmentTexture(((GFXMetalTexture*)command.data.bind_texture.texture)->handle, command.data.bind_texture.index);
} else {
renderEncoder->setVertexTexture(nullptr, command.data.bind_texture.index);
renderEncoder->setFragmentTexture(nullptr, command.data.bind_texture.index);
}
} else if(current_encoder == CurrentEncoder::Compute) {
computeEncoder->setTexture(((GFXMetalTexture*)command.data.bind_texture.texture)->handle, command.data.bind_texture.index);
}
}
break;
case GFXCommandType::BindSampler:
{
needEncoder(CurrentEncoder::Render);
if(command.data.bind_sampler.sampler != nullptr) {
renderEncoder->setFragmentSamplerState(((GFXMetalSampler*)command.data.bind_sampler.sampler)->handle, command.data.bind_sampler.index);
} else {
renderEncoder->setFragmentSamplerState(nullptr, command.data.bind_sampler.index);
}
}
break;
case GFXCommandType::Draw:
{
needEncoder(CurrentEncoder::Render);
if(currentPipeline == nullptr)
continue;
renderEncoder->drawPrimitives(currentPipeline->primitiveType,
command.data.draw.vertex_offset,
command.data.draw.vertex_count,
command.data.draw.instance_count,
command.data.draw.base_instance);
}
break;
case GFXCommandType::DrawIndexed:
{
needEncoder(CurrentEncoder::Render);
if(currentIndexBuffer == nullptr)
continue;
if(currentPipeline == nullptr)
continue;
MTL::IndexType indexType;
int indexSize;
switch(currentIndextype) {
case IndexType::UINT16:
{
indexType = MTL::IndexTypeUInt16;
indexSize = sizeof(uint16_t);
}
break;
case IndexType::UINT32:
{
indexType = MTL::IndexTypeUInt32;
indexSize = sizeof(uint32_t);
}
break;
}
for(auto& stride : currentPipeline->vertexStrides)
renderEncoder->setVertexBufferOffset(command.data.draw_indexed.vertex_offset * stride.stride, stride.location);
renderEncoder->drawIndexedPrimitives(currentPipeline->primitiveType,
command.data.draw_indexed.index_count,
indexType,
currentIndexBuffer->get(currentFrameIndex),
command.data.draw_indexed.first_index * indexSize);
}
break;
case GFXCommandType::MemoryBarrier:
{
needEncoder(CurrentEncoder::Render);
#ifdef PLATFORM_MACOS
renderEncoder->memoryBarrier(MTL::BarrierScopeTextures, MTL::RenderStageFragment, MTL::RenderStageFragment);
#endif
}
break;
case GFXCommandType::CopyTexture:
{
needEncoder(CurrentEncoder::Blit);
auto metalFromTexture = (GFXMetalTexture*)command.data.copy_texture.src;
auto metalToTexture = (GFXMetalTexture*)command.data.copy_texture.dst;
if(metalFromTexture != nullptr && metalToTexture != nullptr) {
const int slice_offset = command.data.copy_texture.to_slice + command.data.copy_texture.to_layer * 6;
blitEncoder->copyFromTexture(metalFromTexture->handle,
0,
0,
MTL::Origin(0, 0, 0),
MTL::Size(command.data.copy_texture.width, command.data.copy_texture.height, 1),
metalToTexture->handle,
slice_offset,
command.data.copy_texture.to_level,
MTL::Origin(0, 0, 0));
}
}
break;
case GFXCommandType::SetViewport:
{
MTL::Viewport viewport = {};
viewport.originX = command.data.set_viewport.viewport.x;
viewport.originY = command.data.set_viewport.viewport.y;
viewport.width = command.data.set_viewport.viewport.width;
viewport.height = command.data.set_viewport.viewport.height;
viewport.znear = command.data.set_viewport.viewport.min_depth;
viewport.zfar = command.data.set_viewport.viewport.max_depth;
if(renderEncoder != nullptr)
renderEncoder->setViewport(viewport);
currentViewport = viewport;
}
break;
case GFXCommandType::SetScissor:
{
needEncoder(CurrentEncoder::Render);
MTL::ScissorRect rect = {};
rect.x = command.data.set_scissor.rect.offset.x;
rect.y = command.data.set_scissor.rect.offset.y;
rect.width = command.data.set_scissor.rect.extent.width;
rect.height = command.data.set_scissor.rect.extent.height;
renderEncoder->setScissorRect(rect);
}
break;
case GFXCommandType::GenerateMipmaps: {
needEncoder(CurrentEncoder::Blit);
auto metalTexture = (GFXMetalTexture*)command.data.generate_mipmaps.texture;
blitEncoder->generateMipmaps(metalTexture->handle);
}
break;
case GFXCommandType::SetDepthBias: {
needEncoder(CurrentEncoder::Render);
renderEncoder->setDepthBias(command.data.set_depth_bias.constant, command.data.set_depth_bias.slope_factor, command.data.set_depth_bias.clamp);
}
break;
case GFXCommandType::PushGroup: {
commandBuffer->pushDebugGroup(NS::String::string(command.data.push_group.name.data(), NS::ASCIIStringEncoding));
}
break;
case GFXCommandType::PopGroup: {
commandBuffer->popDebugGroup();
}
break;
case GFXCommandType::InsertLabel: {
switch(current_encoder) {
case CurrentEncoder::Render:
renderEncoder->insertDebugSignpost(NS::String::string(command.data.insert_label.name.data(), NS::ASCIIStringEncoding));
break;
case CurrentEncoder::Blit:
blitEncoder->insertDebugSignpost(NS::String::string(command.data.insert_label.name.data(), NS::ASCIIStringEncoding));
break;
case CurrentEncoder::Compute:
computeEncoder->insertDebugSignpost(NS::String::string(command.data.insert_label.name.data(), NS::ASCIIStringEncoding));
break;
default:
break;
}
}
break;
case GFXCommandType::Dispatch: {
needEncoder(CurrentEncoder::Compute);
computeEncoder->dispatchThreadgroups(MTL::Size(command.data.dispatch.group_count_x, command.data.dispatch.group_count_y, command.data.dispatch.group_count_z), currentPipeline->threadGroupSize);
}
break;
}
}
if(renderEncoder != nullptr)
renderEncoder->endEncoding();
if(blitEncoder != nullptr)
blitEncoder->endEncoding();
if(computeEncoder != nullptr)
computeEncoder->endEncoding();
commandBuffer->addCompletedHandler([command_buffer](MTL::CommandBuffer* _) {
for(auto [i, buffer] : utility::enumerate(command_buffers)) {
if(buffer == command_buffer)
free_command_buffers[i] = true;
}
});
if(window != nullptr) {
commandBuffer->presentDrawable(drawable);
commandBuffer->commit();
currentFrameIndex = (currentFrameIndex + 1) % 3;
} else {
commandBuffer->commit();
}
pPool->release();
}