Commit 6136cbcb by Le Hoang Quyen Committed by Commit Bot

Metal: Implement transform feedback

- XFB is currently emulated by writing to storage buffers. - Metal doesn't allow vertex shader to both write to storage buffers and to stage output (i.e clip position). So if GL_RASTERIZER_DISCARD is NOT enabled, the draw with XFB enabled will have 2 passes: + First pass: vertex shader writes to XFB buffers + not write to stage output + disable rasterizer. + Second pass: vertex shader writes to stage output (i.e. [[position]]) + enable rasterizer. If GL_RASTERIZER_DISCARD is enabled, the second pass is omitted. + This effectively executes the same vertex shader twice. TODO: possible improvement is writing vertex outputs to buffer in first pass then re-use that buffer as input for second pass which has a passthrough vertex shader. - If GL_RASTERIZER_DISCARD is enabled, and XFB is enabled: + Only first pass above will be executed, and the render pass will use an empty 1x1 texture attachment since rasterization is not needed. - If GL_RASTERIZER_DISCARD is enabled, but XFB is NOT enabled: + we still enable Metal rasterizer. + but vertex shader must emulate the discard by writing gl_Position = (-3, -3, -3, 1). This effectively moves the vertex out of clip space's visible area. + This is because GLSL still allows vertex shader to write to stage output when rasterizer is disabled. However, Metal doesn't allow that. In Metal, if rasterizer is disabled, then vertex shader must not write to stage output. - See src/libANGLE/renderer/metal/doc/TransformFeedback.md for more details. Bug: angleproject:2634 Change-Id: I6c700e031052560326b7f660ee7597202d38e6aa Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2408594Reviewed-by: 's avatarJonah Ryan-Davis <jonahr@google.com> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Commit-Queue: Jonah Ryan-Davis <jonahr@google.com>
parent b09f16ee
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
// Version number for shader translation API. // Version number for shader translation API.
// It is incremented every time the API changes. // It is incremented every time the API changes.
#define ANGLE_SH_VERSION 233 #define ANGLE_SH_VERSION 234
enum ShShaderSpec enum ShShaderSpec
{ {
...@@ -800,6 +800,9 @@ namespace mtl ...@@ -800,6 +800,9 @@ namespace mtl
{ {
// Specialization constant to enable GL_SAMPLE_COVERAGE_VALUE emulation. // Specialization constant to enable GL_SAMPLE_COVERAGE_VALUE emulation.
extern const char kCoverageMaskEnabledConstName[]; extern const char kCoverageMaskEnabledConstName[];
// Specialization constant to emulate rasterizer discard.
extern const char kRasterizerDiscardEnabledConstName[];
} // namespace mtl } // namespace mtl
} // namespace sh } // namespace sh
......
...@@ -34,7 +34,8 @@ namespace sh ...@@ -34,7 +34,8 @@ namespace sh
namespace mtl namespace mtl
{ {
/** extern */ /** extern */
const char kCoverageMaskEnabledConstName[] = "ANGLECoverageMaskEnabled"; const char kCoverageMaskEnabledConstName[] = "ANGLECoverageMaskEnabled";
const char kRasterizerDiscardEnabledConstName[] = "ANGLERasterizerDisabled";
} // namespace mtl } // namespace mtl
namespace namespace
...@@ -157,6 +158,12 @@ bool TranslatorMetal::translate(TIntermBlock *root, ...@@ -157,6 +158,12 @@ bool TranslatorMetal::translate(TIntermBlock *root,
{ {
return false; return false;
} }
// Insert rasterizer discard logic
if (!insertRasterizerDiscardLogic(root))
{
return false;
}
} }
else if (getShaderType() == GL_FRAGMENT_SHADER) else if (getShaderType() == GL_FRAGMENT_SHADER)
{ {
...@@ -286,4 +293,52 @@ ANGLE_NO_DISCARD bool TranslatorMetal::insertSampleMaskWritingLogic(TIntermBlock ...@@ -286,4 +293,52 @@ ANGLE_NO_DISCARD bool TranslatorMetal::insertSampleMaskWritingLogic(TIntermBlock
return RunAtTheEndOfShader(this, root, ifCall, symbolTable); return RunAtTheEndOfShader(this, root, ifCall, symbolTable);
} }
ANGLE_NO_DISCARD bool TranslatorMetal::insertRasterizerDiscardLogic(TIntermBlock *root)
{
TInfoSinkBase &sink = getInfoSink().obj;
TSymbolTable *symbolTable = &getSymbolTable();
// Insert rasterizationDisabled specialization constant.
sink << "layout (constant_id=0) const bool " << mtl::kRasterizerDiscardEnabledConstName;
sink << " = false;\n";
// Create kRasterizerDiscardEnabledConstName variable reference.
TType *boolType = new TType(EbtBool);
boolType->setQualifier(EvqConst);
TVariable *discardEnabledVar =
new TVariable(symbolTable, ImmutableString(mtl::kRasterizerDiscardEnabledConstName),
boolType, SymbolType::AngleInternal);
// Insert this code to the end of main()
// if (ANGLERasterizerDisabled)
// {
// gl_Position = vec4(-3.0, -3.0, -3.0, 1.0);
// }
// Create a symbol reference to "gl_Position"
const TVariable *position = BuiltInVariable::gl_Position();
TIntermSymbol *positionRef = new TIntermSymbol(position);
// Create vec4(-3, -3, -3, 1):
auto vec4Type = new TType(EbtFloat, 4);
TIntermSequence *vec4Args = new TIntermSequence();
vec4Args->push_back(CreateFloatNode(-3.0f));
vec4Args->push_back(CreateFloatNode(-3.0f));
vec4Args->push_back(CreateFloatNode(-3.0f));
vec4Args->push_back(CreateFloatNode(1.0f));
TIntermAggregate *constVarConstructor =
TIntermAggregate::CreateConstructor(*vec4Type, vec4Args);
// Create the assignment "gl_Position = vec4(-3, -3, -3, 1)"
TIntermBinary *assignment =
new TIntermBinary(TOperator::EOpAssign, positionRef->deepCopy(), constVarConstructor);
TIntermBlock *discardBlock = new TIntermBlock;
discardBlock->appendStatement(assignment);
TIntermSymbol *discardEnabled = new TIntermSymbol(discardEnabledVar);
TIntermIfElse *ifCall = new TIntermIfElse(discardEnabled, discardBlock, nullptr);
return RunAtTheEndOfShader(this, root, ifCall, symbolTable);
}
} // namespace sh } // namespace sh
...@@ -37,6 +37,7 @@ class TranslatorMetal : public TranslatorVulkan ...@@ -37,6 +37,7 @@ class TranslatorMetal : public TranslatorVulkan
ANGLE_NO_DISCARD bool insertSampleMaskWritingLogic(TIntermBlock *root, ANGLE_NO_DISCARD bool insertSampleMaskWritingLogic(TIntermBlock *root,
const TVariable *driverUniforms); const TVariable *driverUniforms);
ANGLE_NO_DISCARD bool insertRasterizerDiscardLogic(TIntermBlock *root);
}; };
} // namespace sh } // namespace sh
......
...@@ -354,7 +354,7 @@ std::string GenerateTransformFeedbackVaryingOutput(const gl::TransformFeedbackVa ...@@ -354,7 +354,7 @@ std::string GenerateTransformFeedbackVaryingOutput(const gl::TransformFeedbackVa
return result.str(); return result.str();
} }
void GenerateTransformFeedbackEmulationOutputs(GlslangSourceOptions &options, void GenerateTransformFeedbackEmulationOutputs(const GlslangSourceOptions &options,
const gl::ProgramState &programState, const gl::ProgramState &programState,
GlslangProgramInterfaceInfo *programInterfaceInfo, GlslangProgramInterfaceInfo *programInterfaceInfo,
std::string *vertexShader, std::string *vertexShader,
...@@ -730,7 +730,7 @@ void AssignTransformFeedbackExtensionQualifiers(const gl::ProgramExecutable &pro ...@@ -730,7 +730,7 @@ void AssignTransformFeedbackExtensionQualifiers(const gl::ProgramExecutable &pro
} }
} }
void AssignUniformBindings(GlslangSourceOptions &options, void AssignUniformBindings(const GlslangSourceOptions &options,
const gl::ProgramExecutable &programExecutable, const gl::ProgramExecutable &programExecutable,
const gl::ShaderType shaderType, const gl::ShaderType shaderType,
GlslangProgramInterfaceInfo *programInterfaceInfo, GlslangProgramInterfaceInfo *programInterfaceInfo,
...@@ -752,7 +752,7 @@ void AssignUniformBindings(GlslangSourceOptions &options, ...@@ -752,7 +752,7 @@ void AssignUniformBindings(GlslangSourceOptions &options,
// TODO: http://anglebug.com/4512: Need to combine descriptor set bindings across // TODO: http://anglebug.com/4512: Need to combine descriptor set bindings across
// shader stages. // shader stages.
void AssignInterfaceBlockBindings(GlslangSourceOptions &options, void AssignInterfaceBlockBindings(const GlslangSourceOptions &options,
const gl::ProgramExecutable &programExecutable, const gl::ProgramExecutable &programExecutable,
const std::vector<gl::InterfaceBlock> &blocks, const std::vector<gl::InterfaceBlock> &blocks,
const gl::ShaderType shaderType, const gl::ShaderType shaderType,
...@@ -778,7 +778,7 @@ void AssignInterfaceBlockBindings(GlslangSourceOptions &options, ...@@ -778,7 +778,7 @@ void AssignInterfaceBlockBindings(GlslangSourceOptions &options,
// TODO: http://anglebug.com/4512: Need to combine descriptor set bindings across // TODO: http://anglebug.com/4512: Need to combine descriptor set bindings across
// shader stages. // shader stages.
void AssignAtomicCounterBufferBindings(GlslangSourceOptions &options, void AssignAtomicCounterBufferBindings(const GlslangSourceOptions &options,
const gl::ProgramExecutable &programExecutable, const gl::ProgramExecutable &programExecutable,
const std::vector<gl::AtomicCounterBuffer> &buffers, const std::vector<gl::AtomicCounterBuffer> &buffers,
const gl::ShaderType shaderType, const gl::ShaderType shaderType,
...@@ -801,7 +801,7 @@ void AssignAtomicCounterBufferBindings(GlslangSourceOptions &options, ...@@ -801,7 +801,7 @@ void AssignAtomicCounterBufferBindings(GlslangSourceOptions &options,
// TODO: http://anglebug.com/4512: Need to combine descriptor set bindings across // TODO: http://anglebug.com/4512: Need to combine descriptor set bindings across
// shader stages. // shader stages.
void AssignImageBindings(GlslangSourceOptions &options, void AssignImageBindings(const GlslangSourceOptions &options,
const gl::ProgramExecutable &programExecutable, const gl::ProgramExecutable &programExecutable,
const std::vector<gl::LinkedUniform> &uniforms, const std::vector<gl::LinkedUniform> &uniforms,
const gl::RangeUI &imageUniformRange, const gl::RangeUI &imageUniformRange,
...@@ -828,7 +828,7 @@ void AssignImageBindings(GlslangSourceOptions &options, ...@@ -828,7 +828,7 @@ void AssignImageBindings(GlslangSourceOptions &options,
} }
} }
void AssignNonTextureBindings(GlslangSourceOptions &options, void AssignNonTextureBindings(const GlslangSourceOptions &options,
const gl::ProgramExecutable &programExecutable, const gl::ProgramExecutable &programExecutable,
const gl::ShaderType shaderType, const gl::ShaderType shaderType,
GlslangProgramInterfaceInfo *programInterfaceInfo, GlslangProgramInterfaceInfo *programInterfaceInfo,
...@@ -856,7 +856,7 @@ void AssignNonTextureBindings(GlslangSourceOptions &options, ...@@ -856,7 +856,7 @@ void AssignNonTextureBindings(GlslangSourceOptions &options,
// TODO: http://anglebug.com/4512: Need to combine descriptor set bindings across // TODO: http://anglebug.com/4512: Need to combine descriptor set bindings across
// shader stages. // shader stages.
void AssignTextureBindings(GlslangSourceOptions &options, void AssignTextureBindings(const GlslangSourceOptions &options,
const gl::ProgramExecutable &programExecutable, const gl::ProgramExecutable &programExecutable,
const gl::ShaderType shaderType, const gl::ShaderType shaderType,
GlslangProgramInterfaceInfo *programInterfaceInfo, GlslangProgramInterfaceInfo *programInterfaceInfo,
...@@ -2086,7 +2086,17 @@ std::string GetXfbBufferName(const uint32_t bufferIndex) ...@@ -2086,7 +2086,17 @@ std::string GetXfbBufferName(const uint32_t bufferIndex)
return "xfbBuffer" + Str(bufferIndex); return "xfbBuffer" + Str(bufferIndex);
} }
void GlslangAssignLocations(GlslangSourceOptions &options, void GlslangGenTransformFeedbackEmulationOutputs(const GlslangSourceOptions &options,
const gl::ProgramState &programState,
GlslangProgramInterfaceInfo *programInterfaceInfo,
std::string *vertexShader,
ShaderInterfaceVariableInfoMap *variableInfoMapOut)
{
GenerateTransformFeedbackEmulationOutputs(options, programState, programInterfaceInfo,
vertexShader, variableInfoMapOut);
}
void GlslangAssignLocations(const GlslangSourceOptions &options,
const gl::ProgramExecutable &programExecutable, const gl::ProgramExecutable &programExecutable,
const gl::ShaderType shaderType, const gl::ShaderType shaderType,
GlslangProgramInterfaceInfo *programInterfaceInfo, GlslangProgramInterfaceInfo *programInterfaceInfo,
...@@ -2131,7 +2141,7 @@ void GlslangAssignLocations(GlslangSourceOptions &options, ...@@ -2131,7 +2141,7 @@ void GlslangAssignLocations(GlslangSourceOptions &options,
variableInfoMapOut); variableInfoMapOut);
} }
void GlslangGetShaderSource(GlslangSourceOptions &options, void GlslangGetShaderSource(const GlslangSourceOptions &options,
const gl::ProgramState &programState, const gl::ProgramState &programState,
const gl::ProgramLinkedResources &resources, const gl::ProgramLinkedResources &resources,
GlslangProgramInterfaceInfo *programInterfaceInfo, GlslangProgramInterfaceInfo *programInterfaceInfo,
...@@ -2167,6 +2177,10 @@ void GlslangGetShaderSource(GlslangSourceOptions &options, ...@@ -2167,6 +2177,10 @@ void GlslangGetShaderSource(GlslangSourceOptions &options,
options, programState, programInterfaceInfo, vertexSource, options, programState, programInterfaceInfo, vertexSource,
&(*variableInfoMapOut)[gl::ShaderType::Vertex]); &(*variableInfoMapOut)[gl::ShaderType::Vertex]);
} }
else
{
*vertexSource = SubstituteTransformFeedbackMarkers(*vertexSource, "", "");
}
} }
} }
......
...@@ -105,7 +105,15 @@ std::string GetMappedSamplerNameOld(const std::string &originalName); ...@@ -105,7 +105,15 @@ std::string GetMappedSamplerNameOld(const std::string &originalName);
std::string GlslangGetMappedSamplerName(const std::string &originalName); std::string GlslangGetMappedSamplerName(const std::string &originalName);
std::string GetXfbBufferName(const uint32_t bufferIndex); std::string GetXfbBufferName(const uint32_t bufferIndex);
void GlslangAssignLocations(GlslangSourceOptions &options, // NOTE: options.emulateTransformFeedback is ignored in this case. It is assumed to be always true.
void GlslangGenTransformFeedbackEmulationOutputs(
const GlslangSourceOptions &options,
const gl::ProgramState &programState,
GlslangProgramInterfaceInfo *programInterfaceInfo,
std::string *vertexShader,
ShaderInterfaceVariableInfoMap *variableInfoMapOut);
void GlslangAssignLocations(const GlslangSourceOptions &options,
const gl::ProgramExecutable &programExecutable, const gl::ProgramExecutable &programExecutable,
const gl::ShaderType shaderType, const gl::ShaderType shaderType,
GlslangProgramInterfaceInfo *programInterfaceInfo, GlslangProgramInterfaceInfo *programInterfaceInfo,
...@@ -115,7 +123,7 @@ void GlslangAssignLocations(GlslangSourceOptions &options, ...@@ -115,7 +123,7 @@ void GlslangAssignLocations(GlslangSourceOptions &options,
// buffers, xfb, etc). For some variables, these values are instead output to the variableInfoMap // buffers, xfb, etc). For some variables, these values are instead output to the variableInfoMap
// to be set during a SPIR-V transformation. This is a transitory step towards moving all variables // to be set during a SPIR-V transformation. This is a transitory step towards moving all variables
// to this map, at which point GlslangGetShaderSpirvCode will also be called by this function. // to this map, at which point GlslangGetShaderSpirvCode will also be called by this function.
void GlslangGetShaderSource(GlslangSourceOptions &options, void GlslangGetShaderSource(const GlslangSourceOptions &options,
const gl::ProgramState &programState, const gl::ProgramState &programState,
const gl::ProgramLinkedResources &resources, const gl::ProgramLinkedResources &resources,
GlslangProgramInterfaceInfo *programInterfaceInfo, GlslangProgramInterfaceInfo *programInterfaceInfo,
......
...@@ -30,6 +30,7 @@ class VertexArrayMtl; ...@@ -30,6 +30,7 @@ class VertexArrayMtl;
class ProgramMtl; class ProgramMtl;
class RenderTargetMtl; class RenderTargetMtl;
class WindowSurfaceMtl; class WindowSurfaceMtl;
class TransformFeedbackMtl;
class ContextMtl : public ContextImpl, public mtl::Context class ContextMtl : public ContextImpl, public mtl::Context
{ {
...@@ -289,6 +290,11 @@ class ContextMtl : public ContextImpl, public mtl::Context ...@@ -289,6 +290,11 @@ class ContextMtl : public ContextImpl, public mtl::Context
angle::Result restartActiveOcclusionQueryInRenderPass(); angle::Result restartActiveOcclusionQueryInRenderPass();
const MTLClearColor &getClearColorValue() const; const MTLClearColor &getClearColorValue() const;
// Invoke by TransformFeedbackMtl
void onTransformFeedbackActive(const gl::Context *context, TransformFeedbackMtl *xfb);
void onTransformFeedbackInactive(const gl::Context *context, TransformFeedbackMtl *xfb);
MTLColorWriteMask getColorMask() const; MTLColorWriteMask getColorMask() const;
float getClearDepthValue() const; float getClearDepthValue() const;
uint32_t getClearStencilValue() const; uint32_t getClearStencilValue() const;
...@@ -357,7 +363,8 @@ class ContextMtl : public ContextImpl, public mtl::Context ...@@ -357,7 +363,8 @@ class ContextMtl : public ContextImpl, public mtl::Context
GLsizei vertexOrIndexCount, GLsizei vertexOrIndexCount,
GLsizei instanceCount, GLsizei instanceCount,
gl::DrawElementsType indexTypeOrNone, gl::DrawElementsType indexTypeOrNone,
const void *indices); const void *indices,
bool xfbPass);
angle::Result genLineLoopLastSegment(const gl::Context *context, angle::Result genLineLoopLastSegment(const gl::Context *context,
GLint firstVertex, GLint firstVertex,
GLsizei vertexOrIndexCount, GLsizei vertexOrIndexCount,
...@@ -413,14 +420,22 @@ class ContextMtl : public ContextImpl, public mtl::Context ...@@ -413,14 +420,22 @@ class ContextMtl : public ContextImpl, public mtl::Context
void updateVertexArray(const gl::Context *context); void updateVertexArray(const gl::Context *context);
angle::Result updateDefaultAttribute(size_t attribIndex); angle::Result updateDefaultAttribute(size_t attribIndex);
void filterOutXFBOnlyDirtyBits(const gl::Context *context);
angle::Result handleDirtyActiveTextures(const gl::Context *context); angle::Result handleDirtyActiveTextures(const gl::Context *context);
angle::Result handleDirtyDefaultAttribs(const gl::Context *context); angle::Result handleDirtyDefaultAttribs(const gl::Context *context);
angle::Result handleDirtyDriverUniforms(const gl::Context *context); angle::Result handleDirtyDriverUniforms(const gl::Context *context,
GLint drawCallFirstVertex,
uint32_t verticesPerInstance);
angle::Result fillDriverXFBUniforms(GLint drawCallFirstVertex,
uint32_t verticesPerInstance,
uint32_t skippedInstances);
angle::Result handleDirtyDepthStencilState(const gl::Context *context); angle::Result handleDirtyDepthStencilState(const gl::Context *context);
angle::Result handleDirtyDepthBias(const gl::Context *context); angle::Result handleDirtyDepthBias(const gl::Context *context);
angle::Result handleDirtyRenderPass(const gl::Context *context);
angle::Result checkIfPipelineChanged(const gl::Context *context, angle::Result checkIfPipelineChanged(const gl::Context *context,
gl::PrimitiveMode primitiveMode, gl::PrimitiveMode primitiveMode,
bool *isPipelineDescChanged); bool xfbPass,
bool *pipelineDescChanged);
angle::Result startOcclusionQueryInRenderPass(QueryMtl *query, bool clearOldValue); angle::Result startOcclusionQueryInRenderPass(QueryMtl *query, bool clearOldValue);
...@@ -441,6 +456,7 @@ class ContextMtl : public ContextImpl, public mtl::Context ...@@ -441,6 +456,7 @@ class ContextMtl : public ContextImpl, public mtl::Context
DIRTY_BIT_WINDING, DIRTY_BIT_WINDING,
DIRTY_BIT_RENDER_PIPELINE, DIRTY_BIT_RENDER_PIPELINE,
DIRTY_BIT_UNIFORM_BUFFERS_BINDING, DIRTY_BIT_UNIFORM_BUFFERS_BINDING,
DIRTY_BIT_RASTERIZER_DISCARD,
DIRTY_BIT_MAX, DIRTY_BIT_MAX,
}; };
...@@ -456,7 +472,6 @@ class ContextMtl : public ContextImpl, public mtl::Context ...@@ -456,7 +472,6 @@ class ContextMtl : public ContextImpl, public mtl::Context
// 32 bits for 32 clip distances // 32 bits for 32 clip distances
uint32_t enabledClipDistances; uint32_t enabledClipDistances;
// NOTE(hqle): Transform feedsback is not supported yet.
uint32_t xfbActiveUnpaused; uint32_t xfbActiveUnpaused;
uint32_t xfbVerticesPerDraw; uint32_t xfbVerticesPerDraw;
// NOTE: Explicit padding. Fill in with useful data when needed in the future. // NOTE: Explicit padding. Fill in with useful data when needed in the future.
...@@ -527,6 +542,9 @@ class ContextMtl : public ContextImpl, public mtl::Context ...@@ -527,6 +542,9 @@ class ContextMtl : public ContextImpl, public mtl::Context
// one buffer can be reused for any starting vertex in DrawArrays() // one buffer can be reused for any starting vertex in DrawArrays()
mtl::BufferRef mTriFanArraysIndexBuffer; mtl::BufferRef mTriFanArraysIndexBuffer;
// Dummy texture to be used for transform feedback only pass.
mtl::TextureRef mDummyXFBRenderTexture;
DriverUniforms mDriverUniforms; DriverUniforms mDriverUniforms;
DefaultAttribute mDefaultAttributes[mtl::kMaxVertexAttribs]; DefaultAttribute mDefaultAttributes[mtl::kMaxVertexAttribs];
......
...@@ -177,6 +177,10 @@ class ProgramMtl : public ProgramImpl, public mtl::RenderPipelineCacheSpecialize ...@@ -177,6 +177,10 @@ class ProgramMtl : public ProgramImpl, public mtl::RenderPipelineCacheSpecialize
const std::vector<gl::InterfaceBlock> &blocks, const std::vector<gl::InterfaceBlock> &blocks,
gl::ShaderType shaderType); gl::ShaderType shaderType);
angle::Result updateXfbBuffers(ContextMtl *context,
mtl::RenderCommandEncoder *cmdEncoder,
const mtl::RenderPipelineDesc &pipelineDesc);
void reset(ContextMtl *context); void reset(ContextMtl *context);
void linkResources(const gl::ProgramLinkedResources &resources); void linkResources(const gl::ProgramLinkedResources &resources);
...@@ -210,9 +214,16 @@ class ProgramMtl : public ProgramImpl, public mtl::RenderPipelineCacheSpecialize ...@@ -210,9 +214,16 @@ class ProgramMtl : public ProgramImpl, public mtl::RenderPipelineCacheSpecialize
// Translated metal shaders: // Translated metal shaders:
gl::ShaderMap<mtl::TranslatedShaderInfo> mMslShaderTranslateInfo; gl::ShaderMap<mtl::TranslatedShaderInfo> mMslShaderTranslateInfo;
// Translated metal version for transform feedback only vertex shader:
// - Metal doesn't allow vertex shader to write to both buffers and to stage output
// (gl_Position). Need a special version of vertex shader that only writes to transform feedback
// buffers.
mtl::TranslatedShaderInfo mMslXfbOnlyVertexShaderInfo;
// Compiled native shader object variants: // Compiled native shader object variants:
// - Vertex shader: one variant for now. // - Vertex shader: One with emulated rasterization discard, one with true rasterization
std::array<ProgramShaderObjVariantMtl, 1> mVertexShaderVariants; // discard, one without.
mtl::RenderPipelineRasterStateMap<ProgramShaderObjVariantMtl> mVertexShaderVariants;
// - Fragment shader: One with sample coverage mask enabled, one with it disabled. // - Fragment shader: One with sample coverage mask enabled, one with it disabled.
std::array<ProgramShaderObjVariantMtl, 2> mFragmentShaderVariants; std::array<ProgramShaderObjVariantMtl, 2> mFragmentShaderVariants;
......
...@@ -252,6 +252,7 @@ void ProgramMtl::reset(ContextMtl *context) ...@@ -252,6 +252,7 @@ void ProgramMtl::reset(ContextMtl *context)
{ {
mMslShaderTranslateInfo[shaderType].reset(); mMslShaderTranslateInfo[shaderType].reset();
} }
mMslXfbOnlyVertexShaderInfo.reset();
for (ProgramShaderObjVariantMtl &var : mVertexShaderVariants) for (ProgramShaderObjVariantMtl &var : mVertexShaderVariants)
{ {
...@@ -316,21 +317,38 @@ angle::Result ProgramMtl::linkImpl(const gl::Context *glContext, ...@@ -316,21 +317,38 @@ angle::Result ProgramMtl::linkImpl(const gl::Context *glContext,
// Gather variable info and transform sources. // Gather variable info and transform sources.
gl::ShaderMap<std::string> shaderSources; gl::ShaderMap<std::string> shaderSources;
gl::ShaderMap<std::string> xfbOnlyShaderSources;
ShaderMapInterfaceVariableInfoMap variableInfoMap; ShaderMapInterfaceVariableInfoMap variableInfoMap;
mtl::GlslangGetShaderSource(mState, resources, &shaderSources, &variableInfoMap); ShaderMapInterfaceVariableInfoMap xfbOnlyVariableInfoMap;
mtl::GlslangGetShaderSource(mState, resources, &shaderSources,
&xfbOnlyShaderSources[gl::ShaderType::Vertex], &variableInfoMap,
&xfbOnlyVariableInfoMap[gl::ShaderType::Vertex]);
// Convert GLSL to spirv code // Convert GLSL to spirv code
gl::ShaderMap<std::vector<uint32_t>> shaderCodes; gl::ShaderMap<std::vector<uint32_t>> shaderCodes;
gl::ShaderMap<std::vector<uint32_t>> xfbOnlyShaderCodes; // only vertex shader is needed.
ANGLE_TRY(mtl::GlslangGetShaderSpirvCode( ANGLE_TRY(mtl::GlslangGetShaderSpirvCode(
contextMtl, mState.getExecutable().getLinkedShaderStages(), contextMtl->getCaps(), contextMtl, mState.getExecutable().getLinkedShaderStages(), contextMtl->getCaps(),
shaderSources, variableInfoMap, &shaderCodes)); shaderSources, variableInfoMap, &shaderCodes));
if (!mState.getLinkedTransformFeedbackVaryings().empty())
{
gl::ShaderBitSet onlyVS;
onlyVS.set(gl::ShaderType::Vertex);
ANGLE_TRY(mtl::GlslangGetShaderSpirvCode(contextMtl, onlyVS, contextMtl->getCaps(),
xfbOnlyShaderSources, xfbOnlyVariableInfoMap,
&xfbOnlyShaderCodes));
}
// Convert spirv code to MSL // Convert spirv code to MSL
ANGLE_TRY(mtl::SpirvCodeToMsl(contextMtl, mState, &shaderCodes, &mMslShaderTranslateInfo)); ANGLE_TRY(mtl::SpirvCodeToMsl(contextMtl, mState,
xfbOnlyVariableInfoMap[gl::ShaderType::Vertex], &shaderCodes,
&xfbOnlyShaderCodes[gl::ShaderType::Vertex],
&mMslShaderTranslateInfo, &mMslXfbOnlyVertexShaderInfo));
for (gl::ShaderType shaderType : gl::AllGLES2ShaderTypes()) for (gl::ShaderType shaderType : gl::AllGLES2ShaderTypes())
{ {
// Create actual Metal shader // Create actual Metal shader library
ANGLE_TRY(createMslShaderLib(contextMtl, shaderType, infoLog, ANGLE_TRY(createMslShaderLib(contextMtl, shaderType, infoLog,
&mMslShaderTranslateInfo[shaderType])); &mMslShaderTranslateInfo[shaderType]));
} }
...@@ -442,16 +460,46 @@ angle::Result ProgramMtl::getSpecializedShader(mtl::Context *context, ...@@ -442,16 +460,46 @@ angle::Result ProgramMtl::getSpecializedShader(mtl::Context *context,
if (shaderType == gl::ShaderType::Vertex) if (shaderType == gl::ShaderType::Vertex)
{ {
// NOTE(hqle): Only one vertex shader variant for now. In future, there should be a variant // For vertex shader, we need to create 3 variants, one with emulated rasterization
// with rasterization discard enabled. // discard, one with true rasterization discard and one without.
shaderVariant = &mVertexShaderVariants[0]; shaderVariant = &mVertexShaderVariants[renderPipelineDesc.rasterizationType];
if (shaderVariant->metalShader) if (shaderVariant->metalShader)
{ {
// Already created. // Already created.
*shaderOut = shaderVariant->metalShader; *shaderOut = shaderVariant->metalShader;
return angle::Result::Continue; return angle::Result::Continue;
} }
}
if (renderPipelineDesc.rasterizationType == mtl::RenderPipelineRasterization::Disabled)
{
// Special case: XFB output only vertex shader.
ASSERT(!mState.getLinkedTransformFeedbackVaryings().empty());
translatedMslInfo = &mMslXfbOnlyVertexShaderInfo;
if (!translatedMslInfo->metalLibrary)
{
// Lazily compile XFB only shader
gl::InfoLog infoLog;
ANGLE_TRY(
createMslShaderLib(context, shaderType, infoLog, &mMslXfbOnlyVertexShaderInfo));
translatedMslInfo->metalLibrary.get().label = @"TransformFeedback";
}
}
ANGLE_MTL_OBJC_SCOPE
{
BOOL emulateDiscard = renderPipelineDesc.rasterizationType ==
mtl::RenderPipelineRasterization::EmulatedDiscard;
NSString *discardEnabledStr =
[NSString stringWithFormat:@"%s%s", sh::mtl::kRasterizerDiscardEnabledConstName,
kSpirvCrossSpecConstSuffix];
funcConstants = [[MTLFunctionConstantValues alloc] init];
[funcConstants setConstantValue:&emulateDiscard
type:MTLDataTypeBool
withName:discardEnabledStr];
}
} // if (shaderType == gl::ShaderType::Vertex)
else if (shaderType == gl::ShaderType::Fragment) else if (shaderType == gl::ShaderType::Fragment)
{ {
// For fragment shader, we need to create 2 variants, one with sample coverage mask // For fragment shader, we need to create 2 variants, one with sample coverage mask
...@@ -849,9 +897,12 @@ angle::Result ProgramMtl::setupDraw(const gl::Context *glContext, ...@@ -849,9 +897,12 @@ angle::Result ProgramMtl::setupDraw(const gl::Context *glContext,
mSamplerBindingsDirty.set(); mSamplerBindingsDirty.set();
// Cache current shader variant references for easier querying. // Cache current shader variant references for easier querying.
mCurrentShaderVariants[gl::ShaderType::Vertex] = &mVertexShaderVariants[0]; mCurrentShaderVariants[gl::ShaderType::Vertex] =
&mVertexShaderVariants[pipelineDesc.rasterizationType];
mCurrentShaderVariants[gl::ShaderType::Fragment] = mCurrentShaderVariants[gl::ShaderType::Fragment] =
&mFragmentShaderVariants[pipelineDesc.emulateCoverageMask]; pipelineDesc.rasterizationEnabled()
? &mFragmentShaderVariants[pipelineDesc.emulateCoverageMask]
: nullptr;
} }
ANGLE_TRY(commitUniforms(context, cmdEncoder)); ANGLE_TRY(commitUniforms(context, cmdEncoder));
...@@ -862,6 +913,11 @@ angle::Result ProgramMtl::setupDraw(const gl::Context *glContext, ...@@ -862,6 +913,11 @@ angle::Result ProgramMtl::setupDraw(const gl::Context *glContext,
ANGLE_TRY(updateUniformBuffers(context, cmdEncoder, pipelineDesc)); ANGLE_TRY(updateUniformBuffers(context, cmdEncoder, pipelineDesc));
} }
if (pipelineDescChanged)
{
ANGLE_TRY(updateXfbBuffers(context, cmdEncoder, pipelineDesc));
}
return angle::Result::Continue; return angle::Result::Continue;
} }
...@@ -1169,4 +1225,49 @@ angle::Result ProgramMtl::encodeUniformBuffersInfoArgumentBuffer( ...@@ -1169,4 +1225,49 @@ angle::Result ProgramMtl::encodeUniformBuffersInfoArgumentBuffer(
return angle::Result::Continue; return angle::Result::Continue;
} }
angle::Result ProgramMtl::updateXfbBuffers(ContextMtl *context,
mtl::RenderCommandEncoder *cmdEncoder,
const mtl::RenderPipelineDesc &pipelineDesc)
{
const gl::State &glState = context->getState();
gl::TransformFeedback *transformFeedback = glState.getCurrentTransformFeedback();
if (pipelineDesc.rasterizationEnabled() || !glState.isTransformFeedbackActiveUnpaused() ||
ANGLE_UNLIKELY(!transformFeedback))
{
// XFB output can only be used with rasterization disabled.
return angle::Result::Continue;
}
size_t xfbBufferCount = glState.getProgramExecutable()->getTransformFeedbackBufferCount();
ASSERT(xfbBufferCount > 0);
ASSERT(mState.getTransformFeedbackBufferMode() != GL_INTERLEAVED_ATTRIBS ||
xfbBufferCount == 1);
for (size_t bufferIndex = 0; bufferIndex < xfbBufferCount; ++bufferIndex)
{
uint32_t actualBufferIdx = mMslXfbOnlyVertexShaderInfo.actualXFBBindings[bufferIndex];
if (actualBufferIdx >= mtl::kMaxShaderBuffers)
{
continue;
}
const gl::OffsetBindingPointer<gl::Buffer> &bufferBinding =
transformFeedback->getIndexedBuffer(bufferIndex);
gl::Buffer *buffer = bufferBinding.get();
ASSERT((bufferBinding.getOffset() % 4) == 0);
ASSERT(buffer != nullptr);
BufferMtl *bufferMtl = mtl::GetImpl(buffer);
// Use offset=0, actual offset will be set in Driver Uniform inside ContextMtl.
cmdEncoder->setBufferForWrite(gl::ShaderType::Vertex, bufferMtl->getCurrentBuffer(), 0,
actualBufferIdx);
}
return angle::Result::Continue;
}
} // namespace rx } // namespace rx
...@@ -99,6 +99,8 @@ class QueryMtl : public QueryImpl ...@@ -99,6 +99,8 @@ class QueryMtl : public QueryImpl
// Reset the occlusion query result stored in buffer to zero // Reset the occlusion query result stored in buffer to zero
void resetVisibilityResult(ContextMtl *contextMtl); void resetVisibilityResult(ContextMtl *contextMtl);
void onTransformFeedbackEnd(const gl::Context *context);
private: private:
template <typename T> template <typename T>
angle::Result waitAndGetResult(const gl::Context *context, T *params); angle::Result waitAndGetResult(const gl::Context *context, T *params);
...@@ -106,6 +108,8 @@ class QueryMtl : public QueryImpl ...@@ -106,6 +108,8 @@ class QueryMtl : public QueryImpl
// List of offsets in the render pass's occlusion query pool buffer allocated for this query // List of offsets in the render pass's occlusion query pool buffer allocated for this query
VisibilityBufferOffsetsMtl mVisibilityBufferOffsets; VisibilityBufferOffsetsMtl mVisibilityBufferOffsets;
mtl::BufferRef mVisibilityResultBuffer; mtl::BufferRef mVisibilityResultBuffer;
size_t mTransformFeedbackPrimitivesDrawn = 0;
}; };
} // namespace rx } // namespace rx
......
...@@ -49,6 +49,9 @@ angle::Result QueryMtl::begin(const gl::Context *context) ...@@ -49,6 +49,9 @@ angle::Result QueryMtl::begin(const gl::Context *context)
ANGLE_TRY(contextMtl->onOcclusionQueryBegin(context, this)); ANGLE_TRY(contextMtl->onOcclusionQueryBegin(context, this));
break; break;
case gl::QueryType::TransformFeedbackPrimitivesWritten:
mTransformFeedbackPrimitivesDrawn = 0;
break;
default: default:
UNIMPLEMENTED(); UNIMPLEMENTED();
break; break;
...@@ -65,6 +68,11 @@ angle::Result QueryMtl::end(const gl::Context *context) ...@@ -65,6 +68,11 @@ angle::Result QueryMtl::end(const gl::Context *context)
case gl::QueryType::AnySamplesConservative: case gl::QueryType::AnySamplesConservative:
contextMtl->onOcclusionQueryEnd(context, this); contextMtl->onOcclusionQueryEnd(context, this);
break; break;
case gl::QueryType::TransformFeedbackPrimitivesWritten:
// There could be transform feedback in progress, so add the primitives drawn so far
// from the current transform feedback object.
onTransformFeedbackEnd(context);
break;
default: default:
UNIMPLEMENTED(); UNIMPLEMENTED();
break; break;
...@@ -101,6 +109,9 @@ angle::Result QueryMtl::waitAndGetResult(const gl::Context *context, T *params) ...@@ -101,6 +109,9 @@ angle::Result QueryMtl::waitAndGetResult(const gl::Context *context, T *params)
*params = queryResult ? GL_TRUE : GL_FALSE; *params = queryResult ? GL_TRUE : GL_FALSE;
} }
break; break;
case gl::QueryType::TransformFeedbackPrimitivesWritten:
*params = static_cast<T>(mTransformFeedbackPrimitivesDrawn);
break;
default: default:
UNIMPLEMENTED(); UNIMPLEMENTED();
break; break;
...@@ -125,6 +136,9 @@ angle::Result QueryMtl::isResultAvailable(const gl::Context *context, bool *avai ...@@ -125,6 +136,9 @@ angle::Result QueryMtl::isResultAvailable(const gl::Context *context, bool *avai
*available = !mVisibilityResultBuffer->isBeingUsedByGPU(contextMtl); *available = !mVisibilityResultBuffer->isBeingUsedByGPU(contextMtl);
break; break;
case gl::QueryType::TransformFeedbackPrimitivesWritten:
*available = true;
break;
default: default:
UNIMPLEMENTED(); UNIMPLEMENTED();
break; break;
...@@ -161,4 +175,13 @@ void QueryMtl::resetVisibilityResult(ContextMtl *contextMtl) ...@@ -161,4 +175,13 @@ void QueryMtl::resetVisibilityResult(ContextMtl *contextMtl)
mVisibilityResultBuffer->syncContent(contextMtl, blitEncoder); mVisibilityResultBuffer->syncContent(contextMtl, blitEncoder);
} }
void QueryMtl::onTransformFeedbackEnd(const gl::Context *context)
{
gl::TransformFeedback *transformFeedback = context->getState().getCurrentTransformFeedback();
if (transformFeedback)
{
mTransformFeedbackPrimitivesDrawn += transformFeedback->getPrimitivesDrawn();
}
}
} }
...@@ -166,6 +166,7 @@ class TextureMtl : public TextureImpl ...@@ -166,6 +166,7 @@ class TextureMtl : public TextureImpl
int samplerSlotIndex); int samplerSlotIndex);
const mtl::Format &getFormat() const { return mFormat; } const mtl::Format &getFormat() const { return mFormat; }
const mtl::TextureRef &getNativeTexture() const { return mNativeTexture; }
private: private:
void releaseTexture(bool releaseImages); void releaseTexture(bool releaseImages);
......
...@@ -12,14 +12,11 @@ ...@@ -12,14 +12,11 @@
#include "libANGLE/renderer/TransformFeedbackImpl.h" #include "libANGLE/renderer/TransformFeedbackImpl.h"
namespace gl
{
class ProgramState;
} // namespace gl
namespace rx namespace rx
{ {
class ContextMtl;
class TransformFeedbackMtl : public TransformFeedbackImpl class TransformFeedbackMtl : public TransformFeedbackImpl
{ {
public: public:
...@@ -35,6 +32,16 @@ class TransformFeedbackMtl : public TransformFeedbackImpl ...@@ -35,6 +32,16 @@ class TransformFeedbackMtl : public TransformFeedbackImpl
size_t index, size_t index,
const gl::OffsetBindingPointer<gl::Buffer> &binding) override; const gl::OffsetBindingPointer<gl::Buffer> &binding) override;
// Params:
// - drawCallFirstVertex is first vertex used by glDrawArrays*. This is important because
// gl_VertexIndex is starting from this.
// - skippedVertices is number of skipped vertices (useful for multiple metal draws per GL draw
// call).
angle::Result getBufferOffsets(ContextMtl *contextMtl,
GLint drawCallFirstVertex,
uint32_t skippedVertices,
int32_t *offsetsOut);
private: private:
}; };
......
...@@ -9,10 +9,11 @@ ...@@ -9,10 +9,11 @@
#include "libANGLE/renderer/metal/TransformFeedbackMtl.h" #include "libANGLE/renderer/metal/TransformFeedbackMtl.h"
#include "common/debug.h"
#include "libANGLE/Context.h" #include "libANGLE/Context.h"
#include "libANGLE/Query.h" #include "libANGLE/Query.h"
#include "libANGLE/renderer/metal/ContextMtl.h"
#include "common/debug.h" #include "libANGLE/renderer/metal/QueryMtl.h"
namespace rx namespace rx
{ {
...@@ -26,27 +27,39 @@ TransformFeedbackMtl::~TransformFeedbackMtl() {} ...@@ -26,27 +27,39 @@ TransformFeedbackMtl::~TransformFeedbackMtl() {}
angle::Result TransformFeedbackMtl::begin(const gl::Context *context, angle::Result TransformFeedbackMtl::begin(const gl::Context *context,
gl::PrimitiveMode primitiveMode) gl::PrimitiveMode primitiveMode)
{ {
UNIMPLEMENTED(); mtl::GetImpl(context)->onTransformFeedbackActive(context, this);
return angle::Result::Continue; return angle::Result::Continue;
} }
angle::Result TransformFeedbackMtl::end(const gl::Context *context) angle::Result TransformFeedbackMtl::end(const gl::Context *context)
{ {
UNIMPLEMENTED(); const gl::State &glState = context->getState();
gl::Query *transformFeedbackQuery =
glState.getActiveQuery(gl::QueryType::TransformFeedbackPrimitivesWritten);
if (transformFeedbackQuery)
{
mtl::GetImpl(transformFeedbackQuery)->onTransformFeedbackEnd(context);
}
mtl::GetImpl(context)->onTransformFeedbackInactive(context, this);
return angle::Result::Continue; return angle::Result::Continue;
} }
angle::Result TransformFeedbackMtl::pause(const gl::Context *context) angle::Result TransformFeedbackMtl::pause(const gl::Context *context)
{ {
UNIMPLEMENTED(); // When XFB is paused, OpenGL allows XFB buffers to be bound for other purposes. We need to call
// onTransformFeedbackInactive() to issue a sync.
mtl::GetImpl(context)->onTransformFeedbackInactive(context, this);
return angle::Result::Continue; return angle::Result::Continue;
} }
angle::Result TransformFeedbackMtl::resume(const gl::Context *context) angle::Result TransformFeedbackMtl::resume(const gl::Context *context)
{ {
UNIMPLEMENTED(); mtl::GetImpl(context)->onTransformFeedbackActive(context, this);
return angle::Result::Continue; return angle::Result::Continue;
} }
...@@ -55,7 +68,44 @@ angle::Result TransformFeedbackMtl::bindIndexedBuffer( ...@@ -55,7 +68,44 @@ angle::Result TransformFeedbackMtl::bindIndexedBuffer(
size_t index, size_t index,
const gl::OffsetBindingPointer<gl::Buffer> &binding) const gl::OffsetBindingPointer<gl::Buffer> &binding)
{ {
UNIMPLEMENTED(); // Do nothing for now
return angle::Result::Continue;
}
angle::Result TransformFeedbackMtl::getBufferOffsets(ContextMtl *contextMtl,
GLint drawCallFirstVertex,
uint32_t skippedVertices,
int32_t *offsetsOut)
{
int64_t verticesDrawn = static_cast<int64_t>(mState.getVerticesDrawn()) + skippedVertices;
const std::vector<GLsizei> &bufferStrides =
mState.getBoundProgram()->getTransformFeedbackStrides();
const gl::ProgramExecutable *executable = contextMtl->getState().getProgramExecutable();
ASSERT(executable);
size_t xfbBufferCount = executable->getTransformFeedbackBufferCount();
ASSERT(xfbBufferCount > 0);
for (size_t bufferIndex = 0; bufferIndex < xfbBufferCount; ++bufferIndex)
{
const gl::OffsetBindingPointer<gl::Buffer> &bufferBinding =
mState.getIndexedBuffer(bufferIndex);
ASSERT((bufferBinding.getOffset() % 4) == 0);
// Offset the gl_VertexIndex by drawCallFirstVertex
int64_t drawCallVertexOffset = static_cast<int64_t>(verticesDrawn) - drawCallFirstVertex;
int64_t writeOffset =
(bufferBinding.getOffset() + drawCallVertexOffset * bufferStrides[bufferIndex]) /
static_cast<int64_t>(sizeof(uint32_t));
offsetsOut[bufferIndex] = static_cast<int32_t>(writeOffset);
// Check for overflow.
ANGLE_CHECK_GL_ALLOC(contextMtl, offsetsOut[bufferIndex] == writeOffset);
}
return angle::Result::Continue; return angle::Result::Continue;
} }
......
# Transform Feedback implementation on Metal back-end
### Overview
- OpenGL ES 3.0 introduces Transform Feedback as a way to capture vertex outputs to buffers before
the introduction of Compute Shader in later versions.
- Metal doesn't support Transform Feedback natively but it is possible to be emulated using Compute
Shader or Vertex Shader to write vertex outputs to buffers directly.
- If Vertex Shader writes to buffers directly as well as to stage output (i.e. `[[position]]`,
varying variables, ...) then the Metal runtime won't allow the `MTLRenderPipelineState` to be
created. It is only allowed to either write to buffers or to stage output not both on Metal. This
brings challenges to implement Transform Feedback when `GL_RASTERIZER_DISCARD` is not enabled,
because in that case, by right OpenGL will do both the Transform Feedback and rasterization
(feeding stage output to Fragment Shader) at the same time.
### Current implementation
- Transform Feedback will be implemented by inserting additional code snippet to write vertex's
varying variables to buffers called XFB buffers at compilation time. The buffers' offsets are
calculated based on `[[vertex_id]]`/`gl_VertexIndex` & `[[instance_id]]`/`gl_InstanceID`.
- When Transform Feedback ends, a memory barrier must be inserted because the XFB buffers could be
used as vertex inputs in future draw calls. Due to Metal not supporting explicit memory barrier
(currently only macOS 10.14 and above supports it, ARM based macOS doesn't though), the only
reliable way to insert memory barrier currently is ending the render pass.
- In order to support Transform Feedback capturing and rasterization at the same time, the draw call
must be split into 2 passes:
- First pass: Vertex Shader will write captured varyings to XFB buffers.
`MTLRenderPipelineState`'s rasterization will be disabled. This can be done in `spirv-cross`
translation step. `spirv-cross` can convert the Vertex Shader to a `void` function,
effectively won't produce any stage output values for Fragment Shader.
- Second pass: Vertex Shader will write to stage output normally, but the XFB buffers writing
snippet are disabled. Note that the Vertex Shader in this pass is essential the same as the
first pass's, only difference is the output route (stage output vs XFB buffers). This
effectively executes the same Vertex Shader's internal logic twice.
- If `GL_RASTERIZER_DISCARD` is enabled when Transform Feedback is enabled:
- Only first pass above will be executed, the render pass will use 1x1 empty texture attachment
because rasterization is not needed and small texture attachment's load & store at render
pass's start & end boundary could be cheap. Recall that we have to end the render pass to
enforce XFB buffers' memory barrier as mentioned above.
- If `GL_RASTERIZER_DISCARD` is enabled and Transform Feedback is NOT enabled, we cannot disable
`MTLRenderPipelineState`'s rasterization because if doing so, Metal runtime requires the Vertex
Shader to be a `void` function, i.e. not returning any stage output values. In order to
work-around this:
- `MTLRenderPipelineState`'s rasterization will still be enabled this case.
- However, the Vertex Shader will be translated to write `(-3, -3, -3, 1)` to
`[[position]]`/`gl_Position` variable at the end. Effectively forcing the vertex to be clipped
and preventing it from being sent down to Fragment Shader. Note that the `(-3, -3, -3, 1)`
writing are controlled by a specialized constant, thus it could be turned on and off base on
`GL_RASTERIZER_DISCARD` state. It is more efficient doing this way than re-translating the
whole shader code again using `spirv-cross` to turn it to a `void` function.
### Future improvements
- Use explicit memory barrier on macOS devices supporting it instead of ending the render pass.
- Instead of executing the same Vertex Shader's logic twice, one alternative approach is writing the
vertex outputs to a temporary buffer. Then in second pass, copy the varyings from that buffer to
XFB buffers. If rasterization is still enabled, then the 3rd pass will be invoked to use the
temporary buffer as vertex input, the Vertex Shader in 3rd pass might just a simple passthrough
shader:
1. Original VS -> All outputs to temp buffer.
2. Temp buffer -> Copy captured varying to XFB buffers. Could be done in a Compute Shader.
3. Temp buffer -> VS pass through to FS for rasterization.
- However, this approach might even be slower than executing the Vertex Shader twice. Because a
memory barrier must be inserted after 1st step. This prevents multiple draw calls with Transform
Feedback to be parallelized. Furthermore, on iOS devices or devices not supporting explicit
barrier, the render pass must be ended and restarted after each draw call.
- Most of the time, the application usually uses Transform Feedback with `GL_RASTERIZER_DISCARD`
enabled, the original approach will just simply executes the Vertex Shader once and use a cheap
1x1 render pass, thus it should be fast enough.
...@@ -442,6 +442,8 @@ class RenderCommandEncoder final : public CommandEncoder ...@@ -442,6 +442,8 @@ class RenderCommandEncoder final : public CommandEncoder
RenderCommandEncoder &setDepthLoadAction(MTLLoadAction action, double clearValue); RenderCommandEncoder &setDepthLoadAction(MTLLoadAction action, double clearValue);
RenderCommandEncoder &setStencilLoadAction(MTLLoadAction action, uint32_t clearValue); RenderCommandEncoder &setStencilLoadAction(MTLLoadAction action, uint32_t clearValue);
void setLabel(NSString *label);
const RenderPassDesc &renderPassDesc() const { return mRenderPassDesc; } const RenderPassDesc &renderPassDesc() const { return mRenderPassDesc; }
bool hasDrawCalls() const { return mHasDrawCalls; } bool hasDrawCalls() const { return mHasDrawCalls; }
...@@ -466,6 +468,9 @@ class RenderCommandEncoder final : public CommandEncoder ...@@ -466,6 +468,9 @@ class RenderCommandEncoder final : public CommandEncoder
RenderPassDesc mRenderPassDesc; RenderPassDesc mRenderPassDesc;
// Cached Objective-C render pass desc to avoid re-allocate every frame. // Cached Objective-C render pass desc to avoid re-allocate every frame.
mtl::AutoObjCObj<MTLRenderPassDescriptor> mCachedRenderPassDescObjC; mtl::AutoObjCObj<MTLRenderPassDescriptor> mCachedRenderPassDescObjC;
mtl::AutoObjCObj<NSString> mLabel;
MTLScissorRect mRenderPassMaxScissorRect; MTLScissorRect mRenderPassMaxScissorRect;
const OcclusionQueryPool &mOcclusionQueryPool; const OcclusionQueryPool &mOcclusionQueryPool;
......
...@@ -926,6 +926,11 @@ void RenderCommandEncoder::encodeMetalEncoder() ...@@ -926,6 +926,11 @@ void RenderCommandEncoder::encodeMetalEncoder()
// Verify that it was created successfully // Verify that it was created successfully
ASSERT(metalCmdEncoder); ASSERT(metalCmdEncoder);
if (mLabel)
{
metalCmdEncoder.label = mLabel;
}
while (mCommands.good()) while (mCommands.good())
{ {
CmdType cmdType = mCommands.fetch<CmdType>(); CmdType cmdType = mCommands.fetch<CmdType>();
...@@ -957,6 +962,8 @@ RenderCommandEncoder &RenderCommandEncoder::restart(const RenderPassDesc &desc) ...@@ -957,6 +962,8 @@ RenderCommandEncoder &RenderCommandEncoder::restart(const RenderPassDesc &desc)
return *this; return *this;
} }
mLabel.reset();
mRenderPassDesc = desc; mRenderPassDesc = desc;
mRecording = true; mRecording = true;
mHasDrawCalls = false; mHasDrawCalls = false;
...@@ -1556,6 +1563,11 @@ RenderCommandEncoder &RenderCommandEncoder::setStencilLoadAction(MTLLoadAction a ...@@ -1556,6 +1563,11 @@ RenderCommandEncoder &RenderCommandEncoder::setStencilLoadAction(MTLLoadAction a
return *this; return *this;
} }
void RenderCommandEncoder::setLabel(NSString *label)
{
mLabel.retainAssign(label);
}
// BlitCommandEncoder // BlitCommandEncoder
BlitCommandEncoder::BlitCommandEncoder(CommandBuffer *cmdBuffer) : CommandEncoder(cmdBuffer, BLIT) BlitCommandEncoder::BlitCommandEncoder(CommandBuffer *cmdBuffer) : CommandEncoder(cmdBuffer, BLIT)
{} {}
......
...@@ -102,6 +102,8 @@ constexpr uint32_t kMaxRenderTargets = 4; ...@@ -102,6 +102,8 @@ constexpr uint32_t kMaxRenderTargets = 4;
constexpr uint32_t kMaxShaderUBOs = 12; constexpr uint32_t kMaxShaderUBOs = 12;
constexpr uint32_t kMaxUBOSize = 16384; constexpr uint32_t kMaxUBOSize = 16384;
constexpr uint32_t kMaxShaderXFBs = gl::IMPLEMENTATION_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS;
// The max size of a buffer that will be allocated in shared memory. // The max size of a buffer that will be allocated in shared memory.
// NOTE(hqle): This is just a hint. There is no official document on what is the max allowed size // NOTE(hqle): This is just a hint. There is no official document on what is the max allowed size
// for shared memory. // for shared memory.
......
...@@ -37,13 +37,18 @@ struct TranslatedShaderInfo ...@@ -37,13 +37,18 @@ struct TranslatedShaderInfo
std::array<SamplerBinding, kMaxGLSamplerBindings> actualSamplerBindings; std::array<SamplerBinding, kMaxGLSamplerBindings> actualSamplerBindings;
std::array<uint32_t, kMaxGLUBOBindings> actualUBOBindings; std::array<uint32_t, kMaxGLUBOBindings> actualUBOBindings;
std::array<uint32_t, kMaxShaderXFBs> actualXFBBindings;
bool hasUBOArgumentBuffer; bool hasUBOArgumentBuffer;
}; };
// - shaderSourcesOut is result GLSL code per shader stage when XFB emulation is turned off.
// - xfbOnlyShaderSourceOut will contain vertex shader's GLSL code when XFB emulation is turned on.
void GlslangGetShaderSource(const gl::ProgramState &programState, void GlslangGetShaderSource(const gl::ProgramState &programState,
const gl::ProgramLinkedResources &resources, const gl::ProgramLinkedResources &resources,
gl::ShaderMap<std::string> *shaderSourcesOut, gl::ShaderMap<std::string> *shaderSourcesOut,
ShaderMapInterfaceVariableInfoMap *variableInfoMapOut); std::string *xfbOnlyShaderSourceOut,
ShaderMapInterfaceVariableInfoMap *variableInfoMapOut,
ShaderInterfaceVariableInfoMap *xfbOnlyVSVariableInfoMapOut);
angle::Result GlslangGetShaderSpirvCode(ErrorHandler *context, angle::Result GlslangGetShaderSpirvCode(ErrorHandler *context,
const gl::ShaderBitSet &linkedShaderStages, const gl::ShaderBitSet &linkedShaderStages,
...@@ -53,10 +58,17 @@ angle::Result GlslangGetShaderSpirvCode(ErrorHandler *context, ...@@ -53,10 +58,17 @@ angle::Result GlslangGetShaderSpirvCode(ErrorHandler *context,
gl::ShaderMap<std::vector<uint32_t>> *shaderCodeOut); gl::ShaderMap<std::vector<uint32_t>> *shaderCodeOut);
// Translate from SPIR-V code to Metal shader source code. // Translate from SPIR-V code to Metal shader source code.
// - spirvShaderCode is SPIRV code per shader stage when XFB emulation is turned off.
// - xfbOnlySpirvCode is vertex shader's SPIRV code when XFB emulation is turned on.
// - mslShaderInfoOut is result MSL info per shader stage when XFB emulation is turned off.
// - mslXfbOnlyShaderInfoOut is result vertex shader's MSL info when XFB emulation is turned on.
angle::Result SpirvCodeToMsl(Context *context, angle::Result SpirvCodeToMsl(Context *context,
const gl::ProgramState &programState, const gl::ProgramState &programState,
gl::ShaderMap<std::vector<uint32_t>> *sprivShaderCode, const ShaderInterfaceVariableInfoMap &xfbVSVariableInfoMap,
gl::ShaderMap<TranslatedShaderInfo> *mslShaderInfoOut); gl::ShaderMap<std::vector<uint32_t>> *spirvShaderCode,
std::vector<uint32_t> *xfbOnlySpirvCode /** nullable */,
gl::ShaderMap<TranslatedShaderInfo> *mslShaderInfoOut,
TranslatedShaderInfo *mslXfbOnlyShaderInfoOut /** nullable */);
} // namespace mtl } // namespace mtl
} // namespace rx } // namespace rx
......
...@@ -225,7 +225,7 @@ angle::Result Texture::MakeTexture(ContextMtl *context, ...@@ -225,7 +225,7 @@ angle::Result Texture::MakeTexture(ContextMtl *context,
refOut->reset(new Texture(context, desc, mips, renderTargetOnly, allowFormatView)); refOut->reset(new Texture(context, desc, mips, renderTargetOnly, allowFormatView));
if (!refOut || !refOut->get()) if (!(*refOut) || !(*refOut)->get())
{ {
ANGLE_MTL_CHECK(context, false, GL_OUT_OF_MEMORY); ANGLE_MTL_CHECK(context, false, GL_OUT_OF_MEMORY);
} }
...@@ -666,7 +666,7 @@ angle::Result Buffer::MakeBufferWithSharedMemOpt(ContextMtl *context, ...@@ -666,7 +666,7 @@ angle::Result Buffer::MakeBufferWithSharedMemOpt(ContextMtl *context,
{ {
bufferOut->reset(new Buffer(context, forceUseSharedMem, size, data)); bufferOut->reset(new Buffer(context, forceUseSharedMem, size, data));
if (!bufferOut || !bufferOut->get()) if (!(*bufferOut) || !(*bufferOut)->get())
{ {
ANGLE_MTL_CHECK(context, false, GL_OUT_OF_MEMORY); ANGLE_MTL_CHECK(context, false, GL_OUT_OF_MEMORY);
} }
...@@ -682,7 +682,7 @@ angle::Result Buffer::MakeBufferWithResOpt(ContextMtl *context, ...@@ -682,7 +682,7 @@ angle::Result Buffer::MakeBufferWithResOpt(ContextMtl *context,
{ {
bufferOut->reset(new Buffer(context, options, size, data)); bufferOut->reset(new Buffer(context, options, size, data));
if (!bufferOut || !bufferOut->get()) if (!(*bufferOut) || !(*bufferOut)->get())
{ {
ANGLE_MTL_CHECK(context, false, GL_OUT_OF_MEMORY); ANGLE_MTL_CHECK(context, false, GL_OUT_OF_MEMORY);
} }
......
...@@ -233,6 +233,28 @@ constexpr PrimitiveTopologyClass kPrimitiveTopologyClassTriangle = ...@@ -233,6 +233,28 @@ constexpr PrimitiveTopologyClass kPrimitiveTopologyClassTriangle =
constexpr PrimitiveTopologyClass kPrimitiveTopologyClassPoint = MTLPrimitiveTopologyClassPoint; constexpr PrimitiveTopologyClass kPrimitiveTopologyClassPoint = MTLPrimitiveTopologyClassPoint;
#endif #endif
enum class RenderPipelineRasterization : uint32_t
{
// This flag is used for vertex shader not writing any stage output (e.g gl_Position).
// This will disable fragment shader stage. This is useful for transform feedback ouput vertex
// shader.
Disabled,
// Fragment shader is enabled.
Enabled,
// This flag is for rasterization discard emulation when vertex shader still writes to stage
// output. Disabled flag cannot be used in this case since Metal doesn't allow that. The
// emulation would insert a code snippet to move gl_Position out of clip space's visible area to
// simulate the discard.
EmulatedDiscard,
EnumCount,
};
template <typename T>
using RenderPipelineRasterStateMap = angle::PackedEnumMap<RenderPipelineRasterization, T>;
struct alignas(4) RenderPipelineDesc struct alignas(4) RenderPipelineDesc
{ {
RenderPipelineDesc(); RenderPipelineDesc();
...@@ -245,6 +267,8 @@ struct alignas(4) RenderPipelineDesc ...@@ -245,6 +267,8 @@ struct alignas(4) RenderPipelineDesc
size_t hash() const; size_t hash() const;
bool rasterizationEnabled() const;
VertexDesc vertexDescriptor; VertexDesc vertexDescriptor;
RenderPipelineOutputDesc outputDescriptor; RenderPipelineOutputDesc outputDescriptor;
...@@ -252,12 +276,12 @@ struct alignas(4) RenderPipelineDesc ...@@ -252,12 +276,12 @@ struct alignas(4) RenderPipelineDesc
// Use uint8_t instead of PrimitiveTopologyClass to compact space. // Use uint8_t instead of PrimitiveTopologyClass to compact space.
uint8_t inputPrimitiveTopology : 2; uint8_t inputPrimitiveTopology : 2;
bool rasterizationEnabled : 1;
bool alphaToCoverageEnabled : 1; bool alphaToCoverageEnabled : 1;
// These flags are for emulation and do not correspond to any flags in // These flags are for emulation and do not correspond to any flags in
// MTLRenderPipelineDescriptor descriptor. These flags should be used by // MTLRenderPipelineDescriptor descriptor. These flags should be used by
// RenderPipelineCacheSpecializeShaderFactory. // RenderPipelineCacheSpecializeShaderFactory.
RenderPipelineRasterization rasterizationType : 2;
bool emulateCoverageMask : 1; bool emulateCoverageMask : 1;
}; };
......
...@@ -149,8 +149,6 @@ MTLRenderPipelineDescriptor *ToObjC(id<MTLFunction> vertexShader, ...@@ -149,8 +149,6 @@ MTLRenderPipelineDescriptor *ToObjC(id<MTLFunction> vertexShader,
{ {
MTLRenderPipelineDescriptor *objCDesc = [[MTLRenderPipelineDescriptor alloc] init]; MTLRenderPipelineDescriptor *objCDesc = [[MTLRenderPipelineDescriptor alloc] init];
[objCDesc reset]; [objCDesc reset];
objCDesc.vertexFunction = vertexShader;
objCDesc.fragmentFunction = fragmentShader;
ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, vertexDescriptor); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, vertexDescriptor);
...@@ -166,9 +164,14 @@ MTLRenderPipelineDescriptor *ToObjC(id<MTLFunction> vertexShader, ...@@ -166,9 +164,14 @@ MTLRenderPipelineDescriptor *ToObjC(id<MTLFunction> vertexShader,
#if ANGLE_MTL_PRIMITIVE_TOPOLOGY_CLASS_AVAILABLE #if ANGLE_MTL_PRIMITIVE_TOPOLOGY_CLASS_AVAILABLE
ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, inputPrimitiveTopology); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, inputPrimitiveTopology);
#endif #endif
ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, rasterizationEnabled);
ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, alphaToCoverageEnabled); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, alphaToCoverageEnabled);
// rasterizationEnabled will be true for both EmulatedDiscard & Enabled.
objCDesc.rasterizationEnabled = desc.rasterizationEnabled();
objCDesc.vertexFunction = vertexShader;
objCDesc.fragmentFunction = objCDesc.rasterizationEnabled ? fragmentShader : nil;
return [objCDesc ANGLE_MTL_AUTORELEASE]; return [objCDesc ANGLE_MTL_AUTORELEASE];
} }
...@@ -641,7 +644,7 @@ RenderPipelineDesc::RenderPipelineDesc() ...@@ -641,7 +644,7 @@ RenderPipelineDesc::RenderPipelineDesc()
{ {
memset(this, 0, sizeof(*this)); memset(this, 0, sizeof(*this));
outputDescriptor.sampleCount = 1; outputDescriptor.sampleCount = 1;
rasterizationEnabled = true; rasterizationType = RenderPipelineRasterization::Enabled;
} }
RenderPipelineDesc::RenderPipelineDesc(const RenderPipelineDesc &src) RenderPipelineDesc::RenderPipelineDesc(const RenderPipelineDesc &src)
...@@ -673,6 +676,11 @@ size_t RenderPipelineDesc::hash() const ...@@ -673,6 +676,11 @@ size_t RenderPipelineDesc::hash() const
return angle::ComputeGenericHash(*this); return angle::ComputeGenericHash(*this);
} }
bool RenderPipelineDesc::rasterizationEnabled() const
{
return rasterizationType != RenderPipelineRasterization::Disabled;
}
// RenderPassDesc implementation // RenderPassDesc implementation
RenderPassAttachmentDesc::RenderPassAttachmentDesc() RenderPassAttachmentDesc::RenderPassAttachmentDesc()
{ {
......
...@@ -928,8 +928,10 @@ TEST_P(TransformFeedbackTest, TwoUnreferencedInFragShader) ...@@ -928,8 +928,10 @@ TEST_P(TransformFeedbackTest, TwoUnreferencedInFragShader)
// glBeginTransformFeedback is called // glBeginTransformFeedback is called
TEST_P(TransformFeedbackTest, OffsetResetOnBeginTransformFeedback) TEST_P(TransformFeedbackTest, OffsetResetOnBeginTransformFeedback)
{ {
ANGLE_SKIP_TEST_IF(IsOSX() && IsAMD()); // http://anglebug.com/5069
ANGLE_SKIP_TEST_IF(IsOpenGL() && IsOSX() && IsAMD());
// http://anglebug.com/5069
ANGLE_SKIP_TEST_IF((IsNexus5X() || IsNexus6P()) && IsOpenGLES()); ANGLE_SKIP_TEST_IF((IsNexus5X() || IsNexus6P()) && IsOpenGLES());
// TODO(anglebug.com/4533) This fails after the upgrade to the 26.20.100.7870 driver. // TODO(anglebug.com/4533) This fails after the upgrade to the 26.20.100.7870 driver.
...@@ -2169,10 +2171,164 @@ void main() { ...@@ -2169,10 +2171,164 @@ void main() {
EXPECT_GL_NO_ERROR(); EXPECT_GL_NO_ERROR();
} }
// Test that transform feedback with scissor test enabled works.
TEST_P(TransformFeedbackTest, RecordAndDrawWithScissorTest)
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDepthMask(GL_TRUE);
glEnable(GL_DEPTH_TEST);
glScissor(0, 0, getWindowWidth() / 2 + 1, getWindowHeight() / 2 + 1);
glEnable(GL_SCISSOR_TEST);
// Set the program's transform feedback varyings (just gl_Position)
std::vector<std::string> tfVaryings;
tfVaryings.push_back("gl_Position");
compileDefaultProgram(tfVaryings, GL_INTERLEAVED_ATTRIBS);
glUseProgram(mProgram);
GLint positionLocation = glGetAttribLocation(mProgram, essl1_shaders::PositionAttrib());
// First pass: draw 6 points to the XFB buffer
glEnable(GL_RASTERIZER_DISCARD);
const GLfloat vertices[] = {
-1.0f, 1.0f, 0.5f, -1.0f, -1.0f, 0.5f, 1.0f, -1.0f, 0.5f,
-1.0f, 1.0f, 0.5f, 1.0f, -1.0f, 0.5f, 1.0f, 1.0f, 0.5f,
};
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, vertices);
glEnableVertexAttribArray(positionLocation);
// Bind the buffer for transform feedback output and start transform feedback
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBuffer);
glBeginTransformFeedback(GL_POINTS);
// Create a query to check how many primitives were written
GLuint primitivesWrittenQuery = 0;
glGenQueries(1, &primitivesWrittenQuery);
glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, primitivesWrittenQuery);
glDrawArrays(GL_POINTS, 0, 3);
glDrawArrays(GL_POINTS, 3, 3);
glDisableVertexAttribArray(positionLocation);
glVertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, 0, nullptr);
// End the query and transform feedback
glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
glEndTransformFeedback();
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0);
glDisable(GL_RASTERIZER_DISCARD);
// Check how many primitives were written and verify that some were written even if
// no pixels were rendered
GLuint primitivesWritten = 0;
glGetQueryObjectuiv(primitivesWrittenQuery, GL_QUERY_RESULT_EXT, &primitivesWritten);
EXPECT_GL_NO_ERROR();
EXPECT_EQ(6u, primitivesWritten);
// Second pass: draw from the feedback buffer
glBindBuffer(GL_ARRAY_BUFFER, mTransformFeedbackBuffer);
glVertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(positionLocation);
glDrawArrays(GL_TRIANGLES, 0, 6);
EXPECT_PIXEL_EQ(getWindowWidth() / 2, getWindowHeight() / 2, 255, 0, 0, 255);
EXPECT_PIXEL_EQ(getWindowWidth() / 2 + 1, getWindowHeight() / 2 + 1, 0, 0, 0, 255);
EXPECT_GL_NO_ERROR();
}
// Test XFB with depth write enabled.
class TransformFeedbackWithDepthBufferTest : public TransformFeedbackTest
{
public:
TransformFeedbackWithDepthBufferTest() : TransformFeedbackTest() { setConfigDepthBits(24); }
};
TEST_P(TransformFeedbackWithDepthBufferTest, RecordAndDrawWithDepthWriteEnabled)
{
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDepthMask(GL_TRUE);
glEnable(GL_DEPTH_TEST);
// Set the program's transform feedback varyings (just gl_Position)
std::vector<std::string> tfVaryings;
tfVaryings.push_back("gl_Position");
compileDefaultProgram(tfVaryings, GL_INTERLEAVED_ATTRIBS);
glUseProgram(mProgram);
GLint positionLocation = glGetAttribLocation(mProgram, essl1_shaders::PositionAttrib());
// First pass: draw 6 points to the XFB buffer
glEnable(GL_RASTERIZER_DISCARD);
const GLfloat vertices[] = {
-1.0f, 1.0f, 0.5f, -1.0f, -1.0f, 0.5f, 1.0f, -1.0f, 0.5f,
-1.0f, 1.0f, 0.5f, 1.0f, -1.0f, 0.5f, 1.0f, 1.0f, 0.5f,
};
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, vertices);
glEnableVertexAttribArray(positionLocation);
// Bind the buffer for transform feedback output and start transform feedback
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBuffer);
glBeginTransformFeedback(GL_POINTS);
// Create a query to check how many primitives were written
GLuint primitivesWrittenQuery = 0;
glGenQueries(1, &primitivesWrittenQuery);
glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, primitivesWrittenQuery);
glDrawArrays(GL_POINTS, 0, 3);
glDrawArrays(GL_POINTS, 3, 3);
glDisableVertexAttribArray(positionLocation);
glVertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, 0, nullptr);
// End the query and transform feedback
glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
glEndTransformFeedback();
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0);
glDisable(GL_RASTERIZER_DISCARD);
// Check how many primitives were written and verify that some were written even if
// no pixels were rendered
GLuint primitivesWritten = 0;
glGetQueryObjectuiv(primitivesWrittenQuery, GL_QUERY_RESULT_EXT, &primitivesWritten);
EXPECT_GL_NO_ERROR();
EXPECT_EQ(6u, primitivesWritten);
// Second pass: draw from the feedback buffer
glBindBuffer(GL_ARRAY_BUFFER, mTransformFeedbackBuffer);
glVertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(positionLocation);
glDrawArrays(GL_TRIANGLES, 0, 6);
EXPECT_PIXEL_EQ(getWindowWidth() / 2, getWindowHeight() / 2, 255, 0, 0, 255);
EXPECT_GL_NO_ERROR();
}
// Use this to select which configurations (e.g. which renderer, which GLES major version) these // Use this to select which configurations (e.g. which renderer, which GLES major version) these
// tests should be run against. // tests should be run against.
ANGLE_INSTANTIATE_TEST_ES3(TransformFeedbackTest); ANGLE_INSTANTIATE_TEST_ES3_AND(TransformFeedbackTest, ES3_METAL());
ANGLE_INSTANTIATE_TEST_ES3(TransformFeedbackLifetimeTest); ANGLE_INSTANTIATE_TEST_ES3_AND(TransformFeedbackLifetimeTest, ES3_METAL());
ANGLE_INSTANTIATE_TEST_ES31(TransformFeedbackTestES31); ANGLE_INSTANTIATE_TEST_ES31(TransformFeedbackTestES31);
ANGLE_INSTANTIATE_TEST(TransformFeedbackWithDepthBufferTest, ES3_METAL());
} // anonymous namespace } // anonymous namespace
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment