| // |
| // Copyright 2019 The ANGLE Project Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // |
| // ContextMtl.mm: |
| // Implements the class methods for ContextMtl. |
| // |
| |
| #include "libANGLE/renderer/metal/ContextMtl.h" |
| |
| #include <TargetConditionals.h> |
| #include <cstdint> |
| |
| #include "GLSLANG/ShaderLang.h" |
| #include "common/debug.h" |
| #include "libANGLE/TransformFeedback.h" |
| #include "libANGLE/renderer/OverlayImpl.h" |
| #include "libANGLE/renderer/metal/BufferMtl.h" |
| #include "libANGLE/renderer/metal/CompilerMtl.h" |
| #include "libANGLE/renderer/metal/DisplayMtl.h" |
| #include "libANGLE/renderer/metal/FrameBufferMtl.h" |
| #include "libANGLE/renderer/metal/ProgramMtl.h" |
| #include "libANGLE/renderer/metal/QueryMtl.h" |
| #include "libANGLE/renderer/metal/RenderBufferMtl.h" |
| #include "libANGLE/renderer/metal/RenderTargetMtl.h" |
| #include "libANGLE/renderer/metal/SamplerMtl.h" |
| #include "libANGLE/renderer/metal/ShaderMtl.h" |
| #include "libANGLE/renderer/metal/SyncMtl.h" |
| #include "libANGLE/renderer/metal/TextureMtl.h" |
| #include "libANGLE/renderer/metal/TransformFeedbackMtl.h" |
| #include "libANGLE/renderer/metal/VertexArrayMtl.h" |
| #include "libANGLE/renderer/metal/mtl_command_buffer.h" |
| #include "libANGLE/renderer/metal/mtl_common.h" |
| #include "libANGLE/renderer/metal/mtl_context_device.h" |
| #include "libANGLE/renderer/metal/mtl_format_utils.h" |
| #include "libANGLE/renderer/metal/mtl_utils.h" |
| |
| namespace rx |
| { |
| |
| namespace |
| { |
| #if TARGET_OS_OSX |
| // Unlimited triangle fan buffers |
| constexpr uint32_t kMaxTriFanLineLoopBuffersPerFrame = 0; |
| #else |
| // Allow up to 10 buffers for trifan/line loop generation without stalling the GPU. |
| constexpr uint32_t kMaxTriFanLineLoopBuffersPerFrame = 10; |
| #endif |
| |
| #define ANGLE_MTL_XFB_DRAW(DRAW_PROC) \ |
| if (!mState.isTransformFeedbackActiveUnpaused()) \ |
| { \ |
| /* Normal draw call */ \ |
| DRAW_PROC(false); \ |
| } \ |
| else \ |
| { \ |
| /* First pass: write to XFB buffers in vertex shader, fragment shader inactive */ \ |
| bool rasterizationNotDisabled = \ |
| mRenderPipelineDesc.rasterizationType != mtl::RenderPipelineRasterization::Disabled; \ |
| if (rasterizationNotDisabled) \ |
| { \ |
| invalidateRenderPipeline(); \ |
| } \ |
| DRAW_PROC(true); \ |
| if (rasterizationNotDisabled) \ |
| { \ |
| /* Second pass: full rasterization: vertex shader + fragment shader are active. \ |
| Vertex shader writes to stage output but won't write to XFB buffers */ \ |
| invalidateRenderPipeline(); \ |
| DRAW_PROC(false); \ |
| } \ |
| } |
| |
| angle::Result AllocateTriangleFanBufferFromPool(ContextMtl *context, |
| GLsizei vertexCount, |
| mtl::BufferPool *pool, |
| mtl::BufferRef *bufferOut, |
| uint32_t *offsetOut, |
| uint32_t *numElemsOut) |
| { |
| uint32_t numIndices; |
| ANGLE_TRY(mtl::GetTriangleFanIndicesCount(context, vertexCount, &numIndices)); |
| |
| size_t offset; |
| pool->releaseInFlightBuffers(context); |
| ANGLE_TRY(pool->allocate(context, numIndices * sizeof(uint32_t), nullptr, bufferOut, &offset, |
| nullptr)); |
| |
| *offsetOut = static_cast<uint32_t>(offset); |
| *numElemsOut = numIndices; |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result AllocateBufferFromPool(ContextMtl *context, |
| GLsizei indicesToReserve, |
| mtl::BufferPool *pool, |
| mtl::BufferRef *bufferOut, |
| uint32_t *offsetOut) |
| { |
| size_t offset; |
| pool->releaseInFlightBuffers(context); |
| ANGLE_TRY(pool->allocate(context, indicesToReserve * sizeof(uint32_t), nullptr, bufferOut, |
| &offset, nullptr)); |
| |
| *offsetOut = static_cast<uint32_t>(offset); |
| |
| return angle::Result::Continue; |
| } |
| |
| bool NeedToInvertDepthRange(float near, float far) |
| { |
| return near > far; |
| } |
| |
| bool IsTransformFeedbackOnly(const gl::State &glState) |
| { |
| return glState.isTransformFeedbackActiveUnpaused() && glState.isRasterizerDiscardEnabled(); |
| } |
| |
| std::string ConvertMarkerToString(GLsizei length, const char *marker) |
| { |
| std::string cppString; |
| if (length == 0) |
| { |
| cppString = marker; |
| } |
| else |
| { |
| cppString.assign(marker, length); |
| } |
| return cppString; |
| } |
| |
| // This class constructs line loop's last segment buffer inside begin() method |
| // and perform the draw of the line loop's last segment inside destructor |
| class LineLoopLastSegmentHelper |
| { |
| public: |
| LineLoopLastSegmentHelper() {} |
| |
| ~LineLoopLastSegmentHelper() |
| { |
| if (!mLineLoopIndexBuffer) |
| { |
| return; |
| } |
| |
| // Draw last segment of line loop here |
| mtl::RenderCommandEncoder *encoder = mContextMtl->getRenderCommandEncoder(); |
| ASSERT(encoder); |
| encoder->drawIndexed(MTLPrimitiveTypeLine, 2, MTLIndexTypeUInt32, mLineLoopIndexBuffer, 0); |
| } |
| |
| angle::Result begin(const gl::Context *context, |
| mtl::BufferPool *indexBufferPool, |
| GLint firstVertex, |
| GLsizei vertexOrIndexCount, |
| gl::DrawElementsType indexTypeOrNone, |
| const void *indices) |
| { |
| mContextMtl = mtl::GetImpl(context); |
| |
| indexBufferPool->releaseInFlightBuffers(mContextMtl); |
| |
| ANGLE_TRY(indexBufferPool->allocate(mContextMtl, 2 * sizeof(uint32_t), nullptr, |
| &mLineLoopIndexBuffer, nullptr, nullptr)); |
| |
| if (indexTypeOrNone == gl::DrawElementsType::InvalidEnum) |
| { |
| ANGLE_TRY(mContextMtl->getDisplay()->getUtils().generateLineLoopLastSegment( |
| mContextMtl, firstVertex, firstVertex + vertexOrIndexCount - 1, |
| mLineLoopIndexBuffer, 0)); |
| } |
| else |
| { |
| ASSERT(firstVertex == 0); |
| ANGLE_TRY( |
| mContextMtl->getDisplay()->getUtils().generateLineLoopLastSegmentFromElementsArray( |
| mContextMtl, |
| {indexTypeOrNone, vertexOrIndexCount, indices, mLineLoopIndexBuffer, 0})); |
| } |
| |
| ANGLE_TRY(indexBufferPool->commit(mContextMtl)); |
| |
| return angle::Result::Continue; |
| } |
| |
| private: |
| ContextMtl *mContextMtl = nullptr; |
| mtl::BufferRef mLineLoopIndexBuffer; |
| }; |
| |
| GLint GetOwnershipIdentity(const egl::AttributeMap &attribs) |
| { |
| return attribs.getAsInt(EGL_CONTEXT_METAL_OWNERSHIP_IDENTITY_ANGLE, 0); |
| } |
| |
| } // namespace |
| |
| ContextMtl::ContextMtl(const gl::State &state, |
| gl::ErrorSet *errorSet, |
| const egl::AttributeMap &attribs, |
| DisplayMtl *display) |
| : ContextImpl(state, errorSet), |
| mtl::Context(display), |
| mCmdBuffer(&display->cmdQueue()), |
| mRenderEncoder(&mCmdBuffer, mOcclusionQueryPool), |
| mBlitEncoder(&mCmdBuffer), |
| mComputeEncoder(&mCmdBuffer), |
| mDriverUniforms{}, |
| mProvokingVertexHelper(this, &display->cmdQueue(), display), |
| mContextDevice(GetOwnershipIdentity(attribs)) |
| {} |
| |
| ContextMtl::~ContextMtl() {} |
| |
| angle::Result ContextMtl::initialize() |
| { |
| for (mtl::BlendDesc &blendDesc : mBlendDescArray) |
| { |
| blendDesc.reset(); |
| } |
| |
| mWriteMaskArray.fill(MTLColorWriteMaskAll); |
| |
| mDepthStencilDesc.reset(); |
| |
| mTriFanIndexBuffer.initialize(this, 0, mtl::kIndexBufferOffsetAlignment, |
| kMaxTriFanLineLoopBuffersPerFrame); |
| mLineLoopIndexBuffer.initialize(this, 0, mtl::kIndexBufferOffsetAlignment, |
| kMaxTriFanLineLoopBuffersPerFrame); |
| mLineLoopLastSegmentIndexBuffer.initialize(this, 2 * sizeof(uint32_t), |
| mtl::kIndexBufferOffsetAlignment, |
| kMaxTriFanLineLoopBuffersPerFrame); |
| |
| mContextDevice.set(mDisplay->getMetalDevice()); |
| |
| return angle::Result::Continue; |
| } |
| |
| void ContextMtl::onDestroy(const gl::Context *context) |
| { |
| mTriFanIndexBuffer.destroy(this); |
| mLineLoopIndexBuffer.destroy(this); |
| mLineLoopLastSegmentIndexBuffer.destroy(this); |
| mOcclusionQueryPool.destroy(this); |
| |
| mIncompleteTextures.onDestroy(context); |
| mIncompleteTexturesInitialized = false; |
| mProvokingVertexHelper.onDestroy(this); |
| mDummyXFBRenderTexture = nullptr; |
| |
| mContextDevice.reset(); |
| } |
| |
| angle::Result ContextMtl::ensureIncompleteTexturesCreated(const gl::Context *context) |
| { |
| if (ANGLE_LIKELY(mIncompleteTexturesInitialized)) |
| { |
| return angle::Result::Continue; |
| } |
| constexpr gl::TextureType supportedTextureTypes[] = {gl::TextureType::_2D, gl::TextureType::_3D, |
| gl::TextureType::_2DArray, |
| gl::TextureType::CubeMap}; |
| for (gl::TextureType texType : supportedTextureTypes) |
| { |
| gl::Texture *texture; |
| ANGLE_TRY(mIncompleteTextures.getIncompleteTexture( |
| context, texType, gl::SamplerFormat::Float, nullptr, &texture)); |
| |
| TextureMtl *textureMtl = mtl::GetImpl(texture); |
| textureMtl->getNativeTexture()->get().label = @"IncompleteTexture"; |
| } |
| mIncompleteTexturesInitialized = true; |
| |
| return angle::Result::Continue; |
| } |
| |
| // Flush and finish. |
| angle::Result ContextMtl::flush(const gl::Context *context) |
| { |
| flushCommandBuffer(mtl::NoWait); |
| return angle::Result::Continue; |
| } |
| angle::Result ContextMtl::finish(const gl::Context *context) |
| { |
| ANGLE_TRY(finishCommandBuffer()); |
| return angle::Result::Continue; |
| } |
| |
| // Drawing methods. |
| angle::Result ContextMtl::drawTriFanArraysWithBaseVertex(const gl::Context *context, |
| GLint first, |
| GLsizei count, |
| GLsizei instances, |
| GLuint baseInstance) |
| { |
| ASSERT((getDisplay()->getFeatures().hasBaseVertexInstancedDraw.enabled)); |
| |
| uint32_t genIndicesCount; |
| ANGLE_TRY(mtl::GetTriangleFanIndicesCount(this, count, &genIndicesCount)); |
| |
| size_t indexBufferSize = genIndicesCount * sizeof(uint32_t); |
| // We can reuse the previously generated index buffer if it has more than enough indices |
| // data already. |
| if (mTriFanArraysIndexBuffer == nullptr || mTriFanArraysIndexBuffer->size() < indexBufferSize) |
| { |
| // Re-generate a new index buffer, which the first index will be zero. |
| ANGLE_TRY( |
| mtl::Buffer::MakeBuffer(this, indexBufferSize, nullptr, &mTriFanArraysIndexBuffer)); |
| ANGLE_TRY(getDisplay()->getUtils().generateTriFanBufferFromArrays( |
| this, {0, static_cast<uint32_t>(count), mTriFanArraysIndexBuffer, 0})); |
| } |
| |
| ASSERT(!getState().isTransformFeedbackActiveUnpaused()); |
| |
| ANGLE_TRY(setupDraw(context, gl::PrimitiveMode::TriangleFan, first, count, instances, |
| gl::DrawElementsType::InvalidEnum, reinterpret_cast<const void *>(0), |
| false)); |
| |
| // Draw with the zero starting index buffer, shift the vertex index using baseVertex instanced |
| // draw: |
| mRenderEncoder.drawIndexedInstancedBaseVertexBaseInstance( |
| MTLPrimitiveTypeTriangle, genIndicesCount, MTLIndexTypeUInt32, mTriFanArraysIndexBuffer, 0, |
| instances, first, baseInstance); |
| |
| return angle::Result::Continue; |
| } |
| angle::Result ContextMtl::drawTriFanArraysLegacy(const gl::Context *context, |
| GLint first, |
| GLsizei count, |
| GLsizei instances) |
| { |
| // Legacy method is only used for GPU lacking instanced base vertex draw capabilities. |
| mtl::BufferRef genIdxBuffer; |
| uint32_t genIdxBufferOffset; |
| uint32_t genIndicesCount; |
| ANGLE_TRY(AllocateTriangleFanBufferFromPool(this, count, &mTriFanIndexBuffer, &genIdxBuffer, |
| &genIdxBufferOffset, &genIndicesCount)); |
| ANGLE_TRY(getDisplay()->getUtils().generateTriFanBufferFromArrays( |
| this, {static_cast<uint32_t>(first), static_cast<uint32_t>(count), genIdxBuffer, |
| genIdxBufferOffset})); |
| |
| ANGLE_TRY(mTriFanIndexBuffer.commit(this)); |
| |
| ASSERT(!getState().isTransformFeedbackActiveUnpaused()); |
| |
| ANGLE_TRY(setupDraw(context, gl::PrimitiveMode::TriangleFan, first, count, instances, |
| gl::DrawElementsType::InvalidEnum, reinterpret_cast<const void *>(0), |
| false)); |
| |
| mRenderEncoder.drawIndexedInstanced(MTLPrimitiveTypeTriangle, genIndicesCount, |
| MTLIndexTypeUInt32, genIdxBuffer, genIdxBufferOffset, |
| instances); |
| |
| return angle::Result::Continue; |
| } |
| angle::Result ContextMtl::drawTriFanArrays(const gl::Context *context, |
| GLint first, |
| GLsizei count, |
| GLsizei instances, |
| GLuint baseInstance) |
| { |
| if (count <= 3 && baseInstance == 0) |
| { |
| return drawArraysInstanced(context, gl::PrimitiveMode::Triangles, first, count, instances); |
| } |
| if (getDisplay()->getFeatures().hasBaseVertexInstancedDraw.enabled) |
| { |
| return drawTriFanArraysWithBaseVertex(context, first, count, instances, baseInstance); |
| } |
| return drawTriFanArraysLegacy(context, first, count, instances); |
| } |
| |
| angle::Result ContextMtl::drawLineLoopArraysNonInstanced(const gl::Context *context, |
| GLint first, |
| GLsizei count) |
| { |
| // Generate line loop's last segment. It will be rendered when this function exits. |
| LineLoopLastSegmentHelper lineloopHelper; |
| // Line loop helper needs to generate last segment indices before rendering command encoder |
| // starts. |
| ANGLE_TRY(lineloopHelper.begin(context, &mLineLoopLastSegmentIndexBuffer, first, count, |
| gl::DrawElementsType::InvalidEnum, nullptr)); |
| |
| return drawArraysImpl(context, gl::PrimitiveMode::LineStrip, first, count, 0, 0); |
| } |
| |
| angle::Result ContextMtl::drawLineLoopArrays(const gl::Context *context, |
| GLint first, |
| GLsizei count, |
| GLsizei instances, |
| GLuint baseInstance) |
| { |
| if (instances <= 1 && baseInstance == 0) |
| { |
| return drawLineLoopArraysNonInstanced(context, first, count); |
| } |
| |
| mtl::BufferRef genIdxBuffer; |
| uint32_t genIdxBufferOffset; |
| uint32_t genIndicesCount = count + 1; |
| |
| ANGLE_TRY(AllocateBufferFromPool(this, genIndicesCount, &mLineLoopIndexBuffer, &genIdxBuffer, |
| &genIdxBufferOffset)); |
| ANGLE_TRY(getDisplay()->getUtils().generateLineLoopBufferFromArrays( |
| this, {static_cast<uint32_t>(first), static_cast<uint32_t>(count), genIdxBuffer, |
| genIdxBufferOffset})); |
| |
| ANGLE_TRY(mLineLoopIndexBuffer.commit(this)); |
| |
| ASSERT(!getState().isTransformFeedbackActiveUnpaused()); |
| |
| ANGLE_TRY(setupDraw(context, gl::PrimitiveMode::LineLoop, first, count, instances, |
| gl::DrawElementsType::InvalidEnum, nullptr, false)); |
| |
| if (baseInstance == 0) |
| { |
| mRenderEncoder.drawIndexedInstanced(MTLPrimitiveTypeLineStrip, genIndicesCount, |
| MTLIndexTypeUInt32, genIdxBuffer, genIdxBufferOffset, |
| instances); |
| } |
| else |
| { |
| mRenderEncoder.drawIndexedInstancedBaseVertexBaseInstance( |
| MTLPrimitiveTypeLineStrip, genIndicesCount, MTLIndexTypeUInt32, genIdxBuffer, |
| genIdxBufferOffset, instances, 0, baseInstance); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextMtl::drawArraysImpl(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLint first, |
| GLsizei count, |
| GLsizei instances, |
| GLuint baseInstance) |
| { |
| // Real instances count. Zero means this is not instanced draw. |
| GLsizei instanceCount = instances ? instances : 1; |
| |
| if (mCullAllPolygons && gl::IsPolygonMode(mode)) |
| { |
| return angle::Result::Continue; |
| } |
| if (requiresIndexRewrite(context->getState(), mode)) |
| { |
| return drawArraysProvokingVertexImpl(context, mode, first, count, instances, baseInstance); |
| } |
| if (mode == gl::PrimitiveMode::TriangleFan) |
| { |
| return drawTriFanArrays(context, first, count, instanceCount, baseInstance); |
| } |
| else if (mode == gl::PrimitiveMode::LineLoop) |
| { |
| return drawLineLoopArrays(context, first, count, instanceCount, baseInstance); |
| } |
| |
| MTLPrimitiveType mtlType = mtl::GetPrimitiveType(mode); |
| |
| #define DRAW_GENERIC_ARRAY(xfbPass) \ |
| ANGLE_TRY(setupDraw(context, mode, first, count, instances, gl::DrawElementsType::InvalidEnum, \ |
| nullptr, xfbPass)); \ |
| \ |
| if (instances == 0) \ |
| { \ |
| /* This method is called from normal drawArrays() */ \ |
| mRenderEncoder.draw(mtlType, first, count); \ |
| } \ |
| else \ |
| { \ |
| if (baseInstance == 0) \ |
| { \ |
| mRenderEncoder.drawInstanced(mtlType, first, count, instanceCount); \ |
| } \ |
| else \ |
| { \ |
| mRenderEncoder.drawInstancedBaseInstance(mtlType, first, count, instanceCount, \ |
| baseInstance); \ |
| } \ |
| } |
| |
| ANGLE_MTL_XFB_DRAW(DRAW_GENERIC_ARRAY) |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextMtl::drawArrays(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLint first, |
| GLsizei count) |
| { |
| return drawArraysImpl(context, mode, first, count, 0, 0); |
| } |
| |
| angle::Result ContextMtl::drawArraysInstanced(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLint first, |
| GLsizei count, |
| GLsizei instances) |
| { |
| // Instanced draw calls with zero instances are skipped in the frontend. |
| // The drawArraysImpl function would treat them as non-instanced. |
| ASSERT(instances > 0); |
| return drawArraysImpl(context, mode, first, count, instances, 0); |
| } |
| |
| angle::Result ContextMtl::drawArraysInstancedBaseInstance(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLint first, |
| GLsizei count, |
| GLsizei instanceCount, |
| GLuint baseInstance) |
| { |
| // Instanced draw calls with zero instances are skipped in the frontend. |
| // The drawArraysImpl function would treat them as non-instanced. |
| ASSERT(instanceCount > 0); |
| return drawArraysImpl(context, mode, first, count, instanceCount, baseInstance); |
| } |
| |
| angle::Result ContextMtl::drawTriFanElements(const gl::Context *context, |
| GLsizei count, |
| gl::DrawElementsType type, |
| const void *indices, |
| GLsizei instances, |
| GLint baseVertex, |
| GLuint baseInstance) |
| { |
| if (count > 3) |
| { |
| mtl::BufferRef genIdxBuffer; |
| uint32_t genIdxBufferOffset; |
| uint32_t genIndicesCount; |
| bool primitiveRestart = getState().isPrimitiveRestartEnabled(); |
| ANGLE_TRY(AllocateTriangleFanBufferFromPool(this, count, &mTriFanIndexBuffer, &genIdxBuffer, |
| &genIdxBufferOffset, &genIndicesCount)); |
| |
| ANGLE_TRY(getDisplay()->getUtils().generateTriFanBufferFromElementsArray( |
| this, {type, count, indices, genIdxBuffer, genIdxBufferOffset, primitiveRestart}, |
| &genIndicesCount)); |
| |
| ANGLE_TRY(mTriFanIndexBuffer.commit(this)); |
| |
| ANGLE_TRY(setupDraw(context, gl::PrimitiveMode::TriangleFan, 0, count, instances, type, |
| indices, false)); |
| |
| if (baseVertex == 0 && baseInstance == 0) |
| { |
| mRenderEncoder.drawIndexedInstanced(MTLPrimitiveTypeTriangle, genIndicesCount, |
| MTLIndexTypeUInt32, genIdxBuffer, |
| genIdxBufferOffset, instances); |
| } |
| else |
| { |
| mRenderEncoder.drawIndexedInstancedBaseVertexBaseInstance( |
| MTLPrimitiveTypeTriangle, genIndicesCount, MTLIndexTypeUInt32, genIdxBuffer, |
| genIdxBufferOffset, instances, baseVertex, baseInstance); |
| } |
| |
| return angle::Result::Continue; |
| } // if (count > 3) |
| if (baseVertex == 0 && baseInstance == 0) |
| { |
| return drawElementsInstanced(context, gl::PrimitiveMode::Triangles, count, type, indices, |
| instances); |
| } |
| else |
| { |
| return drawElementsInstancedBaseVertexBaseInstance(context, gl::PrimitiveMode::Triangles, |
| count, type, indices, instances, |
| baseVertex, baseInstance); |
| } |
| } |
| |
| angle::Result ContextMtl::drawLineLoopElementsNonInstancedNoPrimitiveRestart( |
| const gl::Context *context, |
| GLsizei count, |
| gl::DrawElementsType type, |
| const void *indices) |
| { |
| // Generate line loop's last segment. It will be rendered when this function exits. |
| LineLoopLastSegmentHelper lineloopHelper; |
| // Line loop helper needs to generate index before rendering command encoder starts. |
| ANGLE_TRY( |
| lineloopHelper.begin(context, &mLineLoopLastSegmentIndexBuffer, 0, count, type, indices)); |
| |
| return drawElementsImpl(context, gl::PrimitiveMode::LineStrip, count, type, indices, 0, 0, 0); |
| } |
| |
| angle::Result ContextMtl::drawLineLoopElements(const gl::Context *context, |
| GLsizei count, |
| gl::DrawElementsType type, |
| const void *indices, |
| GLsizei instances, |
| GLint baseVertex, |
| GLuint baseInstance) |
| { |
| if (count >= 2) |
| { |
| bool primitiveRestart = getState().isPrimitiveRestartEnabled(); |
| if (instances <= 1 && !primitiveRestart && baseVertex == 0 && baseInstance == 0) |
| { |
| // Non instanced draw and no primitive restart, just use faster version. |
| return drawLineLoopElementsNonInstancedNoPrimitiveRestart(context, count, type, |
| indices); |
| } |
| |
| mtl::BufferRef genIdxBuffer; |
| uint32_t genIdxBufferOffset; |
| uint32_t reservedIndices = count * 2; |
| uint32_t genIndicesCount; |
| ANGLE_TRY(AllocateBufferFromPool(this, reservedIndices, &mLineLoopIndexBuffer, |
| &genIdxBuffer, &genIdxBufferOffset)); |
| |
| ANGLE_TRY(getDisplay()->getUtils().generateLineLoopBufferFromElementsArray( |
| this, {type, count, indices, genIdxBuffer, genIdxBufferOffset, primitiveRestart}, |
| &genIndicesCount)); |
| |
| ANGLE_TRY(mLineLoopIndexBuffer.commit(this)); |
| |
| ANGLE_TRY(setupDraw(context, gl::PrimitiveMode::LineLoop, 0, count, instances, type, |
| indices, false)); |
| |
| if (baseVertex == 0 && baseInstance == 0) |
| { |
| mRenderEncoder.drawIndexedInstanced(MTLPrimitiveTypeLineStrip, genIndicesCount, |
| MTLIndexTypeUInt32, genIdxBuffer, |
| genIdxBufferOffset, instances); |
| } |
| else |
| { |
| mRenderEncoder.drawIndexedInstancedBaseVertexBaseInstance( |
| MTLPrimitiveTypeLineStrip, genIndicesCount, MTLIndexTypeUInt32, genIdxBuffer, |
| genIdxBufferOffset, instances, baseVertex, baseInstance); |
| } |
| |
| return angle::Result::Continue; |
| } // if (count >= 2) |
| if (baseVertex == 0 && baseInstance == 0) |
| { |
| return drawElementsInstanced(context, gl::PrimitiveMode::Lines, count, type, indices, |
| instances); |
| } |
| else |
| { |
| return drawElementsInstancedBaseVertexBaseInstance(context, gl::PrimitiveMode::Lines, count, |
| type, indices, instances, baseVertex, |
| baseInstance); |
| } |
| } |
| |
| angle::Result ContextMtl::drawArraysProvokingVertexImpl(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLsizei first, |
| GLsizei count, |
| GLsizei instances, |
| GLuint baseInstance) |
| { |
| |
| size_t outIndexCount = 0; |
| size_t outIndexOffset = 0; |
| gl::DrawElementsType convertedType = gl::DrawElementsType::UnsignedInt; |
| gl::PrimitiveMode outIndexMode = gl::PrimitiveMode::InvalidEnum; |
| |
| mtl::BufferRef drawIdxBuffer = mProvokingVertexHelper.generateIndexBuffer( |
| mtl::GetImpl(context), first, count, mode, convertedType, outIndexCount, outIndexOffset, |
| outIndexMode); |
| GLsizei outIndexCounti32 = static_cast<GLsizei>(outIndexCount); |
| const uint8_t *mappedIndices = drawIdxBuffer->mapReadOnly(this); |
| if (!drawIdxBuffer || !mappedIndices) |
| { |
| return angle::Result::Stop; |
| } |
| |
| #define DRAW_PROVOKING_VERTEX_ARRAY(xfbPass) \ |
| if (xfbPass) \ |
| { \ |
| ANGLE_TRY(setupDraw(context, mode, first, count, instances, \ |
| gl::DrawElementsType::InvalidEnum, nullptr, xfbPass)); \ |
| MTLPrimitiveType mtlType = mtl::GetPrimitiveType(mode); \ |
| if (instances == 0) \ |
| { \ |
| /* This method is called from normal drawArrays() */ \ |
| mRenderEncoder.draw(mtlType, first, count); \ |
| } \ |
| else \ |
| { \ |
| if (baseInstance == 0) \ |
| { \ |
| mRenderEncoder.drawInstanced(mtlType, first, count, instances); \ |
| } \ |
| else \ |
| { \ |
| mRenderEncoder.drawInstancedBaseInstance(mtlType, first, count, instances, \ |
| baseInstance); \ |
| } \ |
| } \ |
| } \ |
| else \ |
| { \ |
| ANGLE_TRY(setupDraw(context, outIndexMode, 0, outIndexCounti32, instances, convertedType, \ |
| mappedIndices + outIndexOffset, xfbPass)); \ |
| \ |
| MTLPrimitiveType mtlType = mtl::GetPrimitiveType(outIndexMode); \ |
| MTLIndexType mtlIdxType = mtl::GetIndexType(convertedType); \ |
| if (instances == 0) \ |
| { \ |
| mRenderEncoder.drawIndexed(mtlType, outIndexCounti32, mtlIdxType, drawIdxBuffer, \ |
| outIndexOffset); \ |
| } \ |
| else \ |
| { \ |
| if (baseInstance == 0) \ |
| { \ |
| mRenderEncoder.drawIndexedInstanced(mtlType, outIndexCounti32, mtlIdxType, \ |
| drawIdxBuffer, outIndexOffset, instances); \ |
| } \ |
| else \ |
| { \ |
| mRenderEncoder.drawIndexedInstancedBaseVertexBaseInstance( \ |
| mtlType, outIndexCounti32, mtlIdxType, drawIdxBuffer, outIndexOffset, \ |
| instances, 0, baseInstance); \ |
| } \ |
| } \ |
| } |
| |
| ANGLE_MTL_XFB_DRAW(DRAW_PROVOKING_VERTEX_ARRAY) |
| drawIdxBuffer->unmapNoFlush(this); |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextMtl::drawElementsImpl(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLsizei count, |
| gl::DrawElementsType type, |
| const void *indices, |
| GLsizei instances, |
| GLint baseVertex, |
| GLuint baseInstance) |
| { |
| // Real instances count. Zero means this is not instanced draw. |
| GLsizei instanceCount = instances ? instances : 1; |
| |
| if (mCullAllPolygons && gl::IsPolygonMode(mode)) |
| { |
| return angle::Result::Continue; |
| } |
| |
| if (mode == gl::PrimitiveMode::TriangleFan) |
| { |
| return drawTriFanElements(context, count, type, indices, instanceCount, baseVertex, |
| baseInstance); |
| } |
| else if (mode == gl::PrimitiveMode::LineLoop) |
| { |
| return drawLineLoopElements(context, count, type, indices, instanceCount, baseVertex, |
| baseInstance); |
| } |
| |
| mtl::BufferRef idxBuffer; |
| mtl::BufferRef drawIdxBuffer; |
| size_t convertedOffset = 0; |
| gl::DrawElementsType convertedType = type; |
| |
| ANGLE_TRY(mVertexArray->getIndexBuffer(context, type, count, indices, &idxBuffer, |
| &convertedOffset, &convertedType)); |
| |
| ASSERT(idxBuffer); |
| ASSERT((convertedType == gl::DrawElementsType::UnsignedShort && (convertedOffset % 2) == 0) || |
| (convertedType == gl::DrawElementsType::UnsignedInt && (convertedOffset % 4) == 0)); |
| |
| uint32_t convertedCounti32 = (uint32_t)count; |
| |
| size_t provokingVertexAdditionalOffset = 0; |
| |
| if (requiresIndexRewrite(context->getState(), mode)) |
| { |
| size_t outIndexCount = 0; |
| gl::PrimitiveMode newMode = gl::PrimitiveMode::InvalidEnum; |
| drawIdxBuffer = mProvokingVertexHelper.preconditionIndexBuffer( |
| mtl::GetImpl(context), idxBuffer, count, convertedOffset, |
| mState.isPrimitiveRestartEnabled(), mode, convertedType, outIndexCount, |
| provokingVertexAdditionalOffset, newMode); |
| if (!drawIdxBuffer) |
| { |
| return angle::Result::Stop; |
| } |
| // Line strips and triangle strips are rewritten to flat line arrays and tri arrays. |
| convertedCounti32 = (uint32_t)outIndexCount; |
| mode = newMode; |
| } |
| else |
| { |
| drawIdxBuffer = idxBuffer; |
| } |
| // Draw commands will only be broken up if transform feedback is enabled, |
| // if the mode is a simple type, and if the buffer contained any restart |
| // indices. |
| // It's safe to use idxBuffer in this case, as it will contain the same count and restart ranges |
| // as drawIdxBuffer. |
| const std::vector<DrawCommandRange> drawCommands = mVertexArray->getDrawIndices( |
| context, type, convertedType, mode, idxBuffer, convertedCounti32, convertedOffset); |
| |
| ANGLE_TRY(setupDraw(context, mode, 0, count, instances, type, indices, false)); |
| |
| MTLPrimitiveType mtlType = mtl::GetPrimitiveType(mode); |
| |
| MTLIndexType mtlIdxType = mtl::GetIndexType(convertedType); |
| |
| if (instances == 0 && baseVertex == 0 && baseInstance == 0) |
| { |
| // Normal draw |
| for (auto &command : drawCommands) |
| { |
| mRenderEncoder.drawIndexed(mtlType, command.count, mtlIdxType, drawIdxBuffer, |
| command.offset + provokingVertexAdditionalOffset); |
| } |
| } |
| else |
| { |
| // Instanced draw |
| if (baseVertex == 0 && baseInstance == 0) |
| { |
| for (auto &command : drawCommands) |
| { |
| mRenderEncoder.drawIndexedInstanced( |
| mtlType, command.count, mtlIdxType, drawIdxBuffer, |
| command.offset + provokingVertexAdditionalOffset, instanceCount); |
| } |
| } |
| else |
| { |
| for (auto &command : drawCommands) |
| { |
| mRenderEncoder.drawIndexedInstancedBaseVertexBaseInstance( |
| mtlType, command.count, mtlIdxType, drawIdxBuffer, |
| command.offset + provokingVertexAdditionalOffset, instanceCount, baseVertex, |
| baseInstance); |
| } |
| } |
| } |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextMtl::drawElements(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLsizei count, |
| gl::DrawElementsType type, |
| const void *indices) |
| { |
| return drawElementsImpl(context, mode, count, type, indices, 0, 0, 0); |
| } |
| |
| angle::Result ContextMtl::drawElementsBaseVertex(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLsizei count, |
| gl::DrawElementsType type, |
| const void *indices, |
| GLint baseVertex) |
| { |
| UNIMPLEMENTED(); |
| return angle::Result::Stop; |
| } |
| |
| angle::Result ContextMtl::drawElementsInstanced(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLsizei count, |
| gl::DrawElementsType type, |
| const void *indices, |
| GLsizei instanceCount) |
| { |
| // Instanced draw calls with zero instances are skipped in the frontend. |
| // The drawElementsImpl function would treat them as non-instanced. |
| ASSERT(instanceCount > 0); |
| return drawElementsImpl(context, mode, count, type, indices, instanceCount, 0, 0); |
| } |
| |
| angle::Result ContextMtl::drawElementsInstancedBaseVertex(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLsizei count, |
| gl::DrawElementsType type, |
| const void *indices, |
| GLsizei instanceCount, |
| GLint baseVertex) |
| { |
| // Instanced draw calls with zero instances are skipped in the frontend. |
| // The drawElementsImpl function would treat them as non-instanced. |
| ASSERT(instanceCount > 0); |
| return drawElementsImpl(context, mode, count, type, indices, instanceCount, baseVertex, 0); |
| } |
| |
| angle::Result ContextMtl::drawElementsInstancedBaseVertexBaseInstance(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLsizei count, |
| gl::DrawElementsType type, |
| const void *indices, |
| GLsizei instances, |
| GLint baseVertex, |
| GLuint baseInstance) |
| { |
| // Instanced draw calls with zero instances are skipped in the frontend. |
| // The drawElementsImpl function would treat them as non-instanced. |
| ASSERT(instances > 0); |
| return drawElementsImpl(context, mode, count, type, indices, instances, baseVertex, |
| baseInstance); |
| } |
| |
| angle::Result ContextMtl::drawRangeElements(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLuint start, |
| GLuint end, |
| GLsizei count, |
| gl::DrawElementsType type, |
| const void *indices) |
| { |
| return drawElementsImpl(context, mode, count, type, indices, 0, 0, 0); |
| } |
| |
| angle::Result ContextMtl::drawRangeElementsBaseVertex(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLuint start, |
| GLuint end, |
| GLsizei count, |
| gl::DrawElementsType type, |
| const void *indices, |
| GLint baseVertex) |
| { |
| // NOTE(hqle): ES 3.2 |
| UNIMPLEMENTED(); |
| return angle::Result::Stop; |
| } |
| |
| angle::Result ContextMtl::drawArraysIndirect(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| const void *indirect) |
| { |
| // NOTE(hqle): ES 3.0 |
| UNIMPLEMENTED(); |
| return angle::Result::Stop; |
| } |
| angle::Result ContextMtl::drawElementsIndirect(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| gl::DrawElementsType type, |
| const void *indirect) |
| { |
| // NOTE(hqle): ES 3.0 |
| UNIMPLEMENTED(); |
| return angle::Result::Stop; |
| } |
| |
| angle::Result ContextMtl::multiDrawArrays(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| const GLint *firsts, |
| const GLsizei *counts, |
| GLsizei drawcount) |
| { |
| return rx::MultiDrawArraysGeneral(this, context, mode, firsts, counts, drawcount); |
| } |
| |
| angle::Result ContextMtl::multiDrawArraysInstanced(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| const GLint *firsts, |
| const GLsizei *counts, |
| const GLsizei *instanceCounts, |
| GLsizei drawcount) |
| { |
| return rx::MultiDrawArraysInstancedGeneral(this, context, mode, firsts, counts, instanceCounts, |
| drawcount); |
| } |
| |
| angle::Result ContextMtl::multiDrawArraysIndirect(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| const void *indirect, |
| GLsizei drawcount, |
| GLsizei stride) |
| { |
| return rx::MultiDrawArraysIndirectGeneral(this, context, mode, indirect, drawcount, stride); |
| } |
| |
| angle::Result ContextMtl::multiDrawElements(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| const GLsizei *counts, |
| gl::DrawElementsType type, |
| const GLvoid *const *indices, |
| GLsizei drawcount) |
| { |
| return rx::MultiDrawElementsGeneral(this, context, mode, counts, type, indices, drawcount); |
| } |
| |
| angle::Result ContextMtl::multiDrawElementsInstanced(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| const GLsizei *counts, |
| gl::DrawElementsType type, |
| const GLvoid *const *indices, |
| const GLsizei *instanceCounts, |
| GLsizei drawcount) |
| { |
| return rx::MultiDrawElementsInstancedGeneral(this, context, mode, counts, type, indices, |
| instanceCounts, drawcount); |
| } |
| |
| angle::Result ContextMtl::multiDrawElementsIndirect(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| gl::DrawElementsType type, |
| const void *indirect, |
| GLsizei drawcount, |
| GLsizei stride) |
| { |
| return rx::MultiDrawElementsIndirectGeneral(this, context, mode, type, indirect, drawcount, |
| stride); |
| } |
| |
| angle::Result ContextMtl::multiDrawArraysInstancedBaseInstance(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| const GLint *firsts, |
| const GLsizei *counts, |
| const GLsizei *instanceCounts, |
| const GLuint *baseInstances, |
| GLsizei drawcount) |
| { |
| return rx::MultiDrawArraysInstancedBaseInstanceGeneral( |
| this, context, mode, firsts, counts, instanceCounts, baseInstances, drawcount); |
| } |
| |
| angle::Result ContextMtl::multiDrawElementsInstancedBaseVertexBaseInstance( |
| const gl::Context *context, |
| gl::PrimitiveMode mode, |
| const GLsizei *counts, |
| gl::DrawElementsType type, |
| const GLvoid *const *indices, |
| const GLsizei *instanceCounts, |
| const GLint *baseVertices, |
| const GLuint *baseInstances, |
| GLsizei drawcount) |
| { |
| return rx::MultiDrawElementsInstancedBaseVertexBaseInstanceGeneral( |
| this, context, mode, counts, type, indices, instanceCounts, baseVertices, baseInstances, |
| drawcount); |
| } |
| |
| // Device loss |
| gl::GraphicsResetStatus ContextMtl::getResetStatus() |
| { |
| return gl::GraphicsResetStatus::NoError; |
| } |
| |
| // EXT_debug_marker |
| angle::Result ContextMtl::insertEventMarker(GLsizei length, const char *marker) |
| { |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextMtl::pushGroupMarker(GLsizei length, const char *marker) |
| { |
| mCmdBuffer.pushDebugGroup(ConvertMarkerToString(length, marker)); |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextMtl::popGroupMarker() |
| { |
| mCmdBuffer.popDebugGroup(); |
| return angle::Result::Continue; |
| } |
| |
| // KHR_debug |
| angle::Result ContextMtl::pushDebugGroup(const gl::Context *context, |
| GLenum source, |
| GLuint id, |
| const std::string &message) |
| { |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextMtl::popDebugGroup(const gl::Context *context) |
| { |
| return angle::Result::Continue; |
| } |
| |
| // State sync with dirty bits. |
| angle::Result ContextMtl::syncState(const gl::Context *context, |
| const gl::State::DirtyBits &dirtyBits, |
| const gl::State::DirtyBits &bitMask, |
| gl::Command command) |
| { |
| const gl::State &glState = context->getState(); |
| |
| // Initialize incomplete texture set. |
| ANGLE_TRY(ensureIncompleteTexturesCreated(context)); |
| |
| // Metal's blend state is set at once, while ANGLE tracks separate dirty |
| // bits: ENABLED, FUNCS, and EQUATIONS. Merge all three of them to the first one. |
| // PS: these can not be statically initialized on some architectures as there is |
| // no constuctor for DirtyBits that takes an int (which becomes BitSetArray<64>). |
| gl::State::DirtyBits checkBlendBitsMask; |
| checkBlendBitsMask.set(gl::State::DIRTY_BIT_BLEND_ENABLED); |
| checkBlendBitsMask.set(gl::State::DIRTY_BIT_BLEND_FUNCS); |
| checkBlendBitsMask.set(gl::State::DIRTY_BIT_BLEND_EQUATIONS); |
| gl::State::DirtyBits resetBlendBitsMask; |
| resetBlendBitsMask.set(gl::State::DIRTY_BIT_BLEND_FUNCS); |
| resetBlendBitsMask.set(gl::State::DIRTY_BIT_BLEND_EQUATIONS); |
| |
| gl::State::DirtyBits mergedDirtyBits = gl::State::DirtyBits(dirtyBits) & ~resetBlendBitsMask; |
| mergedDirtyBits.set(gl::State::DIRTY_BIT_BLEND_ENABLED, (dirtyBits & checkBlendBitsMask).any()); |
| |
| for (size_t dirtyBit : mergedDirtyBits) |
| { |
| switch (dirtyBit) |
| { |
| case gl::State::DIRTY_BIT_SCISSOR_TEST_ENABLED: |
| case gl::State::DIRTY_BIT_SCISSOR: |
| updateScissor(glState); |
| break; |
| case gl::State::DIRTY_BIT_VIEWPORT: |
| { |
| FramebufferMtl *framebufferMtl = mtl::GetImpl(glState.getDrawFramebuffer()); |
| updateViewport(framebufferMtl, glState.getViewport(), glState.getNearPlane(), |
| glState.getFarPlane()); |
| // Update the scissor, which will be constrained to the viewport |
| updateScissor(glState); |
| break; |
| } |
| case gl::State::DIRTY_BIT_DEPTH_RANGE: |
| updateDepthRange(glState.getNearPlane(), glState.getFarPlane()); |
| break; |
| case gl::State::DIRTY_BIT_BLEND_COLOR: |
| mDirtyBits.set(DIRTY_BIT_BLEND_COLOR); |
| break; |
| case gl::State::DIRTY_BIT_BLEND_ENABLED: |
| updateBlendDescArray(glState.getBlendStateExt()); |
| break; |
| case gl::State::DIRTY_BIT_COLOR_MASK: |
| { |
| const gl::BlendStateExt &blendStateExt = glState.getBlendStateExt(); |
| size_t i = 0; |
| for (; i < blendStateExt.getDrawBufferCount(); i++) |
| { |
| mBlendDescArray[i].updateWriteMask(blendStateExt.getColorMaskIndexed(i)); |
| mWriteMaskArray[i] = mBlendDescArray[i].writeMask; |
| } |
| for (; i < mBlendDescArray.size(); i++) |
| { |
| mBlendDescArray[i].updateWriteMask(0); |
| mWriteMaskArray[i] = mBlendDescArray[i].writeMask; |
| } |
| invalidateRenderPipeline(); |
| break; |
| } |
| case gl::State::DIRTY_BIT_SAMPLE_ALPHA_TO_COVERAGE_ENABLED: |
| invalidateRenderPipeline(); |
| break; |
| case gl::State::DIRTY_BIT_SAMPLE_COVERAGE_ENABLED: |
| invalidateRenderPipeline(); |
| break; |
| case gl::State::DIRTY_BIT_SAMPLE_COVERAGE: |
| invalidateDriverUniforms(); |
| break; |
| case gl::State::DIRTY_BIT_SAMPLE_MASK_ENABLED: |
| // NOTE(hqle): 3.1 MSAA support |
| break; |
| case gl::State::DIRTY_BIT_SAMPLE_MASK: |
| // NOTE(hqle): 3.1 MSAA support |
| break; |
| case gl::State::DIRTY_BIT_DEPTH_TEST_ENABLED: |
| mDepthStencilDesc.updateDepthTestEnabled(glState.getDepthStencilState()); |
| mDirtyBits.set(DIRTY_BIT_DEPTH_STENCIL_DESC); |
| break; |
| case gl::State::DIRTY_BIT_DEPTH_FUNC: |
| mDepthStencilDesc.updateDepthCompareFunc(glState.getDepthStencilState()); |
| mDirtyBits.set(DIRTY_BIT_DEPTH_STENCIL_DESC); |
| break; |
| case gl::State::DIRTY_BIT_DEPTH_MASK: |
| mDepthStencilDesc.updateDepthWriteEnabled(glState.getDepthStencilState()); |
| mDirtyBits.set(DIRTY_BIT_DEPTH_STENCIL_DESC); |
| break; |
| case gl::State::DIRTY_BIT_STENCIL_TEST_ENABLED: |
| mDepthStencilDesc.updateStencilTestEnabled(glState.getDepthStencilState()); |
| mDirtyBits.set(DIRTY_BIT_DEPTH_STENCIL_DESC); |
| break; |
| case gl::State::DIRTY_BIT_STENCIL_FUNCS_FRONT: |
| mDepthStencilDesc.updateStencilFrontFuncs(glState.getDepthStencilState()); |
| mStencilRefFront = glState.getStencilRef(); // clamped on the frontend |
| mDirtyBits.set(DIRTY_BIT_DEPTH_STENCIL_DESC); |
| mDirtyBits.set(DIRTY_BIT_STENCIL_REF); |
| break; |
| case gl::State::DIRTY_BIT_STENCIL_FUNCS_BACK: |
| mDepthStencilDesc.updateStencilBackFuncs(glState.getDepthStencilState()); |
| mStencilRefBack = glState.getStencilBackRef(); // clamped on the frontend |
| mDirtyBits.set(DIRTY_BIT_DEPTH_STENCIL_DESC); |
| mDirtyBits.set(DIRTY_BIT_STENCIL_REF); |
| break; |
| case gl::State::DIRTY_BIT_STENCIL_OPS_FRONT: |
| mDepthStencilDesc.updateStencilFrontOps(glState.getDepthStencilState()); |
| mDirtyBits.set(DIRTY_BIT_DEPTH_STENCIL_DESC); |
| break; |
| case gl::State::DIRTY_BIT_STENCIL_OPS_BACK: |
| mDepthStencilDesc.updateStencilBackOps(glState.getDepthStencilState()); |
| mDirtyBits.set(DIRTY_BIT_DEPTH_STENCIL_DESC); |
| break; |
| case gl::State::DIRTY_BIT_STENCIL_WRITEMASK_FRONT: |
| mDepthStencilDesc.updateStencilFrontWriteMask(glState.getDepthStencilState()); |
| mDirtyBits.set(DIRTY_BIT_DEPTH_STENCIL_DESC); |
| break; |
| case gl::State::DIRTY_BIT_STENCIL_WRITEMASK_BACK: |
| mDepthStencilDesc.updateStencilBackWriteMask(glState.getDepthStencilState()); |
| mDirtyBits.set(DIRTY_BIT_DEPTH_STENCIL_DESC); |
| break; |
| case gl::State::DIRTY_BIT_CULL_FACE_ENABLED: |
| case gl::State::DIRTY_BIT_CULL_FACE: |
| updateCullMode(glState); |
| break; |
| case gl::State::DIRTY_BIT_FRONT_FACE: |
| updateFrontFace(glState); |
| break; |
| case gl::State::DIRTY_BIT_POLYGON_OFFSET_FILL_ENABLED: |
| case gl::State::DIRTY_BIT_POLYGON_OFFSET: |
| updateDepthBias(glState); |
| break; |
| case gl::State::DIRTY_BIT_RASTERIZER_DISCARD_ENABLED: |
| mDirtyBits.set(DIRTY_BIT_RASTERIZER_DISCARD); |
| break; |
| case gl::State::DIRTY_BIT_LINE_WIDTH: |
| // Do nothing |
| break; |
| case gl::State::DIRTY_BIT_PRIMITIVE_RESTART_ENABLED: |
| // NOTE(hqle): ES 3.0 feature. |
| break; |
| case gl::State::DIRTY_BIT_CLEAR_COLOR: |
| mClearColor = mtl::ClearColorValue( |
| glState.getColorClearValue().red, glState.getColorClearValue().green, |
| glState.getColorClearValue().blue, glState.getColorClearValue().alpha); |
| break; |
| case gl::State::DIRTY_BIT_CLEAR_DEPTH: |
| break; |
| case gl::State::DIRTY_BIT_CLEAR_STENCIL: |
| mClearStencil = glState.getStencilClearValue() & mtl::kStencilMaskAll; |
| break; |
| case gl::State::DIRTY_BIT_UNPACK_STATE: |
| // This is a no-op, its only important to use the right unpack state when we do |
| // setImage or setSubImage in TextureMtl, which is plumbed through the frontend call |
| break; |
| case gl::State::DIRTY_BIT_UNPACK_BUFFER_BINDING: |
| break; |
| case gl::State::DIRTY_BIT_PACK_STATE: |
| // This is a no-op, its only important to use the right pack state when we do |
| // call readPixels later on. |
| break; |
| case gl::State::DIRTY_BIT_PACK_BUFFER_BINDING: |
| break; |
| case gl::State::DIRTY_BIT_DITHER_ENABLED: |
| break; |
| case gl::State::DIRTY_BIT_READ_FRAMEBUFFER_BINDING: |
| break; |
| case gl::State::DIRTY_BIT_DRAW_FRAMEBUFFER_BINDING: |
| updateDrawFrameBufferBinding(context); |
| break; |
| case gl::State::DIRTY_BIT_RENDERBUFFER_BINDING: |
| break; |
| case gl::State::DIRTY_BIT_VERTEX_ARRAY_BINDING: |
| updateVertexArray(context); |
| break; |
| case gl::State::DIRTY_BIT_DRAW_INDIRECT_BUFFER_BINDING: |
| break; |
| case gl::State::DIRTY_BIT_DISPATCH_INDIRECT_BUFFER_BINDING: |
| break; |
| case gl::State::DIRTY_BIT_PROGRAM_BINDING: |
| mProgram = mtl::GetImpl(glState.getProgram()); |
| break; |
| case gl::State::DIRTY_BIT_PROGRAM_EXECUTABLE: |
| updateProgramExecutable(context); |
| break; |
| case gl::State::DIRTY_BIT_TEXTURE_BINDINGS: |
| invalidateCurrentTextures(); |
| break; |
| case gl::State::DIRTY_BIT_SAMPLER_BINDINGS: |
| invalidateCurrentTextures(); |
| break; |
| case gl::State::DIRTY_BIT_TRANSFORM_FEEDBACK_BINDING: |
| // Nothing to do. |
| break; |
| case gl::State::DIRTY_BIT_SHADER_STORAGE_BUFFER_BINDING: |
| // NOTE(hqle): ES 3.0 feature. |
| break; |
| case gl::State::DIRTY_BIT_UNIFORM_BUFFER_BINDINGS: |
| mDirtyBits.set(DIRTY_BIT_UNIFORM_BUFFERS_BINDING); |
| break; |
| case gl::State::DIRTY_BIT_ATOMIC_COUNTER_BUFFER_BINDING: |
| break; |
| case gl::State::DIRTY_BIT_IMAGE_BINDINGS: |
| // NOTE(hqle): properly handle GLSL images. |
| invalidateCurrentTextures(); |
| break; |
| case gl::State::DIRTY_BIT_MULTISAMPLING: |
| // NOTE(hqle): MSAA on/off. |
| break; |
| case gl::State::DIRTY_BIT_SAMPLE_ALPHA_TO_ONE: |
| // NOTE(hqle): this is part of EXT_multisample_compatibility. |
| // NOTE(hqle): MSAA feature. |
| break; |
| case gl::State::DIRTY_BIT_COVERAGE_MODULATION: |
| break; |
| case gl::State::DIRTY_BIT_FRAMEBUFFER_SRGB_WRITE_CONTROL_MODE: |
| break; |
| case gl::State::DIRTY_BIT_CURRENT_VALUES: |
| { |
| invalidateDefaultAttributes(glState.getAndResetDirtyCurrentValues()); |
| break; |
| } |
| case gl::State::DIRTY_BIT_PROVOKING_VERTEX: |
| break; |
| case gl::State::DIRTY_BIT_EXTENDED: |
| updateExtendedState(glState); |
| // Nothing to do until EXT_clip_control is implemented. |
| break; |
| case gl::State::DIRTY_BIT_SAMPLE_SHADING: |
| // Nothing to do until OES_sample_shading is implemented. |
| break; |
| case gl::State::DIRTY_BIT_PATCH_VERTICES: |
| // Nothing to do until EXT_tessellation_shader is implemented. |
| break; |
| default: |
| UNREACHABLE(); |
| break; |
| } |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| void ContextMtl::updateExtendedState(const gl::State &glState) |
| { |
| // Handling clip distance enabled flags, mipmap generation hint & shader derivative |
| // hint. |
| invalidateDriverUniforms(); |
| } |
| |
| // Disjoint timer queries |
| GLint ContextMtl::getGPUDisjoint() |
| { |
| UNIMPLEMENTED(); |
| return 0; |
| } |
| GLint64 ContextMtl::getTimestamp() |
| { |
| UNIMPLEMENTED(); |
| return 0; |
| } |
| |
| // Context switching |
| angle::Result ContextMtl::onMakeCurrent(const gl::Context *context) |
| { |
| invalidateState(context); |
| return angle::Result::Continue; |
| } |
| angle::Result ContextMtl::onUnMakeCurrent(const gl::Context *context) |
| { |
| flushCommandBuffer(mtl::WaitUntilScheduled); |
| // Note: this 2nd flush is needed because if there is a query in progress |
| // then during flush, new command buffers are allocated that also need |
| // to be flushed. This is a temporary fix and we should probably refactor |
| // this later. See TODO(anglebug.com/7138) |
| flushCommandBuffer(mtl::WaitUntilScheduled); |
| return angle::Result::Continue; |
| } |
| |
| // Native capabilities, unmodified by gl::Context. |
| gl::Caps ContextMtl::getNativeCaps() const |
| { |
| return getDisplay()->getNativeCaps(); |
| } |
| const gl::TextureCapsMap &ContextMtl::getNativeTextureCaps() const |
| { |
| return getDisplay()->getNativeTextureCaps(); |
| } |
| const gl::Extensions &ContextMtl::getNativeExtensions() const |
| { |
| return getDisplay()->getNativeExtensions(); |
| } |
| const gl::Limitations &ContextMtl::getNativeLimitations() const |
| { |
| return getDisplay()->getNativeLimitations(); |
| } |
| ShPixelLocalStorageType ContextMtl::getNativePixelLocalStorageType() const |
| { |
| return getDisplay()->getNativePixelLocalStorageType(); |
| } |
| |
| // Shader creation |
| CompilerImpl *ContextMtl::createCompiler() |
| { |
| ShShaderOutput outputType = |
| getDisplay()->useDirectToMetalCompiler() ? SH_MSL_METAL_OUTPUT : SH_SPIRV_METAL_OUTPUT; |
| return new CompilerMtl(outputType); |
| } |
| ShaderImpl *ContextMtl::createShader(const gl::ShaderState &state) |
| { |
| return new ShaderMtl(state); |
| } |
| ProgramImpl *ContextMtl::createProgram(const gl::ProgramState &state) |
| { |
| return new ProgramMtl(state); |
| } |
| |
| // Framebuffer creation |
| FramebufferImpl *ContextMtl::createFramebuffer(const gl::FramebufferState &state) |
| { |
| return new FramebufferMtl(state, this, /* flipY */ false); |
| } |
| |
| // Texture creation |
| TextureImpl *ContextMtl::createTexture(const gl::TextureState &state) |
| { |
| return new TextureMtl(state); |
| } |
| |
| // Renderbuffer creation |
| RenderbufferImpl *ContextMtl::createRenderbuffer(const gl::RenderbufferState &state) |
| { |
| return new RenderbufferMtl(state); |
| } |
| |
| // Buffer creation |
| BufferImpl *ContextMtl::createBuffer(const gl::BufferState &state) |
| { |
| return new BufferMtl(state); |
| } |
| |
| // Vertex Array creation |
| VertexArrayImpl *ContextMtl::createVertexArray(const gl::VertexArrayState &state) |
| { |
| return new VertexArrayMtl(state, this); |
| } |
| |
| // Query and Fence creation |
| QueryImpl *ContextMtl::createQuery(gl::QueryType type) |
| { |
| return new QueryMtl(type); |
| } |
| FenceNVImpl *ContextMtl::createFenceNV() |
| { |
| return new FenceNVMtl(); |
| } |
| SyncImpl *ContextMtl::createSync() |
| { |
| return new SyncMtl(); |
| } |
| |
| // Transform Feedback creation |
| TransformFeedbackImpl *ContextMtl::createTransformFeedback(const gl::TransformFeedbackState &state) |
| { |
| // NOTE(hqle): ES 3.0 |
| return new TransformFeedbackMtl(state); |
| } |
| |
| // Sampler object creation |
| SamplerImpl *ContextMtl::createSampler(const gl::SamplerState &state) |
| { |
| return new SamplerMtl(state); |
| } |
| |
| // Program Pipeline object creation |
| ProgramPipelineImpl *ContextMtl::createProgramPipeline(const gl::ProgramPipelineState &data) |
| { |
| // NOTE(hqle): ES 3.0 |
| UNIMPLEMENTED(); |
| return nullptr; |
| } |
| |
| // Memory object creation. |
| MemoryObjectImpl *ContextMtl::createMemoryObject() |
| { |
| UNIMPLEMENTED(); |
| return nullptr; |
| } |
| |
| // Semaphore creation. |
| SemaphoreImpl *ContextMtl::createSemaphore() |
| { |
| UNIMPLEMENTED(); |
| return nullptr; |
| } |
| |
| OverlayImpl *ContextMtl::createOverlay(const gl::OverlayState &state) |
| { |
| // Not implemented. |
| return new OverlayImpl(state); |
| } |
| |
| angle::Result ContextMtl::dispatchCompute(const gl::Context *context, |
| GLuint numGroupsX, |
| GLuint numGroupsY, |
| GLuint numGroupsZ) |
| { |
| // NOTE(hqle): ES 3.0 |
| UNIMPLEMENTED(); |
| return angle::Result::Stop; |
| } |
| angle::Result ContextMtl::dispatchComputeIndirect(const gl::Context *context, GLintptr indirect) |
| { |
| // NOTE(hqle): ES 3.0 |
| UNIMPLEMENTED(); |
| return angle::Result::Stop; |
| } |
| |
| angle::Result ContextMtl::memoryBarrier(const gl::Context *context, GLbitfield barriers) |
| { |
| // NOTE(hqle): ES 3.0 |
| UNIMPLEMENTED(); |
| return angle::Result::Stop; |
| } |
| angle::Result ContextMtl::memoryBarrierByRegion(const gl::Context *context, GLbitfield barriers) |
| { |
| // NOTE(hqle): ES 3.0 |
| UNIMPLEMENTED(); |
| return angle::Result::Stop; |
| } |
| |
| // override mtl::ErrorHandler |
| void ContextMtl::handleError(GLenum glErrorCode, |
| const char *message, |
| const char *file, |
| const char *function, |
| unsigned int line) |
| { |
| mErrors->handleError(glErrorCode, message, file, function, line); |
| } |
| |
| void ContextMtl::handleError(NSError *nserror, |
| const char *message, |
| const char *file, |
| const char *function, |
| unsigned int line) |
| { |
| if (!nserror) |
| { |
| return; |
| } |
| |
| mErrors->handleError(GL_INVALID_OPERATION, message, file, function, line); |
| } |
| |
| void ContextMtl::invalidateState(const gl::Context *context) |
| { |
| mDirtyBits.set(); |
| |
| invalidateDefaultAttributes(context->getStateCache().getActiveDefaultAttribsMask()); |
| } |
| |
| void ContextMtl::invalidateDefaultAttribute(size_t attribIndex) |
| { |
| mDirtyDefaultAttribsMask.set(attribIndex); |
| mDirtyBits.set(DIRTY_BIT_DEFAULT_ATTRIBS); |
| } |
| |
| void ContextMtl::invalidateDefaultAttributes(const gl::AttributesMask &dirtyMask) |
| { |
| if (dirtyMask.any()) |
| { |
| mDirtyDefaultAttribsMask |= dirtyMask; |
| mDirtyBits.set(DIRTY_BIT_DEFAULT_ATTRIBS); |
| } |
| |
| // TODO(anglebug.com/5505): determine how to merge this. |
| #if 0 |
| if (getDisplay()->getFeatures().hasExplicitMemBarrier.enabled) |
| { |
| const gl::ProgramExecutable *executable = mState.getProgramExecutable(); |
| ASSERT(executable); |
| ASSERT(executable->hasTransformFeedbackOutput() || mState.isTransformFeedbackActive()); |
| TransformFeedbackMtl *transformFeedbackMtl = mtl::GetImpl(mState.getCurrentTransformFeedback()); |
| size_t bufferCount = executable->getTransformFeedbackBufferCount(); |
| const gl::TransformFeedbackBuffersArray<BufferMtl *> &bufferHandles = |
| transformFeedbackMtl->getBufferHandles(); |
| for (size_t i = 0; i < bufferCount; i++) |
| { |
| const mtl::BufferRef & constBufferRef = bufferHandles[i]->getCurrentBuffer(); |
| mRenderEncoder.memoryBarrierWithResource(constBufferRef, mtl::kRenderStageVertex, mtl::kRenderStageVertex); |
| } |
| } |
| else |
| { |
| //End the command encoder, so any Transform Feedback changes are available to subsequent draw calls. |
| endEncoding(false); |
| } |
| #endif |
| } |
| |
| void ContextMtl::invalidateCurrentTextures() |
| { |
| mDirtyBits.set(DIRTY_BIT_TEXTURES); |
| } |
| |
| void ContextMtl::invalidateDriverUniforms() |
| { |
| mDirtyBits.set(DIRTY_BIT_DRIVER_UNIFORMS); |
| } |
| |
| void ContextMtl::invalidateRenderPipeline() |
| { |
| mDirtyBits.set(DIRTY_BIT_RENDER_PIPELINE); |
| } |
| |
| const mtl::ClearColorValue &ContextMtl::getClearColorValue() const |
| { |
| return mClearColor; |
| } |
| const mtl::WriteMaskArray &ContextMtl::getWriteMaskArray() const |
| { |
| return mWriteMaskArray; |
| } |
| float ContextMtl::getClearDepthValue() const |
| { |
| return getState().getDepthClearValue(); |
| } |
| uint32_t ContextMtl::getClearStencilValue() const |
| { |
| return mClearStencil; |
| } |
| uint32_t ContextMtl::getStencilMask() const |
| { |
| return getState().getDepthStencilState().stencilWritemask & mtl::kStencilMaskAll; |
| } |
| |
| bool ContextMtl::getDepthMask() const |
| { |
| return getState().getDepthStencilState().depthMask; |
| } |
| |
| const mtl::Format &ContextMtl::getPixelFormat(angle::FormatID angleFormatId) const |
| { |
| return getDisplay()->getPixelFormat(angleFormatId); |
| } |
| |
| // See mtl::FormatTable::getVertexFormat() |
| const mtl::VertexFormat &ContextMtl::getVertexFormat(angle::FormatID angleFormatId, |
| bool tightlyPacked) const |
| { |
| return getDisplay()->getVertexFormat(angleFormatId, tightlyPacked); |
| } |
| |
| const mtl::FormatCaps &ContextMtl::getNativeFormatCaps(MTLPixelFormat mtlFormat) const |
| { |
| return getDisplay()->getNativeFormatCaps(mtlFormat); |
| } |
| |
| angle::Result ContextMtl::getIncompleteTexture(const gl::Context *context, |
| gl::TextureType type, |
| gl::Texture **textureOut) |
| { |
| return mIncompleteTextures.getIncompleteTexture(context, type, gl::SamplerFormat::Float, |
| nullptr, textureOut); |
| } |
| |
| void ContextMtl::endRenderEncoding(mtl::RenderCommandEncoder *encoder) |
| { |
| // End any pending visibility query in the render pass |
| if (mOcclusionQuery) |
| { |
| disableActiveOcclusionQueryInRenderPass(); |
| } |
| |
| encoder->endEncoding(); |
| |
| // Resolve visibility results |
| mOcclusionQueryPool.resolveVisibilityResults(this); |
| } |
| |
| void ContextMtl::endEncoding(bool forceSaveRenderPassContent) |
| { |
| if (mRenderEncoder.valid()) |
| { |
| if (forceSaveRenderPassContent) |
| { |
| // Save the work in progress. |
| mRenderEncoder.setStoreAction(MTLStoreActionStore); |
| } |
| |
| endRenderEncoding(&mRenderEncoder); |
| } |
| |
| if (mBlitEncoder.valid()) |
| { |
| mBlitEncoder.endEncoding(); |
| } |
| |
| if (mComputeEncoder.valid()) |
| { |
| mComputeEncoder.endEncoding(); |
| } |
| } |
| |
| void ContextMtl::flushCommandBuffer(mtl::CommandBufferFinishOperation operation) |
| { |
| mProvokingVertexHelper.commitPreconditionCommandBuffer(this); |
| if (!mCmdBuffer.ready()) |
| { |
| return; |
| } |
| |
| endEncoding(true); |
| mCmdBuffer.commit(operation); |
| mRenderPassesSinceFlush = 0; |
| } |
| |
| void ContextMtl::flushCommandBufferIfNeeded() |
| { |
| if (mRenderPassesSinceFlush >= mtl::kMaxRenderPassesPerCommandBuffer) |
| { |
| // WaitUntilScheduled here is intended to help the CPU-GPU pipeline and |
| // helps to keep the number of inflight render passes in the system to a |
| // minimum. |
| flushCommandBuffer(mtl::WaitUntilScheduled); |
| } |
| |
| if (mCmdBuffer.needsFlushForDrawCallLimits()) |
| { |
| flushCommandBuffer(mtl::NoWait); |
| } |
| } |
| |
| void ContextMtl::present(const gl::Context *context, id<CAMetalDrawable> presentationDrawable) |
| { |
| ensureCommandBufferReady(); |
| mProvokingVertexHelper.commitPreconditionCommandBuffer(this); |
| |
| FramebufferMtl *currentframebuffer = mtl::GetImpl(getState().getDrawFramebuffer()); |
| if (currentframebuffer) |
| { |
| currentframebuffer->onFrameEnd(context); |
| } |
| |
| endEncoding(false); |
| mCmdBuffer.present(presentationDrawable); |
| mCmdBuffer.commit(mtl::NoWait); |
| mRenderPassesSinceFlush = 0; |
| } |
| |
| angle::Result ContextMtl::finishCommandBuffer() |
| { |
| flushCommandBuffer(mtl::WaitUntilFinished); |
| return angle::Result::Continue; |
| } |
| |
| bool ContextMtl::hasStartedRenderPass(const mtl::RenderPassDesc &desc) |
| { |
| return mRenderEncoder.valid() && |
| mRenderEncoder.renderPassDesc().equalIgnoreLoadStoreOptions(desc); |
| } |
| |
| // Get current render encoder |
| mtl::RenderCommandEncoder *ContextMtl::getRenderCommandEncoder() |
| { |
| if (!mRenderEncoder.valid()) |
| { |
| return nullptr; |
| } |
| |
| return &mRenderEncoder; |
| } |
| |
| mtl::RenderCommandEncoder *ContextMtl::getRenderPassCommandEncoder(const mtl::RenderPassDesc &desc) |
| { |
| if (hasStartedRenderPass(desc)) |
| { |
| return &mRenderEncoder; |
| } |
| |
| endEncoding(false); |
| |
| ensureCommandBufferReady(); |
| ++mRenderPassesSinceFlush; |
| |
| // Need to re-apply everything on next draw call. |
| mDirtyBits.set(); |
| |
| const mtl::ContextDevice &metalDevice = getMetalDevice(); |
| if (mtl::DeviceHasMaximumRenderTargetSize(metalDevice)) |
| { |
| ANGLE_MTL_OBJC_SCOPE |
| { |
| MTLRenderPassDescriptor *objCDesc = [MTLRenderPassDescriptor renderPassDescriptor]; |
| desc.convertToMetalDesc(objCDesc, getNativeCaps().maxColorAttachments); |
| NSUInteger maxSize = mtl::GetMaxRenderTargetSizeForDeviceInBytes(metalDevice); |
| NSUInteger renderTargetSize = |
| ComputeTotalSizeUsedForMTLRenderPassDescriptor(objCDesc, this, metalDevice); |
| if (renderTargetSize > maxSize) |
| { |
| std::stringstream errorStream; |
| errorStream << "This set of render targets requires " << renderTargetSize |
| << " bytes of pixel storage. This device supports " << maxSize |
| << " bytes."; |
| ANGLE_MTL_HANDLE_ERROR(this, errorStream.str().c_str(), GL_INVALID_OPERATION); |
| return nil; |
| } |
| } |
| } |
| return &mRenderEncoder.restart(desc, getNativeCaps().maxColorAttachments); |
| } |
| |
| // Utilities to quickly create render command encoder to a specific texture: |
| // The previous content of texture will be loaded |
| mtl::RenderCommandEncoder *ContextMtl::getTextureRenderCommandEncoder( |
| const mtl::TextureRef &textureTarget, |
| const mtl::ImageNativeIndex &index) |
| { |
| ASSERT(textureTarget && textureTarget->valid()); |
| |
| mtl::RenderPassDesc rpDesc; |
| |
| rpDesc.colorAttachments[0].texture = textureTarget; |
| rpDesc.colorAttachments[0].level = index.getNativeLevel(); |
| rpDesc.colorAttachments[0].sliceOrDepth = index.hasLayer() ? index.getLayerIndex() : 0; |
| rpDesc.numColorAttachments = 1; |
| rpDesc.sampleCount = textureTarget->samples(); |
| |
| return getRenderPassCommandEncoder(rpDesc); |
| } |
| |
| // The previous content of texture will be loaded if clearColor is not provided |
| mtl::RenderCommandEncoder *ContextMtl::getRenderTargetCommandEncoderWithClear( |
| const RenderTargetMtl &renderTarget, |
| const Optional<MTLClearColor> &clearColor) |
| { |
| ASSERT(renderTarget.getTexture()); |
| |
| mtl::RenderPassDesc rpDesc; |
| renderTarget.toRenderPassAttachmentDesc(&rpDesc.colorAttachments[0]); |
| rpDesc.numColorAttachments = 1; |
| rpDesc.sampleCount = renderTarget.getRenderSamples(); |
| |
| if (clearColor.valid()) |
| { |
| rpDesc.colorAttachments[0].loadAction = MTLLoadActionClear; |
| rpDesc.colorAttachments[0].clearColor = mtl::EmulatedAlphaClearColor( |
| clearColor.value(), renderTarget.getTexture()->getColorWritableMask()); |
| |
| endEncoding(true); |
| } |
| |
| return getRenderPassCommandEncoder(rpDesc); |
| } |
| // The previous content of texture will be loaded |
| mtl::RenderCommandEncoder *ContextMtl::getRenderTargetCommandEncoder( |
| const RenderTargetMtl &renderTarget) |
| { |
| return getRenderTargetCommandEncoderWithClear(renderTarget, Optional<MTLClearColor>()); |
| } |
| |
| mtl::BlitCommandEncoder *ContextMtl::getBlitCommandEncoder() |
| { |
| if (mBlitEncoder.valid()) |
| { |
| return &mBlitEncoder; |
| } |
| |
| endEncoding(true); |
| |
| ensureCommandBufferReady(); |
| |
| return &mBlitEncoder.restart(); |
| } |
| |
| mtl::ComputeCommandEncoder *ContextMtl::getComputeCommandEncoder() |
| { |
| if (mComputeEncoder.valid()) |
| { |
| return &mComputeEncoder; |
| } |
| |
| endEncoding(true); |
| ensureCommandBufferReady(); |
| |
| return &mComputeEncoder.restart(); |
| } |
| |
| mtl::ComputeCommandEncoder *ContextMtl::getIndexPreprocessingCommandEncoder() |
| { |
| return mProvokingVertexHelper.getComputeCommandEncoder(); |
| } |
| |
| void ContextMtl::ensureCommandBufferReady() |
| { |
| flushCommandBufferIfNeeded(); |
| |
| if (getDisplay()->getFeatures().preemptivelyStartProvokingVertexCommandBuffer.enabled) |
| { |
| mProvokingVertexHelper.ensureCommandBufferReady(); |
| } |
| |
| if (!mCmdBuffer.ready()) |
| { |
| mCmdBuffer.restart(); |
| } |
| |
| ASSERT(mCmdBuffer.ready()); |
| } |
| |
| void ContextMtl::updateViewport(FramebufferMtl *framebufferMtl, |
| const gl::Rectangle &viewport, |
| float nearPlane, |
| float farPlane) |
| { |
| mViewport = mtl::GetViewport(viewport, framebufferMtl->getState().getDimensions().height, |
| framebufferMtl->flipY(), nearPlane, farPlane); |
| mDirtyBits.set(DIRTY_BIT_VIEWPORT); |
| |
| invalidateDriverUniforms(); |
| } |
| |
| void ContextMtl::updateDepthRange(float nearPlane, float farPlane) |
| { |
| if (NeedToInvertDepthRange(nearPlane, farPlane)) |
| { |
| // We also need to invert the depth in shader later by using scale value stored in driver |
| // uniform depthRange.reserved |
| std::swap(nearPlane, farPlane); |
| } |
| mViewport.znear = nearPlane; |
| mViewport.zfar = farPlane; |
| mDirtyBits.set(DIRTY_BIT_VIEWPORT); |
| |
| invalidateDriverUniforms(); |
| } |
| |
| void ContextMtl::updateBlendDescArray(const gl::BlendStateExt &blendStateExt) |
| { |
| for (size_t i = 0; i < mBlendDescArray.size(); i++) |
| { |
| mtl::BlendDesc &blendDesc = mBlendDescArray[i]; |
| if (blendStateExt.getEnabledMask().test(i)) |
| { |
| blendDesc.blendingEnabled = true; |
| |
| blendDesc.sourceRGBBlendFactor = |
| mtl::GetBlendFactor(blendStateExt.getSrcColorIndexed(i)); |
| blendDesc.sourceAlphaBlendFactor = |
| mtl::GetBlendFactor(blendStateExt.getSrcAlphaIndexed(i)); |
| blendDesc.destinationRGBBlendFactor = |
| mtl::GetBlendFactor(blendStateExt.getDstColorIndexed(i)); |
| blendDesc.destinationAlphaBlendFactor = |
| mtl::GetBlendFactor(blendStateExt.getDstAlphaIndexed(i)); |
| |
| blendDesc.rgbBlendOperation = mtl::GetBlendOp(blendStateExt.getEquationColorIndexed(i)); |
| blendDesc.alphaBlendOperation = |
| mtl::GetBlendOp(blendStateExt.getEquationAlphaIndexed(i)); |
| } |
| else |
| { |
| // Enforce default state when blending is disabled, |
| blendDesc.reset(blendDesc.writeMask); |
| } |
| } |
| invalidateRenderPipeline(); |
| } |
| |
| void ContextMtl::updateScissor(const gl::State &glState) |
| { |
| FramebufferMtl *framebufferMtl = mtl::GetImpl(glState.getDrawFramebuffer()); |
| gl::Rectangle renderArea = framebufferMtl->getCompleteRenderArea(); |
| |
| ANGLE_MTL_LOG("renderArea = %d,%d,%d,%d", renderArea.x, renderArea.y, renderArea.width, |
| renderArea.height); |
| |
| // Clip the render area to the viewport. |
| gl::Rectangle viewportClippedRenderArea; |
| if (!gl::ClipRectangle(renderArea, glState.getViewport(), &viewportClippedRenderArea)) |
| { |
| viewportClippedRenderArea = gl::Rectangle(); |
| } |
| |
| gl::Rectangle scissoredArea = ClipRectToScissor(getState(), viewportClippedRenderArea, false); |
| if (framebufferMtl->flipY()) |
| { |
| scissoredArea.y = renderArea.height - scissoredArea.y - scissoredArea.height; |
| } |
| |
| ANGLE_MTL_LOG("scissoredArea = %d,%d,%d,%d", scissoredArea.x, scissoredArea.y, |
| scissoredArea.width, scissoredArea.height); |
| |
| mScissorRect = mtl::GetScissorRect(scissoredArea); |
| mDirtyBits.set(DIRTY_BIT_SCISSOR); |
| } |
| |
| void ContextMtl::updateCullMode(const gl::State &glState) |
| { |
| const gl::RasterizerState &rasterState = glState.getRasterizerState(); |
| |
| mCullAllPolygons = false; |
| if (!rasterState.cullFace) |
| { |
| mCullMode = MTLCullModeNone; |
| } |
| else |
| { |
| switch (rasterState.cullMode) |
| { |
| case gl::CullFaceMode::Back: |
| mCullMode = MTLCullModeBack; |
| break; |
| case gl::CullFaceMode::Front: |
| mCullMode = MTLCullModeFront; |
| break; |
| case gl::CullFaceMode::FrontAndBack: |
| mCullAllPolygons = true; |
| break; |
| default: |
| UNREACHABLE(); |
| break; |
| } |
| } |
| |
| mDirtyBits.set(DIRTY_BIT_CULL_MODE); |
| } |
| |
| void ContextMtl::updateFrontFace(const gl::State &glState) |
| { |
| FramebufferMtl *framebufferMtl = mtl::GetImpl(glState.getDrawFramebuffer()); |
| mWinding = |
| mtl::GetFontfaceWinding(glState.getRasterizerState().frontFace, !framebufferMtl->flipY()); |
| mDirtyBits.set(DIRTY_BIT_WINDING); |
| } |
| |
| void ContextMtl::updateDepthBias(const gl::State &glState) |
| { |
| mDirtyBits.set(DIRTY_BIT_DEPTH_BIAS); |
| } |
| |
| // Index rewrite is required if: |
| // Provkoing vertex mode is 'last' |
| // Program has at least one 'flat' attribute |
| // PrimitiveMode is not POINTS. |
| bool ContextMtl::requiresIndexRewrite(const gl::State &state, gl::PrimitiveMode mode) |
| { |
| return mode != gl::PrimitiveMode::Points && mProgram->hasFlatAttribute() && |
| (state.getProvokingVertex() == gl::ProvokingVertexConvention::LastVertexConvention); |
| } |
| |
| void ContextMtl::updateDrawFrameBufferBinding(const gl::Context *context) |
| { |
| const gl::State &glState = getState(); |
| |
| mDrawFramebuffer = mtl::GetImpl(glState.getDrawFramebuffer()); |
| |
| mDrawFramebuffer->onStartedDrawingToFrameBuffer(context); |
| |
| onDrawFrameBufferChangedState(context, mDrawFramebuffer, true); |
| } |
| |
| void ContextMtl::onDrawFrameBufferChangedState(const gl::Context *context, |
| FramebufferMtl *framebuffer, |
| bool renderPassChanged) |
| { |
| const gl::State &glState = getState(); |
| ASSERT(framebuffer == mtl::GetImpl(glState.getDrawFramebuffer())); |
| |
| updateViewport(framebuffer, glState.getViewport(), glState.getNearPlane(), |
| glState.getFarPlane()); |
| updateFrontFace(glState); |
| updateScissor(glState); |
| |
| if (renderPassChanged) |
| { |
| // End any render encoding using the old render pass. |
| endEncoding(false); |
| // Need to re-apply state to RenderCommandEncoder |
| invalidateState(context); |
| } |
| else |
| { |
| // Invalidate current pipeline only. |
| invalidateRenderPipeline(); |
| } |
| } |
| |
| void ContextMtl::onBackbufferResized(const gl::Context *context, WindowSurfaceMtl *backbuffer) |
| { |
| const gl::State &glState = getState(); |
| FramebufferMtl *framebuffer = mtl::GetImpl(glState.getDrawFramebuffer()); |
| if (framebuffer->getAttachedBackbuffer() != backbuffer) |
| { |
| return; |
| } |
| |
| onDrawFrameBufferChangedState(context, framebuffer, true); |
| } |
| |
| angle::Result ContextMtl::onOcclusionQueryBegin(const gl::Context *context, QueryMtl *query) |
| { |
| ASSERT(mOcclusionQuery == nullptr); |
| mOcclusionQuery = query; |
| |
| if (mRenderEncoder.valid()) |
| { |
| // if render pass has started, start the query in the encoder |
| return startOcclusionQueryInRenderPass(query, true); |
| } |
| else |
| { |
| query->resetVisibilityResult(this); |
| } |
| |
| return angle::Result::Continue; |
| } |
| void ContextMtl::onOcclusionQueryEnd(const gl::Context *context, QueryMtl *query) |
| { |
| ASSERT(mOcclusionQuery == query); |
| |
| if (mRenderEncoder.valid()) |
| { |
| // if render pass has started, end the query in the encoder |
| disableActiveOcclusionQueryInRenderPass(); |
| } |
| |
| mOcclusionQuery = nullptr; |
| } |
| void ContextMtl::onOcclusionQueryDestroy(const gl::Context *context, QueryMtl *query) |
| { |
| if (query->getAllocatedVisibilityOffsets().empty()) |
| { |
| return; |
| } |
| if (mOcclusionQuery == query) |
| { |
| onOcclusionQueryEnd(context, query); |
| } |
| mOcclusionQueryPool.deallocateQueryOffset(this, query); |
| } |
| |
| void ContextMtl::disableActiveOcclusionQueryInRenderPass() |
| { |
| if (!mOcclusionQuery || mOcclusionQuery->getAllocatedVisibilityOffsets().empty()) |
| { |
| return; |
| } |
| |
| ASSERT(mRenderEncoder.valid()); |
| mRenderEncoder.setVisibilityResultMode(MTLVisibilityResultModeDisabled, |
| mOcclusionQuery->getAllocatedVisibilityOffsets().back()); |
| } |
| |
| angle::Result ContextMtl::restartActiveOcclusionQueryInRenderPass() |
| { |
| if (!mOcclusionQuery || mOcclusionQuery->getAllocatedVisibilityOffsets().empty()) |
| { |
| return angle::Result::Continue; |
| } |
| |
| return startOcclusionQueryInRenderPass(mOcclusionQuery, false); |
| } |
| |
| angle::Result ContextMtl::startOcclusionQueryInRenderPass(QueryMtl *query, bool clearOldValue) |
| { |
| ASSERT(mRenderEncoder.valid()); |
| |
| ANGLE_TRY(mOcclusionQueryPool.allocateQueryOffset(this, query, clearOldValue)); |
| |
| mRenderEncoder.setVisibilityResultMode(MTLVisibilityResultModeBoolean, |
| query->getAllocatedVisibilityOffsets().back()); |
| |
| // We need to mark the query's buffer as being written in this command buffer now. Since the |
| // actual writing is deferred until the render pass ends and user could try to read the query |
| // result before the render pass ends. |
| mCmdBuffer.setWriteDependency(query->getVisibilityResultBuffer()); |
| |
| return angle::Result::Continue; |
| } |
| |
| void ContextMtl::onTransformFeedbackActive(const gl::Context *context, TransformFeedbackMtl *xfb) |
| { |
| // NOTE(hqle): We have to end current render pass to enable synchronization before XFB |
| // buffers could be used as vertex input. Consider a better approach. |
| endEncoding(true); |
| } |
| |
| void ContextMtl::onTransformFeedbackInactive(const gl::Context *context, TransformFeedbackMtl *xfb) |
| { |
| // NOTE(hqle): We have to end current render pass to enable synchronization before XFB |
| // buffers could be used as vertex input. Consider a better approach. |
| endEncoding(true); |
| } |
| |
| void ContextMtl::queueEventSignal(const mtl::SharedEventRef &event, uint64_t value) |
| { |
| ensureCommandBufferReady(); |
| mCmdBuffer.queueEventSignal(event, value); |
| } |
| |
| void ContextMtl::serverWaitEvent(const mtl::SharedEventRef &event, uint64_t value) |
| { |
| ensureCommandBufferReady(); |
| |
| // Event waiting cannot be encoded if there is active encoder. |
| endEncoding(true); |
| |
| mCmdBuffer.serverWaitEvent(event, value); |
| } |
| |
| void ContextMtl::updateProgramExecutable(const gl::Context *context) |
| { |
| // Need to rebind textures |
| invalidateCurrentTextures(); |
| // Need to re-upload default attributes |
| invalidateDefaultAttributes(context->getStateCache().getActiveDefaultAttribsMask()); |
| // Render pipeline need to be re-applied |
| invalidateRenderPipeline(); |
| } |
| |
| void ContextMtl::updateVertexArray(const gl::Context *context) |
| { |
| const gl::State &glState = getState(); |
| mVertexArray = mtl::GetImpl(glState.getVertexArray()); |
| invalidateDefaultAttributes(context->getStateCache().getActiveDefaultAttribsMask()); |
| invalidateRenderPipeline(); |
| } |
| |
| angle::Result ContextMtl::updateDefaultAttribute(size_t attribIndex) |
| { |
| const gl::State &glState = mState; |
| const gl::VertexAttribCurrentValueData &defaultValue = |
| glState.getVertexAttribCurrentValues()[attribIndex]; |
| |
| constexpr size_t kDefaultGLAttributeValueSize = |
| sizeof(gl::VertexAttribCurrentValueData::Values); |
| |
| static_assert(kDefaultGLAttributeValueSize == mtl::kDefaultAttributeSize, |
| "Unexpected default attribute size"); |
| memcpy(mDefaultAttributes[attribIndex].values, &defaultValue.Values, |
| mtl::kDefaultAttributeSize); |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextMtl::setupDraw(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLint firstVertex, |
| GLsizei vertexOrIndexCount, |
| GLsizei instances, |
| gl::DrawElementsType indexTypeOrNone, |
| const void *indices, |
| bool xfbPass) |
| { |
| ANGLE_TRY(setupDrawImpl(context, mode, firstVertex, vertexOrIndexCount, instances, |
| indexTypeOrNone, indices, xfbPass)); |
| if (!mRenderEncoder.valid()) |
| { |
| // Flush occurred during setup, due to running out of memory while setting up the render |
| // pass state. This would happen for example when there is no more space in the uniform |
| // buffers in the uniform buffer pool. The rendering would be flushed to free the uniform |
| // buffer memory for new usage. In this case, re-run the setup. |
| ANGLE_TRY(setupDrawImpl(context, mode, firstVertex, vertexOrIndexCount, instances, |
| indexTypeOrNone, indices, xfbPass)); |
| // Setup with flushed state should either produce a working encoder or fail with an error |
| // result. |
| ASSERT(mRenderEncoder.valid()); |
| } |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextMtl::setupDrawImpl(const gl::Context *context, |
| gl::PrimitiveMode mode, |
| GLint firstVertex, |
| GLsizei vertexOrIndexCount, |
| GLsizei instances, |
| gl::DrawElementsType indexTypeOrNone, |
| const void *indices, |
| bool xfbPass) |
| { |
| ASSERT(mProgram); |
| |
| // instances=0 means no instanced draw. |
| GLsizei instanceCount = instances ? instances : 1; |
| |
| if (context->getStateCache().hasAnyActiveClientAttrib()) |
| { |
| ANGLE_TRY(mVertexArray->updateClientAttribs(context, firstVertex, vertexOrIndexCount, |
| instanceCount, indexTypeOrNone, indices)); |
| } |
| |
| // This must be called before render command encoder is started. |
| bool textureChanged = false; |
| if (mDirtyBits.test(DIRTY_BIT_TEXTURES)) |
| { |
| textureChanged = true; |
| ANGLE_TRY(handleDirtyActiveTextures(context)); |
| } |
| |
| if (mDirtyBits.test(DIRTY_BIT_RASTERIZER_DISCARD)) |
| { |
| if (getState().isTransformFeedbackActiveUnpaused()) |
| { |
| // If XFB is active we need to reset render pass since we could use a dummy render |
| // target if only XFB is needed. |
| invalidateState(context); |
| } |
| else |
| { |
| invalidateRenderPipeline(); |
| } |
| } |
| |
| if (!mRenderEncoder.valid()) |
| { |
| // re-apply everything |
| invalidateState(context); |
| } |
| |
| if (mDirtyBits.test(DIRTY_BIT_DRAW_FRAMEBUFFER)) |
| { |
| ANGLE_TRY(handleDirtyRenderPass(context)); |
| } |
| |
| if (mOcclusionQuery && mOcclusionQueryPool.getNumRenderPassAllocatedQueries() == 0) |
| { |
| // The occlusion query is still active, and a new render pass has started. |
| // We need to continue the querying process in the new render encoder. |
| ANGLE_TRY(startOcclusionQueryInRenderPass(mOcclusionQuery, false)); |
| } |
| |
| bool isPipelineDescChanged; |
| ANGLE_TRY(checkIfPipelineChanged(context, mode, xfbPass, &isPipelineDescChanged)); |
| |
| bool uniformBuffersDirty = false; |
| |
| if (IsTransformFeedbackOnly(getState())) |
| { |
| // Filter out unneeded dirty bits |
| filterOutXFBOnlyDirtyBits(context); |
| } |
| |
| for (size_t bit : mDirtyBits) |
| { |
| switch (bit) |
| { |
| case DIRTY_BIT_TEXTURES: |
| // Already handled. |
| break; |
| case DIRTY_BIT_DEFAULT_ATTRIBS: |
| ANGLE_TRY(handleDirtyDefaultAttribs(context)); |
| break; |
| case DIRTY_BIT_DRIVER_UNIFORMS: |
| ANGLE_TRY(handleDirtyDriverUniforms(context, firstVertex, vertexOrIndexCount)); |
| break; |
| case DIRTY_BIT_DEPTH_STENCIL_DESC: |
| ANGLE_TRY(handleDirtyDepthStencilState(context)); |
| break; |
| case DIRTY_BIT_DEPTH_BIAS: |
| ANGLE_TRY(handleDirtyDepthBias(context)); |
| break; |
| case DIRTY_BIT_STENCIL_REF: |
| mRenderEncoder.setStencilRefVals(mStencilRefFront, mStencilRefBack); |
| break; |
| case DIRTY_BIT_BLEND_COLOR: |
| mRenderEncoder.setBlendColor( |
| mState.getBlendColor().red, mState.getBlendColor().green, |
| mState.getBlendColor().blue, mState.getBlendColor().alpha); |
| break; |
| case DIRTY_BIT_VIEWPORT: |
| mRenderEncoder.setViewport(mViewport); |
| break; |
| case DIRTY_BIT_SCISSOR: |
| mRenderEncoder.setScissorRect(mScissorRect); |
| break; |
| case DIRTY_BIT_DRAW_FRAMEBUFFER: |
| // Already handled. |
| break; |
| case DIRTY_BIT_CULL_MODE: |
| mRenderEncoder.setCullMode(mCullMode); |
| break; |
| case DIRTY_BIT_WINDING: |
| mRenderEncoder.setFrontFacingWinding(mWinding); |
| break; |
| case DIRTY_BIT_RENDER_PIPELINE: |
| // Already handled. See checkIfPipelineChanged(). |
| break; |
| case DIRTY_BIT_UNIFORM_BUFFERS_BINDING: |
| uniformBuffersDirty = true; |
| break; |
| case DIRTY_BIT_RASTERIZER_DISCARD: |
| // Already handled. |
| break; |
| default: |
| UNREACHABLE(); |
| break; |
| } |
| } |
| |
| if (xfbPass && !mDirtyBits.test(DIRTY_BIT_DRIVER_UNIFORMS)) |
| { |
| // If handleDirtyDriverUniforms() was not called and this is XFB pass, we still need to |
| // update XFB related uniforms |
| ANGLE_TRY( |
| fillDriverXFBUniforms(firstVertex, vertexOrIndexCount, /** skippedInstances */ 0)); |
| mRenderEncoder.setVertexData(mDriverUniforms, mtl::kDriverUniformsBindingIndex); |
| } |
| |
| mDirtyBits.reset(); |
| |
| ANGLE_TRY(mProgram->setupDraw(context, &mRenderEncoder, mRenderPipelineDesc, |
| isPipelineDescChanged, textureChanged, uniformBuffersDirty)); |
| |
| return angle::Result::Continue; |
| } |
| |
| void ContextMtl::filterOutXFBOnlyDirtyBits(const gl::Context *context) |
| { |
| ASSERT(IsTransformFeedbackOnly(getState())); |
| |
| ASSERT(mRenderEncoder.renderPassDesc().colorAttachments[0].texture == mDummyXFBRenderTexture); |
| |
| // In transform feedback only pass, only vertex shader's related states are needed. |
| constexpr size_t kUnneededBits = |
| angle::Bit<size_t>(DIRTY_BIT_DEPTH_STENCIL_DESC) | |
| angle::Bit<size_t>(DIRTY_BIT_DEPTH_BIAS) | angle::Bit<size_t>(DIRTY_BIT_STENCIL_REF) | |
| angle::Bit<size_t>(DIRTY_BIT_BLEND_COLOR) | angle::Bit<size_t>(DIRTY_BIT_VIEWPORT) | |
| angle::Bit<size_t>(DIRTY_BIT_SCISSOR) | angle::Bit<size_t>(DIRTY_BIT_CULL_MODE) | |
| angle::Bit<size_t>(DIRTY_BIT_WINDING); |
| |
| mDirtyBits &= ~kUnneededBits; |
| } |
| |
| angle::Result ContextMtl::handleDirtyRenderPass(const gl::Context *context) |
| { |
| if (!IsTransformFeedbackOnly(mState)) |
| { |
| // Start new render command encoder |
| ANGLE_MTL_TRY(this, mDrawFramebuffer->ensureRenderPassStarted(context)); |
| } |
| else |
| { |
| // XFB is active and rasterization is disabled. Use dummy render target. |
| // We currently need to end the render pass when XFB is activated/deactivated so using |
| // a small dummy render target would make the render pass ending very cheap. |
| if (!mDummyXFBRenderTexture) |
| { |
| ANGLE_TRY(mtl::Texture::Make2DTexture(this, |
| getPixelFormat(angle::FormatID::R8G8B8A8_UNORM), |
| 1, 1, 1, true, false, &mDummyXFBRenderTexture)); |
| } |
| mtl::RenderCommandEncoder *encoder = getTextureRenderCommandEncoder( |
| mDummyXFBRenderTexture, |
| mtl::ImageNativeIndex::FromBaseZeroGLIndex(gl::ImageIndex::Make2D(0))); |
| encoder->setColorLoadAction(MTLLoadActionDontCare, MTLClearColor(), 0); |
| encoder->setColorStoreAction(MTLStoreActionDontCare); |
| |
| #ifndef NDEBUG |
| encoder->setLabel(@"TransformFeedbackOnlyPass"); |
| #endif |
| } |
| |
| // re-apply everything |
| invalidateState(context); |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextMtl::handleDirtyActiveTextures(const gl::Context *context) |
| { |
| const gl::State &glState = mState; |
| const gl::Program *program = glState.getProgram(); |
| |
| const gl::ActiveTexturesCache &textures = glState.getActiveTexturesCache(); |
| const gl::ActiveTextureMask &activeTextures = program->getExecutable().getActiveSamplersMask(); |
| |
| for (size_t textureUnit : activeTextures) |
| { |
| gl::Texture *texture = textures[textureUnit]; |
| |
| if (texture == nullptr) |
| { |
| continue; |
| } |
| |
| TextureMtl *textureMtl = mtl::GetImpl(texture); |
| |
| // Make sure texture's images update will be transferred to GPU. |
| ANGLE_TRY(textureMtl->ensureTextureCreated(context)); |
| |
| // The binding of this texture will be done by ProgramMtl. |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextMtl::handleDirtyDefaultAttribs(const gl::Context *context) |
| { |
| for (size_t attribIndex : mDirtyDefaultAttribsMask) |
| { |
| ANGLE_TRY(updateDefaultAttribute(attribIndex)); |
| } |
| |
| ASSERT(mRenderEncoder.valid()); |
| mRenderEncoder.setVertexData(mDefaultAttributes, mtl::kDefaultAttribsBindingIndex); |
| |
| mDirtyDefaultAttribsMask.reset(); |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextMtl::handleDirtyDriverUniforms(const gl::Context *context, |
| GLint drawCallFirstVertex, |
| uint32_t verticesPerInstance) |
| { |
| mDriverUniforms.depthRange[0] = mState.getNearPlane(); |
| mDriverUniforms.depthRange[1] = mState.getFarPlane(); |
| |
| mDriverUniforms.renderArea = mDrawFramebuffer->getState().getDimensions().height << 16 | |
| mDrawFramebuffer->getState().getDimensions().width; |
| |
| const float flipX = 1.0; |
| const float flipY = mDrawFramebuffer->flipY() ? -1.0f : 1.0f; |
| mDriverUniforms.flipXY = gl::PackSnorm4x8(flipX, flipY, flipX, -flipY); |
| |
| // gl_ClipDistance |
| const uint32_t enabledClipDistances = mState.getEnabledClipDistances().bits(); |
| ASSERT((enabledClipDistances & ~sh::vk::kDriverUniformsMiscEnabledClipPlanesMask) == 0); |
| |
| mDriverUniforms.misc = enabledClipDistances |
| << sh::vk::kDriverUniformsMiscEnabledClipPlanesOffset; |
| |
| // Sample coverage mask |
| const uint32_t sampleBitCount = mDrawFramebuffer->getSamples(); |
| const uint32_t coverageSampleBitCount = |
| static_cast<uint32_t>(std::round(mState.getSampleCoverageValue() * sampleBitCount)); |
| ASSERT(sampleBitCount < 32); |
| const uint32_t sampleMask = (1u << sampleBitCount) - 1; |
| uint32_t coverageMask = (1u << coverageSampleBitCount) - 1; |
| if (mState.getSampleCoverageInvert()) |
| { |
| coverageMask = sampleMask & (~coverageMask); |
| } |
| mDriverUniforms.coverageMask = coverageMask; |
| |
| ANGLE_TRY( |
| fillDriverXFBUniforms(drawCallFirstVertex, verticesPerInstance, /** skippedInstances */ 0)); |
| |
| ASSERT(mRenderEncoder.valid()); |
| mRenderEncoder.setFragmentData(mDriverUniforms, mtl::kDriverUniformsBindingIndex); |
| mRenderEncoder.setVertexData(mDriverUniforms, mtl::kDriverUniformsBindingIndex); |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextMtl::fillDriverXFBUniforms(GLint drawCallFirstVertex, |
| uint32_t verticesPerInstance, |
| uint32_t skippedInstances) |
| { |
| gl::TransformFeedback *transformFeedback = getState().getCurrentTransformFeedback(); |
| |
| bool xfbActiveUnpaused = getState().isTransformFeedbackActiveUnpaused(); |
| if (!transformFeedback || !xfbActiveUnpaused) |
| { |
| return angle::Result::Continue; |
| } |
| |
| mDriverUniforms.xfbVerticesPerInstance = verticesPerInstance; |
| |
| TransformFeedbackMtl *transformFeedbackMtl = mtl::GetImpl(transformFeedback); |
| |
| return transformFeedbackMtl->getBufferOffsets(this, drawCallFirstVertex, |
| verticesPerInstance * skippedInstances, |
| mDriverUniforms.xfbBufferOffsets); |
| } |
| |
| angle::Result ContextMtl::handleDirtyDepthStencilState(const gl::Context *context) |
| { |
| ASSERT(mRenderEncoder.valid()); |
| |
| // Need to handle the case when render pass doesn't have depth/stencil attachment. |
| mtl::DepthStencilDesc dsDesc = mDepthStencilDesc; |
| const mtl::RenderPassDesc &renderPassDesc = mRenderEncoder.renderPassDesc(); |
| |
| if (!renderPassDesc.depthAttachment.texture) |
| { |
| dsDesc.depthWriteEnabled = false; |
| dsDesc.depthCompareFunction = MTLCompareFunctionAlways; |
| } |
| |
| if (!renderPassDesc.stencilAttachment.texture) |
| { |
| dsDesc.frontFaceStencil.reset(); |
| dsDesc.backFaceStencil.reset(); |
| } |
| |
| // Apply depth stencil state |
| mRenderEncoder.setDepthStencilState( |
| getDisplay()->getStateCache().getDepthStencilState(getMetalDevice(), dsDesc)); |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextMtl::handleDirtyDepthBias(const gl::Context *context) |
| { |
| const gl::RasterizerState &rasterState = mState.getRasterizerState(); |
| ASSERT(mRenderEncoder.valid()); |
| if (!mState.isPolygonOffsetFillEnabled()) |
| { |
| mRenderEncoder.setDepthBias(0, 0, 0); |
| } |
| else |
| { |
| mRenderEncoder.setDepthBias(rasterState.polygonOffsetUnits, rasterState.polygonOffsetFactor, |
| 0); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextMtl::checkIfPipelineChanged(const gl::Context *context, |
| gl::PrimitiveMode primitiveMode, |
| bool xfbPass, |
| bool *isPipelineDescChanged) |
| { |
| ASSERT(mRenderEncoder.valid()); |
| mtl::PrimitiveTopologyClass topologyClass = mtl::GetPrimitiveTopologyClass(primitiveMode); |
| |
| bool rppChange = mDirtyBits.test(DIRTY_BIT_RENDER_PIPELINE) || |
| topologyClass != mRenderPipelineDesc.inputPrimitiveTopology; |
| |
| // Obtain RenderPipelineDesc's vertex array descriptor. |
| ANGLE_TRY(mVertexArray->setupDraw(context, &mRenderEncoder, &rppChange, |
| &mRenderPipelineDesc.vertexDescriptor)); |
| |
| if (rppChange) |
| { |
| const mtl::RenderPassDesc &renderPassDesc = mRenderEncoder.renderPassDesc(); |
| // Obtain RenderPipelineDesc's output descriptor. |
| renderPassDesc.populateRenderPipelineOutputDesc(mBlendDescArray, |
| &mRenderPipelineDesc.outputDescriptor); |
| |
| if (xfbPass) |
| { |
| // In XFB pass, we disable fragment shader. |
| mRenderPipelineDesc.rasterizationType = mtl::RenderPipelineRasterization::Disabled; |
| } |
| else if (mState.isRasterizerDiscardEnabled()) |
| { |
| // If XFB is not active and rasterizer discard is enabled, we need to emulate the |
| // discard. Because in this case, vertex shader might write to stage output values and |
| // Metal doesn't allow rasterization to be disabled. |
| mRenderPipelineDesc.rasterizationType = |
| mtl::RenderPipelineRasterization::EmulatedDiscard; |
| } |
| else |
| { |
| mRenderPipelineDesc.rasterizationType = mtl::RenderPipelineRasterization::Enabled; |
| } |
| mRenderPipelineDesc.inputPrimitiveTopology = topologyClass; |
| mRenderPipelineDesc.alphaToCoverageEnabled = mState.isSampleAlphaToCoverageEnabled(); |
| mRenderPipelineDesc.emulateCoverageMask = mState.isSampleCoverageEnabled(); |
| |
| mRenderPipelineDesc.outputDescriptor.updateEnabledDrawBuffers( |
| mDrawFramebuffer->getState().getEnabledDrawBuffers()); |
| } |
| |
| *isPipelineDescChanged = rppChange; |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextMtl::copy2DTextureSlice0Level0ToWorkTexture(const mtl::TextureRef &srcTexture) |
| { |
| if (!mWorkTexture || !mWorkTexture->sameTypeAndDimemsionsAs(srcTexture)) |
| { |
| auto formatId = mtl::Format::MetalToAngleFormatID(srcTexture->pixelFormat()); |
| auto format = getPixelFormat(formatId); |
| |
| ANGLE_TRY(mtl::Texture::Make2DTexture(this, format, srcTexture->widthAt0(), |
| srcTexture->heightAt0(), srcTexture->mipmapLevels(), |
| false, true, &mWorkTexture)); |
| } |
| auto *blitEncoder = getBlitCommandEncoder(); |
| blitEncoder->copyTexture(srcTexture, |
| 0, // srcStartSlice |
| mtl::MipmapNativeLevel(0), // MipmapNativeLevel |
| mWorkTexture, // dst |
| 0, // dstStartSlice |
| mtl::MipmapNativeLevel(0), // dstStartLevel |
| 1, // sliceCount, |
| 1); // levelCount |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result ContextMtl::copyTextureSliceLevelToWorkBuffer( |
| const gl::Context *context, |
| const mtl::TextureRef &srcTexture, |
| const mtl::MipmapNativeLevel &mipNativeLevel, |
| uint32_t layerIndex) |
| { |
| auto formatId = mtl::Format::MetalToAngleFormatID(srcTexture->pixelFormat()); |
| const mtl::Format &metalFormat = getPixelFormat(formatId); |
| const angle::Format &angleFormat = metalFormat.actualAngleFormat(); |
| |
| uint32_t width = srcTexture->width(mipNativeLevel); |
| uint32_t height = srcTexture->height(mipNativeLevel); |
| uint32_t sizeInBytes = width * height * angleFormat.pixelBytes; |
| |
| // Expand the buffer if it is not big enough. |
| if (!mWorkBuffer || mWorkBuffer->size() < sizeInBytes) |
| { |
| ANGLE_TRY(mtl::Buffer::MakeBuffer(this, sizeInBytes, nullptr, &mWorkBuffer)); |
| } |
| |
| gl::Rectangle region(0, 0, width, height); |
| uint32_t bytesPerRow = angleFormat.pixelBytes * width; |
| uint32_t destOffset = 0; |
| ANGLE_TRY(mtl::ReadTexturePerSliceBytesToBuffer(context, srcTexture, bytesPerRow, region, |
| mipNativeLevel, layerIndex, destOffset, |
| mWorkBuffer)); |
| |
| return angle::Result::Continue; |
| } |
| |
| } // namespace rx |