#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 command_buffers; static inline std::array 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) { NativeMTLView* native = new NativeMTLView(); native->identifier = identifier; //native->layer = (CAMetalLayer*)native_handle; //native->layer.device = device; //native->layer.allowsNextDrawableTimeout = true; 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) { GFXMetalBuffer* 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) { GFXMetalBuffer* metalBuffer = (GFXMetalBuffer*)buffer; const unsigned char * src = reinterpret_cast(data); unsigned char * dest = reinterpret_cast(metalBuffer->get(currentFrameIndex)->contents()); if(dest != nullptr) memcpy(dest + offset, src, size); } void* GFXMetal::get_buffer_contents(GFXBuffer* buffer) { GFXMetalBuffer* metalBuffer = (GFXMetalBuffer*)buffer; return reinterpret_cast(metalBuffer->get(currentFrameIndex)->contents()); } GFXTexture* GFXMetal::create_texture(const GFXTextureCreateInfo& info) { GFXMetalTexture* texture = new GFXMetalTexture(); MTL::TextureDescriptor* textureDescriptor = MTL::TextureDescriptor::alloc(); 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 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->setWidth(info.height); 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(); 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) { GFXMetalTexture* 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) { GFXMetalTexture* metalFromTexture = (GFXMetalTexture*)from; GFXMetalTexture* 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) { GFXMetalTexture* metalFromTexture = (GFXMetalTexture*)from; GFXMetalBuffer* 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) { GFXMetalSampler* sampler = new GFXMetalSampler(); MTL::SamplerDescriptor* samplerDescriptor = MTL::SamplerDescriptor::alloc(); 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) { GFXMetalFramebuffer* framebuffer = new GFXMetalFramebuffer(); for(auto& attachment : info.attachments) framebuffer->attachments.push_back((GFXMetalTexture*)attachment); return framebuffer; } GFXRenderPass* GFXMetal::create_render_pass(const GFXRenderPassCreateInfo& info) { GFXMetalRenderPass* 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(); 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) { GFXMetalPipeline* pipeline = new GFXMetalPipeline(); NS::Error* error = nil; MTL::RenderPipelineDescriptor* pipelineDescriptor = MTL::RenderPipelineDescriptor::alloc(); const bool has_vertex_stage = !info.shaders.vertex_src.empty(); const bool has_fragment_stage = !info.shaders.fragment_src.empty(); 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(); 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), &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); MTL::Function* 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); } } 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(); 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), &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); MTL::Function* 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(); int i = 0; for(auto input : info.vertex_input.inputs) { MTL::VertexBufferLayoutDescriptor* inputDescriptor = MTL::VertexBufferLayoutDescriptor::alloc(); inputDescriptor->setStride(input.stride); inputDescriptor->setStepFunction(MTL::VertexStepFunctionPerVertex); descriptor->layouts()->setObject(inputDescriptor, i); GFXMetalPipeline::VertexStride vs; vs.location = input.location; vs.stride = input.stride; pipeline->vertexStrides.push_back(vs); i++; } i = 0; 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 = MTL::VertexAttributeDescriptor::alloc(); attributeDescriptor->setFormat(format); attributeDescriptor->setBufferIndex(attribute.binding); attributeDescriptor->setOffset(attribute.offset); descriptor->attributes()->setObject(attributeDescriptor, i); i++; } if(info.render_pass != nullptr) { GFXMetalRenderPass* metalRenderPass = (GFXMetalRenderPass*)info.render_pass; unsigned int i = 0; for(const auto& attachment : metalRenderPass->attachments) { if(attachment != MTL::PixelFormatDepth32Float) { MTL::RenderPipelineColorAttachmentDescriptor* colorAttachmentDescriptor = MTL::RenderPipelineColorAttachmentDescriptor::alloc(); 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)); pipelineDescriptor->colorAttachments()->setObject(colorAttachmentDescriptor, i++); } else { pipelineDescriptor->setDepthAttachmentPixelFormat(MTL::PixelFormatDepth32Float); } } } else { MTL::RenderPipelineColorAttachmentDescriptor* colorAttachmentDescriptor = MTL::RenderPipelineColorAttachmentDescriptor::alloc(); //colorAttachmentDescriptor->setPixelFormat(attachment); [nativeViews[0]->layer pixelFormat]; 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)); pipelineDescriptor->colorAttachments()->setObject(colorAttachmentDescriptor, 0); } pipelineDescriptor->setVertexDescriptor(descriptor); 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; } for(auto& binding : info.shader_input.bindings) { if(binding.type == GFXBindingType::PushConstant) pipeline->pushConstantIndex = binding.binding; } pipeline->winding_mode = info.rasterization.winding_mode; MTL::DepthStencilDescriptor* depthStencil = MTL::DepthStencilDescriptor::alloc(); 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) { GFXMetalPipeline* pipeline = new GFXMetalPipeline(); NS::Error* error = nullptr; // vertex 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(); 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), &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(); 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->handle) prism::log("Compute pipeline error: {}", error->debugDescription()->cString(NS::ASCIIStringEncoding)); for(auto& binding : info.shader_input.bindings) { if(binding.type == GFXBindingType::PushConstant) pipeline->pushConstantIndex = binding.binding; } computeLibrary->release(); return pipeline; } 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) { NativeMTLView* native = getNativeView(window); //id drawable = nil; //if(native != nullptr) // drawable = [native->layer nextDrawable]; 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 != nil) renderEncoder->endEncoding(); if(computeEncoder != nil) computeEncoder->endEncoding(); if(blitEncoder != nil) blitEncoder->endEncoding(); renderEncoder = nil; computeEncoder = nil; blitEncoder = nil; } if(current_encoder == encoder && !needs_reset) return; switch(encoder) { case CurrentEncoder::None: break; case CurrentEncoder::Render: { MTL::RenderPassDescriptor* descriptor = MTL::RenderPassDescriptor::alloc(); if(currentRenderPass != nullptr && currentFramebuffer != nullptr) { unsigned int i = 0; for(const auto& attachment : currentFramebuffer->attachments) { if(attachment->format == MTL::PixelFormatDepth32Float) { MTL::RenderPassDepthAttachmentDescriptor* depthAttachment = MTL::RenderPassDepthAttachmentDescriptor::alloc(); depthAttachment->setTexture(attachment->handle); depthAttachment->setLoadAction(MTL::LoadActionClear); depthAttachment->setStoreAction(MTL::StoreActionStore); descriptor->setDepthAttachment(depthAttachment); } else { MTL::RenderPassColorAttachmentDescriptor* colorAttachment = MTL::RenderPassColorAttachmentDescriptor::alloc(); colorAttachment->setTexture(attachment->handle); colorAttachment->setLoadAction(MTL::LoadActionClear); colorAttachment->setStoreAction(MTL::StoreActionStore); colorAttachment->setClearColor(currentClearColor); descriptor->colorAttachments()->setObject(colorAttachment, i++); } } renderEncoder = commandBuffer->renderCommandEncoder(descriptor); } else { MTL::RenderPassColorAttachmentDescriptor* colorAttachment = MTL::RenderPassColorAttachmentDescriptor::alloc(); //colorAttachment->setTexture(attachment->handle); drawable.texture colorAttachment->setLoadAction(MTL::LoadActionClear); colorAttachment->setStoreAction(MTL::StoreActionStore); colorAttachment->setClearColor(currentClearColor); descriptor->colorAttachments()->setObject(colorAttachment, 0); } 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); renderEncoder->setRenderPipelineState(((GFXMetalPipeline*)command.data.set_graphics_pipeline.pipeline)->handle); currentPipeline = (GFXMetalPipeline*)command.data.set_graphics_pipeline.pipeline; 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_offset, 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); GFXMetalTexture* metalFromTexture = (GFXMetalTexture*)command.data.copy_texture.src; GFXMetalTexture* 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 != nil) 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); GFXMetalTexture* 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; 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 != nil) renderEncoder->endEncoding(); if(blitEncoder != nil) blitEncoder->endEncoding(); /*[commandBuffer addCompletedHandler:^(id _Nonnull) { 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]; }*/ if(window != nullptr) { } else { commandBuffer->commit(); } }