blob: a0e89d58aecfbfb74ca0611ea6082f1163f8521c [file] [log] [blame]
//
// 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 "common/debug.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/RenderBufferMtl.h"
#include "libANGLE/renderer/metal/ShaderMtl.h"
#include "libANGLE/renderer/metal/TextureMtl.h"
#include "libANGLE/renderer/metal/VertexArrayMtl.h"
#include "libANGLE/renderer/metal/mtl_command_buffer.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
angle::Result TriangleFanBoundCheck(ContextMtl *context, size_t numTris)
{
bool indexCheck =
(numTris > std::numeric_limits<unsigned int>::max() / (sizeof(unsigned int) * 3));
ANGLE_CHECK(context, !indexCheck,
"Failed to create a scratch index buffer for GL_TRIANGLE_FAN, "
"too many indices required.",
GL_OUT_OF_MEMORY);
return angle::Result::Continue;
}
angle::Result GetTriangleFanIndicesCount(ContextMtl *context,
GLsizei vetexCount,
uint32_t *numElemsOut)
{
size_t numTris = vetexCount - 2;
ANGLE_TRY(TriangleFanBoundCheck(context, numTris));
size_t numIndices = numTris * 3;
ANGLE_CHECK(context, numIndices <= std::numeric_limits<uint32_t>::max(),
"Failed to create a scratch index buffer for GL_TRIANGLE_FAN, "
"too many indices required.",
GL_OUT_OF_MEMORY);
*numElemsOut = static_cast<uint32_t>(numIndices);
return angle::Result::Continue;
}
angle::Result AllocateTriangleFanBufferFromPool(ContextMtl *context,
GLsizei vertexCount,
mtl::BufferPool *pool,
mtl::BufferRef *bufferOut,
uint32_t *offsetOut,
uint32_t *numElemsOut)
{
uint32_t numIndices;
ANGLE_TRY(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;
}
bool NeedToInvertDepthRange(float near, float far)
{
return near > far;
}
} // namespace
ContextMtl::ContextMtl(const gl::State &state, gl::ErrorSet *errorSet, DisplayMtl *display)
: ContextImpl(state, errorSet),
mtl::Context(display),
mCmdBuffer(&display->cmdQueue()),
mRenderEncoder(&mCmdBuffer),
mBlitEncoder(&mCmdBuffer),
mComputeEncoder(&mCmdBuffer)
{}
ContextMtl::~ContextMtl() {}
angle::Result ContextMtl::initialize()
{
mBlendDesc.reset();
mDepthStencilDesc.reset();
mTriFanIndexBuffer.initialize(this, 0, mtl::kBufferSettingOffsetAlignment,
kMaxTriFanLineLoopBuffersPerFrame);
mLineLoopIndexBuffer.initialize(this, 0, 2 * sizeof(uint32_t),
kMaxTriFanLineLoopBuffersPerFrame);
mLineLoopIndexBuffer.setAlwaysAllocateNewBuffer(true);
return angle::Result::Continue;
}
void ContextMtl::onDestroy(const gl::Context *context)
{
mTriFanIndexBuffer.destroy(this);
mLineLoopIndexBuffer.destroy(this);
}
// Flush and finish.
angle::Result ContextMtl::flush(const gl::Context *context)
{
flushCommandBufer();
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)
{
uint32_t genIndicesCount;
ANGLE_TRY(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(
context, {0, static_cast<uint32_t>(count), mTriFanArraysIndexBuffer, 0}));
}
ANGLE_TRY(setupDraw(context, gl::PrimitiveMode::TriangleFan, first, count, instances,
gl::DrawElementsType::InvalidEnum, reinterpret_cast<const void *>(0)));
// Draw with the zero starting index buffer, shift the vertex index using baseVertex instanced
// draw:
mRenderEncoder.drawIndexedInstancedBaseVertex(MTLPrimitiveTypeTriangle, genIndicesCount,
MTLIndexTypeUInt32, mTriFanArraysIndexBuffer, 0,
instances, first);
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 draw capabilities.
ASSERT(instances == 1);
mtl::BufferRef genIdxBuffer;
uint32_t genIdxBufferOffset;
uint32_t genIndicesCount;
ANGLE_TRY(AllocateTriangleFanBufferFromPool(this, count, &mTriFanIndexBuffer, &genIdxBuffer,
&genIdxBufferOffset, &genIndicesCount));
ANGLE_TRY(getDisplay()->getUtils().generateTriFanBufferFromArrays(
context, {static_cast<uint32_t>(first), static_cast<uint32_t>(count), genIdxBuffer,
genIdxBufferOffset}));
ANGLE_TRY(setupDraw(context, gl::PrimitiveMode::TriangleFan, first, count, instances,
gl::DrawElementsType::InvalidEnum, reinterpret_cast<const void *>(0)));
mRenderEncoder.drawIndexed(MTLPrimitiveTypeTriangle, genIndicesCount, MTLIndexTypeUInt32,
genIdxBuffer, genIdxBufferOffset);
return angle::Result::Continue;
}
angle::Result ContextMtl::drawTriFanArrays(const gl::Context *context,
GLint first,
GLsizei count,
GLsizei instances)
{
if (count <= 3)
{
return drawArraysInstanced(context, gl::PrimitiveMode::Triangles, first, count, instances);
}
if (getDisplay()->getFeatures().hasBaseVertexInstancedDraw.enabled)
{
return drawTriFanArraysWithBaseVertex(context, first, count, instances);
}
return drawTriFanArraysLegacy(context, first, count, instances);
}
angle::Result ContextMtl::drawArraysImpl(const gl::Context *context,
gl::PrimitiveMode mode,
GLint first,
GLsizei count,
GLsizei instances)
{
// 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 drawTriFanArrays(context, first, count, instanceCount);
}
MTLPrimitiveType mtlType = mtl::GetPrimitiveType(mode);
ANGLE_TRY(setupDraw(context, mode, first, count, instances, gl::DrawElementsType::InvalidEnum,
nullptr));
if (instances == 0)
{
// This method is called from normal drawArrays()
mRenderEncoder.draw(mtlType, first, count);
}
else
{
mRenderEncoder.drawInstanced(mtlType, first, count, instanceCount);
}
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);
}
angle::Result ContextMtl::drawArraysInstanced(const gl::Context *context,
gl::PrimitiveMode mode,
GLint first,
GLsizei count,
GLsizei instances)
{
if (instances == 0)
{
return angle::Result::Continue;
}
return drawArraysImpl(context, mode, first, count, instances);
}
angle::Result ContextMtl::drawArraysInstancedBaseInstance(const gl::Context *context,
gl::PrimitiveMode mode,
GLint first,
GLsizei count,
GLsizei instanceCount,
GLuint baseInstance)
{
UNIMPLEMENTED();
return angle::Result::Stop;
}
angle::Result ContextMtl::drawTriFanElements(const gl::Context *context,
GLsizei count,
gl::DrawElementsType type,
const void *indices,
GLsizei instances)
{
if (count > 3)
{
mtl::BufferRef genIdxBuffer;
uint32_t genIdxBufferOffset;
uint32_t genIndicesCount;
ANGLE_TRY(AllocateTriangleFanBufferFromPool(this, count, &mTriFanIndexBuffer, &genIdxBuffer,
&genIdxBufferOffset, &genIndicesCount));
ANGLE_TRY(getDisplay()->getUtils().generateTriFanBufferFromElementsArray(
context, {type, count, indices, genIdxBuffer, genIdxBufferOffset}));
ANGLE_TRY(mTriFanIndexBuffer.commit(this));
ANGLE_TRY(
setupDraw(context, gl::PrimitiveMode::TriangleFan, 0, count, instances, type, indices));
mRenderEncoder.drawIndexedInstanced(MTLPrimitiveTypeTriangle, genIndicesCount,
MTLIndexTypeUInt32, genIdxBuffer, genIdxBufferOffset,
instances);
return angle::Result::Continue;
} // if (count > 3)
return drawElementsInstanced(context, gl::PrimitiveMode::Triangles, count, type, indices,
instances);
}
angle::Result ContextMtl::drawElementsImpl(const gl::Context *context,
gl::PrimitiveMode mode,
GLsizei count,
gl::DrawElementsType type,
const void *indices,
GLsizei instances)
{
// 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);
}
mtl::BufferRef idxBuffer;
size_t convertedOffset = 0;
gl::DrawElementsType convertedType = type;
ANGLE_TRY(mVertexArray->getIndexBuffer(context, type, count, indices, &idxBuffer,
&convertedOffset, &convertedType));
ASSERT(idxBuffer);
ASSERT((convertedOffset % mtl::kIndexBufferOffsetAlignment) == 0);
ANGLE_TRY(setupDraw(context, mode, 0, count, instances, type, indices));
MTLPrimitiveType mtlType = mtl::GetPrimitiveType(mode);
MTLIndexType mtlIdxType = mtl::GetIndexType(convertedType);
if (instances == 0)
{
// Normal draw
mRenderEncoder.drawIndexed(mtlType, count, mtlIdxType, idxBuffer, convertedOffset);
}
else
{
// Instanced draw
mRenderEncoder.drawIndexedInstanced(mtlType, count, mtlIdxType, idxBuffer, convertedOffset,
instanceCount);
}
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);
}
angle::Result ContextMtl::drawElementsBaseVertex(const gl::Context *context,
gl::PrimitiveMode mode,
GLsizei count,
gl::DrawElementsType type,
const void *indices,
GLint baseVertex)
{
// NOTE(hqle): ES 3.2
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)
{
if (instanceCount == 0)
{
return angle::Result::Continue;
}
return drawElementsImpl(context, mode, count, type, indices, instanceCount);
}
angle::Result ContextMtl::drawElementsInstancedBaseVertex(const gl::Context *context,
gl::PrimitiveMode mode,
GLsizei count,
gl::DrawElementsType type,
const void *indices,
GLsizei instanceCount,
GLint baseVertex)
{
// NOTE(hqle): ES 3.2
UNIMPLEMENTED();
return angle::Result::Stop;
}
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)
{
UNIMPLEMENTED();
return angle::Result::Stop;
}
angle::Result ContextMtl::drawRangeElements(const gl::Context *context,
gl::PrimitiveMode mode,
GLuint start,
GLuint end,
GLsizei count,
gl::DrawElementsType type,
const void *indices)
{
// NOTE(hqle): ES 3.0
UNIMPLEMENTED();
return angle::Result::Stop;
}
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;
}
// Device loss
gl::GraphicsResetStatus ContextMtl::getResetStatus()
{
return gl::GraphicsResetStatus::NoError;
}
// Vendor and description strings.
std::string ContextMtl::getVendorString() const
{
return getDisplay()->getVendorString();
}
std::string ContextMtl::getRendererDescription() const
{
return getDisplay()->getRendererDescription();
}
// EXT_debug_marker
void ContextMtl::insertEventMarker(GLsizei length, const char *marker) {}
void ContextMtl::pushGroupMarker(GLsizei length, const char *marker) {}
void ContextMtl::popGroupMarker()
{
// TODO(hqle
}
// KHR_debug
void ContextMtl::pushDebugGroup(GLenum source, GLuint id, const std::string &message) {}
void ContextMtl::popDebugGroup() {}
// State sync with dirty bits.
angle::Result ContextMtl::syncState(const gl::Context *context,
const gl::State::DirtyBits &dirtyBits,
const gl::State::DirtyBits &bitMask)
{
const gl::State &glState = context->getState();
for (size_t dirtyBit : dirtyBits)
{
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:
mBlendDesc.updateBlendEnabled(glState.getBlendState());
invalidateRenderPipeline();
break;
case gl::State::DIRTY_BIT_BLEND_FUNCS:
mBlendDesc.updateBlendFactors(glState.getBlendState());
invalidateRenderPipeline();
break;
case gl::State::DIRTY_BIT_BLEND_EQUATIONS:
mBlendDesc.updateBlendOps(glState.getBlendState());
invalidateRenderPipeline();
break;
case gl::State::DIRTY_BIT_COLOR_MASK:
mBlendDesc.updateWriteMask(glState.getBlendState());
invalidateRenderPipeline();
break;
case gl::State::DIRTY_BIT_SAMPLE_ALPHA_TO_COVERAGE_ENABLED:
// NOTE(hqle): MSAA support
break;
case gl::State::DIRTY_BIT_SAMPLE_COVERAGE_ENABLED:
// NOTE(hqle): MSAA support
break;
case gl::State::DIRTY_BIT_SAMPLE_COVERAGE:
// NOTE(hqle): MSAA support
break;
case gl::State::DIRTY_BIT_SAMPLE_MASK_ENABLED:
// NOTE(hqle): MSAA support
break;
case gl::State::DIRTY_BIT_SAMPLE_MASK:
// NOTE(hqle): 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());
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());
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:
// NOTE(hqle): ES 3.0 feature.
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.red = glState.getColorClearValue().red;
mClearColor.green = glState.getColorClearValue().green;
mClearColor.blue = glState.getColorClearValue().blue;
mClearColor.alpha = glState.getColorClearValue().alpha;
break;
case gl::State::DIRTY_BIT_CLEAR_DEPTH:
break;
case gl::State::DIRTY_BIT_CLEAR_STENCIL:
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_GENERATE_MIPMAP_HINT:
break;
case gl::State::DIRTY_BIT_SHADER_DERIVATIVE_HINT:
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:
// NOTE(hqle): ES 3.0 feature.
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 feature.
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_PATH_RENDERING:
break;
case gl::State::DIRTY_BIT_FRAMEBUFFER_SRGB:
break;
case gl::State::DIRTY_BIT_CURRENT_VALUES:
{
invalidateDefaultAttributes(glState.getAndResetDirtyCurrentValues());
break;
}
case gl::State::DIRTY_BIT_PROVOKING_VERTEX:
break;
default:
UNREACHABLE();
break;
}
}
return angle::Result::Continue;
}
// 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)
{
flushCommandBufer();
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();
}
// Shader creation
CompilerImpl *ContextMtl::createCompiler()
{
return new CompilerMtl();
}
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, 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)
{
// NOTE(hqle): ES 3.0
UNIMPLEMENTED();
return nullptr;
}
FenceNVImpl *ContextMtl::createFenceNV()
{
UNIMPLEMENTED();
return nullptr;
}
SyncImpl *ContextMtl::createSync()
{
UNIMPLEMENTED();
return nullptr;
}
// Transform Feedback creation
TransformFeedbackImpl *ContextMtl::createTransformFeedback(const gl::TransformFeedbackState &state)
{
// NOTE(hqle): ES 3.0
UNIMPLEMENTED();
return nullptr;
}
// Sampler object creation
SamplerImpl *ContextMtl::createSampler(const gl::SamplerState &state)
{
// NOTE(hqle): ES 3.0
UNIMPLEMENTED();
return nullptr;
}
// Program Pipeline object creation
ProgramPipelineImpl *ContextMtl::createProgramPipeline(const gl::ProgramPipelineState &data)
{
// NOTE(hqle): ES 3.0
UNIMPLEMENTED();
return nullptr;
}
// Path object creation
std::vector<PathImpl *> ContextMtl::createPaths(GLsizei)
{
UNIMPLEMENTED();
return std::vector<PathImpl *>();
}
// Memory object creation.
MemoryObjectImpl *ContextMtl::createMemoryObject()
{
UNIMPLEMENTED();
return nullptr;
}
// Semaphore creation.
SemaphoreImpl *ContextMtl::createSemaphore()
{
UNIMPLEMENTED();
return nullptr;
}
OverlayImpl *ContextMtl::createOverlay(const gl::OverlayState &state)
{
UNIMPLEMENTED();
return nullptr;
}
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 *file,
const char *function,
unsigned int line)
{
std::stringstream errorStream;
errorStream << "Metal backend encountered an error. Code=" << glErrorCode << ".";
mErrors->handleError(glErrorCode, errorStream.str().c_str(), file, function, line);
}
void ContextMtl::handleError(NSError *nserror,
const char *file,
const char *function,
unsigned int line)
{
if (!nserror)
{
return;
}
std::stringstream errorStream;
errorStream << "Metal backend encountered an error: \n"
<< nserror.localizedDescription.UTF8String;
mErrors->handleError(GL_INVALID_OPERATION, errorStream.str().c_str(), 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);
}
}
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 MTLClearColor &ContextMtl::getClearColorValue() const
{
return mClearColor;
}
MTLColorWriteMask ContextMtl::getColorMask() const
{
return mBlendDesc.writeMask;
}
float ContextMtl::getClearDepthValue() const
{
return getState().getDepthClearValue();
}
uint32_t ContextMtl::getClearStencilValue() const
{
return static_cast<uint32_t>(getState().getStencilClearValue());
}
uint32_t ContextMtl::getStencilMask() const
{
return getState().getDepthStencilState().stencilWritemask;
}
bool ContextMtl::isDepthWriteEnabled() const
{
return mDepthStencilDesc.depthWriteEnabled;
}
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);
}
void ContextMtl::endEncoding(mtl::RenderCommandEncoder *encoder)
{
encoder->endEncoding();
}
void ContextMtl::endEncoding(bool forceSaveRenderPassContent)
{
if (mRenderEncoder.valid())
{
if (forceSaveRenderPassContent)
{
// Save the work in progress.
mRenderEncoder.setColorStoreAction(MTLStoreActionStore);
mRenderEncoder.setDepthStencilStoreAction(MTLStoreActionStore, MTLStoreActionStore);
}
mRenderEncoder.endEncoding();
}
if (mBlitEncoder.valid())
{
mBlitEncoder.endEncoding();
}
if (mComputeEncoder.valid())
{
mComputeEncoder.endEncoding();
}
}
void ContextMtl::flushCommandBufer()
{
if (!mCmdBuffer.valid())
{
return;
}
endEncoding(true);
mCmdBuffer.commit();
}
void ContextMtl::present(const gl::Context *context, id<CAMetalDrawable> presentationDrawable)
{
ensureCommandBufferValid();
// Always discard default FBO's depth stencil buffers at the end of the frame:
if (mDrawFramebufferIsDefault && hasStartedRenderPass(mDrawFramebuffer))
{
constexpr GLenum dsAttachments[] = {GL_DEPTH, GL_STENCIL};
(void)mDrawFramebuffer->invalidate(context, 2, dsAttachments);
endEncoding(false);
// Reset discard flag by notify framebuffer that a new render pass has started.
mDrawFramebuffer->onStartedDrawingToFrameBuffer(context);
}
endEncoding(false);
mCmdBuffer.present(presentationDrawable);
mCmdBuffer.commit();
}
angle::Result ContextMtl::finishCommandBuffer()
{
flushCommandBufer();
if (mCmdBuffer.valid())
{
mCmdBuffer.finish();
}
return angle::Result::Continue;
}
bool ContextMtl::hasStartedRenderPass(const mtl::RenderPassDesc &desc)
{
return mRenderEncoder.valid() &&
mRenderEncoder.renderPassDesc().equalIgnoreLoadStoreOptions(desc);
}
bool ContextMtl::hasStartedRenderPass(FramebufferMtl *framebuffer)
{
return framebuffer && hasStartedRenderPass(framebuffer->getRenderPassDesc(this));
}
// Get current render encoder
mtl::RenderCommandEncoder *ContextMtl::getRenderCommandEncoder()
{
if (!mRenderEncoder.valid())
{
return nullptr;
}
return &mRenderEncoder;
}
mtl::RenderCommandEncoder *ContextMtl::getCurrentFramebufferRenderCommandEncoder()
{
if (!mDrawFramebuffer)
{
return nullptr;
}
return getRenderCommandEncoder(mDrawFramebuffer->getRenderPassDesc(this));
}
mtl::RenderCommandEncoder *ContextMtl::getRenderCommandEncoder(const mtl::RenderPassDesc &desc)
{
if (hasStartedRenderPass(desc))
{
return &mRenderEncoder;
}
endEncoding(false);
ensureCommandBufferValid();
// Need to re-apply everything on next draw call.
mDirtyBits.set();
return &mRenderEncoder.restart(desc);
}
// Utilities to quickly create render command enconder to a specific texture:
// The previous content of texture will be loaded if clearColor is not provided
mtl::RenderCommandEncoder *ContextMtl::getRenderCommandEncoder(
const mtl::TextureRef &textureTarget,
const gl::ImageIndex &index,
const Optional<MTLClearColor> &clearColor)
{
ASSERT(textureTarget && textureTarget->valid());
mtl::RenderPassDesc rpDesc;
rpDesc.colorAttachments[0].texture = textureTarget;
rpDesc.colorAttachments[0].level = index.getLevelIndex();
rpDesc.colorAttachments[0].slice = index.hasLayer() ? index.getLayerIndex() : 0;
rpDesc.numColorAttachments = 1;
if (clearColor.valid())
{
rpDesc.colorAttachments[0].loadAction = MTLLoadActionClear;
rpDesc.colorAttachments[0].clearColor =
mtl::EmulatedAlphaClearColor(clearColor.value(), textureTarget->getColorWritableMask());
}
return getRenderCommandEncoder(rpDesc);
}
// The previous content of texture will be loaded
mtl::RenderCommandEncoder *ContextMtl::getRenderCommandEncoder(const mtl::TextureRef &textureTarget,
const gl::ImageIndex &index)
{
return getRenderCommandEncoder(textureTarget, index, Optional<MTLClearColor>());
}
mtl::BlitCommandEncoder *ContextMtl::getBlitCommandEncoder()
{
if (mBlitEncoder.valid())
{
return &mBlitEncoder;
}
endEncoding(true);
ensureCommandBufferValid();
return &mBlitEncoder.restart();
}
mtl::ComputeCommandEncoder *ContextMtl::getComputeCommandEncoder()
{
if (mComputeEncoder.valid())
{
return &mComputeEncoder;
}
endEncoding(true);
ensureCommandBufferValid();
return &mComputeEncoder.restart();
}
void ContextMtl::ensureCommandBufferValid()
{
if (!mCmdBuffer.valid())
{
mCmdBuffer.restart();
}
ASSERT(mCmdBuffer.valid());
}
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::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;
gl::ClipRectangle(renderArea, glState.getViewport(), &viewportClippedRenderArea);
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);
}
void ContextMtl::updateDrawFrameBufferBinding(const gl::Context *context)
{
const gl::State &glState = getState();
mDrawFramebuffer = mtl::GetImpl(glState.getDrawFramebuffer());
mDrawFramebufferIsDefault = mDrawFramebuffer->getState().isDefault();
mDrawFramebuffer->onStartedDrawingToFrameBuffer(context);
onDrawFrameBufferChange(context, mDrawFramebuffer);
}
void ContextMtl::onDrawFrameBufferChange(const gl::Context *context, FramebufferMtl *framebuffer)
{
const gl::State &glState = getState();
ASSERT(framebuffer == mtl::GetImpl(glState.getDrawFramebuffer()));
mDirtyBits.set(DIRTY_BIT_DRAW_FRAMEBUFFER);
updateViewport(framebuffer, glState.getViewport(), glState.getNearPlane(),
glState.getFarPlane());
updateFrontFace(glState);
updateScissor(glState);
// Need to re-apply state to RenderCommandEncoder
invalidateState(context);
}
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)
{
ASSERT(mProgram);
// instances=0 means no instanced draw.
GLsizei instanceCount = instances ? instances : 1;
mtl::BufferRef lineLoopLastSegmentIndexBuffer;
if (mode == gl::PrimitiveMode::LineLoop)
{
// Generate line loop last segment before render command encoder is created
ANGLE_TRY(genLineLoopLastSegment(context, firstVertex, vertexOrIndexCount, instanceCount,
indexTypeOrNone, indices,
&lineLoopLastSegmentIndexBuffer));
}
// Must be called before the render command encoder is started.
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 (!mRenderEncoder.valid())
{
// re-apply everything
invalidateState(context);
}
if (mDirtyBits.test(DIRTY_BIT_DRAW_FRAMEBUFFER))
{
// Start new render command encoder
const mtl::RenderPassDesc &rpDesc = mDrawFramebuffer->getRenderPassDesc(this);
ANGLE_MTL_TRY(this, getRenderCommandEncoder(rpDesc));
// re-apply everything
invalidateState(context);
}
Optional<mtl::RenderPipelineDesc> changedPipelineDesc;
ANGLE_TRY(checkIfPipelineChanged(context, mode, &changedPipelineDesc));
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));
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(mState.getStencilRef(),
mState.getStencilBackRef());
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;
default:
UNREACHABLE();
break;
}
}
mDirtyBits.reset();
ANGLE_TRY(mProgram->setupDraw(context, &mRenderEncoder, changedPipelineDesc, textureChanged));
if (mode == gl::PrimitiveMode::LineLoop)
{
// Draw last segment of line loop here
if (instances == 0)
{
mRenderEncoder.drawIndexed(MTLPrimitiveTypeLine, 2, MTLIndexTypeUInt32,
lineLoopLastSegmentIndexBuffer, 0);
}
else
{
mRenderEncoder.drawIndexedInstanced(MTLPrimitiveTypeLine, 2, MTLIndexTypeUInt32,
lineLoopLastSegmentIndexBuffer, 0, instanceCount);
}
}
return angle::Result::Continue;
}
angle::Result ContextMtl::genLineLoopLastSegment(const gl::Context *context,
GLint firstVertex,
GLsizei vertexOrIndexCount,
GLsizei instanceCount,
gl::DrawElementsType indexTypeOrNone,
const void *indices,
mtl::BufferRef *lastSegmentIndexBufferOut)
{
mLineLoopIndexBuffer.releaseInFlightBuffers(this);
mtl::BufferRef newBuffer;
ANGLE_TRY(mLineLoopIndexBuffer.allocate(this, 2 * sizeof(uint32_t), nullptr, &newBuffer,
nullptr, nullptr));
if (indexTypeOrNone == gl::DrawElementsType::InvalidEnum)
{
ANGLE_TRY(getDisplay()->getUtils().generateLineLoopLastSegment(
context, firstVertex, firstVertex + vertexOrIndexCount - 1, newBuffer, 0));
}
else
{
// NOTE(hqle): Support drawRangeElements & instanced draw, which means firstVertex has to be
// taken into account
ASSERT(firstVertex == 0);
ANGLE_TRY(getDisplay()->getUtils().generateLineLoopLastSegmentFromElementsArray(
context, {indexTypeOrNone, vertexOrIndexCount, indices, newBuffer, 0}));
}
ANGLE_TRY(mLineLoopIndexBuffer.commit(this));
*lastSegmentIndexBufferOut = newBuffer;
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::ActiveTexturePointerArray &textures = glState.getActiveTexturesCache();
const gl::ActiveTextureMask &activeTextures = program->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.setFragmentData(mDefaultAttributes, mtl::kDefaultAttribsBindingIndex);
mRenderEncoder.setVertexData(mDefaultAttributes, mtl::kDefaultAttribsBindingIndex);
mDirtyDefaultAttribsMask.reset();
return angle::Result::Continue;
}
angle::Result ContextMtl::handleDirtyDriverUniforms(const gl::Context *context)
{
const gl::Rectangle &glViewport = mState.getViewport();
float depthRangeNear = mState.getNearPlane();
float depthRangeFar = mState.getFarPlane();
float depthRangeDiff = depthRangeFar - depthRangeNear;
mDriverUniforms.viewport[0] = glViewport.x;
mDriverUniforms.viewport[1] = glViewport.y;
mDriverUniforms.viewport[2] = glViewport.width;
mDriverUniforms.viewport[3] = glViewport.height;
mDriverUniforms.halfRenderAreaHeight =
static_cast<float>(mDrawFramebuffer->getState().getDimensions().height) * 0.5f;
mDriverUniforms.viewportYScale = mDrawFramebuffer->flipY() ? -1.0f : 1.0f;
mDriverUniforms.negViewportYScale = -mDriverUniforms.viewportYScale;
mDriverUniforms.depthRange[0] = depthRangeNear;
mDriverUniforms.depthRange[1] = depthRangeFar;
mDriverUniforms.depthRange[2] = depthRangeDiff;
mDriverUniforms.depthRange[3] = NeedToInvertDepthRange(depthRangeNear, depthRangeFar) ? -1 : 1;
ASSERT(mRenderEncoder.valid());
mRenderEncoder.setFragmentData(mDriverUniforms, mtl::kDriverUniformsBindingIndex);
mRenderEncoder.setVertexData(mDriverUniforms, mtl::kDriverUniformsBindingIndex);
return angle::Result::Continue;
}
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 = mDrawFramebuffer->getRenderPassDesc(this);
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()->getDepthStencilState(dsDesc));
return angle::Result::Continue;
}
angle::Result ContextMtl::handleDirtyDepthBias(const gl::Context *context)
{
const gl::RasterizerState &raserState = mState.getRasterizerState();
ASSERT(mRenderEncoder.valid());
if (!mState.isPolygonOffsetFillEnabled())
{
mRenderEncoder.setDepthBias(0, 0, 0);
}
else
{
mRenderEncoder.setDepthBias(raserState.polygonOffsetUnits, raserState.polygonOffsetFactor,
0);
}
return angle::Result::Continue;
}
angle::Result ContextMtl::checkIfPipelineChanged(
const gl::Context *context,
gl::PrimitiveMode primitiveMode,
Optional<mtl::RenderPipelineDesc> *changedPipelineDesc)
{
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 = mDrawFramebuffer->getRenderPassDesc(this);
// Obtain RenderPipelineDesc's output descriptor.
renderPassDesc.populateRenderPipelineOutputDesc(mBlendDesc,
&mRenderPipelineDesc.outputDescriptor);
mRenderPipelineDesc.inputPrimitiveTopology = topologyClass;
*changedPipelineDesc = mRenderPipelineDesc;
}
return angle::Result::Continue;
}
}