#include "Metal/Metal.hpp" #include "math3d.h" #include "filesystem.h" #include "rendering.h" #include "tier0/lib.h" #include "tier1/utlvector.h" #include "tier0/platform.h" #include "rendering.h" #include "ml_video.h" #define STB_IMAGE_IMPLEMENTATION #if defined(__APPLE__) && defined(__MACH__) #include "TargetConditionals.h" #if TARGET_OS_IPHONE // iOS #define STBI_NO_THREAD_LOCALS #else // macOS #endif #else // Other platforms #endif #include "stb_image.h" mat4 g_cameraView; IImage *g_meshDepth; IImage *g_meshDepthMSAA; IImage *g_meshColor; IImage *g_meshColorMSAA; CUtlVector g_textures; IBuffer *g_cameraProperties; struct CameraProjection { mat4 viewprojection; }; CameraProjection *g_cameraDataMap; static MTL::RenderPassDescriptor *s_pRenderPass; static MTL::RenderCommandEncoder *s_pEncoder; static uint32_t s_nNumAttachments; CUtlVector g_destroyImageBuffer; CUtlVector g_destroyBuffersBuffer; extern MTL::PixelFormat g_swapchainFormat; MTL::PixelFormat IRenderer_FormatToMl( EImageFormat format ) { switch (format) { case IMAGE_FORMAT_R8G8B8A8: return MTL::PixelFormatRGBA8Unorm; case IMAGE_FORMAT_R16G16B16A16: return MTL::PixelFormatRGBA16Unorm; case IMAGE_FORMAT_DEPTH: return MTL::PixelFormatDepth32Float; case IMAGE_FORMAT_WINDOW: return g_swapchainFormat; default: return MTL::PixelFormatRGBA8Unorm; } }; MTL::VertexFormat IRenderer_VertexToMl( EVertexFormat format ) { switch (format) { case VERTEX_FORMAT_X16: return MTL::VertexFormatHalf; case VERTEX_FORMAT_X16Y16: return MTL::VertexFormatHalf2; case VERTEX_FORMAT_X16Y16Z16: return MTL::VertexFormatHalf3; case VERTEX_FORMAT_X16Y16Z16W16: return MTL::VertexFormatHalf4; case VERTEX_FORMAT_X32: return MTL::VertexFormatFloat; case VERTEX_FORMAT_X32Y32: return MTL::VertexFormatFloat2; case VERTEX_FORMAT_X32Y32Z32: return MTL::VertexFormatFloat3; case VERTEX_FORMAT_X32Y32Z32W32: return MTL::VertexFormatFloat4; default: return MTL::VertexFormatFloat3; } }; MTL::LoadAction IRenderer_LoadOpMl( EAttachmentLoadMode mode ) { switch (mode) { case ATTACHMENT_LOAD_MODE_DONT_CARE: return MTL::LoadActionDontCare; case ATTACHMENT_LOAD_MODE_CLEAR: return MTL::LoadActionClear; case ATTACHMENT_LOAD_MODE_LOAD: return MTL::LoadActionLoad; default: return MTL::LoadActionDontCare; } } MTL::StoreAction IRenderer_StoreOpMl( EAttachmentStoreMode mode ) { switch (mode) { case ATTACHMENT_STORE_MODE_DONT_CARE: return MTL::StoreActionStore; case ATTACHMENT_STORE_MODE_STORE: return MTL::StoreActionStore; default: return MTL::StoreActionDontCare; } } class CMlBuffer: public IBuffer { public: void *Map() override; void Unmap() override; MTL::Buffer *m_buffer; uint32_t m_nSize; }; class CMlImage: public IImage { public: MTL::Texture *m_image; }; void *CMlBuffer::Map() { return m_buffer->contents(); } void CMlBuffer::Unmap() { } struct MLShader_t { EShaderType shaderType; MTL::Library *library; MTL::Function *function; NS::String *szMain; }; class CMlGraphicsPipeline: public IGraphicsPipeline { public: CUtlVector m_shaders; MTL::RenderPipelineState *m_pipeline; CUtlVector m_inputs; uint32_t m_nVertexSize; }; class CMlTexture: public ITexture { public: MTL::Texture *m_texture; }; IStorageBuffer *IRenderer::CreateStorageBuffer( uint32_t uSize ) { CMlBuffer *pBuffer = new CMlBuffer; pBuffer->m_buffer = g_mlDevice->newBuffer(uSize, MTL::ResourceStorageModeShared); pBuffer->m_nSize = uSize; return pBuffer; } IUniformBuffer *IRenderer::CreateUniformBuffer( uint32_t uSize ) { CMlBuffer *pBuffer = new CMlBuffer; pBuffer->m_buffer = g_mlDevice->newBuffer(uSize, MTL::ResourceStorageModeShared); pBuffer->m_nSize = uSize; return pBuffer; } IVertexBuffer *IRenderer::CreateVertexBuffer( uint32_t uSize ) { CMlBuffer *pBuffer = new CMlBuffer; pBuffer->m_buffer = g_mlDevice->newBuffer(uSize, MTL::ResourceStorageModeShared); pBuffer->m_nSize = uSize; return pBuffer; } IIndexBuffer *IRenderer::CreateIndexBuffer( uint32_t uSize ) { CMlBuffer *pBuffer = new CMlBuffer; pBuffer->m_buffer = g_mlDevice->newBuffer(uSize, MTL::ResourceStorageModeShared); pBuffer->m_nSize = uSize; return pBuffer; } IImage *IRenderer::CreateImage( EImageFormat format, uint32_t usage, uint32_t nWidth, uint32_t nHeight, uint32_t nSamples ) { CMlImage *pImage = new CMlImage; MTL::TextureDescriptor *pCreateInfo = MTL::TextureDescriptor::alloc()->init(); pCreateInfo->setPixelFormat(IRenderer_FormatToMl(format)); pCreateInfo->setWidth(nWidth); pCreateInfo->setHeight(nHeight); MTL::TextureUsage mlusage = 0; if (usage&IMAGE_USAGE_COLOR_ATTACHMENT) mlusage |= MTL::TextureUsageRenderTarget; if (usage&IMAGE_USAGE_DEPTH_ATTACHMENT) mlusage |= MTL::TextureUsageRenderTarget; mlusage |= MTL::TextureUsageShaderRead; mlusage |= MTL::TextureUsageShaderWrite; pCreateInfo->setUsage(mlusage); pImage->m_image = g_mlDevice->newTexture(pCreateInfo); pCreateInfo->release(); return pImage; }; void IRenderer::DestroyBuffer( IBuffer *pBuffer ) { if (!pBuffer) return; CMlBuffer *pMlBuffer = (CMlBuffer*)pBuffer; g_destroyBuffersBuffer.AppendTail(pMlBuffer->m_buffer); delete (pMlBuffer); } void IRenderer::DestroyImage( IImage *pImage ) { if (!pImage) return; CMlImage *pMlImage = (CMlImage*)pImage; g_destroyImageBuffer.AppendTail(pMlImage->m_image); delete (pMlImage); } IPipeline *g_pCurrentPipeline; void IRenderer::SetConstants( uint32_t nSize, void *pData ) { if (!g_pCurrentPipeline) return; uint32_t nRoundedSize = nSize/8*8; void *pTemporaryBuffer = V_malloc(nRoundedSize); V_memcpy(pTemporaryBuffer, pData, nSize); if (g_pCurrentPipeline->type == PIPELINE_TYPE_RASTERIZATION) { CMlGraphicsPipeline *pMlPipeline = (CMlGraphicsPipeline*)g_pCurrentPipeline; s_pEncoder->setVertexBytes(pTemporaryBuffer, nRoundedSize, 29); s_pEncoder->setFragmentBytes(pTemporaryBuffer, nRoundedSize, 29); } V_free(pTemporaryBuffer); } void IRenderer::Barrier( uint32_t stageIn, uint32_t stageOut, CUtlVector buffers, CUtlVector images ) { } void IRenderer::BindData( uint32_t binding, IBuffer *pBuffer, IImage* pImage) { if (!g_pCurrentPipeline) return; if (g_pCurrentPipeline->type == PIPELINE_TYPE_RASTERIZATION) { CMlBuffer *pMlBuffer = (CMlBuffer*)pBuffer; CMlGraphicsPipeline *pMlPipeline = (CMlGraphicsPipeline*)g_pCurrentPipeline; for (auto &input: pMlPipeline->m_inputs) { if (input.binding != binding) continue; switch (input.type) { case SHADER_INPUT_TYPE_STORAGE_BUFFER: case SHADER_INPUT_TYPE_UNIFORM_BUFFER: if (!pBuffer) Plat_FatalErrorFunc("pBuffer is NULL\n"); s_pEncoder->setVertexBuffer(pMlBuffer->m_buffer, 0, binding); s_pEncoder->setFragmentBuffer(pMlBuffer->m_buffer, 0, binding); break; case SHADER_INPUT_TYPE_IMAGE: break; case SHADER_INPUT_TYPE_TLAS: break; case SHADER_INPUT_TYPE_TEXTURES: break; }; } }; } void IRenderer::BindPipeline( IPipeline *pPipeline ) { if (!pPipeline) return; if (pPipeline->type == PIPELINE_TYPE_RASTERIZATION) { CMlGraphicsPipeline *pVkPipeline = (CMlGraphicsPipeline*)pPipeline; s_pEncoder->setRenderPipelineState(pVkPipeline->m_pipeline); } g_pCurrentPipeline = pPipeline; } void IRenderer::PushBindings() { if (!g_pCurrentPipeline) return; if (g_pCurrentPipeline->type == PIPELINE_TYPE_RASTERIZATION) { CMlGraphicsPipeline *pPipeline = (CMlGraphicsPipeline*)g_pCurrentPipeline; bool bHasTextures = false; for ( int i = 0; i < pPipeline->m_inputs.GetSize(); i++ ) { if (pPipeline->m_inputs[i].type == SHADER_INPUT_TYPE_TEXTURES) { bHasTextures = true; break; } } if (!bHasTextures) return; MTL::ArgumentEncoder *argumentEncoder = 0; for (auto &shader: pPipeline->m_shaders) { if (shader.shaderType == SHADER_TYPE_FRAGMENT) { argumentEncoder = shader.function->newArgumentEncoder(21); break; }; } MTL::Buffer *argumentBuffer = g_mlDevice->newBuffer(argumentEncoder->encodedLength(), MTL::ResourceStorageModeShared); argumentEncoder->setArgumentBuffer(argumentBuffer, 0); for ( uint32_t i = 0; i < g_textures.GetSize(); i++ ) { CMlTexture *texture = (CMlTexture*)g_textures[i]; argumentEncoder->setTexture(texture->m_texture, i); s_pEncoder->useResource(texture->m_texture, MTL::ResourceUsageRead); } s_pEncoder->useResource(argumentBuffer, MTL::ResourceUsageRead); s_pEncoder->setFragmentBuffer(argumentBuffer, 0, 21); g_destroyBuffersBuffer.AppendTail(argumentBuffer); argumentEncoder->release(); } } void IRenderer::Begin( uint32_t nWidth, uint32_t nHeight, CUtlVector attachments, RenderingDepthAttachment_t depth ) { s_pRenderPass = MTL::RenderPassDescriptor::alloc()->init(); uint32_t i = 0; for (auto &attachment: attachments) { CMlImage *pImage = (CMlImage*)attachment.pOutput; CMlImage *pTemporary = (CMlImage*)attachment.pTemporary; auto mlattachment = s_pRenderPass->colorAttachments()->object(i); mlattachment->setTexture(pImage->m_image); mlattachment->setLoadAction(IRenderer_LoadOpMl(attachment.loadMode)); mlattachment->setStoreAction(IRenderer_StoreOpMl(attachment.storeMode)); mlattachment->setClearColor(MTL::ClearColor(attachment.clearColor[0],attachment.clearColor[1],attachment.clearColor[2],attachment.clearColor[3])); i++; }; if (depth.pOutput) { CMlImage *pImage = (CMlImage*)depth.pOutput; CMlImage *pTemporary = (CMlImage*)depth.pTemporary; s_pRenderPass->depthAttachment()->setClearDepth(depth.clearValue); s_pRenderPass->depthAttachment()->setTexture(pImage->m_image); s_pRenderPass->depthAttachment()->setLoadAction(IRenderer_LoadOpMl(depth.loadMode)); s_pRenderPass->depthAttachment()->setStoreAction(IRenderer_StoreOpMl(depth.storeMode)); } s_pEncoder = g_mlCommandBuffer->renderCommandEncoder(s_pRenderPass); } void IRenderer::ResetState() { } void IRenderer::SetDepthMode( EDepthMode mode ) { MTL::DepthStencilDescriptor* depthStencilDesc = MTL::DepthStencilDescriptor::alloc()->init(); if (mode == DEPTH_MODE_DISABLED) { depthStencilDesc->setDepthWriteEnabled(false); } else { depthStencilDesc->setDepthWriteEnabled(true); switch (mode) { case DEPTH_MODE_LESS: depthStencilDesc->setDepthCompareFunction(MTL::CompareFunctionLess); break; default: break; } } auto depthStencilState = g_mlDevice->newDepthStencilState(depthStencilDesc); s_pEncoder->setDepthStencilState(depthStencilState); depthStencilDesc->release(); } void IRenderer::Draw( IVertexBuffer *pVertex, IIndexBuffer *pIndex ) { CMlBuffer *pMlVertex = (CMlBuffer*)pVertex; CMlBuffer *pMlIndex = (CMlBuffer*)pIndex; if (pMlIndex) { } else { CMlGraphicsPipeline *pPipeline = (CMlGraphicsPipeline*)g_pCurrentPipeline; s_pEncoder->setVertexBuffer(pMlVertex->m_buffer, 0, 30); s_pEncoder->drawPrimitives(MTL::PrimitiveTypeTriangle, NS::UInteger(0), NS::UInteger(pMlVertex->m_nSize/pPipeline->m_nVertexSize)); } } void IRenderer::End() { s_pEncoder->endEncoding(); } IBuffer *IRenderer::GetCameraMatrix() { return g_cameraProperties; } static CMlImage *pOutputImage = new CMlImage; IImage *IRenderer::GetOutputImage() { pOutputImage->m_image = g_mlDrawableTexture; return pOutputImage; } IGraphicsPipeline *IRenderer::CreateGraphicsPipeline( CUtlVector shaders, CUtlVector inputs, uint32_t nConstantsSize, uint32_t nVertexSize, CUtlVector vertexFormat, CUtlVector outputFormats, bool bDepth ) { CMlGraphicsPipeline *pPipeline = new CMlGraphicsPipeline; CUtlVector mlshaders; MTL::RenderPipelineDescriptor *pCreateInfo = MTL::RenderPipelineDescriptor::alloc()->init(); MTL::VertexDescriptor *vertexDescriptor = MTL::VertexDescriptor::alloc()->init(); uint32_t i = 0; for ( auto &vertex: vertexFormat ) { vertexDescriptor->attributes()->object(i)->setFormat(IRenderer_VertexToMl(vertex.format)); vertexDescriptor->attributes()->object(i)->setOffset(vertex.offset); vertexDescriptor->attributes()->object(i)->setBufferIndex(30); i++; } vertexDescriptor->layouts()->object(30)->setStride(nVertexSize); vertexDescriptor->layouts()->object(30)->setStepRate(1); vertexDescriptor->layouts()->object(30)->setStepFunction(MTL::VertexStepFunctionPerVertex); NS::Error *error = NULL; for ( auto &shader: shaders ) { MLShader_t mlshader = {}; FileHandle_t f = IFileSystem::Open(shader.szPath, IFILE_READ); if (!f) Plat_FatalErrorFunc("Failed to open shader %s\n", shader.szPath.GetString()); CUtlBuffer buffer(IFileSystem::Size(f)+1); IFileSystem::Read(f, buffer.GetMemory(), buffer.GetSize()); buffer[IFileSystem::Size(f)] = 0; IFileSystem::Close(f); NS::String *szSourceCode = NS::String::string((const char*)buffer.GetMemory(), NS::StringEncoding::UTF8StringEncoding); MTL::CompileOptions *options = NULL; mlshader.library = g_mlDevice->newLibrary(szSourceCode, options, &error); if (error) Plat_FatalErrorFunc("%s\n",error->localizedDescription()->utf8String()); mlshader.szMain = NS::String::string("_main", NS::StringEncoding::UTF8StringEncoding); mlshader.function = mlshader.library->newFunction(mlshader.szMain); if (shader.type == SHADER_TYPE_FRAGMENT) pCreateInfo->setFragmentFunction(mlshader.function); if (shader.type == SHADER_TYPE_VERTEX) pCreateInfo->setVertexFunction(mlshader.function); mlshader.shaderType = shader.type; mlshaders.AppendTail(mlshader); } for ( i = 0; i < outputFormats.GetSize(); i++ ) { pCreateInfo->colorAttachments()->object(i)->setPixelFormat(IRenderer_FormatToMl(outputFormats[i])); }; if (bDepth) pCreateInfo->setDepthAttachmentPixelFormat(MTL::PixelFormatDepth32Float); pCreateInfo->setVertexDescriptor(vertexDescriptor); pPipeline->type = PIPELINE_TYPE_RASTERIZATION; pPipeline->m_pipeline = g_mlDevice->newRenderPipelineState(pCreateInfo, &error); pPipeline->m_nVertexSize = nVertexSize; pPipeline->m_inputs = inputs; pPipeline->m_shaders = mlshaders; if (error) Plat_FatalErrorFunc("%s\n",error->localizedDescription()->utf8String()); return pPipeline; }; uint32_t ITextureManager::GetTextureID(ITexture *pTexture) { uint32_t i = 0; for (auto &t: g_textures) { if (pTexture == t) return i; i++; } return 0; } ITexture *ITextureManager::LoadTexture( void *pData, uint32_t X, uint32_t Y, uint32_t numChannels ) { CMlTexture *pTexture = new CMlTexture; *pTexture = {}; pTexture->x = X; pTexture->y = Y; MTL::TextureDescriptor *pCreateInfo = MTL::TextureDescriptor::alloc()->init(); pCreateInfo->setPixelFormat(MTL::PixelFormatRGBA8Unorm_sRGB); pCreateInfo->setWidth(X); pCreateInfo->setHeight(Y); pCreateInfo->setUsage(MTL::TextureUsageShaderRead); pTexture->m_texture = g_mlDevice->newTexture(pCreateInfo); MTL::Region region = MTL::Region(0, 0, 0, X, Y, 1); NS::UInteger bytesPerRow = 4 * X; pTexture->m_texture->replaceRegion(region, 0, pData, bytesPerRow); pCreateInfo->release(); g_textures.AppendTail(pTexture); return pTexture; }; ITexture *ITextureManager::LoadTexture( const char *szName ) { for (ITexture *texture: g_textures) { if (!texture->szName) continue; if (!V_strcmp(texture->szName, szName)) return texture; }; FileHandle_t file = IFileSystem::Open(szName, IFILE_READ); if (!file) Plat_FatalErrorFunc("Failed to load %s\n", szName); CUtlBuffer buffer(IFileSystem::Size(file)); IFileSystem::Read(file, buffer.GetMemory(), buffer.GetSize()); int nImageX; int nImageY; int nImageChannels; stbi_uc *pixels = stbi_load_from_memory((stbi_uc*)buffer.GetMemory(), buffer.GetSize(), &nImageX, &nImageY, &nImageChannels, STBI_rgb_alpha); ITexture *pTexture = ITextureManager::LoadTexture(pixels, nImageX, nImageY, 4); pTexture->szName = szName; return pTexture; }; void IMetal::Init() { char invalidTexture[1024] = {}; for (int i = 0; i < 16; i++) { for (int j = 0; j < 16; j++) { int index = i * 16 + j; if ((i + j) % 2 == 0) { invalidTexture[index*4] = 255; } } } g_cameraProperties = IRenderer::CreateUniformBuffer(sizeof(CameraProjection)); g_cameraDataMap = (CameraProjection*)g_cameraProperties->Map(); ITextureManager::LoadTexture(invalidTexture, 16, 16, 4); g_meshDepth = IRenderer::CreateImage(IMAGE_FORMAT_DEPTH, IMAGE_USAGE_DEPTH_ATTACHMENT, 1280, 720, 1); g_meshDepthMSAA = IRenderer::CreateImage(IMAGE_FORMAT_DEPTH, IMAGE_USAGE_DEPTH_ATTACHMENT, 1280, 720, 4); g_meshColor = IRenderer::CreateImage(IMAGE_FORMAT_R8G8B8A8, IMAGE_USAGE_COLOR_ATTACHMENT, 1280, 720, 1); g_meshColorMSAA = IRenderer::CreateImage(IMAGE_FORMAT_R8G8B8A8, IMAGE_USAGE_COLOR_ATTACHMENT, 1280, 720, 4); glm_mat4_identity(g_cameraView); } static MTL::RenderPipelineState* s_fullScreenDraw; void IMetal::CreatePipelines() { for (auto &step: g_StepPrepass) step.pPipeline->Init(); for (auto &step: g_StepMeshRendering) step.pPipeline->Init(); for (auto &step: g_StepShading) step.pPipeline->Init(); for (auto &step: g_StepPostProcessing) step.pPipeline->Init(); for (auto &step: g_StepUI) step.pPipeline->Init(); NS::Error *error = 0; FileHandle_t f = IFileSystem::Open("gfx/ml_quad.metal", IFILE_READ); CUtlBuffer buffer(IFileSystem::Size(f)+1); IFileSystem::Read(f, buffer.GetMemory(), buffer.GetSize()); buffer[IFileSystem::Size(f)] = 0; IFileSystem::Close(f); NS::String *szSourceCode = NS::String::string((const char*)buffer.GetMemory(), NS::StringEncoding::UTF8StringEncoding); MTL::CompileOptions *options = NULL; MTL::Library *library = g_mlDevice->newLibrary(szSourceCode, options, &error); MTL::Function *vertexFunc = library->newFunction(NS::String::string("vertex_main", NS::UTF8StringEncoding)); MTL::Function *fragmentFunc = library->newFunction(NS::String::string("fragment_main", NS::UTF8StringEncoding)); MTL::RenderPipelineDescriptor *pipelineDesc = MTL::RenderPipelineDescriptor::alloc()->init(); pipelineDesc->setVertexFunction(vertexFunc); pipelineDesc->setFragmentFunction(fragmentFunc); pipelineDesc->colorAttachments()->object(0)->setPixelFormat(MTL::PixelFormatBGRA8Unorm); s_fullScreenDraw = g_mlDevice->newRenderPipelineState(pipelineDesc, &error); } void IMetal::Frame() { mat4 perspective; glm_mat4_inv(g_cameraView, g_cameraDataMap->viewprojection); glm_perspective(glm_rad(90),(float)g_nWindowWidth/g_nWindowHeight, 0.01, 100, perspective); glm_rotate(perspective, glm_rad(90), (vec4){1,0,0,0}); glm_scale(perspective, (vec4){1,-1,1,1}); glm_rotate(perspective, glm_rad(90), (vec4){0,0,1,0}); glm_mat4_mul(perspective,g_cameraDataMap->viewprojection,g_cameraDataMap->viewprojection); if (g_bConfigNotify) { IRenderer::DestroyImage(g_meshDepth); IRenderer::DestroyImage(g_meshDepthMSAA); IRenderer::DestroyImage(g_meshColor); IRenderer::DestroyImage(g_meshColorMSAA); g_meshDepth = IRenderer::CreateImage(IMAGE_FORMAT_DEPTH, IMAGE_USAGE_DEPTH_ATTACHMENT, g_nWindowWidth, g_nWindowHeight, 1); g_meshDepthMSAA = IRenderer::CreateImage(IMAGE_FORMAT_DEPTH, IMAGE_USAGE_DEPTH_ATTACHMENT, g_nWindowWidth, g_nWindowHeight, 4); g_meshColor = IRenderer::CreateImage(IMAGE_FORMAT_R8G8B8A8, IMAGE_USAGE_COLOR_ATTACHMENT, g_nWindowWidth, g_nWindowHeight, 1); g_meshColorMSAA = IRenderer::CreateImage(IMAGE_FORMAT_R8G8B8A8, IMAGE_USAGE_COLOR_ATTACHMENT, g_nWindowWidth, g_nWindowHeight, 4); } for (auto &step: g_StepPrepass) step.pPipeline->Frame(0); IRenderer::Begin(g_nWindowWidth, g_nWindowHeight, { { g_meshColor, 0, ATTACHMENT_LOAD_MODE_CLEAR, ATTACHMENT_STORE_MODE_STORE, } }, { g_meshDepth, 0, ATTACHMENT_LOAD_MODE_CLEAR, ATTACHMENT_STORE_MODE_STORE, 1, }); for (auto &step: g_StepMeshRendering) step.pPipeline->Frame(0); IRenderer::End(); CMlImage *pInImage = (CMlImage*)g_meshColor; MTL::RenderPassDescriptor *renderPassDesc = MTL::RenderPassDescriptor::renderPassDescriptor(); renderPassDesc->colorAttachments()->object(0)->setTexture(g_mlDrawableTexture); renderPassDesc->colorAttachments()->object(0)->setLoadAction(MTL::LoadActionClear); renderPassDesc->colorAttachments()->object(0)->setStoreAction(MTL::StoreActionStore); renderPassDesc->colorAttachments()->object(0)->setClearColor(MTL::ClearColor(0.1, 0.1, 0.1, 1.0)); MTL::RenderCommandEncoder *encoder = g_mlCommandBuffer->renderCommandEncoder(renderPassDesc); encoder->setRenderPipelineState(s_fullScreenDraw); encoder->setFragmentTexture(pInImage->m_image, 0); encoder->drawPrimitives(MTL::PrimitiveTypeTriangleStrip, NS::UInteger(0), NS::UInteger(4)); encoder->endEncoding(); for (auto &step: g_StepShading) step.pPipeline->Frame(0); for (auto &step: g_StepPostProcessing) step.pPipeline->Frame(0); for (auto &step: g_StepUI) step.pPipeline->Frame(0); } void IMetal::Deinit() { }