diff --git a/filament/src/driver/metal/MetalContext.h b/filament/src/driver/metal/MetalContext.h index 6d915b7bb0..9296f94104 100644 --- a/filament/src/driver/metal/MetalContext.h +++ b/filament/src/driver/metal/MetalContext.h @@ -26,7 +26,7 @@ namespace filament { namespace driver { namespace metal { -struct MetalRenderTarget; +class MetalRenderTarget; struct MetalSamplerBuffer; struct MetalSwapChain; diff --git a/filament/src/driver/metal/MetalDriver.mm b/filament/src/driver/metal/MetalDriver.mm index 4c3451cd21..2392f42ea6 100644 --- a/filament/src/driver/metal/MetalDriver.mm +++ b/filament/src/driver/metal/MetalDriver.mm @@ -124,25 +124,27 @@ void MetalDriver::createProgramR(Driver::ProgramHandle rph, Program&& program) { } void MetalDriver::createDefaultRenderTargetR(Driver::RenderTargetHandle rth, int dummy) { - construct_handle(mHandleMap, rth); + construct_handle(mHandleMap, rth, mContext); } void MetalDriver::createRenderTargetR(Driver::RenderTargetHandle rth, Driver::TargetBufferFlags targetBufferFlags, uint32_t width, uint32_t height, uint8_t samples, Driver::TextureFormat format, Driver::TargetBufferInfo color, Driver::TargetBufferInfo depth, Driver::TargetBufferInfo stencil) { - auto renderTarget = construct_handle(mHandleMap, rth, width, height); + + id mtlColor = nil; + id mtlDepth = nil; if (color.handle) { auto colorTexture = handle_cast(mHandleMap, color.handle); - renderTarget->color = [colorTexture->texture retain]; + mtlColor = colorTexture->texture; } else if (targetBufferFlags & TargetBufferFlags::COLOR) { ASSERT_POSTCONDITION(false, "A color buffer is required for a render target."); } if (depth.handle) { auto depthTexture = handle_cast(mHandleMap, depth.handle); - renderTarget->depth = [depthTexture->texture retain]; + mtlDepth = depthTexture->texture; } else if (targetBufferFlags & TargetBufferFlags::DEPTH) { MTLTextureDescriptor* depthTextureDesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatDepth32Float @@ -151,9 +153,12 @@ void MetalDriver::createRenderTargetR(Driver::RenderTargetHandle rth, mipmapped:NO]; depthTextureDesc.usage = MTLTextureUsageRenderTarget; depthTextureDesc.resourceOptions = MTLResourceStorageModePrivate; - renderTarget->depth = [mContext->device newTextureWithDescriptor:depthTextureDesc]; + mtlDepth = [mContext->device newTextureWithDescriptor:depthTextureDesc]; } + construct_handle(mHandleMap, rth, mContext, width, height, samples, format, + mtlColor, mtlDepth); + ASSERT_POSTCONDITION( !stencil.handle && !(targetBufferFlags & TargetBufferFlags::STENCIL), "Stencil buffer not supported."); @@ -402,13 +407,9 @@ void MetalDriver::beginRenderPass(Driver::RenderTargetHandle rth, // Color - if (renderTarget->isDefaultRenderTarget) { - descriptor.colorAttachments[0].texture = acquireDrawable(mContext).texture; - mContext->currentSurfacePixelFormat = mContext->currentDrawable.texture.pixelFormat; - } else { - descriptor.colorAttachments[0].texture = renderTarget->color; - mContext->currentSurfacePixelFormat = renderTarget->color.pixelFormat; - } + descriptor.colorAttachments[0].texture = renderTarget->getColor(); + descriptor.colorAttachments[0].resolveTexture = renderTarget->getColorResolve(); + mContext->currentSurfacePixelFormat = descriptor.colorAttachments[0].texture.pixelFormat; // Metal clears the entire attachment without respect to viewport or scissor. // TODO: Might need to clear the scissor area manually via a draw if we need that functionality. @@ -424,20 +425,17 @@ void MetalDriver::beginRenderPass(Driver::RenderTargetHandle rth, // Depth - if (renderTarget->isDefaultRenderTarget) { - descriptor.depthAttachment.texture = mContext->currentSurface->depthTexture; - mContext->currentDepthPixelFormat = mContext->currentSurface->depthTexture.pixelFormat; - } else { - descriptor.depthAttachment.texture = renderTarget->depth; - if (renderTarget->depth) { - mContext->currentDepthPixelFormat = renderTarget->depth.pixelFormat; - } else { - mContext->currentDepthPixelFormat = MTLPixelFormatInvalid; - } - } - + descriptor.depthAttachment.texture = renderTarget->getDepth(); + descriptor.depthAttachment.resolveTexture = renderTarget->getDepthResolve(); descriptor.depthAttachment.loadAction = clearDepth ? MTLLoadActionClear : MTLLoadActionDontCare; descriptor.depthAttachment.clearDepth = params.clearDepth; + mContext->currentDepthPixelFormat = descriptor.depthAttachment.texture.pixelFormat; + + if (renderTarget->isMultisampled()) { + descriptor.colorAttachments[0].storeAction = MTLStoreActionMultisampleResolve; + // TODO: We don't need to resolve the depth texture if we don't need it. + descriptor.depthAttachment.storeAction = MTLStoreActionMultisampleResolve; + } mContext->currentCommandEncoder = [mContext->currentCommandBuffer renderCommandEncoderWithDescriptor:descriptor]; @@ -520,7 +518,7 @@ void MetalDriver::viewport(ssize_t left, ssize_t bottom, size_t width, size_t he ASSERT_PRECONDITION(mContext->currentCommandEncoder != nullptr, "currentCommandEncoder is null"); // Flip the viewport, because Metal's screen space is vertically flipped that of Filament's. NSInteger renderTargetHeight = - mContext->currentRenderTarget->isDefaultRenderTarget ? + mContext->currentRenderTarget->isDefaultRenderTarget() ? mContext->currentSurface->surfaceHeight : mContext->currentRenderTarget->height; MTLViewport metalViewport { .originX = static_cast(left), @@ -581,6 +579,7 @@ void MetalDriver::readStreamPixels(Driver::StreamHandle sh, uint32_t x, uint32_t void MetalDriver::blit(Driver::TargetBufferFlags buffers, Driver::RenderTargetHandle dst, driver::Viewport dstRect, Driver::RenderTargetHandle src, driver::Viewport srcRect) { + } void MetalDriver::draw(Driver::PipelineState ps, Driver::RenderPrimitiveHandle rph) { @@ -597,6 +596,7 @@ void MetalDriver::draw(Driver::PipelineState ps, Driver::RenderPrimitiveHandle r .vertexDescription = primitive->vertexDescription, .colorAttachmentPixelFormat = mContext->currentSurfacePixelFormat, .depthAttachmentPixelFormat = mContext->currentDepthPixelFormat, + .sampleCount = mContext->currentRenderTarget->getSamples(), .blendState = BlendState { .blendingEnabled = rs.hasBlending(), .rgbBlendOperation = getMetalBlendOperation(rs.blendEquationRGB), diff --git a/filament/src/driver/metal/MetalHandles.h b/filament/src/driver/metal/MetalHandles.h index afcd2eeae9..006e32e20e 100644 --- a/filament/src/driver/metal/MetalHandles.h +++ b/filament/src/driver/metal/MetalHandles.h @@ -120,14 +120,37 @@ struct MetalSamplerBuffer : public HwSamplerBuffer { explicit MetalSamplerBuffer(size_t size) : HwSamplerBuffer(size) {} }; -struct MetalRenderTarget : public HwRenderTarget { - MetalRenderTarget(uint32_t width, uint32_t height) : HwRenderTarget(width, height) {} - MetalRenderTarget() : HwRenderTarget(0, 0), isDefaultRenderTarget(true) {} +class MetalRenderTarget : public HwRenderTarget { +public: + MetalRenderTarget(MetalContext* context, uint32_t width, uint32_t height, uint8_t samples, + TextureFormat format, id color, id depth); + explicit MetalRenderTarget(MetalContext* context) + : HwRenderTarget(0, 0), context(context), defaultRenderTarget(true) {} ~MetalRenderTarget(); - bool isDefaultRenderTarget = false; + bool isDefaultRenderTarget() const { return defaultRenderTarget; } + bool isMultisampled() const { return samples > 1; } + uint8_t getSamples() const { return samples; } + + id getColor(); + id getColorResolve(); + id getDepth(); + id getDepthResolve(); + +private: + static id createMultisampledTexture(id device, TextureFormat format, + uint32_t width, uint32_t height, uint8_t samples); + + MetalContext* context; id color = nil; id depth = nil; + bool defaultRenderTarget = false; + uint8_t samples = 1; + + // These textures are only used if this render target is multisampled. + id multisampledColor = nil; + id multisampledDepth = nil; + }; } // namespace metal diff --git a/filament/src/driver/metal/MetalHandles.mm b/filament/src/driver/metal/MetalHandles.mm index 4a91a64593..8c860b0ee4 100644 --- a/filament/src/driver/metal/MetalHandles.mm +++ b/filament/src/driver/metal/MetalHandles.mm @@ -21,6 +21,7 @@ #include
// for FTexture::getFormatSize #include +#include namespace filament { namespace driver { @@ -252,6 +253,7 @@ MetalTexture::MetalTexture(id device, driver::SamplerType target, uin height:height mipmapped:mipmapped]; descriptor.mipmapLevelCount = levels; + descriptor.textureType = MTLTextureType2D; } else if (target == driver::SamplerType::SAMPLER_CUBEMAP) { ASSERT_POSTCONDITION(width == height, "Cubemap faces must be square."); descriptor = [MTLTextureDescriptor textureCubeDescriptorWithPixelFormat:pixelFormat @@ -315,9 +317,73 @@ void MetalTexture::loadCubeImage(const PixelBufferDescriptor& data, const FaceOf } } +MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint32_t height, + uint8_t samples, TextureFormat format, id color, id depth) + : HwRenderTarget(width, height), context(context), color(color), depth(depth), + samples(samples) { + [color retain]; + [depth retain]; + + if (samples > 1) { + multisampledColor = + createMultisampledTexture(context->device, format, width, height, samples); + + if (depth != nil) { + multisampledDepth = createMultisampledTexture(context->device, TextureFormat::DEPTH32F, + width, height, samples); + } + } +} + +id MetalRenderTarget::getColor() { + if (defaultRenderTarget) { + return acquireDrawable(context).texture; + } + return isMultisampled() ? multisampledColor : color; +} + +id MetalRenderTarget::getColorResolve() { + return isMultisampled() ? color : nil; +} + +id MetalRenderTarget::getDepthResolve() { + return isMultisampled() ? depth : nil; +} + +id MetalRenderTarget::getDepth() { + if (defaultRenderTarget) { + return context->currentSurface->depthTexture; + } + return isMultisampled() ? multisampledDepth : depth; +} + MetalRenderTarget::~MetalRenderTarget() { [color release]; [depth release]; + [multisampledColor release]; + [multisampledDepth release]; +} + +id MetalRenderTarget::createMultisampledTexture(id device, + TextureFormat format, uint32_t width, uint32_t height, uint8_t samples) { + MTLPixelFormat metalFormat = getMetalFormat(format); + ASSERT_POSTCONDITION(metalFormat != MTLPixelFormatInvalid, "Pixel format not supported."); + + MTLTextureDescriptor* descriptor = + [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:metalFormat + width:width + height:height + mipmapped:NO]; + descriptor.textureType = MTLTextureType2DMultisample; + descriptor.sampleCount = samples; + descriptor.usage = MTLTextureUsageRenderTarget; +#if defined(IOS) + descriptor.resourceOptions = MTLResourceStorageModeMemoryless; +#else + descriptor.resourceOptions = MTLResourceStorageModePrivate; +#endif + + return [device newTextureWithDescriptor:descriptor]; } } // namespace metal diff --git a/filament/src/driver/metal/MetalState.h b/filament/src/driver/metal/MetalState.h index f7100aaedb..c68ce3172e 100644 --- a/filament/src/driver/metal/MetalState.h +++ b/filament/src/driver/metal/MetalState.h @@ -207,6 +207,7 @@ struct PipelineState { VertexDescription vertexDescription; MTLPixelFormat colorAttachmentPixelFormat = MTLPixelFormatInvalid; MTLPixelFormat depthAttachmentPixelFormat = MTLPixelFormatInvalid; + NSUInteger sampleCount = 1; BlendState blendState; bool operator==(const PipelineState& rhs) const noexcept { @@ -216,6 +217,7 @@ struct PipelineState { this->vertexDescription == rhs.vertexDescription && this->colorAttachmentPixelFormat == rhs.colorAttachmentPixelFormat && this->depthAttachmentPixelFormat == rhs.depthAttachmentPixelFormat && + this->sampleCount == rhs.sampleCount && this->blendState == rhs.blendState ); } diff --git a/filament/src/driver/metal/MetalState.mm b/filament/src/driver/metal/MetalState.mm index 3b4cad425a..72843fa078 100644 --- a/filament/src/driver/metal/MetalState.mm +++ b/filament/src/driver/metal/MetalState.mm @@ -73,6 +73,9 @@ id PipelineStateCreator::operator()(id device // Depth attachment descriptor.depthAttachmentPixelFormat = state.depthAttachmentPixelFormat; + // MSAA + descriptor.rasterSampleCount = state.sampleCount; + NSError* error = nullptr; id pipeline = [device newRenderPipelineStateWithDescriptor:descriptor error:&error];