diff options
Diffstat (limited to 'src/Ryujinx.Graphics.OpenGL/Pipeline.cs')
-rw-r--r-- | src/Ryujinx.Graphics.OpenGL/Pipeline.cs | 1770 |
1 files changed, 1770 insertions, 0 deletions
diff --git a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs new file mode 100644 index 00000000..6b6d0289 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs @@ -0,0 +1,1770 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.OpenGL.Image; +using Ryujinx.Graphics.OpenGL.Queries; +using Ryujinx.Graphics.Shader; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.OpenGL +{ + class Pipeline : IPipeline, IDisposable + { + private const int SavedImages = 2; + + private readonly DrawTextureEmulation _drawTexture; + + internal ulong DrawCount { get; private set; } + + private Program _program; + + private bool _rasterizerDiscard; + + private VertexArray _vertexArray; + private Framebuffer _framebuffer; + + private IntPtr _indexBaseOffset; + + private DrawElementsType _elementsType; + + private PrimitiveType _primitiveType; + + private int _stencilFrontMask; + private bool _depthMask; + private bool _depthTestEnable; + private bool _stencilTestEnable; + private bool _cullEnable; + + private float[] _viewportArray = Array.Empty<float>(); + private double[] _depthRangeArray = Array.Empty<double>(); + + private int _boundDrawFramebuffer; + private int _boundReadFramebuffer; + + private CounterQueueEvent _activeConditionalRender; + + private Vector4<int>[] _fpIsBgra = new Vector4<int>[SupportBuffer.FragmentIsBgraCount]; + private Vector4<float>[] _renderScale = new Vector4<float>[73]; + private int _fragmentScaleCount; + + private (TextureBase, Format)[] _images; + private TextureBase _unit0Texture; + private Sampler _unit0Sampler; + + private FrontFaceDirection _frontFace; + private ClipOrigin _clipOrigin; + private ClipDepthMode _clipDepthMode; + + private uint _fragmentOutputMap; + private uint _componentMasks; + private uint _currentComponentMasks; + private bool _advancedBlendEnable; + + private uint _scissorEnables; + + private bool _tfEnabled; + private TransformFeedbackPrimitiveType _tfTopology; + + private SupportBufferUpdater _supportBuffer; + private readonly BufferHandle[] _tfbs; + private readonly BufferRange[] _tfbTargets; + + private ColorF _blendConstant; + + internal Pipeline() + { + _drawTexture = new DrawTextureEmulation(); + _rasterizerDiscard = false; + _clipOrigin = ClipOrigin.LowerLeft; + _clipDepthMode = ClipDepthMode.NegativeOneToOne; + + _fragmentOutputMap = uint.MaxValue; + _componentMasks = uint.MaxValue; + + _images = new (TextureBase, Format)[SavedImages]; + + var defaultScale = new Vector4<float> { X = 1f, Y = 0f, Z = 0f, W = 0f }; + new Span<Vector4<float>>(_renderScale).Fill(defaultScale); + + _tfbs = new BufferHandle[Constants.MaxTransformFeedbackBuffers]; + _tfbTargets = new BufferRange[Constants.MaxTransformFeedbackBuffers]; + } + + public void Initialize(OpenGLRenderer renderer) + { + _supportBuffer = new SupportBufferUpdater(renderer); + GL.BindBufferBase(BufferRangeTarget.UniformBuffer, 0, Unsafe.As<BufferHandle, int>(ref _supportBuffer.Handle)); + + _supportBuffer.UpdateFragmentIsBgra(_fpIsBgra, 0, SupportBuffer.FragmentIsBgraCount); + _supportBuffer.UpdateRenderScale(_renderScale, 0, SupportBuffer.RenderScaleMaxCount); + } + + public void Barrier() + { + GL.MemoryBarrier(MemoryBarrierFlags.AllBarrierBits); + } + + public void BeginTransformFeedback(PrimitiveTopology topology) + { + GL.BeginTransformFeedback(_tfTopology = topology.ConvertToTfType()); + _tfEnabled = true; + } + + public void ClearBuffer(BufferHandle destination, int offset, int size, uint value) + { + Buffer.Clear(destination, offset, size, value); + } + + public void ClearRenderTargetColor(int index, int layer, int layerCount, uint componentMask, ColorF color) + { + EnsureFramebuffer(); + + GL.ColorMask( + index, + (componentMask & 1) != 0, + (componentMask & 2) != 0, + (componentMask & 4) != 0, + (componentMask & 8) != 0); + + float[] colors = new float[] { color.Red, color.Green, color.Blue, color.Alpha }; + + if (layer != 0 || layerCount != _framebuffer.GetColorLayerCount(index)) + { + for (int l = layer; l < layer + layerCount; l++) + { + _framebuffer.AttachColorLayerForClear(index, l); + + GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Color, index, colors); + } + + _framebuffer.DetachColorLayerForClear(index); + } + else + { + GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Color, index, colors); + } + + RestoreComponentMask(index); + } + + public void ClearRenderTargetDepthStencil(int layer, int layerCount, float depthValue, bool depthMask, int stencilValue, int stencilMask) + { + EnsureFramebuffer(); + + bool stencilMaskChanged = + stencilMask != 0 && + stencilMask != _stencilFrontMask; + + bool depthMaskChanged = depthMask && depthMask != _depthMask; + + if (stencilMaskChanged) + { + GL.StencilMaskSeparate(StencilFace.Front, stencilMask); + } + + if (depthMaskChanged) + { + GL.DepthMask(depthMask); + } + + if (layer != 0 || layerCount != _framebuffer.GetDepthStencilLayerCount()) + { + for (int l = layer; l < layer + layerCount; l++) + { + _framebuffer.AttachDepthStencilLayerForClear(l); + + ClearDepthStencil(depthValue, depthMask, stencilValue, stencilMask); + } + + _framebuffer.DetachDepthStencilLayerForClear(); + } + else + { + ClearDepthStencil(depthValue, depthMask, stencilValue, stencilMask); + } + + if (stencilMaskChanged) + { + GL.StencilMaskSeparate(StencilFace.Front, _stencilFrontMask); + } + + if (depthMaskChanged) + { + GL.DepthMask(_depthMask); + } + } + + private static void ClearDepthStencil(float depthValue, bool depthMask, int stencilValue, int stencilMask) + { + if (depthMask && stencilMask != 0) + { + GL.ClearBuffer(ClearBufferCombined.DepthStencil, 0, depthValue, stencilValue); + } + else if (depthMask) + { + GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Depth, 0, ref depthValue); + } + else if (stencilMask != 0) + { + GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Stencil, 0, ref stencilValue); + } + } + + public void CommandBufferBarrier() + { + GL.MemoryBarrier(MemoryBarrierFlags.CommandBarrierBit); + } + + public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size) + { + Buffer.Copy(source, destination, srcOffset, dstOffset, size); + } + + public void DispatchCompute(int groupsX, int groupsY, int groupsZ) + { + if (!_program.IsLinked) + { + Logger.Debug?.Print(LogClass.Gpu, "Dispatch error, shader not linked."); + return; + } + + PrepareForDispatch(); + + GL.DispatchCompute(groupsX, groupsY, groupsZ); + } + + public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance) + { + if (!_program.IsLinked) + { + Logger.Debug?.Print(LogClass.Gpu, "Draw error, shader not linked."); + return; + } + + PreDraw(vertexCount); + + if (_primitiveType == PrimitiveType.Quads && !HwCapabilities.SupportsQuads) + { + DrawQuadsImpl(vertexCount, instanceCount, firstVertex, firstInstance); + } + else if (_primitiveType == PrimitiveType.QuadStrip && !HwCapabilities.SupportsQuads) + { + DrawQuadStripImpl(vertexCount, instanceCount, firstVertex, firstInstance); + } + else + { + DrawImpl(vertexCount, instanceCount, firstVertex, firstInstance); + } + + PostDraw(); + } + + private void DrawQuadsImpl( + int vertexCount, + int instanceCount, + int firstVertex, + int firstInstance) + { + // TODO: Instanced rendering. + int quadsCount = vertexCount / 4; + + int[] firsts = new int[quadsCount]; + int[] counts = new int[quadsCount]; + + for (int quadIndex = 0; quadIndex < quadsCount; quadIndex++) + { + firsts[quadIndex] = firstVertex + quadIndex * 4; + counts[quadIndex] = 4; + } + + GL.MultiDrawArrays( + PrimitiveType.TriangleFan, + firsts, + counts, + quadsCount); + } + + private void DrawQuadStripImpl( + int vertexCount, + int instanceCount, + int firstVertex, + int firstInstance) + { + int quadsCount = (vertexCount - 2) / 2; + + if (firstInstance != 0 || instanceCount != 1) + { + for (int quadIndex = 0; quadIndex < quadsCount; quadIndex++) + { + GL.DrawArraysInstancedBaseInstance(PrimitiveType.TriangleFan, firstVertex + quadIndex * 2, 4, instanceCount, firstInstance); + } + } + else + { + int[] firsts = new int[quadsCount]; + int[] counts = new int[quadsCount]; + + firsts[0] = firstVertex; + counts[0] = 4; + + for (int quadIndex = 1; quadIndex < quadsCount; quadIndex++) + { + firsts[quadIndex] = firstVertex + quadIndex * 2; + counts[quadIndex] = 4; + } + + GL.MultiDrawArrays( + PrimitiveType.TriangleFan, + firsts, + counts, + quadsCount); + } + } + + private void DrawImpl( + int vertexCount, + int instanceCount, + int firstVertex, + int firstInstance) + { + if (firstInstance == 0 && instanceCount == 1) + { + GL.DrawArrays(_primitiveType, firstVertex, vertexCount); + } + else if (firstInstance == 0) + { + GL.DrawArraysInstanced(_primitiveType, firstVertex, vertexCount, instanceCount); + } + else + { + GL.DrawArraysInstancedBaseInstance( + _primitiveType, + firstVertex, + vertexCount, + instanceCount, + firstInstance); + } + } + + public void DrawIndexed( + int indexCount, + int instanceCount, + int firstIndex, + int firstVertex, + int firstInstance) + { + if (!_program.IsLinked) + { + Logger.Debug?.Print(LogClass.Gpu, "Draw error, shader not linked."); + return; + } + + PreDrawVbUnbounded(); + + int indexElemSize = 1; + + switch (_elementsType) + { + case DrawElementsType.UnsignedShort: indexElemSize = 2; break; + case DrawElementsType.UnsignedInt: indexElemSize = 4; break; + } + + IntPtr indexBaseOffset = _indexBaseOffset + firstIndex * indexElemSize; + + if (_primitiveType == PrimitiveType.Quads && !HwCapabilities.SupportsQuads) + { + DrawQuadsIndexedImpl( + indexCount, + instanceCount, + indexBaseOffset, + indexElemSize, + firstVertex, + firstInstance); + } + else if (_primitiveType == PrimitiveType.QuadStrip && !HwCapabilities.SupportsQuads) + { + DrawQuadStripIndexedImpl( + indexCount, + instanceCount, + indexBaseOffset, + indexElemSize, + firstVertex, + firstInstance); + } + else + { + DrawIndexedImpl( + indexCount, + instanceCount, + indexBaseOffset, + firstVertex, + firstInstance); + } + + PostDraw(); + } + + private void DrawQuadsIndexedImpl( + int indexCount, + int instanceCount, + IntPtr indexBaseOffset, + int indexElemSize, + int firstVertex, + int firstInstance) + { + int quadsCount = indexCount / 4; + + if (firstInstance != 0 || instanceCount != 1) + { + if (firstVertex != 0 && firstInstance != 0) + { + for (int quadIndex = 0; quadIndex < quadsCount; quadIndex++) + { + GL.DrawElementsInstancedBaseVertexBaseInstance( + PrimitiveType.TriangleFan, + 4, + _elementsType, + indexBaseOffset + quadIndex * 4 * indexElemSize, + instanceCount, + firstVertex, + firstInstance); + } + } + else if (firstInstance != 0) + { + for (int quadIndex = 0; quadIndex < quadsCount; quadIndex++) + { + GL.DrawElementsInstancedBaseInstance( + PrimitiveType.TriangleFan, + 4, + _elementsType, + indexBaseOffset + quadIndex * 4 * indexElemSize, + instanceCount, + firstInstance); + } + } + else + { + for (int quadIndex = 0; quadIndex < quadsCount; quadIndex++) + { + GL.DrawElementsInstanced( + PrimitiveType.TriangleFan, + 4, + _elementsType, + indexBaseOffset + quadIndex * 4 * indexElemSize, + instanceCount); + } + } + } + else + { + IntPtr[] indices = new IntPtr[quadsCount]; + + int[] counts = new int[quadsCount]; + + int[] baseVertices = new int[quadsCount]; + + for (int quadIndex = 0; quadIndex < quadsCount; quadIndex++) + { + indices[quadIndex] = indexBaseOffset + quadIndex * 4 * indexElemSize; + + counts[quadIndex] = 4; + + baseVertices[quadIndex] = firstVertex; + } + + GL.MultiDrawElementsBaseVertex( + PrimitiveType.TriangleFan, + counts, + _elementsType, + indices, + quadsCount, + baseVertices); + } + } + + private void DrawQuadStripIndexedImpl( + int indexCount, + int instanceCount, + IntPtr indexBaseOffset, + int indexElemSize, + int firstVertex, + int firstInstance) + { + // TODO: Instanced rendering. + int quadsCount = (indexCount - 2) / 2; + + IntPtr[] indices = new IntPtr[quadsCount]; + + int[] counts = new int[quadsCount]; + + int[] baseVertices = new int[quadsCount]; + + indices[0] = indexBaseOffset; + + counts[0] = 4; + + baseVertices[0] = firstVertex; + + for (int quadIndex = 1; quadIndex < quadsCount; quadIndex++) + { + indices[quadIndex] = indexBaseOffset + quadIndex * 2 * indexElemSize; + + counts[quadIndex] = 4; + + baseVertices[quadIndex] = firstVertex; + } + + GL.MultiDrawElementsBaseVertex( + PrimitiveType.TriangleFan, + counts, + _elementsType, + indices, + quadsCount, + baseVertices); + } + + private void DrawIndexedImpl( + int indexCount, + int instanceCount, + IntPtr indexBaseOffset, + int firstVertex, + int firstInstance) + { + if (firstInstance == 0 && firstVertex == 0 && instanceCount == 1) + { + GL.DrawElements(_primitiveType, indexCount, _elementsType, indexBaseOffset); + } + else if (firstInstance == 0 && instanceCount == 1) + { + GL.DrawElementsBaseVertex( + _primitiveType, + indexCount, + _elementsType, + indexBaseOffset, + firstVertex); + } + else if (firstInstance == 0 && firstVertex == 0) + { + GL.DrawElementsInstanced( + _primitiveType, + indexCount, + _elementsType, + indexBaseOffset, + instanceCount); + } + else if (firstInstance == 0) + { + GL.DrawElementsInstancedBaseVertex( + _primitiveType, + indexCount, + _elementsType, + indexBaseOffset, + instanceCount, + firstVertex); + } + else if (firstVertex == 0) + { + GL.DrawElementsInstancedBaseInstance( + _primitiveType, + indexCount, + _elementsType, + indexBaseOffset, + instanceCount, + firstInstance); + } + else + { + GL.DrawElementsInstancedBaseVertexBaseInstance( + _primitiveType, + indexCount, + _elementsType, + indexBaseOffset, + instanceCount, + firstVertex, + firstInstance); + } + } + + public void DrawIndexedIndirect(BufferRange indirectBuffer) + { + if (!_program.IsLinked) + { + Logger.Debug?.Print(LogClass.Gpu, "Draw error, shader not linked."); + return; + } + + PreDrawVbUnbounded(); + + _vertexArray.SetRangeOfIndexBuffer(); + + GL.BindBuffer((BufferTarget)All.DrawIndirectBuffer, indirectBuffer.Handle.ToInt32()); + + GL.DrawElementsIndirect(_primitiveType, _elementsType, (IntPtr)indirectBuffer.Offset); + + _vertexArray.RestoreIndexBuffer(); + + PostDraw(); + } + + public void DrawIndexedIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride) + { + if (!_program.IsLinked) + { + Logger.Debug?.Print(LogClass.Gpu, "Draw error, shader not linked."); + return; + } + + PreDrawVbUnbounded(); + + _vertexArray.SetRangeOfIndexBuffer(); + + GL.BindBuffer((BufferTarget)All.DrawIndirectBuffer, indirectBuffer.Handle.ToInt32()); + GL.BindBuffer((BufferTarget)All.ParameterBuffer, parameterBuffer.Handle.ToInt32()); + + GL.MultiDrawElementsIndirectCount( + _primitiveType, + (All)_elementsType, + (IntPtr)indirectBuffer.Offset, + (IntPtr)parameterBuffer.Offset, + maxDrawCount, + stride); + + _vertexArray.RestoreIndexBuffer(); + + PostDraw(); + } + + public void DrawIndirect(BufferRange indirectBuffer) + { + if (!_program.IsLinked) + { + Logger.Debug?.Print(LogClass.Gpu, "Draw error, shader not linked."); + return; + } + + PreDrawVbUnbounded(); + + GL.BindBuffer((BufferTarget)All.DrawIndirectBuffer, indirectBuffer.Handle.ToInt32()); + + GL.DrawArraysIndirect(_primitiveType, (IntPtr)indirectBuffer.Offset); + + PostDraw(); + } + + public void DrawIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride) + { + if (!_program.IsLinked) + { + Logger.Debug?.Print(LogClass.Gpu, "Draw error, shader not linked."); + return; + } + + PreDrawVbUnbounded(); + + GL.BindBuffer((BufferTarget)All.DrawIndirectBuffer, indirectBuffer.Handle.ToInt32()); + GL.BindBuffer((BufferTarget)All.ParameterBuffer, parameterBuffer.Handle.ToInt32()); + + GL.MultiDrawArraysIndirectCount( + _primitiveType, + (IntPtr)indirectBuffer.Offset, + (IntPtr)parameterBuffer.Offset, + maxDrawCount, + stride); + + PostDraw(); + } + + public void DrawTexture(ITexture texture, ISampler sampler, Extents2DF srcRegion, Extents2DF dstRegion) + { + if (texture is TextureView view && sampler is Sampler samp) + { + _supportBuffer.Commit(); + + if (HwCapabilities.SupportsDrawTexture) + { + GL.NV.DrawTexture( + view.Handle, + samp.Handle, + dstRegion.X1, + dstRegion.Y1, + dstRegion.X2, + dstRegion.Y2, + 0, + srcRegion.X1 / view.Width, + srcRegion.Y1 / view.Height, + srcRegion.X2 / view.Width, + srcRegion.Y2 / view.Height); + } + else + { + static void Disable(EnableCap cap, bool enabled) + { + if (enabled) + { + GL.Disable(cap); + } + } + + static void Enable(EnableCap cap, bool enabled) + { + if (enabled) + { + GL.Enable(cap); + } + } + + Disable(EnableCap.CullFace, _cullEnable); + Disable(EnableCap.StencilTest, _stencilTestEnable); + Disable(EnableCap.DepthTest, _depthTestEnable); + + if (_depthMask) + { + GL.DepthMask(false); + } + + if (_tfEnabled) + { + GL.EndTransformFeedback(); + } + + GL.ClipControl(ClipOrigin.UpperLeft, ClipDepthMode.NegativeOneToOne); + + _drawTexture.Draw( + view, + samp, + dstRegion.X1, + dstRegion.Y1, + dstRegion.X2, + dstRegion.Y2, + srcRegion.X1 / view.Width, + srcRegion.Y1 / view.Height, + srcRegion.X2 / view.Width, + srcRegion.Y2 / view.Height); + + _program?.Bind(); + _unit0Sampler?.Bind(0); + + RestoreViewport0(); + + Enable(EnableCap.CullFace, _cullEnable); + Enable(EnableCap.StencilTest, _stencilTestEnable); + Enable(EnableCap.DepthTest, _depthTestEnable); + + if (_depthMask) + { + GL.DepthMask(true); + } + + if (_tfEnabled) + { + GL.BeginTransformFeedback(_tfTopology); + } + + RestoreClipControl(); + } + } + } + + public void EndTransformFeedback() + { + GL.EndTransformFeedback(); + _tfEnabled = false; + } + + public double GetCounterDivisor(CounterType type) + { + if (type == CounterType.SamplesPassed) + { + return _renderScale[0].X * _renderScale[0].X; + } + + return 1; + } + + public void SetAlphaTest(bool enable, float reference, CompareOp op) + { + if (!enable) + { + GL.Disable(EnableCap.AlphaTest); + return; + } + + GL.AlphaFunc((AlphaFunction)op.Convert(), reference); + GL.Enable(EnableCap.AlphaTest); + } + + public void SetBlendState(AdvancedBlendDescriptor blend) + { + if (HwCapabilities.SupportsBlendEquationAdvanced) + { + GL.BlendEquation((BlendEquationMode)blend.Op.Convert()); + GL.NV.BlendParameter(NvBlendEquationAdvanced.BlendOverlapNv, (int)blend.Overlap.Convert()); + GL.NV.BlendParameter(NvBlendEquationAdvanced.BlendPremultipliedSrcNv, blend.SrcPreMultiplied ? 1 : 0); + GL.Enable(EnableCap.Blend); + _advancedBlendEnable = true; + } + } + + public void SetBlendState(int index, BlendDescriptor blend) + { + if (_advancedBlendEnable) + { + GL.Disable(EnableCap.Blend); + _advancedBlendEnable = false; + } + + if (!blend.Enable) + { + GL.Disable(IndexedEnableCap.Blend, index); + return; + } + + GL.BlendEquationSeparate( + index, + blend.ColorOp.Convert(), + blend.AlphaOp.Convert()); + + GL.BlendFuncSeparate( + index, + (BlendingFactorSrc)blend.ColorSrcFactor.Convert(), + (BlendingFactorDest)blend.ColorDstFactor.Convert(), + (BlendingFactorSrc)blend.AlphaSrcFactor.Convert(), + (BlendingFactorDest)blend.AlphaDstFactor.Convert()); + + EnsureFramebuffer(); + + _framebuffer.SetDualSourceBlend( + blend.ColorSrcFactor.IsDualSource() || + blend.ColorDstFactor.IsDualSource() || + blend.AlphaSrcFactor.IsDualSource() || + blend.AlphaDstFactor.IsDualSource()); + + if (_blendConstant != blend.BlendConstant) + { + _blendConstant = blend.BlendConstant; + + GL.BlendColor( + blend.BlendConstant.Red, + blend.BlendConstant.Green, + blend.BlendConstant.Blue, + blend.BlendConstant.Alpha); + } + + GL.Enable(IndexedEnableCap.Blend, index); + } + + public void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp) + { + if ((enables & PolygonModeMask.Point) != 0) + { + GL.Enable(EnableCap.PolygonOffsetPoint); + } + else + { + GL.Disable(EnableCap.PolygonOffsetPoint); + } + + if ((enables & PolygonModeMask.Line) != 0) + { + GL.Enable(EnableCap.PolygonOffsetLine); + } + else + { + GL.Disable(EnableCap.PolygonOffsetLine); + } + + if ((enables & PolygonModeMask.Fill) != 0) + { + GL.Enable(EnableCap.PolygonOffsetFill); + } + else + { + GL.Disable(EnableCap.PolygonOffsetFill); + } + + if (enables == 0) + { + return; + } + + if (HwCapabilities.SupportsPolygonOffsetClamp) + { + GL.PolygonOffsetClamp(factor, units, clamp); + } + else + { + GL.PolygonOffset(factor, units); + } + } + + public void SetDepthClamp(bool clamp) + { + if (!clamp) + { + GL.Disable(EnableCap.DepthClamp); + return; + } + + GL.Enable(EnableCap.DepthClamp); + } + + public void SetDepthMode(DepthMode mode) + { + ClipDepthMode depthMode = mode.Convert(); + + if (_clipDepthMode != depthMode) + { + _clipDepthMode = depthMode; + + GL.ClipControl(_clipOrigin, depthMode); + } + } + + public void SetDepthTest(DepthTestDescriptor depthTest) + { + if (depthTest.TestEnable) + { + GL.Enable(EnableCap.DepthTest); + GL.DepthFunc((DepthFunction)depthTest.Func.Convert()); + } + else + { + GL.Disable(EnableCap.DepthTest); + } + + GL.DepthMask(depthTest.WriteEnable); + _depthMask = depthTest.WriteEnable; + _depthTestEnable = depthTest.TestEnable; + } + + public void SetFaceCulling(bool enable, Face face) + { + _cullEnable = enable; + + if (!enable) + { + GL.Disable(EnableCap.CullFace); + return; + } + + GL.CullFace(face.Convert()); + + GL.Enable(EnableCap.CullFace); + } + + public void SetFrontFace(FrontFace frontFace) + { + SetFrontFace(_frontFace = frontFace.Convert()); + } + + public void SetImage(int binding, ITexture texture, Format imageFormat) + { + if ((uint)binding < SavedImages) + { + _images[binding] = (texture as TextureBase, imageFormat); + } + + if (texture == null) + { + GL.BindImageTexture(binding, 0, 0, true, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8); + return; + } + + TextureBase texBase = (TextureBase)texture; + + SizedInternalFormat format = FormatTable.GetImageFormat(imageFormat); + + if (format != 0) + { + GL.BindImageTexture(binding, texBase.Handle, 0, true, 0, TextureAccess.ReadWrite, format); + } + } + + public void SetIndexBuffer(BufferRange buffer, IndexType type) + { + _elementsType = type.Convert(); + + _indexBaseOffset = (IntPtr)buffer.Offset; + + EnsureVertexArray(); + + _vertexArray.SetIndexBuffer(buffer); + } + + public void SetLogicOpState(bool enable, LogicalOp op) + { + if (enable) + { + GL.Enable(EnableCap.ColorLogicOp); + + GL.LogicOp((LogicOp)op.Convert()); + } + else + { + GL.Disable(EnableCap.ColorLogicOp); + } + } + + public void SetMultisampleState(MultisampleDescriptor multisample) + { + if (multisample.AlphaToCoverageEnable) + { + GL.Enable(EnableCap.SampleAlphaToCoverage); + + if (multisample.AlphaToOneEnable) + { + GL.Enable(EnableCap.SampleAlphaToOne); + } + else + { + GL.Disable(EnableCap.SampleAlphaToOne); + } + + if (HwCapabilities.SupportsAlphaToCoverageDitherControl) + { + GL.NV.AlphaToCoverageDitherControl(multisample.AlphaToCoverageDitherEnable + ? NvAlphaToCoverageDitherControl.AlphaToCoverageDitherEnableNv + : NvAlphaToCoverageDitherControl.AlphaToCoverageDitherDisableNv); + } + } + else + { + GL.Disable(EnableCap.SampleAlphaToCoverage); + } + } + + public void SetLineParameters(float width, bool smooth) + { + if (smooth) + { + GL.Enable(EnableCap.LineSmooth); + } + else + { + GL.Disable(EnableCap.LineSmooth); + } + + GL.LineWidth(width); + } + + public unsafe void SetPatchParameters(int vertices, ReadOnlySpan<float> defaultOuterLevel, ReadOnlySpan<float> defaultInnerLevel) + { + GL.PatchParameter(PatchParameterInt.PatchVertices, vertices); + + fixed (float* pOuterLevel = defaultOuterLevel) + { + GL.PatchParameter(PatchParameterFloat.PatchDefaultOuterLevel, pOuterLevel); + } + + fixed (float* pInnerLevel = defaultInnerLevel) + { + GL.PatchParameter(PatchParameterFloat.PatchDefaultInnerLevel, pInnerLevel); + } + } + + public void SetPointParameters(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin) + { + // GL_POINT_SPRITE was deprecated in core profile 3.2+ and causes GL_INVALID_ENUM when set. + // As we don't know if the current context is core or compat, it's safer to keep this code. + if (enablePointSprite) + { + GL.Enable(EnableCap.PointSprite); + } + else + { + GL.Disable(EnableCap.PointSprite); + } + + if (isProgramPointSize) + { + GL.Enable(EnableCap.ProgramPointSize); + } + else + { + GL.Disable(EnableCap.ProgramPointSize); + } + + GL.PointParameter(origin == Origin.LowerLeft + ? PointSpriteCoordOriginParameter.LowerLeft + : PointSpriteCoordOriginParameter.UpperLeft); + + // Games seem to set point size to 0 which generates a GL_INVALID_VALUE + // From the spec, GL_INVALID_VALUE is generated if size is less than or equal to 0. + GL.PointSize(Math.Max(float.Epsilon, size)); + } + + public void SetPolygonMode(GAL.PolygonMode frontMode, GAL.PolygonMode backMode) + { + if (frontMode == backMode) + { + GL.PolygonMode(MaterialFace.FrontAndBack, frontMode.Convert()); + } + else + { + GL.PolygonMode(MaterialFace.Front, frontMode.Convert()); + GL.PolygonMode(MaterialFace.Back, backMode.Convert()); + } + } + + public void SetPrimitiveRestart(bool enable, int index) + { + if (!enable) + { + GL.Disable(EnableCap.PrimitiveRestart); + return; + } + + GL.PrimitiveRestartIndex(index); + + GL.Enable(EnableCap.PrimitiveRestart); + } + + public void SetPrimitiveTopology(PrimitiveTopology topology) + { + _primitiveType = topology.Convert(); + } + + public void SetProgram(IProgram program) + { + Program prg = (Program)program; + + if (_tfEnabled) + { + GL.EndTransformFeedback(); + prg.Bind(); + GL.BeginTransformFeedback(_tfTopology); + } + else + { + prg.Bind(); + } + + if (prg.HasFragmentShader && _fragmentOutputMap != (uint)prg.FragmentOutputMap) + { + _fragmentOutputMap = (uint)prg.FragmentOutputMap; + + for (int index = 0; index < Constants.MaxRenderTargets; index++) + { + RestoreComponentMask(index, force: false); + } + } + + _program = prg; + } + + public void SetRasterizerDiscard(bool discard) + { + if (discard) + { + GL.Enable(EnableCap.RasterizerDiscard); + } + else + { + GL.Disable(EnableCap.RasterizerDiscard); + } + + _rasterizerDiscard = discard; + } + + public void SetRenderTargetScale(float scale) + { + _renderScale[0].X = scale; + _supportBuffer.UpdateRenderScale(_renderScale, 0, 1); // Just the first element. + } + + public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMasks) + { + _componentMasks = 0; + + for (int index = 0; index < componentMasks.Length; index++) + { + _componentMasks |= componentMasks[index] << (index * 4); + + RestoreComponentMask(index, force: false); + } + } + + public void SetRenderTargets(ITexture[] colors, ITexture depthStencil) + { + EnsureFramebuffer(); + + bool isBgraChanged = false; + + for (int index = 0; index < colors.Length; index++) + { + TextureView color = (TextureView)colors[index]; + + _framebuffer.AttachColor(index, color); + + if (color != null) + { + int isBgra = color.Format.IsBgr() ? 1 : 0; + + if (_fpIsBgra[index].X != isBgra) + { + _fpIsBgra[index].X = isBgra; + isBgraChanged = true; + + RestoreComponentMask(index); + } + } + } + + if (isBgraChanged) + { + _supportBuffer.UpdateFragmentIsBgra(_fpIsBgra, 0, SupportBuffer.FragmentIsBgraCount); + } + + TextureView depthStencilView = (TextureView)depthStencil; + + _framebuffer.AttachDepthStencil(depthStencilView); + _framebuffer.SetDrawBuffers(colors.Length); + } + + public void SetScissors(ReadOnlySpan<Rectangle<int>> regions) + { + int count = Math.Min(regions.Length, Constants.MaxViewports); + + Span<int> v = stackalloc int[count * 4]; + + for (int index = 0; index < count; index++) + { + int vIndex = index * 4; + + var region = regions[index]; + + bool enabled = (region.X | region.Y) != 0 || region.Width != 0xffff || region.Height != 0xffff; + uint mask = 1u << index; + + if (enabled) + { + v[vIndex] = region.X; + v[vIndex + 1] = region.Y; + v[vIndex + 2] = region.Width; + v[vIndex + 3] = region.Height; + + if ((_scissorEnables & mask) == 0) + { + _scissorEnables |= mask; + GL.Enable(IndexedEnableCap.ScissorTest, index); + } + } + else + { + if ((_scissorEnables & mask) != 0) + { + _scissorEnables &= ~mask; + GL.Disable(IndexedEnableCap.ScissorTest, index); + } + } + } + + GL.ScissorArray(0, count, ref v[0]); + } + + public void SetStencilTest(StencilTestDescriptor stencilTest) + { + _stencilTestEnable = stencilTest.TestEnable; + + if (!stencilTest.TestEnable) + { + GL.Disable(EnableCap.StencilTest); + return; + } + + GL.StencilOpSeparate( + StencilFace.Front, + stencilTest.FrontSFail.Convert(), + stencilTest.FrontDpFail.Convert(), + stencilTest.FrontDpPass.Convert()); + + GL.StencilFuncSeparate( + StencilFace.Front, + (StencilFunction)stencilTest.FrontFunc.Convert(), + stencilTest.FrontFuncRef, + stencilTest.FrontFuncMask); + + GL.StencilMaskSeparate(StencilFace.Front, stencilTest.FrontMask); + + GL.StencilOpSeparate( + StencilFace.Back, + stencilTest.BackSFail.Convert(), + stencilTest.BackDpFail.Convert(), + stencilTest.BackDpPass.Convert()); + + GL.StencilFuncSeparate( + StencilFace.Back, + (StencilFunction)stencilTest.BackFunc.Convert(), + stencilTest.BackFuncRef, + stencilTest.BackFuncMask); + + GL.StencilMaskSeparate(StencilFace.Back, stencilTest.BackMask); + + GL.Enable(EnableCap.StencilTest); + + _stencilFrontMask = stencilTest.FrontMask; + } + + public void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers) + { + SetBuffers(buffers, isStorage: true); + } + + public void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler) + { + if (texture != null) + { + if (binding == 0) + { + _unit0Texture = (TextureBase)texture; + } + else + { + ((TextureBase)texture).Bind(binding); + } + } + else + { + TextureBase.ClearBinding(binding); + } + + Sampler glSampler = (Sampler)sampler; + + glSampler?.Bind(binding); + + if (binding == 0) + { + _unit0Sampler = glSampler; + } + } + + + public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers) + { + if (_tfEnabled) + { + GL.EndTransformFeedback(); + } + + int count = Math.Min(buffers.Length, Constants.MaxTransformFeedbackBuffers); + + for (int i = 0; i < count; i++) + { + BufferRange buffer = buffers[i]; + _tfbTargets[i] = buffer; + + if (buffer.Handle == BufferHandle.Null) + { + GL.BindBufferBase(BufferRangeTarget.TransformFeedbackBuffer, i, 0); + continue; + } + + if (_tfbs[i] == BufferHandle.Null) + { + _tfbs[i] = Buffer.Create(); + } + + Buffer.Resize(_tfbs[i], buffer.Size); + Buffer.Copy(buffer.Handle, _tfbs[i], buffer.Offset, 0, buffer.Size); + GL.BindBufferBase(BufferRangeTarget.TransformFeedbackBuffer, i, _tfbs[i].ToInt32()); + } + + if (_tfEnabled) + { + GL.BeginTransformFeedback(_tfTopology); + } + } + + public void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers) + { + SetBuffers(buffers, isStorage: false); + } + + public void SetUserClipDistance(int index, bool enableClip) + { + if (!enableClip) + { + GL.Disable(EnableCap.ClipDistance0 + index); + return; + } + + GL.Enable(EnableCap.ClipDistance0 + index); + } + + public void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs) + { + EnsureVertexArray(); + + _vertexArray.SetVertexAttributes(vertexAttribs); + } + + public void SetVertexBuffers(ReadOnlySpan<VertexBufferDescriptor> vertexBuffers) + { + EnsureVertexArray(); + + _vertexArray.SetVertexBuffers(vertexBuffers); + } + + public void SetViewports(ReadOnlySpan<Viewport> viewports, bool disableTransform) + { + Array.Resize(ref _viewportArray, viewports.Length * 4); + Array.Resize(ref _depthRangeArray, viewports.Length * 2); + + float[] viewportArray = _viewportArray; + double[] depthRangeArray = _depthRangeArray; + + for (int index = 0; index < viewports.Length; index++) + { + int viewportElemIndex = index * 4; + + Viewport viewport = viewports[index]; + + viewportArray[viewportElemIndex + 0] = viewport.Region.X; + viewportArray[viewportElemIndex + 1] = viewport.Region.Y + (viewport.Region.Height < 0 ? viewport.Region.Height : 0); + viewportArray[viewportElemIndex + 2] = viewport.Region.Width; + viewportArray[viewportElemIndex + 3] = MathF.Abs(viewport.Region.Height); + + if (HwCapabilities.SupportsViewportSwizzle) + { + GL.NV.ViewportSwizzle( + index, + viewport.SwizzleX.Convert(), + viewport.SwizzleY.Convert(), + viewport.SwizzleZ.Convert(), + viewport.SwizzleW.Convert()); + } + + depthRangeArray[index * 2 + 0] = viewport.DepthNear; + depthRangeArray[index * 2 + 1] = viewport.DepthFar; + } + + bool flipY = viewports.Length != 0 && viewports[0].Region.Height < 0; + + SetOrigin(flipY ? ClipOrigin.UpperLeft : ClipOrigin.LowerLeft); + + GL.ViewportArray(0, viewports.Length, viewportArray); + GL.DepthRangeArray(0, viewports.Length, depthRangeArray); + + float disableTransformF = disableTransform ? 1.0f : 0.0f; + if (_supportBuffer.Data.ViewportInverse.W != disableTransformF || disableTransform) + { + float scale = _renderScale[0].X; + _supportBuffer.UpdateViewportInverse(new Vector4<float> + { + X = scale * 2f / viewports[0].Region.Width, + Y = scale * 2f / viewports[0].Region.Height, + Z = 1, + W = disableTransformF + }); + } + } + + public void TextureBarrier() + { + GL.MemoryBarrier(MemoryBarrierFlags.TextureFetchBarrierBit); + } + + public void TextureBarrierTiled() + { + GL.MemoryBarrier(MemoryBarrierFlags.TextureFetchBarrierBit); + } + + private void SetBuffers(ReadOnlySpan<BufferAssignment> buffers, bool isStorage) + { + BufferRangeTarget target = isStorage ? BufferRangeTarget.ShaderStorageBuffer : BufferRangeTarget.UniformBuffer; + + for (int index = 0; index < buffers.Length; index++) + { + BufferAssignment assignment = buffers[index]; + BufferRange buffer = assignment.Range; + + if (buffer.Handle == BufferHandle.Null) + { + GL.BindBufferRange(target, assignment.Binding, 0, IntPtr.Zero, 0); + continue; + } + + GL.BindBufferRange(target, assignment.Binding, buffer.Handle.ToInt32(), (IntPtr)buffer.Offset, buffer.Size); + } + } + + private void SetOrigin(ClipOrigin origin) + { + if (_clipOrigin != origin) + { + _clipOrigin = origin; + + GL.ClipControl(origin, _clipDepthMode); + + SetFrontFace(_frontFace); + } + } + + private void SetFrontFace(FrontFaceDirection frontFace) + { + // Changing clip origin will also change the front face to compensate + // for the flipped viewport, we flip it again here to compensate as + // this effect is undesirable for us. + if (_clipOrigin == ClipOrigin.UpperLeft) + { + frontFace = frontFace == FrontFaceDirection.Ccw ? FrontFaceDirection.Cw : FrontFaceDirection.Ccw; + } + + GL.FrontFace(frontFace); + } + + private void EnsureVertexArray() + { + if (_vertexArray == null) + { + _vertexArray = new VertexArray(); + + _vertexArray.Bind(); + } + } + + private void EnsureFramebuffer() + { + if (_framebuffer == null) + { + _framebuffer = new Framebuffer(); + + int boundHandle = _framebuffer.Bind(); + _boundDrawFramebuffer = _boundReadFramebuffer = boundHandle; + + GL.Enable(EnableCap.FramebufferSrgb); + } + } + + internal (int drawHandle, int readHandle) GetBoundFramebuffers() + { + if (BackgroundContextWorker.InBackground) + { + return (0, 0); + } + + return (_boundDrawFramebuffer, _boundReadFramebuffer); + } + + public void UpdateRenderScale(ReadOnlySpan<float> scales, int totalCount, int fragmentCount) + { + bool changed = false; + + for (int index = 0; index < totalCount; index++) + { + if (_renderScale[1 + index].X != scales[index]) + { + _renderScale[1 + index].X = scales[index]; + changed = true; + } + } + + // Only update fragment count if there are scales after it for the vertex stage. + if (fragmentCount != totalCount && fragmentCount != _fragmentScaleCount) + { + _fragmentScaleCount = fragmentCount; + _supportBuffer.UpdateFragmentRenderScaleCount(_fragmentScaleCount); + } + + if (changed) + { + _supportBuffer.UpdateRenderScale(_renderScale, 0, 1 + totalCount); + } + } + + private void PrepareForDispatch() + { + _unit0Texture?.Bind(0); + _supportBuffer.Commit(); + } + + private void PreDraw(int vertexCount) + { + _vertexArray.PreDraw(vertexCount); + PreDraw(); + } + + private void PreDrawVbUnbounded() + { + _vertexArray.PreDrawVbUnbounded(); + PreDraw(); + } + + private void PreDraw() + { + DrawCount++; + + _unit0Texture?.Bind(0); + _supportBuffer.Commit(); + } + + private void PostDraw() + { + if (_tfEnabled) + { + for (int i = 0; i < Constants.MaxTransformFeedbackBuffers; i++) + { + if (_tfbTargets[i].Handle != BufferHandle.Null) + { + Buffer.Copy(_tfbs[i], _tfbTargets[i].Handle, 0, _tfbTargets[i].Offset, _tfbTargets[i].Size); + } + } + } + } + + public void RestoreComponentMask(int index, bool force = true) + { + // If the bound render target is bgra, swap the red and blue masks. + uint redMask = _fpIsBgra[index].X == 0 ? 1u : 4u; + uint blueMask = _fpIsBgra[index].X == 0 ? 4u : 1u; + + int shift = index * 4; + uint componentMask = _componentMasks & _fragmentOutputMap; + uint checkMask = 0xfu << shift; + uint componentMaskAtIndex = componentMask & checkMask; + + if (!force && componentMaskAtIndex == (_currentComponentMasks & checkMask)) + { + return; + } + + componentMask >>= shift; + componentMask &= 0xfu; + + GL.ColorMask( + index, + (componentMask & redMask) != 0, + (componentMask & 2u) != 0, + (componentMask & blueMask) != 0, + (componentMask & 8u) != 0); + + _currentComponentMasks &= ~checkMask; + _currentComponentMasks |= componentMaskAtIndex; + } + + public void RestoreClipControl() + { + GL.ClipControl(_clipOrigin, _clipDepthMode); + } + + public void RestoreScissor0Enable() + { + if ((_scissorEnables & 1u) != 0) + { + GL.Enable(IndexedEnableCap.ScissorTest, 0); + } + } + + public void RestoreRasterizerDiscard() + { + if (_rasterizerDiscard) + { + GL.Enable(EnableCap.RasterizerDiscard); + } + } + + public void RestoreViewport0() + { + if (_viewportArray.Length > 0) + { + GL.ViewportArray(0, 1, _viewportArray); + } + } + + public void RestoreProgram() + { + _program?.Bind(); + } + + public void RestoreImages1And2() + { + for (int i = 0; i < SavedImages; i++) + { + (TextureBase texBase, Format imageFormat) = _images[i]; + + if (texBase != null) + { + SizedInternalFormat format = FormatTable.GetImageFormat(imageFormat); + + if (format != 0) + { + GL.BindImageTexture(i, texBase.Handle, 0, true, 0, TextureAccess.ReadWrite, format); + continue; + } + } + + GL.BindImageTexture(i, 0, 0, true, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8); + } + } + + public bool TryHostConditionalRendering(ICounterEvent value, ulong compare, bool isEqual) + { + if (value is CounterQueueEvent) + { + // Compare an event and a constant value. + CounterQueueEvent evt = (CounterQueueEvent)value; + + // Easy host conditional rendering when the check matches what GL can do: + // - Event is of type samples passed. + // - Result is not a combination of multiple queries. + // - Comparing against 0. + // - Event has not already been flushed. + + if (compare == 0 && evt.Type == QueryTarget.SamplesPassed && evt.ClearCounter) + { + if (!value.ReserveForHostAccess()) + { + // If the event has been flushed, then just use the values on the CPU. + // The query object may already be repurposed for another draw (eg. begin + end). + return false; + } + + GL.BeginConditionalRender(evt.Query, isEqual ? ConditionalRenderType.QueryNoWaitInverted : ConditionalRenderType.QueryNoWait); + _activeConditionalRender = evt; + + return true; + } + } + + // The GPU will flush the queries to CPU and evaluate the condition there instead. + + GL.Flush(); // The thread will be stalled manually flushing the counter, so flush GL commands now. + return false; + } + + public bool TryHostConditionalRendering(ICounterEvent value, ICounterEvent compare, bool isEqual) + { + GL.Flush(); // The GPU thread will be stalled manually flushing the counter, so flush GL commands now. + return false; // We don't currently have a way to compare two counters for conditional rendering. + } + + public void EndHostConditionalRendering() + { + GL.EndConditionalRender(); + + _activeConditionalRender?.ReleaseHostAccess(); + _activeConditionalRender = null; + } + + public void Dispose() + { + _supportBuffer?.Dispose(); + + for (int i = 0; i < Constants.MaxTransformFeedbackBuffers; i++) + { + if (_tfbs[i] != BufferHandle.Null) + { + Buffer.Delete(_tfbs[i]); + _tfbs[i] = BufferHandle.Null; + } + } + + _activeConditionalRender?.ReleaseHostAccess(); + _framebuffer?.Dispose(); + _vertexArray?.Dispose(); + _drawTexture.Dispose(); + } + } +} |