|
|
#region License
|
|
|
/* FNA - XNA4 Reimplementation for Desktop Platforms
|
|
|
* Copyright 2009-2020 Ethan Lee and the MonoGame Team
|
|
|
*
|
|
|
* Released under the Microsoft Public License.
|
|
|
* See LICENSE for details.
|
|
|
*/
|
|
|
#endregion
|
|
|
|
|
|
#region Using Statements
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
|
using SDL2;
|
|
|
#endregion
|
|
|
|
|
|
namespace Microsoft.Xna.Framework.Graphics
|
|
|
{
|
|
|
internal partial class MetalDevice : IGLDevice
|
|
|
{
|
|
|
#region Metal Texture Container Class
|
|
|
|
|
|
private class MetalTexture : IGLTexture
|
|
|
{
|
|
|
public IntPtr Handle
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public bool HasMipmaps
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public int Width
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public int Height
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public bool IsPrivate
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public SurfaceFormat Format;
|
|
|
public TextureAddressMode WrapS;
|
|
|
public TextureAddressMode WrapT;
|
|
|
public TextureAddressMode WrapR;
|
|
|
public TextureFilter Filter;
|
|
|
public float Anisotropy;
|
|
|
public int MaxMipmapLevel;
|
|
|
public float LODBias;
|
|
|
|
|
|
public MetalTexture(
|
|
|
IntPtr handle,
|
|
|
int width,
|
|
|
int height,
|
|
|
SurfaceFormat format,
|
|
|
int levelCount,
|
|
|
bool isPrivate
|
|
|
) {
|
|
|
Handle = handle;
|
|
|
Width = width;
|
|
|
Height = height;
|
|
|
Format = format;
|
|
|
HasMipmaps = levelCount > 1;
|
|
|
IsPrivate = isPrivate;
|
|
|
|
|
|
WrapS = TextureAddressMode.Wrap;
|
|
|
WrapT = TextureAddressMode.Wrap;
|
|
|
WrapR = TextureAddressMode.Wrap;
|
|
|
Filter = TextureFilter.Linear;
|
|
|
Anisotropy = 4.0f;
|
|
|
MaxMipmapLevel = 0;
|
|
|
LODBias = 0.0f;
|
|
|
}
|
|
|
|
|
|
private MetalTexture()
|
|
|
{
|
|
|
Handle = IntPtr.Zero;
|
|
|
}
|
|
|
public static readonly MetalTexture NullTexture = new MetalTexture();
|
|
|
|
|
|
public void Dispose()
|
|
|
{
|
|
|
objc_release(Handle);
|
|
|
Handle = IntPtr.Zero;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Metal Renderbuffer Container Class
|
|
|
|
|
|
private class MetalRenderbuffer : IGLRenderbuffer
|
|
|
{
|
|
|
public IntPtr Handle
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public IntPtr MultiSampleHandle
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public MTLPixelFormat PixelFormat
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public int MultiSampleCount
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public MetalRenderbuffer(
|
|
|
IntPtr handle,
|
|
|
MTLPixelFormat pixelFormat,
|
|
|
int multiSampleCount,
|
|
|
IntPtr multiSampleHandle
|
|
|
) {
|
|
|
Handle = handle;
|
|
|
PixelFormat = pixelFormat;
|
|
|
MultiSampleCount = multiSampleCount;
|
|
|
MultiSampleHandle = multiSampleHandle;
|
|
|
}
|
|
|
|
|
|
public void Dispose()
|
|
|
{
|
|
|
if (MultiSampleHandle == IntPtr.Zero)
|
|
|
{
|
|
|
objc_release(Handle);
|
|
|
Handle = IntPtr.Zero;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
objc_release(MultiSampleHandle);
|
|
|
MultiSampleHandle = IntPtr.Zero;
|
|
|
|
|
|
/* Don't release the regular Handle since
|
|
|
* it's owned by the associated IGLTexture.
|
|
|
*/
|
|
|
Handle = IntPtr.Zero;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Metal Buffer Container Class
|
|
|
|
|
|
private class MetalBuffer : IGLBuffer
|
|
|
{
|
|
|
public IntPtr Handle
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public IntPtr Contents
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public IntPtr BufferSize
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public int InternalOffset
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
private MetalDevice device;
|
|
|
private IntPtr mtlDevice = IntPtr.Zero;
|
|
|
private int internalBufferSize = 0;
|
|
|
private int prevDataLength = 0;
|
|
|
private int prevInternalOffset;
|
|
|
private BufferUsage usage;
|
|
|
private bool boundThisFrame;
|
|
|
|
|
|
public MetalBuffer(
|
|
|
MetalDevice device,
|
|
|
bool dynamic,
|
|
|
BufferUsage usage,
|
|
|
IntPtr bufferSize
|
|
|
) {
|
|
|
this.device = device;
|
|
|
this.mtlDevice = device.device;
|
|
|
this.usage = usage;
|
|
|
|
|
|
BufferSize = bufferSize;
|
|
|
internalBufferSize = (int) bufferSize;
|
|
|
|
|
|
CreateBackingBuffer(-1);
|
|
|
}
|
|
|
|
|
|
private void CreateBackingBuffer(int prevSize)
|
|
|
{
|
|
|
IntPtr oldBuffer = Handle;
|
|
|
IntPtr oldContents = Contents;
|
|
|
|
|
|
Handle = mtlNewBufferWithLength(
|
|
|
mtlDevice,
|
|
|
internalBufferSize,
|
|
|
usage == BufferUsage.WriteOnly ?
|
|
|
MTLResourceOptions.CPUCacheModeWriteCombined :
|
|
|
MTLResourceOptions.CPUCacheModeDefaultCache
|
|
|
);
|
|
|
Contents = mtlGetBufferContentsPtr(Handle);
|
|
|
|
|
|
// Copy over data from old buffer
|
|
|
if (oldBuffer != IntPtr.Zero)
|
|
|
{
|
|
|
SDL.SDL_memcpy(
|
|
|
Contents,
|
|
|
oldContents,
|
|
|
(IntPtr) prevSize
|
|
|
);
|
|
|
objc_release(oldBuffer);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void SetData(
|
|
|
int offsetInBytes,
|
|
|
IntPtr data,
|
|
|
int dataLength,
|
|
|
SetDataOptions options
|
|
|
) {
|
|
|
if (options == SetDataOptions.None && boundThisFrame)
|
|
|
{
|
|
|
device.Stall();
|
|
|
boundThisFrame = true;
|
|
|
}
|
|
|
else if (options == SetDataOptions.Discard && boundThisFrame)
|
|
|
{
|
|
|
InternalOffset += (int) BufferSize;
|
|
|
if (InternalOffset + dataLength > internalBufferSize)
|
|
|
{
|
|
|
// Expand!
|
|
|
int prevSize = internalBufferSize;
|
|
|
internalBufferSize *= 2;
|
|
|
CreateBackingBuffer(prevSize);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Copy previous contents, if needed
|
|
|
if (prevInternalOffset != InternalOffset && dataLength < (int) BufferSize)
|
|
|
{
|
|
|
SDL.SDL_memcpy(
|
|
|
Contents + InternalOffset,
|
|
|
Contents + prevInternalOffset,
|
|
|
BufferSize
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// Copy the data into the buffer
|
|
|
SDL.SDL_memcpy(
|
|
|
Contents + InternalOffset + offsetInBytes,
|
|
|
data,
|
|
|
(IntPtr) dataLength
|
|
|
);
|
|
|
|
|
|
prevInternalOffset = InternalOffset;
|
|
|
prevDataLength = (int) BufferSize;
|
|
|
}
|
|
|
|
|
|
public void SetData(
|
|
|
int offsetInBytes,
|
|
|
IntPtr data,
|
|
|
int dataLength
|
|
|
) {
|
|
|
InternalOffset += prevDataLength;
|
|
|
if (InternalOffset + dataLength > internalBufferSize)
|
|
|
{
|
|
|
// Expand!
|
|
|
int prevSize = internalBufferSize;
|
|
|
internalBufferSize = Math.Max(
|
|
|
internalBufferSize * 2,
|
|
|
internalBufferSize + dataLength
|
|
|
);
|
|
|
CreateBackingBuffer(prevSize);
|
|
|
}
|
|
|
|
|
|
// Copy the data into the buffer
|
|
|
SDL.SDL_memcpy(
|
|
|
Contents + InternalOffset,
|
|
|
data + offsetInBytes,
|
|
|
(IntPtr) dataLength
|
|
|
);
|
|
|
|
|
|
prevDataLength = dataLength;
|
|
|
}
|
|
|
|
|
|
public void Bound()
|
|
|
{
|
|
|
boundThisFrame = true;
|
|
|
}
|
|
|
|
|
|
public void Reset()
|
|
|
{
|
|
|
InternalOffset = 0;
|
|
|
boundThisFrame = false;
|
|
|
prevDataLength = 0;
|
|
|
}
|
|
|
|
|
|
public void Dispose()
|
|
|
{
|
|
|
objc_release(Handle);
|
|
|
Handle = IntPtr.Zero;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Metal Effect Container Class
|
|
|
|
|
|
private class MetalEffect : IGLEffect
|
|
|
{
|
|
|
public IntPtr EffectData
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public IntPtr MTLEffectData
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public MetalEffect(IntPtr effect, IntPtr mtlEffect)
|
|
|
{
|
|
|
EffectData = effect;
|
|
|
MTLEffectData = mtlEffect;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Metal Query Container Class
|
|
|
|
|
|
private class MetalQuery : IGLQuery
|
|
|
{
|
|
|
public IntPtr Handle
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public MetalQuery(IntPtr handle)
|
|
|
{
|
|
|
Handle = handle;
|
|
|
}
|
|
|
|
|
|
public void Dispose()
|
|
|
{
|
|
|
objc_release(Handle);
|
|
|
Handle = IntPtr.Zero;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Blending State Variables
|
|
|
|
|
|
private Color blendColor = Color.Transparent;
|
|
|
public Color BlendFactor
|
|
|
{
|
|
|
get
|
|
|
{
|
|
|
return blendColor;
|
|
|
}
|
|
|
set
|
|
|
{
|
|
|
if (value != blendColor)
|
|
|
{
|
|
|
blendColor = value;
|
|
|
SetEncoderBlendColor();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private int multisampleMask = -1; // AKA 0xFFFFFFFF
|
|
|
public int MultiSampleMask
|
|
|
{
|
|
|
get
|
|
|
{
|
|
|
return multisampleMask;
|
|
|
}
|
|
|
set
|
|
|
{
|
|
|
multisampleMask = value;
|
|
|
// FIXME: Metal does not support multisample masks. Workarounds...?
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Stencil State Variables
|
|
|
|
|
|
private int stencilRef = 0;
|
|
|
public int ReferenceStencil
|
|
|
{
|
|
|
get
|
|
|
{
|
|
|
return stencilRef;
|
|
|
}
|
|
|
set
|
|
|
{
|
|
|
if (value != stencilRef)
|
|
|
{
|
|
|
stencilRef = value;
|
|
|
SetEncoderStencilReferenceValue();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Rasterizer State Variables
|
|
|
|
|
|
private bool scissorTestEnable = false;
|
|
|
private CullMode cullFrontFace = CullMode.None;
|
|
|
private FillMode fillMode = FillMode.Solid;
|
|
|
private float depthBias = 0.0f;
|
|
|
private float slopeScaleDepthBias = 0.0f;
|
|
|
private bool multiSampleEnable = true;
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Viewport State Variables
|
|
|
|
|
|
private Rectangle scissorRectangle = new Rectangle();
|
|
|
private Rectangle viewport = new Rectangle();
|
|
|
private float depthRangeMin = 0.0f;
|
|
|
private float depthRangeMax = 1.0f;
|
|
|
|
|
|
/* Used for resetting scissor rectangle */
|
|
|
private int currentAttachmentWidth;
|
|
|
private int currentAttachmentHeight;
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Sampler State Variables
|
|
|
|
|
|
private MetalTexture[] Textures;
|
|
|
private IntPtr[] Samplers;
|
|
|
private bool[] textureNeedsUpdate;
|
|
|
private bool[] samplerNeedsUpdate;
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Depth Stencil State Variables
|
|
|
|
|
|
private DepthStencilState depthStencilState;
|
|
|
|
|
|
private IntPtr defaultDepthStencilState; // MTLDepthStencilState*
|
|
|
private IntPtr ldDepthStencilState; // MTLDepthStencilState*
|
|
|
|
|
|
private MTLPixelFormat D16Format;
|
|
|
private MTLPixelFormat D24Format;
|
|
|
private MTLPixelFormat D24S8Format;
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Buffer Binding Cache Variables
|
|
|
|
|
|
private List<MetalBuffer> Buffers = new List<MetalBuffer>();
|
|
|
|
|
|
private MetalBuffer userVertexBuffer = null;
|
|
|
private MetalBuffer userIndexBuffer = null;
|
|
|
private int userVertexStride = 0;
|
|
|
|
|
|
// Some vertex declarations may have overlapping attributes :/
|
|
|
private bool[,] attrUse = new bool[(int) MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_TOTAL, 16];
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Render Target Cache Variables
|
|
|
|
|
|
private readonly IntPtr[] currentAttachments;
|
|
|
private readonly MTLPixelFormat[] currentColorFormats;
|
|
|
private readonly IntPtr[] currentMSAttachments;
|
|
|
private readonly CubeMapFace[] currentAttachmentSlices;
|
|
|
private IntPtr currentDepthStencilBuffer;
|
|
|
private DepthFormat currentDepthFormat;
|
|
|
private int currentSampleCount;
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Clear Cache Variables
|
|
|
|
|
|
private Vector4 clearColor = new Vector4(0, 0, 0, 0);
|
|
|
private float clearDepth = 1.0f;
|
|
|
private int clearStencil = 0;
|
|
|
|
|
|
private bool shouldClearColor = false;
|
|
|
private bool shouldClearDepth = false;
|
|
|
private bool shouldClearStencil = false;
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Private Metal State Variables
|
|
|
|
|
|
private IntPtr view; // SDL_MetalView*
|
|
|
private IntPtr layer; // CAMetalLayer*
|
|
|
private IntPtr device; // MTLDevice*
|
|
|
private IntPtr queue; // MTLCommandQueue*
|
|
|
private IntPtr commandBuffer; // MTLCommandBuffer*
|
|
|
private IntPtr renderCommandEncoder; // MTLRenderCommandEncoder*
|
|
|
private IntPtr currentVertexDescriptor; // MTLVertexDescriptor*
|
|
|
private IntPtr currentVisibilityBuffer; // MTLBuffer*
|
|
|
|
|
|
private bool needNewRenderPass;
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Operating System Variables
|
|
|
|
|
|
private bool isMac;
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Frame Tracking Variables
|
|
|
|
|
|
/* FIXME:
|
|
|
* In theory, double- or even triple-buffering could
|
|
|
* significantly help performance by reducing CPU idle
|
|
|
* time. The trade-off is that buffer synchronization
|
|
|
* becomes much more complicated and error-prone.
|
|
|
*
|
|
|
* I've attempted a few implementations of multi-
|
|
|
* buffering, but they all had serious issues and
|
|
|
* typically performed worse than single buffering.
|
|
|
*
|
|
|
* I'm leaving these variables here in case any brave
|
|
|
* souls want to attempt a multi-buffer implementation.
|
|
|
* This could be a huge win for performance, but it'll
|
|
|
* take someone smarter than me to figure this out. ;)
|
|
|
*
|
|
|
* -caleb
|
|
|
*/
|
|
|
private const int MAX_FRAMES_IN_FLIGHT = 1;
|
|
|
private Queue<IntPtr> committedCommandBuffers = new Queue<IntPtr>();
|
|
|
|
|
|
private bool frameInProgress = false;
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Objective-C Memory Management Variables
|
|
|
|
|
|
private IntPtr pool; // NSAutoreleasePool*
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Faux-Backbuffer Variables
|
|
|
|
|
|
public IGLBackbuffer Backbuffer
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
private MTLSamplerMinMagFilter backbufferScaleMode;
|
|
|
|
|
|
// Cached data for rendering the faux-backbuffer
|
|
|
private Rectangle fauxBackbufferDestBounds;
|
|
|
private IntPtr fauxBackbufferDrawBuffer;
|
|
|
private IntPtr fauxBackbufferRenderPipeline;
|
|
|
private IntPtr fauxBackbufferSamplerState;
|
|
|
private bool fauxBackbufferSizeChanged;
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Metal Device Capabilities
|
|
|
|
|
|
public bool SupportsDxt1
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public bool SupportsS3tc
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public bool SupportsHardwareInstancing
|
|
|
{
|
|
|
get
|
|
|
{
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public bool SupportsNoOverwrite
|
|
|
{
|
|
|
get
|
|
|
{
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public int MaxTextureSlots
|
|
|
{
|
|
|
get
|
|
|
{
|
|
|
return 16;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public int MaxMultiSampleCount
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
private bool supportsOcclusionQueries;
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Private State Object Caches
|
|
|
|
|
|
private Dictionary<ulong, IntPtr> VertexDescriptorCache =
|
|
|
new Dictionary<ulong, IntPtr>();
|
|
|
|
|
|
private Dictionary<PipelineHash, IntPtr> PipelineStateCache =
|
|
|
new Dictionary<PipelineHash, IntPtr>();
|
|
|
|
|
|
private Dictionary<StateHash, IntPtr> DepthStencilStateCache =
|
|
|
new Dictionary<StateHash, IntPtr>();
|
|
|
|
|
|
private Dictionary<StateHash, IntPtr> SamplerStateCache =
|
|
|
new Dictionary<StateHash, IntPtr>();
|
|
|
|
|
|
private List<MetalTexture> transientTextures =
|
|
|
new List<MetalTexture>();
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Private Render Pipeline State Variables
|
|
|
|
|
|
private BlendState blendState;
|
|
|
private IntPtr ldPipelineState = IntPtr.Zero;
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Private Buffer Binding Cache
|
|
|
|
|
|
private const int MAX_BOUND_VERTEX_BUFFERS = 16;
|
|
|
|
|
|
private IntPtr ldVertUniformBuffer = IntPtr.Zero;
|
|
|
private IntPtr ldFragUniformBuffer = IntPtr.Zero;
|
|
|
private int ldVertUniformOffset = 0;
|
|
|
private int ldFragUniformOffset = 0;
|
|
|
|
|
|
private IntPtr[] ldVertexBuffers;
|
|
|
private int[] ldVertexBufferOffsets;
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Private MojoShader Interop
|
|
|
|
|
|
private IntPtr currentEffect = IntPtr.Zero;
|
|
|
private IntPtr currentTechnique = IntPtr.Zero;
|
|
|
private uint currentPass = 0;
|
|
|
|
|
|
private IntPtr prevEffect = IntPtr.Zero;
|
|
|
|
|
|
private MojoShader.MOJOSHADER_mtlShaderState shaderState = new MojoShader.MOJOSHADER_mtlShaderState();
|
|
|
private MojoShader.MOJOSHADER_mtlShaderState prevShaderState;
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Public Constructor
|
|
|
|
|
|
public MetalDevice(PresentationParameters presentationParameters)
|
|
|
{
|
|
|
device = MTLCreateSystemDefaultDevice();
|
|
|
queue = mtlNewCommandQueue(device);
|
|
|
|
|
|
// Create the Metal view
|
|
|
view = SDL.SDL_Metal_CreateView(
|
|
|
presentationParameters.DeviceWindowHandle
|
|
|
);
|
|
|
|
|
|
// Get the layer from the view
|
|
|
layer = mtlGetLayer(view);
|
|
|
|
|
|
// Set up the CAMetalLayer
|
|
|
mtlSetLayerDevice(layer, device);
|
|
|
mtlSetLayerFramebufferOnly(layer, true);
|
|
|
mtlSetLayerMagnificationFilter(layer, UTF8ToNSString("nearest"));
|
|
|
|
|
|
// Log GLDevice info
|
|
|
FNALoggerEXT.LogInfo("IGLDevice: MetalDevice");
|
|
|
FNALoggerEXT.LogInfo("Device Name: " + mtlGetDeviceName(device));
|
|
|
FNALoggerEXT.LogInfo("MojoShader Profile: metal");
|
|
|
|
|
|
// Some users might want pixely upscaling...
|
|
|
backbufferScaleMode = Environment.GetEnvironmentVariable(
|
|
|
"FNA_GRAPHICS_BACKBUFFER_SCALE_NEAREST"
|
|
|
) == "1" ? MTLSamplerMinMagFilter.Nearest : MTLSamplerMinMagFilter.Linear;
|
|
|
|
|
|
// Set device properties
|
|
|
isMac = SDL.SDL_GetPlatform().Equals("Mac OS X");
|
|
|
SupportsS3tc = SupportsDxt1 = isMac;
|
|
|
MaxMultiSampleCount = mtlSupportsSampleCount(device, 8) ? 8 : 4;
|
|
|
supportsOcclusionQueries = isMac || HasModernAppleGPU();
|
|
|
|
|
|
// Determine supported depth formats
|
|
|
D16Format = MTLPixelFormat.Depth32Float;
|
|
|
D24Format = MTLPixelFormat.Depth32Float;
|
|
|
D24S8Format = MTLPixelFormat.Depth32Float_Stencil8;
|
|
|
|
|
|
if (isMac)
|
|
|
{
|
|
|
bool supportsD24S8 = mtlSupportsDepth24Stencil8(device);
|
|
|
if (supportsD24S8)
|
|
|
{
|
|
|
D24S8Format = MTLPixelFormat.Depth24Unorm_Stencil8;
|
|
|
|
|
|
// Gross, but at least it's a unorm format! -caleb
|
|
|
D24Format = MTLPixelFormat.Depth24Unorm_Stencil8;
|
|
|
D16Format = MTLPixelFormat.Depth24Unorm_Stencil8;
|
|
|
}
|
|
|
|
|
|
// Depth16Unorm requires macOS 10.12+
|
|
|
if (OperatingSystemAtLeast(10, 12, 0))
|
|
|
{
|
|
|
D16Format = MTLPixelFormat.Depth16Unorm;
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
// Depth16Unorm requires iOS 13+
|
|
|
if (OperatingSystemAtLeast(13, 0, 0))
|
|
|
{
|
|
|
D16Format = MTLPixelFormat.Depth16Unorm;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Add fallbacks for missing texture formats on macOS
|
|
|
if (isMac)
|
|
|
{
|
|
|
XNAToMTL.TextureFormat[(int) SurfaceFormat.Bgr565]
|
|
|
= MTLPixelFormat.BGRA8Unorm;
|
|
|
XNAToMTL.TextureFormat[(int) SurfaceFormat.Bgra5551]
|
|
|
= MTLPixelFormat.BGRA8Unorm;
|
|
|
XNAToMTL.TextureFormat[(int) SurfaceFormat.Bgra4444]
|
|
|
= MTLPixelFormat.BGRA8Unorm;
|
|
|
}
|
|
|
|
|
|
// Initialize texture and sampler collections
|
|
|
Textures = new MetalTexture[MaxTextureSlots];
|
|
|
Samplers = new IntPtr[MaxTextureSlots];
|
|
|
for (int i = 0; i < MaxTextureSlots; i += 1)
|
|
|
{
|
|
|
Textures[i] = MetalTexture.NullTexture;
|
|
|
Samplers[i] = IntPtr.Zero;
|
|
|
}
|
|
|
textureNeedsUpdate = new bool[MaxTextureSlots];
|
|
|
samplerNeedsUpdate = new bool[MaxTextureSlots];
|
|
|
|
|
|
// Initialize attachment arrays
|
|
|
int numAttachments = GraphicsDevice.MAX_RENDERTARGET_BINDINGS;
|
|
|
currentAttachments = new IntPtr[numAttachments];
|
|
|
currentColorFormats = new MTLPixelFormat[numAttachments];
|
|
|
currentMSAttachments = new IntPtr[numAttachments];
|
|
|
currentAttachmentSlices = new CubeMapFace[numAttachments];
|
|
|
|
|
|
// Initialize vertex buffer cache
|
|
|
ldVertexBuffers = new IntPtr[MAX_BOUND_VERTEX_BUFFERS];
|
|
|
ldVertexBufferOffsets = new int[MAX_BOUND_VERTEX_BUFFERS];
|
|
|
|
|
|
// Create a default depth stencil state
|
|
|
IntPtr defDS = mtlNewDepthStencilDescriptor();
|
|
|
defaultDepthStencilState = mtlNewDepthStencilStateWithDescriptor(device, defDS);
|
|
|
objc_release(defDS);
|
|
|
|
|
|
// Create and setup the faux-backbuffer
|
|
|
InitializeFauxBackbuffer(presentationParameters);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Dispose Method
|
|
|
|
|
|
public void Dispose()
|
|
|
{
|
|
|
// Stop rendering
|
|
|
EndPass();
|
|
|
|
|
|
// Release vertex descriptors
|
|
|
foreach (IntPtr vdesc in VertexDescriptorCache.Values)
|
|
|
{
|
|
|
objc_release(vdesc);
|
|
|
}
|
|
|
VertexDescriptorCache.Clear();
|
|
|
VertexDescriptorCache = null;
|
|
|
|
|
|
// Release depth stencil states
|
|
|
foreach (IntPtr ds in DepthStencilStateCache.Values)
|
|
|
{
|
|
|
objc_release(ds);
|
|
|
}
|
|
|
DepthStencilStateCache.Clear();
|
|
|
DepthStencilStateCache = null;
|
|
|
|
|
|
// Release pipeline states
|
|
|
foreach (IntPtr pso in PipelineStateCache.Values)
|
|
|
{
|
|
|
objc_release(pso);
|
|
|
}
|
|
|
PipelineStateCache.Clear();
|
|
|
PipelineStateCache = null;
|
|
|
|
|
|
// Release sampler states
|
|
|
foreach (IntPtr ss in SamplerStateCache.Values)
|
|
|
{
|
|
|
objc_release(ss);
|
|
|
}
|
|
|
SamplerStateCache.Clear();
|
|
|
SamplerStateCache = null;
|
|
|
|
|
|
// Release transient textures
|
|
|
foreach (MetalTexture tex in transientTextures)
|
|
|
{
|
|
|
objc_release(tex.Handle);
|
|
|
}
|
|
|
transientTextures.Clear();
|
|
|
transientTextures = null;
|
|
|
|
|
|
// Dispose the backbuffer
|
|
|
(Backbuffer as MetalBackbuffer).Dispose();
|
|
|
|
|
|
// Destroy the view
|
|
|
SDL.SDL_Metal_DestroyView(view);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region GetDrawableSize Methods
|
|
|
|
|
|
public static void GetDrawableSize(
|
|
|
IntPtr layer,
|
|
|
out int w,
|
|
|
out int h
|
|
|
) {
|
|
|
CGSize size = mtlGetDrawableSize(layer);
|
|
|
w = (int) size.width;
|
|
|
h = (int) size.height;
|
|
|
}
|
|
|
|
|
|
public static void GetDrawableSizeFromView(
|
|
|
IntPtr view,
|
|
|
out int w,
|
|
|
out int h
|
|
|
) {
|
|
|
GetDrawableSize(mtlGetLayer(view), out w, out h);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Window Backbuffer Reset Method
|
|
|
|
|
|
public void ResetBackbuffer(PresentationParameters presentationParameters)
|
|
|
{
|
|
|
Backbuffer.ResetFramebuffer(presentationParameters);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region BeginFrame Method
|
|
|
|
|
|
public void BeginFrame()
|
|
|
{
|
|
|
if (frameInProgress) return;
|
|
|
|
|
|
// Wait for command buffers to complete...
|
|
|
while (committedCommandBuffers.Count >= MAX_FRAMES_IN_FLIGHT)
|
|
|
{
|
|
|
IntPtr cmdbuf = committedCommandBuffers.Dequeue();
|
|
|
mtlCommandBufferWaitUntilCompleted(cmdbuf);
|
|
|
objc_release(cmdbuf);
|
|
|
}
|
|
|
|
|
|
// The cycle begins anew!
|
|
|
frameInProgress = true;
|
|
|
pool = objc_autoreleasePoolPush();
|
|
|
commandBuffer = mtlMakeCommandBuffer(queue);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Window SwapBuffers Method
|
|
|
|
|
|
public void SwapBuffers(
|
|
|
Rectangle? sourceRectangle,
|
|
|
Rectangle? destinationRectangle,
|
|
|
IntPtr overrideWindowHandle
|
|
|
) {
|
|
|
/* Just in case Present() is called
|
|
|
* before any rendering happens...
|
|
|
*/
|
|
|
BeginFrame();
|
|
|
|
|
|
// Bind the backbuffer and finalize rendering
|
|
|
SetRenderTargets(null, null, DepthFormat.None);
|
|
|
EndPass();
|
|
|
|
|
|
// Determine the regions to present
|
|
|
int srcX, srcY, srcW, srcH;
|
|
|
int dstX, dstY, dstW, dstH;
|
|
|
if (sourceRectangle.HasValue)
|
|
|
{
|
|
|
srcX = sourceRectangle.Value.X;
|
|
|
srcY = sourceRectangle.Value.Y;
|
|
|
srcW = sourceRectangle.Value.Width;
|
|
|
srcH = sourceRectangle.Value.Height;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
srcX = 0;
|
|
|
srcY = 0;
|
|
|
srcW = Backbuffer.Width;
|
|
|
srcH = Backbuffer.Height;
|
|
|
}
|
|
|
if (destinationRectangle.HasValue)
|
|
|
{
|
|
|
dstX = destinationRectangle.Value.X;
|
|
|
dstY = destinationRectangle.Value.Y;
|
|
|
dstW = destinationRectangle.Value.Width;
|
|
|
dstH = destinationRectangle.Value.Height;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
dstX = 0;
|
|
|
dstY = 0;
|
|
|
GetDrawableSize(
|
|
|
layer,
|
|
|
out dstW,
|
|
|
out dstH
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// Get the next drawable
|
|
|
IntPtr drawable = mtlNextDrawable(layer);
|
|
|
|
|
|
// "Blit" the backbuffer to the drawable
|
|
|
BlitFramebuffer(
|
|
|
currentAttachments[0],
|
|
|
new Rectangle(srcX, srcY, srcW, srcH),
|
|
|
mtlGetTextureFromDrawable(drawable),
|
|
|
new Rectangle(dstX, dstY, dstW, dstH)
|
|
|
);
|
|
|
|
|
|
// Commit the command buffer for presentation
|
|
|
mtlPresentDrawable(commandBuffer, drawable);
|
|
|
mtlCommitCommandBuffer(commandBuffer);
|
|
|
|
|
|
// Enqueue the command buffer for tracking
|
|
|
objc_retain(commandBuffer);
|
|
|
committedCommandBuffers.Enqueue(commandBuffer);
|
|
|
commandBuffer = IntPtr.Zero;
|
|
|
|
|
|
// Release allocations from the past frame
|
|
|
objc_autoreleasePoolPop(pool);
|
|
|
|
|
|
// Reset buffers
|
|
|
for (int i = 0; i < Buffers.Count; i += 1)
|
|
|
{
|
|
|
Buffers[i].Reset();
|
|
|
}
|
|
|
MojoShader.MOJOSHADER_mtlEndFrame();
|
|
|
|
|
|
// We're done here.
|
|
|
frameInProgress = false;
|
|
|
}
|
|
|
|
|
|
private void BlitFramebuffer(
|
|
|
IntPtr srcTex,
|
|
|
Rectangle srcRect,
|
|
|
IntPtr dstTex,
|
|
|
Rectangle dstRect
|
|
|
) {
|
|
|
if ( srcRect.Width == 0 ||
|
|
|
srcRect.Height == 0 ||
|
|
|
dstRect.Width == 0 ||
|
|
|
dstRect.Height == 0 )
|
|
|
{
|
|
|
// Enjoy that bright red window!
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// Update cached vertex buffer if needed
|
|
|
if (fauxBackbufferDestBounds != dstRect || fauxBackbufferSizeChanged)
|
|
|
{
|
|
|
fauxBackbufferDestBounds = dstRect;
|
|
|
fauxBackbufferSizeChanged = false;
|
|
|
|
|
|
// Scale the coordinates to (-1, 1)
|
|
|
int dw, dh;
|
|
|
GetDrawableSize(layer, out dw, out dh);
|
|
|
float sx = -1 + (dstRect.X / (float) dw);
|
|
|
float sy = -1 + (dstRect.Y / (float) dh);
|
|
|
float sw = (dstRect.Width / (float) dw) * 2;
|
|
|
float sh = (dstRect.Height / (float) dh) * 2;
|
|
|
|
|
|
// Update the vertex buffer contents
|
|
|
float[] data = new float[]
|
|
|
{
|
|
|
sx, sy, 0, 0,
|
|
|
sx + sw, sy, 1, 0,
|
|
|
sx + sw, sy + sh, 1, 1,
|
|
|
sx, sy + sh, 0, 1
|
|
|
};
|
|
|
GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
|
|
|
SDL.SDL_memcpy(
|
|
|
mtlGetBufferContentsPtr(fauxBackbufferDrawBuffer),
|
|
|
handle.AddrOfPinnedObject(),
|
|
|
(IntPtr) (16 * sizeof(float))
|
|
|
);
|
|
|
handle.Free();
|
|
|
}
|
|
|
|
|
|
// Render the source texture to the destination texture
|
|
|
IntPtr backbufferRenderPass = mtlMakeRenderPassDescriptor();
|
|
|
mtlSetAttachmentTexture(
|
|
|
mtlGetColorAttachment(backbufferRenderPass, 0),
|
|
|
dstTex
|
|
|
);
|
|
|
IntPtr rce = mtlMakeRenderCommandEncoder(
|
|
|
commandBuffer,
|
|
|
backbufferRenderPass
|
|
|
);
|
|
|
mtlSetRenderPipelineState(rce, fauxBackbufferRenderPipeline);
|
|
|
mtlSetVertexBuffer(rce, fauxBackbufferDrawBuffer, 0, 0);
|
|
|
mtlSetFragmentTexture(rce, srcTex, 0);
|
|
|
mtlSetFragmentSamplerState(rce, fauxBackbufferSamplerState, 0);
|
|
|
mtlDrawIndexedPrimitives(
|
|
|
rce,
|
|
|
MTLPrimitiveType.Triangle,
|
|
|
6,
|
|
|
MTLIndexType.UInt16,
|
|
|
fauxBackbufferDrawBuffer,
|
|
|
16 * sizeof(float),
|
|
|
1
|
|
|
);
|
|
|
mtlEndEncoding(rce);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Render Command Encoder Methods
|
|
|
|
|
|
private void EndPass()
|
|
|
{
|
|
|
if (renderCommandEncoder != IntPtr.Zero)
|
|
|
{
|
|
|
mtlEndEncoding(renderCommandEncoder);
|
|
|
renderCommandEncoder = IntPtr.Zero;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void UpdateRenderPass()
|
|
|
{
|
|
|
if (!needNewRenderPass) return;
|
|
|
|
|
|
/* Normally the frame begins in BeginDraw(),
|
|
|
* but some games perform drawing outside
|
|
|
* of the Draw method (e.g. initializing
|
|
|
* render targets in LoadContent). This call
|
|
|
* ensures that we catch any unexpected draws.
|
|
|
* -caleb
|
|
|
*/
|
|
|
BeginFrame();
|
|
|
|
|
|
// Wrap up rendering with the old encoder
|
|
|
EndPass();
|
|
|
|
|
|
// Generate the descriptor
|
|
|
IntPtr passDesc = mtlMakeRenderPassDescriptor();
|
|
|
|
|
|
// Bind color attachments
|
|
|
for (int i = 0; i < currentAttachments.Length; i += 1)
|
|
|
{
|
|
|
if (currentAttachments[i] == IntPtr.Zero)
|
|
|
{
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
IntPtr colorAttachment = mtlGetColorAttachment(passDesc, i);
|
|
|
mtlSetAttachmentTexture(
|
|
|
colorAttachment,
|
|
|
currentAttachments[i]
|
|
|
);
|
|
|
mtlSetAttachmentSlice(
|
|
|
colorAttachment,
|
|
|
(int) currentAttachmentSlices[i]
|
|
|
);
|
|
|
|
|
|
// Multisample?
|
|
|
if (currentSampleCount > 0)
|
|
|
{
|
|
|
mtlSetAttachmentTexture(
|
|
|
colorAttachment,
|
|
|
currentMSAttachments[i]
|
|
|
);
|
|
|
mtlSetAttachmentSlice(
|
|
|
colorAttachment,
|
|
|
0
|
|
|
);
|
|
|
mtlSetAttachmentResolveTexture(
|
|
|
colorAttachment,
|
|
|
currentAttachments[i]
|
|
|
);
|
|
|
mtlSetAttachmentStoreAction(
|
|
|
colorAttachment,
|
|
|
MTLStoreAction.MultisampleResolve
|
|
|
);
|
|
|
mtlSetAttachmentResolveSlice(
|
|
|
colorAttachment,
|
|
|
(int) currentAttachmentSlices[i]
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// Clear color
|
|
|
if (shouldClearColor)
|
|
|
{
|
|
|
mtlSetAttachmentLoadAction(
|
|
|
colorAttachment,
|
|
|
MTLLoadAction.Clear
|
|
|
);
|
|
|
mtlSetColorAttachmentClearColor(
|
|
|
colorAttachment,
|
|
|
clearColor.X,
|
|
|
clearColor.Y,
|
|
|
clearColor.Z,
|
|
|
clearColor.W
|
|
|
);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
mtlSetAttachmentLoadAction(
|
|
|
colorAttachment,
|
|
|
MTLLoadAction.Load
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Bind depth attachment
|
|
|
if (currentDepthFormat != DepthFormat.None)
|
|
|
{
|
|
|
IntPtr depthAttachment = mtlGetDepthAttachment(passDesc);
|
|
|
mtlSetAttachmentTexture(
|
|
|
depthAttachment,
|
|
|
currentDepthStencilBuffer
|
|
|
);
|
|
|
mtlSetAttachmentStoreAction(
|
|
|
depthAttachment,
|
|
|
MTLStoreAction.Store
|
|
|
);
|
|
|
|
|
|
// Clear?
|
|
|
if (shouldClearDepth)
|
|
|
{
|
|
|
mtlSetAttachmentLoadAction(
|
|
|
depthAttachment,
|
|
|
MTLLoadAction.Clear
|
|
|
);
|
|
|
mtlSetDepthAttachmentClearDepth(
|
|
|
depthAttachment,
|
|
|
clearDepth
|
|
|
);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
mtlSetAttachmentLoadAction(
|
|
|
depthAttachment,
|
|
|
MTLLoadAction.Load
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Bind stencil buffer
|
|
|
if (currentDepthFormat == DepthFormat.Depth24Stencil8)
|
|
|
{
|
|
|
IntPtr stencilAttachment = mtlGetStencilAttachment(passDesc);
|
|
|
mtlSetAttachmentTexture(
|
|
|
stencilAttachment,
|
|
|
currentDepthStencilBuffer
|
|
|
);
|
|
|
mtlSetAttachmentStoreAction(
|
|
|
stencilAttachment,
|
|
|
MTLStoreAction.Store
|
|
|
);
|
|
|
|
|
|
// Clear?
|
|
|
if (shouldClearStencil)
|
|
|
{
|
|
|
mtlSetAttachmentLoadAction(
|
|
|
stencilAttachment,
|
|
|
MTLLoadAction.Clear
|
|
|
);
|
|
|
mtlSetStencilAttachmentClearStencil(
|
|
|
stencilAttachment,
|
|
|
clearStencil
|
|
|
);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
mtlSetAttachmentLoadAction(
|
|
|
stencilAttachment,
|
|
|
MTLLoadAction.Load
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Get attachment size
|
|
|
currentAttachmentWidth = (int) mtlGetTextureWidth(
|
|
|
currentAttachments[0]
|
|
|
);
|
|
|
currentAttachmentHeight = (int) mtlGetTextureHeight(
|
|
|
currentAttachments[0]
|
|
|
);
|
|
|
|
|
|
// Attach the visibility buffer, if needed
|
|
|
if (currentVisibilityBuffer != IntPtr.Zero)
|
|
|
{
|
|
|
mtlSetVisibilityResultBuffer(
|
|
|
passDesc,
|
|
|
currentVisibilityBuffer
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// Make a new encoder
|
|
|
renderCommandEncoder = mtlMakeRenderCommandEncoder(
|
|
|
commandBuffer,
|
|
|
passDesc
|
|
|
);
|
|
|
|
|
|
// Reset the flags
|
|
|
needNewRenderPass = false;
|
|
|
shouldClearColor = false;
|
|
|
shouldClearDepth = false;
|
|
|
shouldClearStencil = false;
|
|
|
|
|
|
// Apply the dynamic state
|
|
|
SetEncoderViewport();
|
|
|
SetEncoderScissorRect();
|
|
|
SetEncoderBlendColor();
|
|
|
SetEncoderStencilReferenceValue();
|
|
|
SetEncoderCullMode();
|
|
|
SetEncoderFillMode();
|
|
|
SetEncoderDepthBias();
|
|
|
|
|
|
// Start visibility buffer counting
|
|
|
if (currentVisibilityBuffer != IntPtr.Zero)
|
|
|
{
|
|
|
mtlSetVisibilityResultMode(
|
|
|
renderCommandEncoder,
|
|
|
MTLVisibilityResultMode.Counting,
|
|
|
0
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// Reset the bindings
|
|
|
for (int i = 0; i < MaxTextureSlots; i += 1)
|
|
|
{
|
|
|
if (Textures[i] != MetalTexture.NullTexture)
|
|
|
{
|
|
|
textureNeedsUpdate[i] = true;
|
|
|
}
|
|
|
if (Samplers[i] != IntPtr.Zero)
|
|
|
{
|
|
|
samplerNeedsUpdate[i] = true;
|
|
|
}
|
|
|
}
|
|
|
ldDepthStencilState = IntPtr.Zero;
|
|
|
ldFragUniformBuffer = IntPtr.Zero;
|
|
|
ldFragUniformOffset = 0;
|
|
|
ldVertUniformBuffer = IntPtr.Zero;
|
|
|
ldVertUniformOffset = 0;
|
|
|
ldPipelineState = IntPtr.Zero;
|
|
|
for (int i = 0; i < MAX_BOUND_VERTEX_BUFFERS; i += 1)
|
|
|
{
|
|
|
ldVertexBuffers[i] = IntPtr.Zero;
|
|
|
ldVertexBufferOffsets[i] = 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void SetEncoderStencilReferenceValue()
|
|
|
{
|
|
|
if (renderCommandEncoder != IntPtr.Zero && !needNewRenderPass)
|
|
|
{
|
|
|
mtlSetStencilReferenceValue(
|
|
|
renderCommandEncoder,
|
|
|
(uint) stencilRef
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void SetEncoderBlendColor()
|
|
|
{
|
|
|
if (renderCommandEncoder != IntPtr.Zero && !needNewRenderPass)
|
|
|
{
|
|
|
mtlSetBlendColor(
|
|
|
renderCommandEncoder,
|
|
|
blendColor.R / 255f,
|
|
|
blendColor.G / 255f,
|
|
|
blendColor.B / 255f,
|
|
|
blendColor.A / 255f
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void SetEncoderViewport()
|
|
|
{
|
|
|
if (renderCommandEncoder != IntPtr.Zero && !needNewRenderPass)
|
|
|
{
|
|
|
mtlSetViewport(
|
|
|
renderCommandEncoder,
|
|
|
viewport.X,
|
|
|
viewport.Y,
|
|
|
viewport.Width,
|
|
|
viewport.Height,
|
|
|
(double) depthRangeMin,
|
|
|
(double) depthRangeMax
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void SetEncoderCullMode()
|
|
|
{
|
|
|
if (renderCommandEncoder != IntPtr.Zero && !needNewRenderPass)
|
|
|
{
|
|
|
mtlSetCullMode(
|
|
|
renderCommandEncoder,
|
|
|
XNAToMTL.CullingEnabled[(int) cullFrontFace]
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void SetEncoderFillMode()
|
|
|
{
|
|
|
if (renderCommandEncoder != IntPtr.Zero && !needNewRenderPass)
|
|
|
{
|
|
|
mtlSetTriangleFillMode(
|
|
|
renderCommandEncoder,
|
|
|
XNAToMTL.FillMode[(int) fillMode]
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void SetEncoderDepthBias()
|
|
|
{
|
|
|
if (renderCommandEncoder != IntPtr.Zero && !needNewRenderPass)
|
|
|
{
|
|
|
mtlSetDepthBias(
|
|
|
renderCommandEncoder,
|
|
|
depthBias,
|
|
|
slopeScaleDepthBias,
|
|
|
0.0f // no clamp
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void SetEncoderScissorRect()
|
|
|
{
|
|
|
if (renderCommandEncoder != IntPtr.Zero && !needNewRenderPass)
|
|
|
{
|
|
|
if (!scissorTestEnable)
|
|
|
{
|
|
|
// Set to the default scissor rect
|
|
|
mtlSetScissorRect(
|
|
|
renderCommandEncoder,
|
|
|
0,
|
|
|
0,
|
|
|
currentAttachmentWidth,
|
|
|
currentAttachmentHeight
|
|
|
);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
mtlSetScissorRect(
|
|
|
renderCommandEncoder,
|
|
|
scissorRectangle.X,
|
|
|
scissorRectangle.Y,
|
|
|
scissorRectangle.Width,
|
|
|
scissorRectangle.Height
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Metal Object Disposal Wrappers
|
|
|
|
|
|
public void AddDisposeEffect(IGLEffect effect)
|
|
|
{
|
|
|
DeleteEffect(effect);
|
|
|
}
|
|
|
|
|
|
public void AddDisposeIndexBuffer(IGLBuffer buffer)
|
|
|
{
|
|
|
DeleteBuffer(buffer);
|
|
|
}
|
|
|
|
|
|
public void AddDisposeQuery(IGLQuery query)
|
|
|
{
|
|
|
DeleteQuery(query);
|
|
|
}
|
|
|
|
|
|
public void AddDisposeRenderbuffer(IGLRenderbuffer renderbuffer)
|
|
|
{
|
|
|
DeleteRenderbuffer(renderbuffer);
|
|
|
}
|
|
|
|
|
|
public void AddDisposeTexture(IGLTexture texture)
|
|
|
{
|
|
|
DeleteTexture(texture);
|
|
|
}
|
|
|
|
|
|
public void AddDisposeVertexBuffer(IGLBuffer buffer)
|
|
|
{
|
|
|
DeleteBuffer(buffer);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Pipeline Stall Method
|
|
|
|
|
|
private void Stall()
|
|
|
{
|
|
|
EndPass();
|
|
|
mtlCommitCommandBuffer(commandBuffer);
|
|
|
mtlCommandBufferWaitUntilCompleted(commandBuffer);
|
|
|
|
|
|
commandBuffer = mtlMakeCommandBuffer(queue);
|
|
|
needNewRenderPass = true;
|
|
|
committedCommandBuffers.Clear();
|
|
|
|
|
|
for (int i = 0; i < Buffers.Count; i += 1)
|
|
|
{
|
|
|
Buffers[i].Reset();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region String Marker Method
|
|
|
|
|
|
public void SetStringMarker(string text)
|
|
|
{
|
|
|
#if DEBUG
|
|
|
if (renderCommandEncoder != IntPtr.Zero)
|
|
|
{
|
|
|
mtlInsertDebugSignpost(renderCommandEncoder, text);
|
|
|
}
|
|
|
#endif
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Drawing Methods
|
|
|
|
|
|
public void DrawIndexedPrimitives(
|
|
|
PrimitiveType primitiveType,
|
|
|
int baseVertex,
|
|
|
int minVertexIndex,
|
|
|
int numVertices,
|
|
|
int startIndex,
|
|
|
int primitiveCount,
|
|
|
IGLBuffer indices,
|
|
|
IndexElementSize indexElementSize
|
|
|
) {
|
|
|
MetalBuffer indexBuffer = indices as MetalBuffer;
|
|
|
indexBuffer.Bound();
|
|
|
int totalIndexOffset = (
|
|
|
(startIndex * XNAToMTL.IndexSize[(int) indexElementSize]) +
|
|
|
indexBuffer.InternalOffset
|
|
|
);
|
|
|
mtlDrawIndexedPrimitives(
|
|
|
renderCommandEncoder,
|
|
|
XNAToMTL.Primitive[(int) primitiveType],
|
|
|
XNAToMTL.PrimitiveVerts(primitiveType, primitiveCount),
|
|
|
XNAToMTL.IndexType[(int) indexElementSize],
|
|
|
indexBuffer.Handle,
|
|
|
totalIndexOffset,
|
|
|
1
|
|
|
);
|
|
|
}
|
|
|
|
|
|
public void DrawInstancedPrimitives(
|
|
|
PrimitiveType primitiveType,
|
|
|
int baseVertex,
|
|
|
int minVertexIndex,
|
|
|
int numVertices,
|
|
|
int startIndex,
|
|
|
int primitiveCount,
|
|
|
int instanceCount,
|
|
|
IGLBuffer indices,
|
|
|
IndexElementSize indexElementSize
|
|
|
) {
|
|
|
MetalBuffer indexBuffer = indices as MetalBuffer;
|
|
|
indexBuffer.Bound();
|
|
|
int totalIndexOffset = (
|
|
|
(startIndex * XNAToMTL.IndexSize[(int) indexElementSize]) +
|
|
|
indexBuffer.InternalOffset
|
|
|
);
|
|
|
mtlDrawIndexedPrimitives(
|
|
|
renderCommandEncoder,
|
|
|
XNAToMTL.Primitive[(int) primitiveType],
|
|
|
XNAToMTL.PrimitiveVerts(primitiveType, primitiveCount),
|
|
|
XNAToMTL.IndexType[(int) indexElementSize],
|
|
|
indexBuffer.Handle,
|
|
|
totalIndexOffset,
|
|
|
instanceCount
|
|
|
);
|
|
|
}
|
|
|
|
|
|
public void DrawPrimitives(
|
|
|
PrimitiveType primitiveType,
|
|
|
int vertexStart,
|
|
|
int primitiveCount
|
|
|
) {
|
|
|
mtlDrawPrimitives(
|
|
|
renderCommandEncoder,
|
|
|
XNAToMTL.Primitive[(int) primitiveType],
|
|
|
vertexStart,
|
|
|
XNAToMTL.PrimitiveVerts(primitiveType, primitiveCount)
|
|
|
);
|
|
|
}
|
|
|
|
|
|
private void BindUserVertexBuffer(
|
|
|
IntPtr vertexData,
|
|
|
int vertexCount,
|
|
|
int vertexOffset
|
|
|
) {
|
|
|
// Update the buffer contents
|
|
|
int len = vertexCount * userVertexStride;
|
|
|
if (userVertexBuffer == null)
|
|
|
{
|
|
|
userVertexBuffer = new MetalBuffer(
|
|
|
this,
|
|
|
true,
|
|
|
BufferUsage.WriteOnly,
|
|
|
(IntPtr) len
|
|
|
);
|
|
|
Buffers.Add(userVertexBuffer);
|
|
|
}
|
|
|
userVertexBuffer.SetData(
|
|
|
vertexOffset * userVertexStride,
|
|
|
vertexData,
|
|
|
len
|
|
|
);
|
|
|
|
|
|
// Bind the buffer
|
|
|
int offset = userVertexBuffer.InternalOffset;
|
|
|
IntPtr handle = userVertexBuffer.Handle;
|
|
|
if (ldVertexBuffers[0] != handle)
|
|
|
{
|
|
|
mtlSetVertexBuffer(
|
|
|
renderCommandEncoder,
|
|
|
handle,
|
|
|
offset,
|
|
|
0
|
|
|
);
|
|
|
ldVertexBuffers[0] = handle;
|
|
|
ldVertexBufferOffsets[0] = offset;
|
|
|
}
|
|
|
else if (ldVertexBufferOffsets[0] != offset)
|
|
|
{
|
|
|
mtlSetVertexBufferOffset(
|
|
|
renderCommandEncoder,
|
|
|
offset,
|
|
|
0
|
|
|
);
|
|
|
ldVertexBufferOffsets[0] = offset;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void DrawUserIndexedPrimitives(
|
|
|
PrimitiveType primitiveType,
|
|
|
IntPtr vertexData,
|
|
|
int vertexOffset,
|
|
|
int numVertices,
|
|
|
IntPtr indexData,
|
|
|
int indexOffset,
|
|
|
IndexElementSize indexElementSize,
|
|
|
int primitiveCount
|
|
|
) {
|
|
|
// Bind the vertex buffer
|
|
|
BindUserVertexBuffer(
|
|
|
vertexData,
|
|
|
numVertices,
|
|
|
vertexOffset
|
|
|
);
|
|
|
|
|
|
// Prepare the index buffer
|
|
|
int numIndices = XNAToMTL.PrimitiveVerts(
|
|
|
primitiveType,
|
|
|
primitiveCount
|
|
|
);
|
|
|
int indexSize = XNAToMTL.IndexSize[(int) indexElementSize];
|
|
|
int len = (int) numIndices * indexSize;
|
|
|
if (userIndexBuffer == null)
|
|
|
{
|
|
|
userIndexBuffer = new MetalBuffer(
|
|
|
this,
|
|
|
true,
|
|
|
BufferUsage.WriteOnly,
|
|
|
(IntPtr) len
|
|
|
);
|
|
|
Buffers.Add(userIndexBuffer);
|
|
|
}
|
|
|
userIndexBuffer.SetData(
|
|
|
indexOffset * indexSize,
|
|
|
indexData,
|
|
|
len
|
|
|
);
|
|
|
|
|
|
// Draw!
|
|
|
mtlDrawIndexedPrimitives(
|
|
|
renderCommandEncoder,
|
|
|
XNAToMTL.Primitive[(int) primitiveType],
|
|
|
numIndices,
|
|
|
XNAToMTL.IndexType[(int) indexElementSize],
|
|
|
userIndexBuffer.Handle,
|
|
|
userIndexBuffer.InternalOffset,
|
|
|
1
|
|
|
);
|
|
|
}
|
|
|
|
|
|
public void DrawUserPrimitives(
|
|
|
PrimitiveType primitiveType,
|
|
|
IntPtr vertexData,
|
|
|
int vertexOffset,
|
|
|
int primitiveCount
|
|
|
) {
|
|
|
// Bind the vertex buffer
|
|
|
int numVerts = XNAToMTL.PrimitiveVerts(
|
|
|
primitiveType,
|
|
|
primitiveCount
|
|
|
);
|
|
|
BindUserVertexBuffer(
|
|
|
vertexData,
|
|
|
numVerts,
|
|
|
vertexOffset
|
|
|
);
|
|
|
|
|
|
// Draw!
|
|
|
mtlDrawPrimitives(
|
|
|
renderCommandEncoder,
|
|
|
XNAToMTL.Primitive[(int) primitiveType],
|
|
|
0,
|
|
|
numVerts
|
|
|
);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region State Management Methods
|
|
|
|
|
|
public void SetPresentationInterval(PresentInterval interval)
|
|
|
{
|
|
|
// Toggling vsync is only supported on macOS 10.13+
|
|
|
if (!RespondsToSelector(layer, selDisplaySyncEnabled))
|
|
|
{
|
|
|
FNALoggerEXT.LogWarn(
|
|
|
"Cannot set presentation interval! " +
|
|
|
"Only vsync is supported."
|
|
|
);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (interval == PresentInterval.Default || interval == PresentInterval.One)
|
|
|
{
|
|
|
mtlSetDisplaySyncEnabled(layer, true);
|
|
|
}
|
|
|
else if (interval == PresentInterval.Immediate)
|
|
|
{
|
|
|
mtlSetDisplaySyncEnabled(layer, false);
|
|
|
}
|
|
|
else if (interval == PresentInterval.Two)
|
|
|
{
|
|
|
/* FIXME:
|
|
|
* There is no built-in support for
|
|
|
* present-every-other-frame in Metal.
|
|
|
* We could work around this, but do
|
|
|
* any games actually use this mode...?
|
|
|
* -caleb
|
|
|
*/
|
|
|
mtlSetDisplaySyncEnabled(layer, true);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
throw new NotSupportedException("Unrecognized PresentInterval!");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void SetViewport(Viewport vp)
|
|
|
{
|
|
|
if ( vp.Bounds != viewport ||
|
|
|
vp.MinDepth != depthRangeMin ||
|
|
|
vp.MaxDepth != depthRangeMax )
|
|
|
{
|
|
|
viewport = vp.Bounds;
|
|
|
depthRangeMin = vp.MinDepth;
|
|
|
depthRangeMax = vp.MaxDepth;
|
|
|
SetEncoderViewport(); // Dynamic state!
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void SetScissorRect(Rectangle scissorRect)
|
|
|
{
|
|
|
if (scissorRectangle != scissorRect)
|
|
|
{
|
|
|
scissorRectangle = scissorRect;
|
|
|
SetEncoderScissorRect(); // Dynamic state!
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void ApplyRasterizerState(RasterizerState rasterizerState)
|
|
|
{
|
|
|
if (rasterizerState.ScissorTestEnable != scissorTestEnable)
|
|
|
{
|
|
|
scissorTestEnable = rasterizerState.ScissorTestEnable;
|
|
|
SetEncoderScissorRect(); // Dynamic state!
|
|
|
}
|
|
|
|
|
|
if (rasterizerState.CullMode != cullFrontFace)
|
|
|
{
|
|
|
cullFrontFace = rasterizerState.CullMode;
|
|
|
SetEncoderCullMode(); // Dynamic state!
|
|
|
}
|
|
|
|
|
|
if (rasterizerState.FillMode != fillMode)
|
|
|
{
|
|
|
fillMode = rasterizerState.FillMode;
|
|
|
SetEncoderFillMode(); // Dynamic state!
|
|
|
}
|
|
|
|
|
|
float realDepthBias = rasterizerState.DepthBias;
|
|
|
realDepthBias *= XNAToMTL.DepthBiasScale(
|
|
|
GetDepthFormat(currentDepthFormat)
|
|
|
);
|
|
|
if ( realDepthBias != depthBias ||
|
|
|
rasterizerState.SlopeScaleDepthBias != slopeScaleDepthBias )
|
|
|
{
|
|
|
depthBias = realDepthBias;
|
|
|
slopeScaleDepthBias = rasterizerState.SlopeScaleDepthBias;
|
|
|
SetEncoderDepthBias(); // Dynamic state!
|
|
|
}
|
|
|
|
|
|
if (rasterizerState.MultiSampleAntiAlias != multiSampleEnable)
|
|
|
{
|
|
|
multiSampleEnable = rasterizerState.MultiSampleAntiAlias;
|
|
|
// FIXME: Metal does not support toggling MSAA. Workarounds...?
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void VerifySampler(int index, Texture texture, SamplerState sampler)
|
|
|
{
|
|
|
if (texture == null)
|
|
|
{
|
|
|
if (Textures[index] != MetalTexture.NullTexture)
|
|
|
{
|
|
|
Textures[index] = MetalTexture.NullTexture;
|
|
|
textureNeedsUpdate[index] = true;
|
|
|
}
|
|
|
if (Samplers[index] == IntPtr.Zero)
|
|
|
{
|
|
|
/* Some shaders require non-null samplers
|
|
|
* even if they aren't actually used.
|
|
|
* -caleb
|
|
|
*/
|
|
|
Samplers[index] = FetchSamplerState(sampler, false);
|
|
|
samplerNeedsUpdate[index] = true;
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
MetalTexture tex = texture.texture as MetalTexture;
|
|
|
if ( tex == Textures[index] &&
|
|
|
sampler.AddressU == tex.WrapS &&
|
|
|
sampler.AddressV == tex.WrapT &&
|
|
|
sampler.AddressW == tex.WrapR &&
|
|
|
sampler.Filter == tex.Filter &&
|
|
|
sampler.MaxAnisotropy == tex.Anisotropy &&
|
|
|
sampler.MaxMipLevel == tex.MaxMipmapLevel &&
|
|
|
sampler.MipMapLevelOfDetailBias == tex.LODBias )
|
|
|
{
|
|
|
// Nothing's changing, forget it.
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// Bind the correct texture
|
|
|
if (tex != Textures[index])
|
|
|
{
|
|
|
Textures[index] = tex;
|
|
|
textureNeedsUpdate[index] = true;
|
|
|
}
|
|
|
|
|
|
// Update the texture sampler info
|
|
|
tex.WrapS = sampler.AddressU;
|
|
|
tex.WrapT = sampler.AddressV;
|
|
|
tex.WrapR = sampler.AddressW;
|
|
|
tex.Filter = sampler.Filter;
|
|
|
tex.Anisotropy = sampler.MaxAnisotropy;
|
|
|
tex.MaxMipmapLevel = sampler.MaxMipLevel;
|
|
|
tex.LODBias = sampler.MipMapLevelOfDetailBias;
|
|
|
|
|
|
// Update the sampler state, if needed
|
|
|
IntPtr ss = FetchSamplerState(sampler, tex.HasMipmaps);
|
|
|
if (ss != Samplers[index])
|
|
|
{
|
|
|
Samplers[index] = ss;
|
|
|
samplerNeedsUpdate[index] = true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void SetBlendState(BlendState blendState)
|
|
|
{
|
|
|
this.blendState = blendState;
|
|
|
BlendFactor = blendState.BlendFactor; // Dynamic state!
|
|
|
}
|
|
|
|
|
|
public void SetDepthStencilState(DepthStencilState depthStencilState)
|
|
|
{
|
|
|
this.depthStencilState = depthStencilState;
|
|
|
ReferenceStencil = depthStencilState.ReferenceStencil; // Dynamic state!
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region State Creation/Retrieval Methods
|
|
|
|
|
|
private struct PipelineHash : IEquatable<PipelineHash>
|
|
|
{
|
|
|
readonly ulong a;
|
|
|
readonly ulong b;
|
|
|
readonly ulong c;
|
|
|
readonly ulong d;
|
|
|
|
|
|
public PipelineHash(
|
|
|
ulong vertexShader,
|
|
|
ulong fragmentShader,
|
|
|
ulong vertexDescriptor,
|
|
|
MTLPixelFormat[] formats,
|
|
|
DepthFormat depthFormat,
|
|
|
int sampleCount,
|
|
|
BlendState blendState
|
|
|
) {
|
|
|
this.a = vertexShader;
|
|
|
this.b = fragmentShader;
|
|
|
this.c = vertexDescriptor;
|
|
|
|
|
|
unchecked
|
|
|
{
|
|
|
this.d = (
|
|
|
((ulong) blendState.GetHashCode() << 32) |
|
|
|
((ulong) sampleCount << 22) |
|
|
|
((ulong) depthFormat << 20) |
|
|
|
((ulong) HashFormat(formats[3]) << 15) |
|
|
|
((ulong) HashFormat(formats[2]) << 10) |
|
|
|
((ulong) HashFormat(formats[1]) << 5) |
|
|
|
((ulong) HashFormat(formats[0]))
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private static uint HashFormat(MTLPixelFormat format)
|
|
|
{
|
|
|
switch (format)
|
|
|
{
|
|
|
case MTLPixelFormat.Invalid:
|
|
|
return 0;
|
|
|
case MTLPixelFormat.R16Float:
|
|
|
return 1;
|
|
|
case MTLPixelFormat.R32Float:
|
|
|
return 2;
|
|
|
case MTLPixelFormat.RG16Float:
|
|
|
return 3;
|
|
|
case MTLPixelFormat.RG16Snorm:
|
|
|
return 4;
|
|
|
case MTLPixelFormat.RG16Unorm:
|
|
|
return 5;
|
|
|
case MTLPixelFormat.RG32Float:
|
|
|
return 6;
|
|
|
case MTLPixelFormat.RG8Snorm:
|
|
|
return 7;
|
|
|
case MTLPixelFormat.RGB10A2Unorm:
|
|
|
return 8;
|
|
|
case MTLPixelFormat.RGBA16Float:
|
|
|
return 9;
|
|
|
case MTLPixelFormat.RGBA16Unorm:
|
|
|
return 10;
|
|
|
case MTLPixelFormat.RGBA32Float:
|
|
|
return 11;
|
|
|
case MTLPixelFormat.RGBA8Unorm:
|
|
|
return 12;
|
|
|
case MTLPixelFormat.A8Unorm:
|
|
|
return 13;
|
|
|
case MTLPixelFormat.ABGR4Unorm:
|
|
|
return 14;
|
|
|
case MTLPixelFormat.B5G6R5Unorm:
|
|
|
return 15;
|
|
|
case MTLPixelFormat.BC1_RGBA:
|
|
|
return 16;
|
|
|
case MTLPixelFormat.BC2_RGBA:
|
|
|
return 17;
|
|
|
case MTLPixelFormat.BC3_RGBA:
|
|
|
return 18;
|
|
|
case MTLPixelFormat.BGR5A1Unorm:
|
|
|
return 19;
|
|
|
case MTLPixelFormat.BGRA8Unorm:
|
|
|
return 20;
|
|
|
}
|
|
|
|
|
|
throw new NotSupportedException();
|
|
|
}
|
|
|
|
|
|
public override int GetHashCode()
|
|
|
{
|
|
|
unchecked
|
|
|
{
|
|
|
int i1 = (int) (a ^ (a >> 32));
|
|
|
int i2 = (int) (b ^ (b >> 32));
|
|
|
int i3 = (int) (c ^ (c >> 32));
|
|
|
int i4 = (int) (d ^ (d >> 32));
|
|
|
return i1 + i2 + i3 + i4;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public bool Equals(PipelineHash other)
|
|
|
{
|
|
|
return (
|
|
|
a == other.a &&
|
|
|
b == other.b &&
|
|
|
c == other.c &&
|
|
|
d == other.d
|
|
|
);
|
|
|
}
|
|
|
|
|
|
public override bool Equals(object obj)
|
|
|
{
|
|
|
if (obj == null || obj.GetType() != GetType())
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
PipelineHash hash = (PipelineHash) obj;
|
|
|
return (
|
|
|
a == hash.a &&
|
|
|
b == hash.b &&
|
|
|
c == hash.c &&
|
|
|
d == hash.d
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private IntPtr FetchRenderPipeline()
|
|
|
{
|
|
|
// Can we just reuse an existing pipeline?
|
|
|
PipelineHash hash = new PipelineHash(
|
|
|
(ulong) shaderState.vertexShader,
|
|
|
(ulong) shaderState.fragmentShader,
|
|
|
(ulong) currentVertexDescriptor,
|
|
|
currentColorFormats,
|
|
|
currentDepthFormat,
|
|
|
currentSampleCount,
|
|
|
blendState
|
|
|
);
|
|
|
IntPtr pipeline = IntPtr.Zero;
|
|
|
if (PipelineStateCache.TryGetValue(hash, out pipeline))
|
|
|
{
|
|
|
// We have this state already cached!
|
|
|
return pipeline;
|
|
|
}
|
|
|
|
|
|
// We have to make a new pipeline...
|
|
|
IntPtr pipelineDesc = mtlNewRenderPipelineDescriptor();
|
|
|
IntPtr vertHandle = MojoShader.MOJOSHADER_mtlGetFunctionHandle(
|
|
|
shaderState.vertexShader
|
|
|
);
|
|
|
IntPtr fragHandle = MojoShader.MOJOSHADER_mtlGetFunctionHandle(
|
|
|
shaderState.fragmentShader
|
|
|
);
|
|
|
mtlSetPipelineVertexFunction(
|
|
|
pipelineDesc,
|
|
|
vertHandle
|
|
|
);
|
|
|
mtlSetPipelineFragmentFunction(
|
|
|
pipelineDesc,
|
|
|
fragHandle
|
|
|
);
|
|
|
mtlSetPipelineVertexDescriptor(
|
|
|
pipelineDesc,
|
|
|
currentVertexDescriptor
|
|
|
);
|
|
|
mtlSetDepthAttachmentPixelFormat(
|
|
|
pipelineDesc,
|
|
|
GetDepthFormat(currentDepthFormat)
|
|
|
);
|
|
|
if (currentDepthFormat == DepthFormat.Depth24Stencil8)
|
|
|
{
|
|
|
mtlSetStencilAttachmentPixelFormat(
|
|
|
pipelineDesc,
|
|
|
GetDepthFormat(currentDepthFormat)
|
|
|
);
|
|
|
}
|
|
|
mtlSetPipelineSampleCount(
|
|
|
pipelineDesc,
|
|
|
Math.Max(1, currentSampleCount)
|
|
|
);
|
|
|
|
|
|
// Apply the blend state
|
|
|
bool alphaBlendEnable = !(
|
|
|
blendState.ColorSourceBlend == Blend.One &&
|
|
|
blendState.ColorDestinationBlend == Blend.Zero &&
|
|
|
blendState.AlphaSourceBlend == Blend.One &&
|
|
|
blendState.AlphaDestinationBlend == Blend.Zero
|
|
|
);
|
|
|
for (int i = 0; i < currentAttachments.Length; i += 1)
|
|
|
{
|
|
|
if (currentAttachments[i] == IntPtr.Zero)
|
|
|
{
|
|
|
// There's no attachment bound at this index.
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
IntPtr colorAttachment = mtlGetColorAttachment(
|
|
|
pipelineDesc,
|
|
|
i
|
|
|
);
|
|
|
mtlSetAttachmentPixelFormat(
|
|
|
colorAttachment,
|
|
|
currentColorFormats[i]
|
|
|
);
|
|
|
mtlSetAttachmentBlendingEnabled(
|
|
|
colorAttachment,
|
|
|
alphaBlendEnable
|
|
|
);
|
|
|
if (alphaBlendEnable)
|
|
|
{
|
|
|
mtlSetAttachmentSourceRGBBlendFactor(
|
|
|
colorAttachment,
|
|
|
XNAToMTL.BlendMode[
|
|
|
(int) blendState.ColorSourceBlend
|
|
|
]
|
|
|
);
|
|
|
mtlSetAttachmentDestinationRGBBlendFactor(
|
|
|
colorAttachment,
|
|
|
XNAToMTL.BlendMode[
|
|
|
(int) blendState.ColorDestinationBlend
|
|
|
]
|
|
|
);
|
|
|
mtlSetAttachmentSourceAlphaBlendFactor(
|
|
|
colorAttachment,
|
|
|
XNAToMTL.BlendMode[
|
|
|
(int) blendState.AlphaSourceBlend
|
|
|
]
|
|
|
);
|
|
|
mtlSetAttachmentDestinationAlphaBlendFactor(
|
|
|
colorAttachment,
|
|
|
XNAToMTL.BlendMode[
|
|
|
(int) blendState.AlphaDestinationBlend
|
|
|
]
|
|
|
);
|
|
|
mtlSetAttachmentRGBBlendOperation(
|
|
|
colorAttachment,
|
|
|
XNAToMTL.BlendOperation[
|
|
|
(int) blendState.ColorBlendFunction
|
|
|
]
|
|
|
);
|
|
|
mtlSetAttachmentAlphaBlendOperation(
|
|
|
colorAttachment,
|
|
|
XNAToMTL.BlendOperation[
|
|
|
(int) blendState.AlphaBlendFunction
|
|
|
]
|
|
|
);
|
|
|
}
|
|
|
|
|
|
/* FIXME: So how exactly do we factor in
|
|
|
* COLORWRITEENABLE for buffer 0? Do we just assume that
|
|
|
* the default is just buffer 0, and all other calls
|
|
|
* update the other write masks?
|
|
|
*/
|
|
|
if (i == 0)
|
|
|
{
|
|
|
mtlSetAttachmentWriteMask(
|
|
|
colorAttachment,
|
|
|
XNAToMTL.ColorWriteMask(blendState.ColorWriteChannels)
|
|
|
);
|
|
|
}
|
|
|
else if (i == 1)
|
|
|
{
|
|
|
mtlSetAttachmentWriteMask(
|
|
|
mtlGetColorAttachment(pipelineDesc, 1),
|
|
|
XNAToMTL.ColorWriteMask(blendState.ColorWriteChannels1)
|
|
|
);
|
|
|
}
|
|
|
else if (i == 2)
|
|
|
{
|
|
|
mtlSetAttachmentWriteMask(
|
|
|
mtlGetColorAttachment(pipelineDesc, 2),
|
|
|
XNAToMTL.ColorWriteMask(blendState.ColorWriteChannels2)
|
|
|
);
|
|
|
}
|
|
|
else if (i == 3)
|
|
|
{
|
|
|
mtlSetAttachmentWriteMask(
|
|
|
mtlGetColorAttachment(pipelineDesc, 3),
|
|
|
XNAToMTL.ColorWriteMask(blendState.ColorWriteChannels3)
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Bake the render pipeline!
|
|
|
IntPtr pipelineState = mtlNewRenderPipelineStateWithDescriptor(
|
|
|
device,
|
|
|
pipelineDesc
|
|
|
);
|
|
|
PipelineStateCache[hash] = pipelineState;
|
|
|
|
|
|
// Clean up
|
|
|
objc_release(pipelineDesc);
|
|
|
objc_release(vertHandle);
|
|
|
objc_release(fragHandle);
|
|
|
|
|
|
// Return the pipeline!
|
|
|
return pipelineState;
|
|
|
}
|
|
|
|
|
|
private IntPtr FetchDepthStencilState()
|
|
|
{
|
|
|
/* Just use the default depth-stencil state
|
|
|
* if depth and stencil testing are disabled,
|
|
|
* or if there is no bound depth attachment.
|
|
|
* This wards off Metal validation errors.
|
|
|
* -caleb
|
|
|
*/
|
|
|
bool zEnable = depthStencilState.DepthBufferEnable;
|
|
|
bool sEnable = depthStencilState.StencilEnable;
|
|
|
bool zFormat = (currentDepthFormat != DepthFormat.None);
|
|
|
if ((!zEnable && !sEnable) || (!zFormat))
|
|
|
{
|
|
|
return defaultDepthStencilState;
|
|
|
}
|
|
|
|
|
|
// Can we just reuse an existing state?
|
|
|
StateHash hash = PipelineCache.GetDepthStencilHash(depthStencilState);
|
|
|
IntPtr state = IntPtr.Zero;
|
|
|
if (DepthStencilStateCache.TryGetValue(hash, out state))
|
|
|
{
|
|
|
// This state has already been cached!
|
|
|
return state;
|
|
|
}
|
|
|
|
|
|
// We have to make a new DepthStencilState...
|
|
|
IntPtr dsDesc = mtlNewDepthStencilDescriptor();
|
|
|
if (zEnable)
|
|
|
{
|
|
|
mtlSetDepthCompareFunction(
|
|
|
dsDesc,
|
|
|
XNAToMTL.CompareFunc[(int) depthStencilState.DepthBufferFunction]
|
|
|
);
|
|
|
mtlSetDepthWriteEnabled(
|
|
|
dsDesc,
|
|
|
depthStencilState.DepthBufferWriteEnable
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// Create stencil descriptors
|
|
|
IntPtr front = IntPtr.Zero;
|
|
|
IntPtr back = IntPtr.Zero;
|
|
|
|
|
|
if (sEnable)
|
|
|
{
|
|
|
front = mtlNewStencilDescriptor();
|
|
|
mtlSetStencilFailureOperation(
|
|
|
front,
|
|
|
XNAToMTL.StencilOp[(int) depthStencilState.StencilFail]
|
|
|
);
|
|
|
mtlSetDepthFailureOperation(
|
|
|
front,
|
|
|
XNAToMTL.StencilOp[(int) depthStencilState.StencilDepthBufferFail]
|
|
|
);
|
|
|
mtlSetDepthStencilPassOperation(
|
|
|
front,
|
|
|
XNAToMTL.StencilOp[(int) depthStencilState.StencilPass]
|
|
|
);
|
|
|
mtlSetStencilCompareFunction(
|
|
|
front,
|
|
|
XNAToMTL.CompareFunc[(int) depthStencilState.StencilFunction]
|
|
|
);
|
|
|
mtlSetStencilReadMask(
|
|
|
front,
|
|
|
(uint) depthStencilState.StencilMask
|
|
|
);
|
|
|
mtlSetStencilWriteMask(
|
|
|
front,
|
|
|
(uint) depthStencilState.StencilWriteMask
|
|
|
);
|
|
|
|
|
|
if (!depthStencilState.TwoSidedStencilMode)
|
|
|
{
|
|
|
back = front;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (front != back)
|
|
|
{
|
|
|
back = mtlNewStencilDescriptor();
|
|
|
mtlSetStencilFailureOperation(
|
|
|
back,
|
|
|
XNAToMTL.StencilOp[(int) depthStencilState.CounterClockwiseStencilFail]
|
|
|
);
|
|
|
mtlSetDepthFailureOperation(
|
|
|
back,
|
|
|
XNAToMTL.StencilOp[(int) depthStencilState.CounterClockwiseStencilDepthBufferFail]
|
|
|
);
|
|
|
mtlSetDepthStencilPassOperation(
|
|
|
back,
|
|
|
XNAToMTL.StencilOp[(int) depthStencilState.CounterClockwiseStencilPass]
|
|
|
);
|
|
|
mtlSetStencilCompareFunction(
|
|
|
back,
|
|
|
XNAToMTL.CompareFunc[(int) depthStencilState.CounterClockwiseStencilFunction]
|
|
|
);
|
|
|
mtlSetStencilReadMask(
|
|
|
back,
|
|
|
(uint) depthStencilState.StencilMask
|
|
|
);
|
|
|
mtlSetStencilWriteMask(
|
|
|
back,
|
|
|
(uint) depthStencilState.StencilWriteMask
|
|
|
);
|
|
|
}
|
|
|
|
|
|
mtlSetFrontFaceStencil(
|
|
|
dsDesc,
|
|
|
front
|
|
|
);
|
|
|
mtlSetBackFaceStencil(
|
|
|
dsDesc,
|
|
|
back
|
|
|
);
|
|
|
|
|
|
// Bake the state!
|
|
|
state = mtlNewDepthStencilStateWithDescriptor(
|
|
|
device,
|
|
|
dsDesc
|
|
|
);
|
|
|
DepthStencilStateCache[hash] = state;
|
|
|
|
|
|
// Clean up
|
|
|
objc_release(dsDesc);
|
|
|
|
|
|
// Return the state!
|
|
|
return state;
|
|
|
}
|
|
|
|
|
|
private IntPtr FetchSamplerState(SamplerState samplerState, bool hasMipmaps)
|
|
|
{
|
|
|
// Can we just reuse an existing state?
|
|
|
StateHash hash = PipelineCache.GetSamplerHash(samplerState);
|
|
|
IntPtr state = IntPtr.Zero;
|
|
|
if (SamplerStateCache.TryGetValue(hash, out state))
|
|
|
{
|
|
|
// The value is already cached!
|
|
|
return state;
|
|
|
}
|
|
|
|
|
|
// We have to make a new sampler state...
|
|
|
IntPtr samplerDesc = mtlNewSamplerDescriptor();
|
|
|
|
|
|
mtlSetSampler_sAddressMode(
|
|
|
samplerDesc,
|
|
|
XNAToMTL.Wrap[(int) samplerState.AddressU]
|
|
|
);
|
|
|
mtlSetSampler_tAddressMode(
|
|
|
samplerDesc,
|
|
|
XNAToMTL.Wrap[(int) samplerState.AddressV]
|
|
|
);
|
|
|
mtlSetSampler_rAddressMode(
|
|
|
samplerDesc,
|
|
|
XNAToMTL.Wrap[(int) samplerState.AddressW]
|
|
|
);
|
|
|
mtlSetSamplerMagFilter(
|
|
|
samplerDesc,
|
|
|
XNAToMTL.MagFilter[(int) samplerState.Filter]
|
|
|
);
|
|
|
mtlSetSamplerMinFilter(
|
|
|
samplerDesc,
|
|
|
XNAToMTL.MinFilter[(int) samplerState.Filter]
|
|
|
);
|
|
|
if (hasMipmaps)
|
|
|
{
|
|
|
mtlSetSamplerMipFilter(
|
|
|
samplerDesc,
|
|
|
XNAToMTL.MipFilter[(int) samplerState.Filter]
|
|
|
);
|
|
|
}
|
|
|
mtlSetSamplerLodMinClamp(
|
|
|
samplerDesc,
|
|
|
samplerState.MaxMipLevel
|
|
|
);
|
|
|
mtlSetSamplerMaxAnisotropy(
|
|
|
samplerDesc,
|
|
|
(samplerState.Filter == TextureFilter.Anisotropic) ?
|
|
|
Math.Max(1, samplerState.MaxAnisotropy) :
|
|
|
1
|
|
|
);
|
|
|
|
|
|
/* FIXME:
|
|
|
* The only way to set lod bias in metal is via the MSL
|
|
|
* bias() function in a shader. So we can't do:
|
|
|
*
|
|
|
* mtlSetSamplerLodBias(
|
|
|
* samplerDesc,
|
|
|
* samplerState.MipMapLevelOfDetailBias
|
|
|
* );
|
|
|
*
|
|
|
* What should we do instead?
|
|
|
*
|
|
|
* -caleb
|
|
|
*/
|
|
|
|
|
|
// Bake the sampler state!
|
|
|
state = mtlNewSamplerStateWithDescriptor(
|
|
|
device,
|
|
|
samplerDesc
|
|
|
);
|
|
|
SamplerStateCache[hash] = state;
|
|
|
|
|
|
// Clean up
|
|
|
objc_release(samplerDesc);
|
|
|
|
|
|
// Return the sampler state!
|
|
|
return state;
|
|
|
}
|
|
|
|
|
|
private IntPtr FetchVertexDescriptor(
|
|
|
VertexBufferBinding[] bindings,
|
|
|
int numBindings
|
|
|
) {
|
|
|
// Can we just reuse an existing descriptor?
|
|
|
ulong hash = PipelineCache.GetVertexBindingHash(
|
|
|
bindings,
|
|
|
numBindings,
|
|
|
(ulong) shaderState.vertexShader
|
|
|
);
|
|
|
IntPtr descriptor;
|
|
|
if (VertexDescriptorCache.TryGetValue(hash, out descriptor))
|
|
|
{
|
|
|
// The value is already cached!
|
|
|
return descriptor;
|
|
|
}
|
|
|
|
|
|
// We have to make a new vertex descriptor...
|
|
|
descriptor = mtlMakeVertexDescriptor();
|
|
|
objc_retain(descriptor);
|
|
|
|
|
|
/* There's this weird case where you can have overlapping
|
|
|
* vertex usage/index combinations. It seems like the first
|
|
|
* attrib gets priority, so whenever a duplicate attribute
|
|
|
* exists, give it the next available index. If that fails, we
|
|
|
* have to crash :/
|
|
|
* -flibit
|
|
|
*/
|
|
|
Array.Clear(attrUse, 0, attrUse.Length);
|
|
|
for (int i = 0; i < numBindings; i += 1)
|
|
|
{
|
|
|
// Describe vertex attributes
|
|
|
VertexDeclaration vertexDeclaration = bindings[i].VertexBuffer.VertexDeclaration;
|
|
|
foreach (VertexElement element in vertexDeclaration.elements)
|
|
|
{
|
|
|
int usage = (int) element.VertexElementUsage;
|
|
|
int index = element.UsageIndex;
|
|
|
if (attrUse[usage, index])
|
|
|
{
|
|
|
index = -1;
|
|
|
for (int j = 0; j < 16; j += 1)
|
|
|
{
|
|
|
if (!attrUse[usage, j])
|
|
|
{
|
|
|
index = j;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
if (index < 0)
|
|
|
{
|
|
|
throw new InvalidOperationException("Vertex usage collision!");
|
|
|
}
|
|
|
}
|
|
|
attrUse[usage, index] = true;
|
|
|
int attribLoc = MojoShader.MOJOSHADER_mtlGetVertexAttribLocation(
|
|
|
shaderState.vertexShader,
|
|
|
XNAToMTL.VertexAttribUsage[usage],
|
|
|
index
|
|
|
);
|
|
|
if (attribLoc == -1)
|
|
|
{
|
|
|
// Stream not in use!
|
|
|
continue;
|
|
|
}
|
|
|
IntPtr attrib = mtlGetVertexAttributeDescriptor(
|
|
|
descriptor,
|
|
|
attribLoc
|
|
|
);
|
|
|
mtlSetVertexAttributeFormat(
|
|
|
attrib,
|
|
|
XNAToMTL.VertexAttribType[(int) element.VertexElementFormat]
|
|
|
);
|
|
|
mtlSetVertexAttributeOffset(
|
|
|
attrib,
|
|
|
element.Offset
|
|
|
);
|
|
|
mtlSetVertexAttributeBufferIndex(
|
|
|
attrib,
|
|
|
i
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// Describe vertex buffer layout
|
|
|
IntPtr layout = mtlGetVertexBufferLayoutDescriptor(
|
|
|
descriptor,
|
|
|
i
|
|
|
);
|
|
|
mtlSetVertexBufferLayoutStride(
|
|
|
layout,
|
|
|
vertexDeclaration.VertexStride
|
|
|
);
|
|
|
if (bindings[i].InstanceFrequency > 0)
|
|
|
{
|
|
|
mtlSetVertexBufferLayoutStepFunction(
|
|
|
layout,
|
|
|
MTLVertexStepFunction.PerInstance
|
|
|
);
|
|
|
mtlSetVertexBufferLayoutStepRate(
|
|
|
layout,
|
|
|
bindings[i].InstanceFrequency
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
VertexDescriptorCache[hash] = descriptor;
|
|
|
return descriptor;
|
|
|
}
|
|
|
|
|
|
private IntPtr FetchVertexDescriptor(
|
|
|
VertexDeclaration vertexDeclaration,
|
|
|
int vertexOffset
|
|
|
) {
|
|
|
// Can we just reuse an existing descriptor?
|
|
|
ulong hash = PipelineCache.GetVertexDeclarationHash(
|
|
|
vertexDeclaration,
|
|
|
(ulong) shaderState.vertexShader
|
|
|
);
|
|
|
IntPtr descriptor;
|
|
|
if (VertexDescriptorCache.TryGetValue(hash, out descriptor))
|
|
|
{
|
|
|
// The value is already cached!
|
|
|
return descriptor;
|
|
|
}
|
|
|
|
|
|
// We have to make a new vertex descriptor...
|
|
|
descriptor = mtlMakeVertexDescriptor();
|
|
|
objc_retain(descriptor);
|
|
|
|
|
|
/* There's this weird case where you can have overlapping
|
|
|
* vertex usage/index combinations. It seems like the first
|
|
|
* attrib gets priority, so whenever a duplicate attribute
|
|
|
* exists, give it the next available index. If that fails, we
|
|
|
* have to crash :/
|
|
|
* -flibit
|
|
|
*/
|
|
|
Array.Clear(attrUse, 0, attrUse.Length);
|
|
|
foreach (VertexElement element in vertexDeclaration.elements)
|
|
|
{
|
|
|
int usage = (int) element.VertexElementUsage;
|
|
|
int index = element.UsageIndex;
|
|
|
if (attrUse[usage, index])
|
|
|
{
|
|
|
index = -1;
|
|
|
for (int j = 0; j < 16; j += 1)
|
|
|
{
|
|
|
if (!attrUse[usage, j])
|
|
|
{
|
|
|
index = j;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
if (index < 0)
|
|
|
{
|
|
|
throw new InvalidOperationException("Vertex usage collision!");
|
|
|
}
|
|
|
}
|
|
|
attrUse[usage, index] = true;
|
|
|
int attribLoc = MojoShader.MOJOSHADER_mtlGetVertexAttribLocation(
|
|
|
shaderState.vertexShader,
|
|
|
XNAToMTL.VertexAttribUsage[usage],
|
|
|
index
|
|
|
);
|
|
|
if (attribLoc == -1)
|
|
|
{
|
|
|
// Stream not in use!
|
|
|
continue;
|
|
|
}
|
|
|
IntPtr attrib = mtlGetVertexAttributeDescriptor(
|
|
|
descriptor,
|
|
|
attribLoc
|
|
|
);
|
|
|
mtlSetVertexAttributeFormat(
|
|
|
attrib,
|
|
|
XNAToMTL.VertexAttribType[(int) element.VertexElementFormat]
|
|
|
);
|
|
|
mtlSetVertexAttributeOffset(
|
|
|
attrib,
|
|
|
element.Offset
|
|
|
);
|
|
|
mtlSetVertexAttributeBufferIndex(
|
|
|
attrib,
|
|
|
0
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// Describe vertex buffer layout
|
|
|
IntPtr layout = mtlGetVertexBufferLayoutDescriptor(
|
|
|
descriptor,
|
|
|
0
|
|
|
);
|
|
|
mtlSetVertexBufferLayoutStride(
|
|
|
layout,
|
|
|
vertexDeclaration.VertexStride
|
|
|
);
|
|
|
|
|
|
VertexDescriptorCache[hash] = descriptor;
|
|
|
return descriptor;
|
|
|
}
|
|
|
|
|
|
private IntPtr FetchTransientTexture(MetalTexture fromTexture)
|
|
|
{
|
|
|
// Can we just reuse an existing texture?
|
|
|
for (int i = 0; i < transientTextures.Count; i += 1)
|
|
|
{
|
|
|
MetalTexture tex = transientTextures[i];
|
|
|
if ( tex.Format == fromTexture.Format &&
|
|
|
tex.Width == fromTexture.Width &&
|
|
|
tex.Height == fromTexture.Height &&
|
|
|
tex.HasMipmaps == fromTexture.HasMipmaps )
|
|
|
{
|
|
|
mtlSetPurgeableState(
|
|
|
tex.Handle,
|
|
|
MTLPurgeableState.NonVolatile
|
|
|
);
|
|
|
return tex.Handle;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// We have to make a new texture...
|
|
|
IntPtr texDesc = mtlMakeTexture2DDescriptor(
|
|
|
XNAToMTL.TextureFormat[(int) fromTexture.Format],
|
|
|
fromTexture.Width,
|
|
|
fromTexture.Height,
|
|
|
fromTexture.HasMipmaps
|
|
|
);
|
|
|
MetalTexture ret = new MetalTexture(
|
|
|
mtlNewTextureWithDescriptor(device, texDesc),
|
|
|
fromTexture.Width,
|
|
|
fromTexture.Height,
|
|
|
fromTexture.Format,
|
|
|
fromTexture.HasMipmaps ? 2 : 0,
|
|
|
false
|
|
|
);
|
|
|
transientTextures.Add(ret);
|
|
|
return ret.Handle;
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region DepthFormat Conversion Method
|
|
|
|
|
|
private MTLPixelFormat GetDepthFormat(DepthFormat format)
|
|
|
{
|
|
|
switch (format)
|
|
|
{
|
|
|
case DepthFormat.Depth16: return D16Format;
|
|
|
case DepthFormat.Depth24: return D24Format;
|
|
|
case DepthFormat.Depth24Stencil8: return D24S8Format;
|
|
|
default: return MTLPixelFormat.Invalid;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Effect Methods
|
|
|
|
|
|
public IGLEffect CreateEffect(byte[] effectCode)
|
|
|
{
|
|
|
IntPtr effect = IntPtr.Zero;
|
|
|
IntPtr mtlEffect = IntPtr.Zero;
|
|
|
|
|
|
effect = MojoShader.MOJOSHADER_parseEffect(
|
|
|
"metal",
|
|
|
effectCode,
|
|
|
(uint) effectCode.Length,
|
|
|
null,
|
|
|
0,
|
|
|
null,
|
|
|
0,
|
|
|
null,
|
|
|
null,
|
|
|
IntPtr.Zero
|
|
|
);
|
|
|
|
|
|
#if DEBUG
|
|
|
unsafe
|
|
|
{
|
|
|
MojoShader.MOJOSHADER_effect *effectPtr = (MojoShader.MOJOSHADER_effect*) effect;
|
|
|
MojoShader.MOJOSHADER_error* err = (MojoShader.MOJOSHADER_error*) effectPtr->errors;
|
|
|
for (int i = 0; i < effectPtr->error_count; i += 1)
|
|
|
{
|
|
|
// From the SDL2# LPToUtf8StringMarshaler
|
|
|
byte* endPtr = (byte*) err[i].error;
|
|
|
while (*endPtr != 0)
|
|
|
{
|
|
|
endPtr++;
|
|
|
}
|
|
|
byte[] bytes = new byte[endPtr - (byte*) err[i].error];
|
|
|
Marshal.Copy(err[i].error, bytes, 0, bytes.Length);
|
|
|
|
|
|
FNALoggerEXT.LogError(
|
|
|
"MOJOSHADER_parseEffect Error: " +
|
|
|
System.Text.Encoding.UTF8.GetString(bytes)
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
mtlEffect = MojoShader.MOJOSHADER_mtlCompileEffect(
|
|
|
effect,
|
|
|
device,
|
|
|
MAX_FRAMES_IN_FLIGHT
|
|
|
);
|
|
|
if (mtlEffect == IntPtr.Zero)
|
|
|
{
|
|
|
throw new InvalidOperationException(
|
|
|
MojoShader.MOJOSHADER_mtlGetError()
|
|
|
);
|
|
|
}
|
|
|
|
|
|
return new MetalEffect(effect, mtlEffect);
|
|
|
}
|
|
|
|
|
|
private void DeleteEffect(IGLEffect effect)
|
|
|
{
|
|
|
IntPtr mtlEffectData = (effect as MetalEffect).MTLEffectData;
|
|
|
if (mtlEffectData == currentEffect)
|
|
|
{
|
|
|
MojoShader.MOJOSHADER_mtlEffectEndPass(currentEffect);
|
|
|
MojoShader.MOJOSHADER_mtlEffectEnd(
|
|
|
currentEffect,
|
|
|
ref shaderState
|
|
|
);
|
|
|
currentEffect = IntPtr.Zero;
|
|
|
currentTechnique = IntPtr.Zero;
|
|
|
currentPass = 0;
|
|
|
shaderState = new MojoShader.MOJOSHADER_mtlShaderState();
|
|
|
}
|
|
|
MojoShader.MOJOSHADER_mtlDeleteEffect(mtlEffectData);
|
|
|
MojoShader.MOJOSHADER_freeEffect(effect.EffectData);
|
|
|
}
|
|
|
|
|
|
public IGLEffect CloneEffect(IGLEffect cloneSource)
|
|
|
{
|
|
|
IntPtr effect = IntPtr.Zero;
|
|
|
IntPtr mtlEffect = IntPtr.Zero;
|
|
|
|
|
|
effect = MojoShader.MOJOSHADER_cloneEffect(cloneSource.EffectData);
|
|
|
mtlEffect = MojoShader.MOJOSHADER_mtlCompileEffect(
|
|
|
effect,
|
|
|
device,
|
|
|
1
|
|
|
);
|
|
|
if (mtlEffect == IntPtr.Zero)
|
|
|
{
|
|
|
throw new InvalidOperationException(
|
|
|
MojoShader.MOJOSHADER_mtlGetError()
|
|
|
);
|
|
|
}
|
|
|
|
|
|
return new MetalEffect(effect, mtlEffect);
|
|
|
}
|
|
|
|
|
|
public void ApplyEffect(
|
|
|
IGLEffect effect,
|
|
|
IntPtr technique,
|
|
|
uint pass,
|
|
|
IntPtr stateChanges
|
|
|
) {
|
|
|
/* If a frame isn't already in progress,
|
|
|
* wait until one begins to avoid overwriting
|
|
|
* the previous frame's uniform buffers.
|
|
|
*/
|
|
|
BeginFrame();
|
|
|
|
|
|
IntPtr mtlEffectData = (effect as MetalEffect).MTLEffectData;
|
|
|
if (mtlEffectData == currentEffect)
|
|
|
{
|
|
|
if (technique == currentTechnique && pass == currentPass)
|
|
|
{
|
|
|
MojoShader.MOJOSHADER_mtlEffectCommitChanges(
|
|
|
currentEffect,
|
|
|
ref shaderState
|
|
|
);
|
|
|
return;
|
|
|
}
|
|
|
MojoShader.MOJOSHADER_mtlEffectEndPass(currentEffect);
|
|
|
MojoShader.MOJOSHADER_mtlEffectBeginPass(
|
|
|
currentEffect,
|
|
|
pass,
|
|
|
ref shaderState
|
|
|
);
|
|
|
currentTechnique = technique;
|
|
|
currentPass = pass;
|
|
|
return;
|
|
|
}
|
|
|
else if (currentEffect != IntPtr.Zero)
|
|
|
{
|
|
|
MojoShader.MOJOSHADER_mtlEffectEndPass(currentEffect);
|
|
|
MojoShader.MOJOSHADER_mtlEffectEnd(
|
|
|
currentEffect,
|
|
|
ref shaderState
|
|
|
);
|
|
|
}
|
|
|
uint whatever;
|
|
|
MojoShader.MOJOSHADER_mtlEffectBegin(
|
|
|
mtlEffectData,
|
|
|
out whatever,
|
|
|
0,
|
|
|
stateChanges
|
|
|
);
|
|
|
MojoShader.MOJOSHADER_mtlEffectBeginPass(
|
|
|
mtlEffectData,
|
|
|
pass,
|
|
|
ref shaderState
|
|
|
);
|
|
|
currentEffect = mtlEffectData;
|
|
|
currentTechnique = technique;
|
|
|
currentPass = pass;
|
|
|
}
|
|
|
|
|
|
public void BeginPassRestore(IGLEffect effect, IntPtr stateChanges)
|
|
|
{
|
|
|
/* If a frame isn't already in progress,
|
|
|
* wait until one begins to avoid overwriting
|
|
|
* the previous frame's uniform buffers.
|
|
|
*/
|
|
|
BeginFrame();
|
|
|
|
|
|
// Store the current data
|
|
|
prevEffect = currentEffect;
|
|
|
prevShaderState = shaderState;
|
|
|
|
|
|
IntPtr mtlEffectData = (effect as MetalEffect).MTLEffectData;
|
|
|
uint whatever;
|
|
|
MojoShader.MOJOSHADER_mtlEffectBegin(
|
|
|
mtlEffectData,
|
|
|
out whatever,
|
|
|
1,
|
|
|
stateChanges
|
|
|
);
|
|
|
MojoShader.MOJOSHADER_mtlEffectBeginPass(
|
|
|
mtlEffectData,
|
|
|
0,
|
|
|
ref shaderState
|
|
|
);
|
|
|
currentEffect = mtlEffectData;
|
|
|
}
|
|
|
|
|
|
public void EndPassRestore(IGLEffect effect)
|
|
|
{
|
|
|
IntPtr mtlEffectData = (effect as MetalEffect).MTLEffectData;
|
|
|
MojoShader.MOJOSHADER_mtlEffectEndPass(mtlEffectData);
|
|
|
MojoShader.MOJOSHADER_mtlEffectEnd(
|
|
|
mtlEffectData,
|
|
|
ref shaderState
|
|
|
);
|
|
|
|
|
|
// Restore the old data
|
|
|
shaderState = prevShaderState;
|
|
|
currentEffect = prevEffect;
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Resource Binding Method
|
|
|
|
|
|
private void BindResources()
|
|
|
{
|
|
|
// Bind textures and their sampler states
|
|
|
for (int i = 0; i < Textures.Length; i += 1)
|
|
|
{
|
|
|
if (textureNeedsUpdate[i])
|
|
|
{
|
|
|
mtlSetFragmentTexture(
|
|
|
renderCommandEncoder,
|
|
|
Textures[i].Handle,
|
|
|
i
|
|
|
);
|
|
|
textureNeedsUpdate[i] = false;
|
|
|
}
|
|
|
if (samplerNeedsUpdate[i])
|
|
|
{
|
|
|
mtlSetFragmentSamplerState(
|
|
|
renderCommandEncoder,
|
|
|
Samplers[i],
|
|
|
i
|
|
|
);
|
|
|
samplerNeedsUpdate[i] = false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Bind the uniform buffers
|
|
|
const int UNIFORM_REG = 16; // In MojoShader output it's always 16
|
|
|
|
|
|
IntPtr vUniform = shaderState.vertexUniformBuffer;
|
|
|
int vOff = shaderState.vertexUniformOffset;
|
|
|
if (vUniform != ldVertUniformBuffer)
|
|
|
{
|
|
|
mtlSetVertexBuffer(
|
|
|
renderCommandEncoder,
|
|
|
vUniform,
|
|
|
vOff,
|
|
|
UNIFORM_REG
|
|
|
);
|
|
|
ldVertUniformBuffer = vUniform;
|
|
|
ldVertUniformOffset = vOff;
|
|
|
}
|
|
|
else if (vOff != ldVertUniformOffset)
|
|
|
{
|
|
|
mtlSetVertexBufferOffset(
|
|
|
renderCommandEncoder,
|
|
|
vOff,
|
|
|
UNIFORM_REG
|
|
|
);
|
|
|
ldVertUniformOffset = vOff;
|
|
|
}
|
|
|
|
|
|
IntPtr fUniform = shaderState.fragmentUniformBuffer;
|
|
|
int fOff = shaderState.fragmentUniformOffset;
|
|
|
if (fUniform != ldFragUniformBuffer)
|
|
|
{
|
|
|
mtlSetFragmentBuffer(
|
|
|
renderCommandEncoder,
|
|
|
fUniform,
|
|
|
fOff,
|
|
|
UNIFORM_REG
|
|
|
);
|
|
|
ldFragUniformBuffer = fUniform;
|
|
|
ldFragUniformOffset = fOff;
|
|
|
}
|
|
|
else if (fOff != ldFragUniformOffset)
|
|
|
{
|
|
|
mtlSetFragmentBufferOffset(
|
|
|
renderCommandEncoder,
|
|
|
fOff,
|
|
|
UNIFORM_REG
|
|
|
);
|
|
|
ldFragUniformOffset = fOff;
|
|
|
}
|
|
|
|
|
|
// Bind the depth-stencil state
|
|
|
IntPtr depthStencilState = FetchDepthStencilState();
|
|
|
if (depthStencilState != ldDepthStencilState)
|
|
|
{
|
|
|
mtlSetDepthStencilState(
|
|
|
renderCommandEncoder,
|
|
|
depthStencilState
|
|
|
);
|
|
|
ldDepthStencilState = depthStencilState;
|
|
|
}
|
|
|
|
|
|
// Finally, bind the pipeline state
|
|
|
IntPtr pipelineState = FetchRenderPipeline();
|
|
|
if (pipelineState != ldPipelineState)
|
|
|
{
|
|
|
mtlSetRenderPipelineState(
|
|
|
renderCommandEncoder,
|
|
|
pipelineState
|
|
|
);
|
|
|
ldPipelineState = pipelineState;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region ApplyVertexAttributes Methods
|
|
|
|
|
|
public void ApplyVertexAttributes(
|
|
|
VertexBufferBinding[] bindings,
|
|
|
int numBindings,
|
|
|
bool bindingsUpdated,
|
|
|
int baseVertex
|
|
|
) {
|
|
|
// Translate the bindings array into a descriptor
|
|
|
currentVertexDescriptor = FetchVertexDescriptor(
|
|
|
bindings,
|
|
|
numBindings
|
|
|
);
|
|
|
|
|
|
// Prepare for rendering
|
|
|
UpdateRenderPass();
|
|
|
BindResources();
|
|
|
|
|
|
// Bind the vertex buffers
|
|
|
for (int i = 0; i < bindings.Length; i += 1)
|
|
|
{
|
|
|
VertexBuffer vertexBuffer = bindings[i].VertexBuffer;
|
|
|
if (vertexBuffer != null)
|
|
|
{
|
|
|
int stride = bindings[i].VertexBuffer.VertexDeclaration.VertexStride;
|
|
|
int offset = (
|
|
|
((bindings[i].VertexOffset + baseVertex) * stride) +
|
|
|
(vertexBuffer.buffer as MetalBuffer).InternalOffset
|
|
|
);
|
|
|
|
|
|
IntPtr handle = (vertexBuffer.buffer as MetalBuffer).Handle;
|
|
|
(vertexBuffer.buffer as MetalBuffer).Bound();
|
|
|
if (ldVertexBuffers[i] != handle)
|
|
|
{
|
|
|
mtlSetVertexBuffer(
|
|
|
renderCommandEncoder,
|
|
|
handle,
|
|
|
offset,
|
|
|
i
|
|
|
);
|
|
|
ldVertexBuffers[i] = handle;
|
|
|
ldVertexBufferOffsets[i] = offset;
|
|
|
}
|
|
|
else if (ldVertexBufferOffsets[i] != offset)
|
|
|
{
|
|
|
mtlSetVertexBufferOffset(
|
|
|
renderCommandEncoder,
|
|
|
offset,
|
|
|
i
|
|
|
);
|
|
|
ldVertexBufferOffsets[i] = offset;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void ApplyVertexAttributes(
|
|
|
VertexDeclaration vertexDeclaration,
|
|
|
IntPtr ptr,
|
|
|
int vertexOffset
|
|
|
) {
|
|
|
// Translate the declaration into a descriptor
|
|
|
currentVertexDescriptor = FetchVertexDescriptor(
|
|
|
vertexDeclaration,
|
|
|
vertexOffset
|
|
|
);
|
|
|
userVertexStride = vertexDeclaration.VertexStride;
|
|
|
|
|
|
// Prepare for rendering
|
|
|
UpdateRenderPass();
|
|
|
BindResources();
|
|
|
|
|
|
// The rest happens in DrawUser[Indexed]Primitives.
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region GenBuffers Methods
|
|
|
|
|
|
public IGLBuffer GenIndexBuffer(
|
|
|
bool dynamic,
|
|
|
BufferUsage usage,
|
|
|
int indexCount,
|
|
|
IndexElementSize indexElementSize
|
|
|
) {
|
|
|
int elementSize = XNAToMTL.IndexSize[(int) indexElementSize];
|
|
|
MetalBuffer newbuf = new MetalBuffer(
|
|
|
this,
|
|
|
dynamic,
|
|
|
usage,
|
|
|
(IntPtr) (indexCount * elementSize)
|
|
|
);
|
|
|
Buffers.Add(newbuf);
|
|
|
return newbuf;
|
|
|
}
|
|
|
|
|
|
public IGLBuffer GenVertexBuffer(
|
|
|
bool dynamic,
|
|
|
BufferUsage usage,
|
|
|
int vertexCount,
|
|
|
int vertexStride
|
|
|
) {
|
|
|
MetalBuffer newbuf = new MetalBuffer(
|
|
|
this,
|
|
|
dynamic,
|
|
|
usage,
|
|
|
(IntPtr) (vertexCount * vertexStride)
|
|
|
);
|
|
|
Buffers.Add(newbuf);
|
|
|
return newbuf;
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Renderbuffer Methods
|
|
|
|
|
|
public IGLRenderbuffer GenRenderbuffer(
|
|
|
int width,
|
|
|
int height,
|
|
|
SurfaceFormat format,
|
|
|
int multiSampleCount,
|
|
|
IGLTexture texture
|
|
|
) {
|
|
|
MTLPixelFormat pixelFormat = XNAToMTL.TextureFormat[(int) format];
|
|
|
int sampleCount = GetCompatibleSampleCount(multiSampleCount);
|
|
|
|
|
|
// Generate a multisample texture
|
|
|
IntPtr desc = mtlMakeTexture2DDescriptor(
|
|
|
pixelFormat,
|
|
|
width,
|
|
|
height,
|
|
|
false
|
|
|
);
|
|
|
mtlSetStorageMode(
|
|
|
desc,
|
|
|
MTLStorageMode.Private
|
|
|
);
|
|
|
mtlSetTextureUsage(
|
|
|
desc,
|
|
|
MTLTextureUsage.RenderTarget
|
|
|
);
|
|
|
mtlSetTextureType(
|
|
|
desc,
|
|
|
MTLTextureType.Multisample2D
|
|
|
);
|
|
|
mtlSetTextureSampleCount(
|
|
|
desc,
|
|
|
sampleCount
|
|
|
);
|
|
|
IntPtr multisampleTexture = mtlNewTextureWithDescriptor(
|
|
|
device,
|
|
|
desc
|
|
|
);
|
|
|
|
|
|
// We're done!
|
|
|
return new MetalRenderbuffer(
|
|
|
(texture as MetalTexture).Handle,
|
|
|
pixelFormat,
|
|
|
sampleCount,
|
|
|
multisampleTexture
|
|
|
);
|
|
|
}
|
|
|
|
|
|
public IGLRenderbuffer GenRenderbuffer(
|
|
|
int width,
|
|
|
int height,
|
|
|
DepthFormat format,
|
|
|
int multiSampleCount
|
|
|
) {
|
|
|
MTLPixelFormat pixelFormat = GetDepthFormat(format);
|
|
|
int sampleCount = GetCompatibleSampleCount(multiSampleCount);
|
|
|
|
|
|
// Generate a depth texture
|
|
|
IntPtr desc = mtlMakeTexture2DDescriptor(
|
|
|
pixelFormat,
|
|
|
width,
|
|
|
height,
|
|
|
false
|
|
|
);
|
|
|
mtlSetStorageMode(
|
|
|
desc,
|
|
|
MTLStorageMode.Private
|
|
|
);
|
|
|
mtlSetTextureUsage(
|
|
|
desc,
|
|
|
MTLTextureUsage.RenderTarget
|
|
|
);
|
|
|
if (multiSampleCount > 0)
|
|
|
{
|
|
|
mtlSetTextureType(
|
|
|
desc,
|
|
|
MTLTextureType.Multisample2D
|
|
|
);
|
|
|
mtlSetTextureSampleCount(
|
|
|
desc,
|
|
|
sampleCount
|
|
|
);
|
|
|
}
|
|
|
IntPtr handle = mtlNewTextureWithDescriptor(
|
|
|
device,
|
|
|
desc
|
|
|
);
|
|
|
|
|
|
// We're done!
|
|
|
return new MetalRenderbuffer(
|
|
|
handle,
|
|
|
pixelFormat,
|
|
|
sampleCount,
|
|
|
IntPtr.Zero
|
|
|
);
|
|
|
}
|
|
|
|
|
|
private void DeleteRenderbuffer(IGLRenderbuffer renderbuffer)
|
|
|
{
|
|
|
MetalRenderbuffer rb = renderbuffer as MetalRenderbuffer;
|
|
|
bool isDepthStencil = rb.MultiSampleHandle == IntPtr.Zero;
|
|
|
|
|
|
if (isDepthStencil)
|
|
|
{
|
|
|
if (rb.Handle == currentDepthStencilBuffer)
|
|
|
{
|
|
|
currentDepthStencilBuffer = IntPtr.Zero;
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
for (int i = 0; i < currentAttachments.Length; i += 1)
|
|
|
{
|
|
|
if (rb.MultiSampleHandle == currentMSAttachments[i])
|
|
|
{
|
|
|
currentMSAttachments[i] = IntPtr.Zero;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
rb.Dispose();
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region SetBufferData Methods
|
|
|
|
|
|
public void SetIndexBufferData(
|
|
|
IGLBuffer buffer,
|
|
|
int offsetInBytes,
|
|
|
IntPtr data,
|
|
|
int dataLength,
|
|
|
SetDataOptions options
|
|
|
) {
|
|
|
(buffer as MetalBuffer).SetData(
|
|
|
offsetInBytes,
|
|
|
data,
|
|
|
dataLength,
|
|
|
options
|
|
|
);
|
|
|
}
|
|
|
|
|
|
public void SetVertexBufferData(
|
|
|
IGLBuffer buffer,
|
|
|
int offsetInBytes,
|
|
|
IntPtr data,
|
|
|
int dataLength,
|
|
|
SetDataOptions options
|
|
|
) {
|
|
|
(buffer as MetalBuffer).SetData(
|
|
|
offsetInBytes,
|
|
|
data,
|
|
|
dataLength,
|
|
|
options
|
|
|
);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region GetBufferData Methods
|
|
|
|
|
|
public void GetIndexBufferData(
|
|
|
IGLBuffer buffer,
|
|
|
int offsetInBytes,
|
|
|
IntPtr data,
|
|
|
int startIndex,
|
|
|
int elementCount,
|
|
|
int elementSizeInBytes
|
|
|
) {
|
|
|
SDL.SDL_memcpy(
|
|
|
data + (startIndex * elementSizeInBytes),
|
|
|
(buffer as MetalBuffer).Contents + offsetInBytes,
|
|
|
(IntPtr) (elementCount * elementSizeInBytes)
|
|
|
);
|
|
|
}
|
|
|
|
|
|
public void GetVertexBufferData(
|
|
|
IGLBuffer buffer,
|
|
|
int offsetInBytes,
|
|
|
IntPtr data,
|
|
|
int startIndex,
|
|
|
int elementCount,
|
|
|
int elementSizeInBytes,
|
|
|
int vertexStride
|
|
|
) {
|
|
|
IntPtr cpy;
|
|
|
bool useStagingBuffer = elementSizeInBytes < vertexStride;
|
|
|
if (useStagingBuffer)
|
|
|
{
|
|
|
cpy = Marshal.AllocHGlobal(elementCount * vertexStride);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
cpy = data + (startIndex * elementSizeInBytes);
|
|
|
}
|
|
|
|
|
|
SDL.SDL_memcpy(
|
|
|
cpy,
|
|
|
(buffer as MetalBuffer).Contents + offsetInBytes,
|
|
|
(IntPtr) (elementCount * vertexStride)
|
|
|
);
|
|
|
|
|
|
if (useStagingBuffer)
|
|
|
{
|
|
|
IntPtr src = cpy;
|
|
|
IntPtr dst = data + (startIndex * elementSizeInBytes);
|
|
|
for (int i = 0; i < elementCount; i += 1)
|
|
|
{
|
|
|
SDL.SDL_memcpy(dst, src, (IntPtr) elementSizeInBytes);
|
|
|
dst += elementSizeInBytes;
|
|
|
src += vertexStride;
|
|
|
}
|
|
|
Marshal.FreeHGlobal(cpy);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region DeleteBuffer Methods
|
|
|
|
|
|
private void DeleteBuffer(IGLBuffer buffer)
|
|
|
{
|
|
|
Buffers.Remove(buffer as MetalBuffer);
|
|
|
(buffer as MetalBuffer).Dispose();
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region CreateTexture Methods
|
|
|
|
|
|
public IGLTexture CreateTexture2D(
|
|
|
SurfaceFormat format,
|
|
|
int width,
|
|
|
int height,
|
|
|
int levelCount,
|
|
|
bool isRenderTarget
|
|
|
) {
|
|
|
IntPtr texDesc = mtlMakeTexture2DDescriptor(
|
|
|
XNAToMTL.TextureFormat[(int) format],
|
|
|
width,
|
|
|
height,
|
|
|
levelCount > 1
|
|
|
);
|
|
|
|
|
|
if (isRenderTarget)
|
|
|
{
|
|
|
mtlSetStorageMode(
|
|
|
texDesc,
|
|
|
MTLStorageMode.Private
|
|
|
);
|
|
|
mtlSetTextureUsage(
|
|
|
texDesc,
|
|
|
MTLTextureUsage.RenderTarget | MTLTextureUsage.ShaderRead
|
|
|
);
|
|
|
}
|
|
|
|
|
|
return new MetalTexture(
|
|
|
mtlNewTextureWithDescriptor(device, texDesc),
|
|
|
width,
|
|
|
height,
|
|
|
format,
|
|
|
levelCount,
|
|
|
isRenderTarget
|
|
|
);
|
|
|
}
|
|
|
|
|
|
public IGLTexture CreateTexture3D(
|
|
|
SurfaceFormat format,
|
|
|
int width,
|
|
|
int height,
|
|
|
int depth,
|
|
|
int levelCount
|
|
|
) {
|
|
|
IntPtr texDesc = mtlMakeTexture2DDescriptor(
|
|
|
XNAToMTL.TextureFormat[(int) format],
|
|
|
width,
|
|
|
height,
|
|
|
levelCount > 1
|
|
|
);
|
|
|
|
|
|
// Make it 3D!
|
|
|
mtlSetTextureDepth(texDesc, depth);
|
|
|
mtlSetTextureType(texDesc, MTLTextureType.Texture3D);
|
|
|
|
|
|
return new MetalTexture(
|
|
|
mtlNewTextureWithDescriptor(device, texDesc),
|
|
|
width,
|
|
|
height,
|
|
|
format,
|
|
|
levelCount,
|
|
|
false
|
|
|
);
|
|
|
}
|
|
|
|
|
|
public IGLTexture CreateTextureCube(
|
|
|
SurfaceFormat format,
|
|
|
int size,
|
|
|
int levelCount,
|
|
|
bool isRenderTarget
|
|
|
) {
|
|
|
IntPtr texDesc = mtlMakeTextureCubeDescriptor(
|
|
|
XNAToMTL.TextureFormat[(int) format],
|
|
|
size,
|
|
|
levelCount > 1
|
|
|
);
|
|
|
|
|
|
if (isRenderTarget)
|
|
|
{
|
|
|
mtlSetStorageMode(
|
|
|
texDesc,
|
|
|
MTLStorageMode.Private
|
|
|
);
|
|
|
mtlSetTextureUsage(
|
|
|
texDesc,
|
|
|
MTLTextureUsage.RenderTarget | MTLTextureUsage.ShaderRead
|
|
|
);
|
|
|
}
|
|
|
|
|
|
return new MetalTexture(
|
|
|
mtlNewTextureWithDescriptor(device, texDesc),
|
|
|
size,
|
|
|
size,
|
|
|
format,
|
|
|
levelCount,
|
|
|
isRenderTarget
|
|
|
);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region DeleteTexture Method
|
|
|
|
|
|
private void DeleteTexture(IGLTexture texture)
|
|
|
{
|
|
|
MetalTexture tex = texture as MetalTexture;
|
|
|
for (int i = 0; i < currentAttachments.Length; i += 1)
|
|
|
{
|
|
|
if (tex.Handle == currentAttachments[i])
|
|
|
{
|
|
|
currentAttachments[i] = IntPtr.Zero;
|
|
|
}
|
|
|
}
|
|
|
for (int i = 0; i < Textures.Length; i += 1)
|
|
|
{
|
|
|
if (tex.Handle == Textures[i].Handle)
|
|
|
{
|
|
|
Textures[i] = MetalTexture.NullTexture;
|
|
|
textureNeedsUpdate[i] = true;
|
|
|
}
|
|
|
}
|
|
|
tex.Dispose();
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Texture Data Helper Methods
|
|
|
|
|
|
private int BytesPerRow(int width, SurfaceFormat format)
|
|
|
{
|
|
|
int blocksPerRow = width;
|
|
|
|
|
|
if ( format == SurfaceFormat.Dxt1 ||
|
|
|
format == SurfaceFormat.Dxt3 ||
|
|
|
format == SurfaceFormat.Dxt5 )
|
|
|
{
|
|
|
blocksPerRow = (width + 3) / 4;
|
|
|
}
|
|
|
|
|
|
return blocksPerRow * Texture.GetFormatSize(format);
|
|
|
}
|
|
|
|
|
|
private int BytesPerImage(int width, int height, SurfaceFormat format)
|
|
|
{
|
|
|
int blocksPerRow = width;
|
|
|
int blocksPerColumn = height;
|
|
|
int formatSize = Texture.GetFormatSize(format);
|
|
|
|
|
|
if ( format == SurfaceFormat.Dxt1 ||
|
|
|
format == SurfaceFormat.Dxt3 ||
|
|
|
format == SurfaceFormat.Dxt5 )
|
|
|
{
|
|
|
blocksPerRow = (width + 3) / 4;
|
|
|
blocksPerColumn = (height + 3) / 4;
|
|
|
}
|
|
|
|
|
|
return blocksPerRow * blocksPerColumn * formatSize;
|
|
|
}
|
|
|
|
|
|
private int GetCompatibleSampleCount(int sampleCount)
|
|
|
{
|
|
|
/* If the device does not support the requested
|
|
|
* multisample count, halve it until we find a
|
|
|
* value that is supported.
|
|
|
*/
|
|
|
while (sampleCount > 0 && !mtlSupportsSampleCount(device, sampleCount))
|
|
|
{
|
|
|
sampleCount = MathHelper.ClosestMSAAPower(
|
|
|
sampleCount / 2
|
|
|
);
|
|
|
}
|
|
|
return sampleCount;
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region SetTextureData Methods
|
|
|
|
|
|
public void SetTextureData2D(
|
|
|
IGLTexture texture,
|
|
|
SurfaceFormat format,
|
|
|
int x,
|
|
|
int y,
|
|
|
int w,
|
|
|
int h,
|
|
|
int level,
|
|
|
IntPtr data,
|
|
|
int dataLength
|
|
|
) {
|
|
|
MetalTexture tex = texture as MetalTexture;
|
|
|
IntPtr handle = tex.Handle;
|
|
|
|
|
|
MTLOrigin origin = new MTLOrigin(x, y, 0);
|
|
|
MTLSize size = new MTLSize(w, h, 1);
|
|
|
|
|
|
if (tex.IsPrivate)
|
|
|
{
|
|
|
// We need an active command buffer
|
|
|
BeginFrame();
|
|
|
|
|
|
// Fetch a CPU-accessible texture
|
|
|
handle = FetchTransientTexture(tex);
|
|
|
}
|
|
|
|
|
|
// Write the data
|
|
|
mtlReplaceRegion(
|
|
|
handle,
|
|
|
new MTLRegion(origin, size),
|
|
|
level,
|
|
|
0,
|
|
|
data,
|
|
|
BytesPerRow(w, format),
|
|
|
0
|
|
|
);
|
|
|
|
|
|
// Blit the temp texture to the actual texture
|
|
|
if (tex.IsPrivate)
|
|
|
{
|
|
|
// End the render pass
|
|
|
EndPass();
|
|
|
|
|
|
// Blit!
|
|
|
IntPtr blit = mtlMakeBlitCommandEncoder(commandBuffer);
|
|
|
mtlBlitTextureToTexture(
|
|
|
blit,
|
|
|
handle,
|
|
|
0,
|
|
|
level,
|
|
|
origin,
|
|
|
size,
|
|
|
tex.Handle,
|
|
|
0,
|
|
|
level,
|
|
|
origin
|
|
|
);
|
|
|
|
|
|
// Submit the blit command to the GPU and wait...
|
|
|
mtlEndEncoding(blit);
|
|
|
Stall();
|
|
|
|
|
|
// We're done with the temp texture
|
|
|
mtlSetPurgeableState(
|
|
|
handle,
|
|
|
MTLPurgeableState.Empty
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void SetTextureDataYUV(Texture2D[] textures, IntPtr ptr)
|
|
|
{
|
|
|
for (int i = 0; i < 3; i += 1)
|
|
|
{
|
|
|
Texture2D tex = textures[i];
|
|
|
MTLRegion region = new MTLRegion(
|
|
|
MTLOrigin.Zero,
|
|
|
new MTLSize(tex.Width, tex.Height, 1)
|
|
|
);
|
|
|
mtlReplaceRegion(
|
|
|
(tex.texture as MetalTexture).Handle,
|
|
|
region,
|
|
|
0,
|
|
|
0,
|
|
|
ptr,
|
|
|
tex.Width,
|
|
|
0
|
|
|
);
|
|
|
ptr += tex.Width * tex.Height;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void SetTextureData3D(
|
|
|
IGLTexture texture,
|
|
|
SurfaceFormat format,
|
|
|
int level,
|
|
|
int left,
|
|
|
int top,
|
|
|
int right,
|
|
|
int bottom,
|
|
|
int front,
|
|
|
int back,
|
|
|
IntPtr data,
|
|
|
int dataLength
|
|
|
) {
|
|
|
int w = right - left;
|
|
|
int h = bottom - top;
|
|
|
int d = back - front;
|
|
|
|
|
|
MTLRegion region = new MTLRegion(
|
|
|
new MTLOrigin(left, top, front),
|
|
|
new MTLSize(w, h, d)
|
|
|
);
|
|
|
mtlReplaceRegion(
|
|
|
(texture as MetalTexture).Handle,
|
|
|
region,
|
|
|
level,
|
|
|
0,
|
|
|
data,
|
|
|
BytesPerRow(w, format),
|
|
|
BytesPerImage(w, h, format)
|
|
|
);
|
|
|
}
|
|
|
|
|
|
public void SetTextureDataCube(
|
|
|
IGLTexture texture,
|
|
|
SurfaceFormat format,
|
|
|
int xOffset,
|
|
|
int yOffset,
|
|
|
int width,
|
|
|
int height,
|
|
|
CubeMapFace cubeMapFace,
|
|
|
int level,
|
|
|
IntPtr data,
|
|
|
int dataLength
|
|
|
) {
|
|
|
MetalTexture tex = texture as MetalTexture;
|
|
|
IntPtr handle = tex.Handle;
|
|
|
|
|
|
MTLOrigin origin = new MTLOrigin(xOffset, yOffset, 0);
|
|
|
MTLSize size = new MTLSize(width, height, 1);
|
|
|
int slice = (int) cubeMapFace;
|
|
|
|
|
|
if (tex.IsPrivate)
|
|
|
{
|
|
|
// We need an active command buffer
|
|
|
BeginFrame();
|
|
|
|
|
|
// Fetch a CPU-accessible texture
|
|
|
handle = FetchTransientTexture(tex);
|
|
|
|
|
|
// Transient textures have no slices
|
|
|
slice = 0;
|
|
|
}
|
|
|
|
|
|
// Write the data
|
|
|
mtlReplaceRegion(
|
|
|
handle,
|
|
|
new MTLRegion(origin, size),
|
|
|
level,
|
|
|
slice,
|
|
|
data,
|
|
|
BytesPerRow(width, format),
|
|
|
0
|
|
|
);
|
|
|
|
|
|
// Blit the temp texture to the actual texture
|
|
|
if (tex.IsPrivate)
|
|
|
{
|
|
|
// End the render pass
|
|
|
EndPass();
|
|
|
|
|
|
// Blit!
|
|
|
IntPtr blit = mtlMakeBlitCommandEncoder(commandBuffer);
|
|
|
mtlBlitTextureToTexture(
|
|
|
blit,
|
|
|
handle,
|
|
|
slice,
|
|
|
level,
|
|
|
origin,
|
|
|
size,
|
|
|
tex.Handle,
|
|
|
slice,
|
|
|
level,
|
|
|
origin
|
|
|
);
|
|
|
|
|
|
// Submit the blit command to the GPU and wait...
|
|
|
mtlEndEncoding(blit);
|
|
|
Stall();
|
|
|
|
|
|
// We're done with the temp texture
|
|
|
mtlSetPurgeableState(
|
|
|
handle,
|
|
|
MTLPurgeableState.Empty
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region GetTextureData Methods
|
|
|
|
|
|
public void GetTextureData2D(
|
|
|
IGLTexture texture,
|
|
|
SurfaceFormat format,
|
|
|
int width,
|
|
|
int height,
|
|
|
int level,
|
|
|
int subX,
|
|
|
int subY,
|
|
|
int subW,
|
|
|
int subH,
|
|
|
IntPtr data,
|
|
|
int startIndex,
|
|
|
int elementCount,
|
|
|
int elementSizeInBytes
|
|
|
) {
|
|
|
MetalTexture tex = texture as MetalTexture;
|
|
|
IntPtr handle = tex.Handle;
|
|
|
|
|
|
MTLSize size = new MTLSize(subW, subH, 1);
|
|
|
MTLOrigin origin = new MTLOrigin(subX, subY, 0);
|
|
|
|
|
|
if (tex.IsPrivate)
|
|
|
{
|
|
|
// We need an active command buffer
|
|
|
BeginFrame();
|
|
|
|
|
|
// Fetch a CPU-accessible texture
|
|
|
handle = FetchTransientTexture(tex);
|
|
|
|
|
|
// End the render pass
|
|
|
EndPass();
|
|
|
|
|
|
// Blit the actual texture to a CPU-accessible texture
|
|
|
IntPtr blit = mtlMakeBlitCommandEncoder(commandBuffer);
|
|
|
mtlBlitTextureToTexture(
|
|
|
blit,
|
|
|
tex.Handle,
|
|
|
0,
|
|
|
level,
|
|
|
origin,
|
|
|
size,
|
|
|
handle,
|
|
|
0,
|
|
|
level,
|
|
|
origin
|
|
|
);
|
|
|
|
|
|
// Managed resources require explicit synchronization
|
|
|
if (isMac)
|
|
|
{
|
|
|
mtlSynchronizeResource(blit, handle);
|
|
|
}
|
|
|
|
|
|
// Submit the blit command to the GPU and wait...
|
|
|
mtlEndEncoding(blit);
|
|
|
Stall();
|
|
|
}
|
|
|
|
|
|
mtlGetTextureBytes(
|
|
|
handle,
|
|
|
data,
|
|
|
BytesPerRow(subW, format),
|
|
|
0,
|
|
|
new MTLRegion(origin, size),
|
|
|
level,
|
|
|
0
|
|
|
);
|
|
|
|
|
|
if (tex.IsPrivate)
|
|
|
{
|
|
|
// We're done with the temp texture
|
|
|
mtlSetPurgeableState(
|
|
|
handle,
|
|
|
MTLPurgeableState.Empty
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void GetTextureData3D(
|
|
|
IGLTexture texture,
|
|
|
SurfaceFormat format,
|
|
|
int left,
|
|
|
int top,
|
|
|
int front,
|
|
|
int right,
|
|
|
int bottom,
|
|
|
int back,
|
|
|
int level,
|
|
|
IntPtr data,
|
|
|
int startIndex,
|
|
|
int elementCount,
|
|
|
int elementSizeInBytes
|
|
|
) {
|
|
|
int w = right - left;
|
|
|
int h = bottom - top;
|
|
|
int d = back - front;
|
|
|
|
|
|
MTLRegion region = new MTLRegion(
|
|
|
new MTLOrigin(left, top, front),
|
|
|
new MTLSize(w, h, d)
|
|
|
);
|
|
|
mtlGetTextureBytes(
|
|
|
(texture as MetalTexture).Handle,
|
|
|
data,
|
|
|
BytesPerRow(w, format),
|
|
|
BytesPerImage(w, h, format),
|
|
|
region,
|
|
|
level,
|
|
|
0
|
|
|
);
|
|
|
}
|
|
|
|
|
|
public void GetTextureDataCube(
|
|
|
IGLTexture texture,
|
|
|
SurfaceFormat format,
|
|
|
int size,
|
|
|
CubeMapFace cubeMapFace,
|
|
|
int level,
|
|
|
int subX,
|
|
|
int subY,
|
|
|
int subW,
|
|
|
int subH,
|
|
|
IntPtr data,
|
|
|
int startIndex,
|
|
|
int elementCount,
|
|
|
int elementSizeInBytes
|
|
|
) {
|
|
|
MetalTexture tex = texture as MetalTexture;
|
|
|
IntPtr handle = tex.Handle;
|
|
|
|
|
|
MTLSize regionSize = new MTLSize(subW, subH, 1);
|
|
|
MTLOrigin origin = new MTLOrigin(subX, subY, 0);
|
|
|
int slice = (int) cubeMapFace;
|
|
|
|
|
|
if (tex.IsPrivate)
|
|
|
{
|
|
|
// We need an active command buffer
|
|
|
BeginFrame();
|
|
|
|
|
|
// Fetch a CPU-accessible texture
|
|
|
handle = FetchTransientTexture(tex);
|
|
|
|
|
|
// Transient textures have no slices
|
|
|
slice = 0;
|
|
|
|
|
|
// End the render pass
|
|
|
EndPass();
|
|
|
|
|
|
// Blit the actual texture to a CPU-accessible texture
|
|
|
IntPtr blit = mtlMakeBlitCommandEncoder(commandBuffer);
|
|
|
mtlBlitTextureToTexture(
|
|
|
blit,
|
|
|
tex.Handle,
|
|
|
(int) cubeMapFace,
|
|
|
level,
|
|
|
origin,
|
|
|
regionSize,
|
|
|
handle,
|
|
|
slice,
|
|
|
level,
|
|
|
origin
|
|
|
);
|
|
|
|
|
|
// Managed resources require explicit synchronization
|
|
|
if (isMac)
|
|
|
{
|
|
|
mtlSynchronizeResource(blit, handle);
|
|
|
}
|
|
|
|
|
|
// Submit the blit command to the GPU and wait...
|
|
|
mtlEndEncoding(blit);
|
|
|
Stall();
|
|
|
}
|
|
|
|
|
|
mtlGetTextureBytes(
|
|
|
handle,
|
|
|
data,
|
|
|
BytesPerRow(subW, format),
|
|
|
0,
|
|
|
new MTLRegion(origin, regionSize),
|
|
|
level,
|
|
|
slice
|
|
|
);
|
|
|
|
|
|
if (tex.IsPrivate)
|
|
|
{
|
|
|
// We're done with the temp texture
|
|
|
mtlSetPurgeableState(
|
|
|
handle,
|
|
|
MTLPurgeableState.Empty
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region ReadBackbuffer Method
|
|
|
|
|
|
public void ReadBackbuffer(
|
|
|
IntPtr data,
|
|
|
int dataLen,
|
|
|
int startIndex,
|
|
|
int elementCount,
|
|
|
int elementSizeInBytes,
|
|
|
int subX,
|
|
|
int subY,
|
|
|
int subW,
|
|
|
int subH
|
|
|
) {
|
|
|
/* FIXME: Right now we're expecting one of the following:
|
|
|
* - byte[]
|
|
|
* - int[]
|
|
|
* - uint[]
|
|
|
* - Color[]
|
|
|
* Anything else will freak out because we're using
|
|
|
* color backbuffers. Maybe check this out when adding
|
|
|
* support for more backbuffer types!
|
|
|
* -flibit
|
|
|
*/
|
|
|
|
|
|
if (startIndex > 0 || elementCount != (dataLen / elementSizeInBytes))
|
|
|
{
|
|
|
throw new NotImplementedException(
|
|
|
"ReadBackbuffer startIndex/elementCount"
|
|
|
);
|
|
|
}
|
|
|
|
|
|
GetTextureData2D(
|
|
|
(Backbuffer as MetalBackbuffer).Texture,
|
|
|
SurfaceFormat.Color,
|
|
|
Backbuffer.Width,
|
|
|
Backbuffer.Height,
|
|
|
0,
|
|
|
subX,
|
|
|
subY,
|
|
|
subW,
|
|
|
subH,
|
|
|
data,
|
|
|
0,
|
|
|
dataLen,
|
|
|
1
|
|
|
);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region RenderTarget->Texture Method
|
|
|
|
|
|
public void ResolveTarget(RenderTargetBinding target)
|
|
|
{
|
|
|
// The target is resolved at the end of each render pass.
|
|
|
|
|
|
// If the target has mipmaps, regenerate them now
|
|
|
if (target.RenderTarget.LevelCount > 1)
|
|
|
{
|
|
|
EndPass();
|
|
|
|
|
|
IntPtr blit = mtlMakeBlitCommandEncoder(commandBuffer);
|
|
|
mtlGenerateMipmapsForTexture(
|
|
|
blit,
|
|
|
(target.RenderTarget.texture as MetalTexture).Handle
|
|
|
);
|
|
|
mtlEndEncoding(blit);
|
|
|
|
|
|
needNewRenderPass = true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Clear Method
|
|
|
|
|
|
public void Clear(
|
|
|
ClearOptions options,
|
|
|
Vector4 color,
|
|
|
float depth,
|
|
|
int stencil
|
|
|
) {
|
|
|
bool clearTarget = (options & ClearOptions.Target) == ClearOptions.Target;
|
|
|
bool clearDepth = (options & ClearOptions.DepthBuffer) == ClearOptions.DepthBuffer;
|
|
|
bool clearStencil = (options & ClearOptions.Stencil) == ClearOptions.Stencil;
|
|
|
|
|
|
if (clearTarget)
|
|
|
{
|
|
|
clearColor = color;
|
|
|
shouldClearColor = true;
|
|
|
}
|
|
|
if (clearDepth)
|
|
|
{
|
|
|
this.clearDepth = depth;
|
|
|
shouldClearDepth = true;
|
|
|
}
|
|
|
if (clearStencil)
|
|
|
{
|
|
|
this.clearStencil = stencil;
|
|
|
shouldClearStencil = true;
|
|
|
}
|
|
|
|
|
|
needNewRenderPass |= clearTarget | clearDepth | clearStencil;
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region SetRenderTargets Methods
|
|
|
|
|
|
public void SetRenderTargets(
|
|
|
RenderTargetBinding[] renderTargets,
|
|
|
IGLRenderbuffer renderbuffer,
|
|
|
DepthFormat depthFormat
|
|
|
) {
|
|
|
// Perform any pending clears before switching render targets
|
|
|
if (shouldClearColor || shouldClearDepth || shouldClearStencil)
|
|
|
{
|
|
|
UpdateRenderPass();
|
|
|
}
|
|
|
|
|
|
// Force an update to the render pass
|
|
|
needNewRenderPass = true;
|
|
|
|
|
|
// Bind the correct framebuffer
|
|
|
ResetAttachments();
|
|
|
if (renderTargets == null)
|
|
|
{
|
|
|
BindBackbuffer();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// Update color buffers
|
|
|
int i;
|
|
|
for (i = 0; i < renderTargets.Length; i += 1)
|
|
|
{
|
|
|
IRenderTarget rt = renderTargets[i].RenderTarget as IRenderTarget;
|
|
|
currentAttachmentSlices[i] = renderTargets[i].CubeMapFace;
|
|
|
if (rt.ColorBuffer != null)
|
|
|
{
|
|
|
MetalRenderbuffer rb = rt.ColorBuffer as MetalRenderbuffer;
|
|
|
currentAttachments[i] = rb.Handle;
|
|
|
currentColorFormats[i] = rb.PixelFormat;
|
|
|
currentSampleCount = rb.MultiSampleCount;
|
|
|
currentMSAttachments[i] = rb.MultiSampleHandle;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
MetalTexture tex = renderTargets[i].RenderTarget.texture as MetalTexture;
|
|
|
currentAttachments[i] = tex.Handle;
|
|
|
currentColorFormats[i] = XNAToMTL.TextureFormat[(int) tex.Format];
|
|
|
currentSampleCount = 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Update depth stencil buffer
|
|
|
IntPtr handle = IntPtr.Zero;
|
|
|
if (renderbuffer != null)
|
|
|
{
|
|
|
handle = (renderbuffer as MetalRenderbuffer).Handle;
|
|
|
}
|
|
|
currentDepthStencilBuffer = handle;
|
|
|
currentDepthFormat = (
|
|
|
(currentDepthStencilBuffer == IntPtr.Zero) ?
|
|
|
DepthFormat.None :
|
|
|
depthFormat
|
|
|
);
|
|
|
}
|
|
|
|
|
|
private void ResetAttachments()
|
|
|
{
|
|
|
for (int i = 0; i < currentAttachments.Length; i += 1)
|
|
|
{
|
|
|
currentAttachments[i] = IntPtr.Zero;
|
|
|
currentColorFormats[i] = MTLPixelFormat.Invalid;
|
|
|
currentMSAttachments[i] = IntPtr.Zero;
|
|
|
currentAttachmentSlices[i] = (CubeMapFace) 0;
|
|
|
}
|
|
|
currentDepthStencilBuffer = IntPtr.Zero;
|
|
|
currentDepthFormat = DepthFormat.None;
|
|
|
currentSampleCount = 0;
|
|
|
}
|
|
|
|
|
|
private void BindBackbuffer()
|
|
|
{
|
|
|
MetalBackbuffer bb = Backbuffer as MetalBackbuffer;
|
|
|
currentAttachments[0] = bb.ColorBuffer;
|
|
|
currentColorFormats[0] = bb.PixelFormat;
|
|
|
currentDepthStencilBuffer = bb.DepthStencilBuffer;
|
|
|
currentDepthFormat = bb.DepthFormat;
|
|
|
currentSampleCount = bb.MultiSampleCount;
|
|
|
currentMSAttachments[0] = bb.MultiSampleColorBuffer;
|
|
|
currentAttachmentSlices[0] = (CubeMapFace) 0;
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Query Object Methods
|
|
|
|
|
|
public IGLQuery CreateQuery()
|
|
|
{
|
|
|
if (!supportsOcclusionQueries)
|
|
|
{
|
|
|
throw new NotSupportedException(
|
|
|
"Occlusion queries are not supported on this device!"
|
|
|
);
|
|
|
}
|
|
|
|
|
|
IntPtr buf = mtlNewBufferWithLength(device, sizeof(ulong), 0);
|
|
|
return new MetalQuery(buf);
|
|
|
}
|
|
|
|
|
|
private void DeleteQuery(IGLQuery query)
|
|
|
{
|
|
|
(query as MetalQuery).Dispose();
|
|
|
}
|
|
|
|
|
|
public void QueryBegin(IGLQuery query)
|
|
|
{
|
|
|
// Stop the current pass
|
|
|
EndPass();
|
|
|
|
|
|
// Attach the visibility buffer to a new render pass
|
|
|
currentVisibilityBuffer = (query as MetalQuery).Handle;
|
|
|
needNewRenderPass = true;
|
|
|
}
|
|
|
|
|
|
public bool QueryComplete(IGLQuery query)
|
|
|
{
|
|
|
/* FIXME:
|
|
|
* There's no easy way to check for completion
|
|
|
* of the query. The only accurate way would be
|
|
|
* to monitor the completion of the command buffer
|
|
|
* associated with each query, but that gets tricky
|
|
|
* since we can't use completion callbacks.
|
|
|
* (Thank Objective-C and its stupid "block" lambdas.)
|
|
|
*
|
|
|
* Futhermore, I don't know how visibility queries
|
|
|
* work across command buffers, in the event of a
|
|
|
* stalled buffer overwrite or something similar.
|
|
|
*
|
|
|
* The below code is obviously wrong, but it happens
|
|
|
* to work for the Lens Flare XNA sample. Maybe it'll
|
|
|
* work for your game too?
|
|
|
*
|
|
|
* (Although if you're making a new game with FNA,
|
|
|
* you really shouldn't be using queries anyway...)
|
|
|
*
|
|
|
* -caleb
|
|
|
*/
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
public void QueryEnd(IGLQuery query)
|
|
|
{
|
|
|
if (renderCommandEncoder != IntPtr.Zero)
|
|
|
{
|
|
|
// Stop counting.
|
|
|
mtlSetVisibilityResultMode(
|
|
|
renderCommandEncoder,
|
|
|
MTLVisibilityResultMode.Disabled,
|
|
|
0
|
|
|
);
|
|
|
}
|
|
|
currentVisibilityBuffer = IntPtr.Zero;
|
|
|
}
|
|
|
|
|
|
public int QueryPixelCount(IGLQuery query)
|
|
|
{
|
|
|
IntPtr contents = mtlGetBufferContentsPtr(
|
|
|
(query as MetalQuery).Handle
|
|
|
);
|
|
|
ulong result;
|
|
|
unsafe
|
|
|
{
|
|
|
result = *((ulong *) contents);
|
|
|
}
|
|
|
return (int) result;
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region XNA->GL Enum Conversion Class
|
|
|
|
|
|
private static class XNAToMTL
|
|
|
{
|
|
|
public static readonly MTLPixelFormat[] TextureFormat = new MTLPixelFormat[]
|
|
|
{
|
|
|
MTLPixelFormat.RGBA8Unorm, // SurfaceFormat.Color
|
|
|
MTLPixelFormat.B5G6R5Unorm, // SurfaceFormat.Bgr565
|
|
|
MTLPixelFormat.BGR5A1Unorm, // SurfaceFormat.Bgra5551
|
|
|
MTLPixelFormat.ABGR4Unorm, // SurfaceFormat.Bgra4444
|
|
|
MTLPixelFormat.BC1_RGBA, // SurfaceFormat.Dxt1
|
|
|
MTLPixelFormat.BC2_RGBA, // SurfaceFormat.Dxt3
|
|
|
MTLPixelFormat.BC3_RGBA, // SurfaceFormat.Dxt5
|
|
|
MTLPixelFormat.RG8Snorm, // SurfaceFormat.NormalizedByte2
|
|
|
MTLPixelFormat.RG16Snorm, // SurfaceFormat.NormalizedByte4
|
|
|
MTLPixelFormat.RGB10A2Unorm, // SurfaceFormat.Rgba1010102
|
|
|
MTLPixelFormat.RG16Unorm, // SurfaceFormat.Rg32
|
|
|
MTLPixelFormat.RGBA16Unorm, // SurfaceFormat.Rgba64
|
|
|
MTLPixelFormat.A8Unorm, // SurfaceFormat.Alpha8
|
|
|
MTLPixelFormat.R32Float, // SurfaceFormat.Single
|
|
|
MTLPixelFormat.RG32Float, // SurfaceFormat.Vector2
|
|
|
MTLPixelFormat.RGBA32Float, // SurfaceFormat.Vector4
|
|
|
MTLPixelFormat.R16Float, // SurfaceFormat.HalfSingle
|
|
|
MTLPixelFormat.RG16Float, // SurfaceFormat.HalfVector2
|
|
|
MTLPixelFormat.RGBA16Float, // SurfaceFormat.HalfVector4
|
|
|
MTLPixelFormat.RGBA16Float, // SurfaceFormat.HdrBlendable
|
|
|
MTLPixelFormat.BGRA8Unorm // SurfaceFormat.ColorBgraEXT
|
|
|
};
|
|
|
|
|
|
public static readonly MojoShader.MOJOSHADER_usage[] VertexAttribUsage = new MojoShader.MOJOSHADER_usage[]
|
|
|
{
|
|
|
MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_POSITION, // VertexElementUsage.Position
|
|
|
MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_COLOR, // VertexElementUsage.Color
|
|
|
MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_TEXCOORD, // VertexElementUsage.TextureCoordinate
|
|
|
MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_NORMAL, // VertexElementUsage.Normal
|
|
|
MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_BINORMAL, // VertexElementUsage.Binormal
|
|
|
MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_TANGENT, // VertexElementUsage.Tangent
|
|
|
MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_BLENDINDICES, // VertexElementUsage.BlendIndices
|
|
|
MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_BLENDWEIGHT, // VertexElementUsage.BlendWeight
|
|
|
MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_DEPTH, // VertexElementUsage.Depth
|
|
|
MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_FOG, // VertexElementUsage.Fog
|
|
|
MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_POINTSIZE, // VertexElementUsage.PointSize
|
|
|
MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_SAMPLE, // VertexElementUsage.Sample
|
|
|
MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_TESSFACTOR // VertexElementUsage.TessellateFactor
|
|
|
};
|
|
|
|
|
|
public static readonly MTLVertexFormat[] VertexAttribType = new MTLVertexFormat[]
|
|
|
{
|
|
|
MTLVertexFormat.Float, // VertexElementFormat.Single
|
|
|
MTLVertexFormat.Float2, // VertexElementFormat.Vector2
|
|
|
MTLVertexFormat.Float3, // VertexElementFormat.Vector3
|
|
|
MTLVertexFormat.Float4, // VertexElementFormat.Vector4
|
|
|
MTLVertexFormat.UChar4Normalized, // VertexElementFormat.Color
|
|
|
MTLVertexFormat.UChar4, // VertexElementFormat.Byte4
|
|
|
MTLVertexFormat.Short2, // VertexElementFormat.Short2
|
|
|
MTLVertexFormat.Short4, // VertexElementFormat.Short4
|
|
|
MTLVertexFormat.Short2Normalized, // VertexElementFormat.NormalizedShort2
|
|
|
MTLVertexFormat.Short4Normalized, // VertexElementFormat.NormalizedShort4
|
|
|
MTLVertexFormat.Half2, // VertexElementFormat.HalfVector2
|
|
|
MTLVertexFormat.Half4 // VertexElementFormat.HalfVector4
|
|
|
};
|
|
|
|
|
|
public static readonly MTLIndexType[] IndexType = new MTLIndexType[]
|
|
|
{
|
|
|
MTLIndexType.UInt16, // IndexElementSize.SixteenBits
|
|
|
MTLIndexType.UInt32 // IndexElementSize.ThirtyTwoBits
|
|
|
};
|
|
|
|
|
|
public static readonly int[] IndexSize = new int[]
|
|
|
{
|
|
|
2, // IndexElementSize.SixteenBits
|
|
|
4 // IndexElementSize.ThirtyTwoBits
|
|
|
};
|
|
|
|
|
|
public static readonly MTLBlendFactor[] BlendMode = new MTLBlendFactor[]
|
|
|
{
|
|
|
MTLBlendFactor.One, // Blend.One
|
|
|
MTLBlendFactor.Zero, // Blend.Zero
|
|
|
MTLBlendFactor.SourceColor, // Blend.SourceColor
|
|
|
MTLBlendFactor.OneMinusSourceColor, // Blend.InverseSourceColor
|
|
|
MTLBlendFactor.SourceAlpha, // Blend.SourceAlpha
|
|
|
MTLBlendFactor.OneMinusSourceAlpha, // Blend.InverseSourceAlpha
|
|
|
MTLBlendFactor.DestinationColor, // Blend.DestinationColor
|
|
|
MTLBlendFactor.OneMinusDestinationColor,// Blend.InverseDestinationColor
|
|
|
MTLBlendFactor.DestinationAlpha, // Blend.DestinationAlpha
|
|
|
MTLBlendFactor.OneMinusDestinationAlpha,// Blend.InverseDestinationAlpha
|
|
|
MTLBlendFactor.BlendColor, // Blend.BlendFactor
|
|
|
MTLBlendFactor.OneMinusBlendColor, // Blend.InverseBlendFactor
|
|
|
MTLBlendFactor.SourceAlphaSaturated // Blend.SourceAlphaSaturation
|
|
|
};
|
|
|
|
|
|
public static readonly MTLBlendOperation[] BlendOperation = new MTLBlendOperation[]
|
|
|
{
|
|
|
MTLBlendOperation.Add, // BlendFunction.Add
|
|
|
MTLBlendOperation.Subtract, // BlendFunction.Subtract
|
|
|
MTLBlendOperation.ReverseSubtract, // BlendFunction.ReverseSubtract
|
|
|
MTLBlendOperation.Max, // BlendFunction.Max
|
|
|
MTLBlendOperation.Min // BlendFunction.Min
|
|
|
};
|
|
|
|
|
|
public static int ColorWriteMask(ColorWriteChannels channels)
|
|
|
{
|
|
|
if (channels == ColorWriteChannels.None)
|
|
|
{
|
|
|
return 0x0;
|
|
|
}
|
|
|
if (channels == ColorWriteChannels.All)
|
|
|
{
|
|
|
return 0xf;
|
|
|
}
|
|
|
|
|
|
int ret = 0;
|
|
|
if ((channels & ColorWriteChannels.Red) != 0)
|
|
|
{
|
|
|
ret |= (0x1 << 3);
|
|
|
}
|
|
|
if ((channels & ColorWriteChannels.Green) != 0)
|
|
|
{
|
|
|
ret |= (0x1 << 2);
|
|
|
}
|
|
|
if ((channels & ColorWriteChannels.Blue) != 0)
|
|
|
{
|
|
|
ret |= (0x1 << 1);
|
|
|
}
|
|
|
if ((channels & ColorWriteChannels.Alpha) != 0)
|
|
|
{
|
|
|
ret |= (0x1 << 0);
|
|
|
}
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
public static readonly MTLCompareFunction[] CompareFunc = new MTLCompareFunction[]
|
|
|
{
|
|
|
MTLCompareFunction.Always, // CompareFunction.Always
|
|
|
MTLCompareFunction.Never, // CompareFunction.Never
|
|
|
MTLCompareFunction.Less, // CompareFunction.Less
|
|
|
MTLCompareFunction.LessEqual, // CompareFunction.LessEqual
|
|
|
MTLCompareFunction.Equal, // CompareFunction.Equal
|
|
|
MTLCompareFunction.GreaterEqual,// CompareFunction.GreaterEqual
|
|
|
MTLCompareFunction.Greater, // CompareFunction.Greater
|
|
|
MTLCompareFunction.NotEqual // CompareFunction.NotEqual
|
|
|
};
|
|
|
|
|
|
public static readonly MTLStencilOperation[] StencilOp = new MTLStencilOperation[]
|
|
|
{
|
|
|
MTLStencilOperation.Keep, // StencilOperation.Keep
|
|
|
MTLStencilOperation.Zero, // StencilOperation.Zero
|
|
|
MTLStencilOperation.Replace, // StencilOperation.Replace
|
|
|
MTLStencilOperation.IncrementWrap, // StencilOperation.Increment
|
|
|
MTLStencilOperation.DecrementWrap, // StencilOperation.Decrement
|
|
|
MTLStencilOperation.IncrementClamp, // StencilOperation.IncrementSaturation
|
|
|
MTLStencilOperation.DecrementClamp, // StencilOperation.DecrementSaturation
|
|
|
MTLStencilOperation.Invert // StencilOperation.Invert
|
|
|
};
|
|
|
|
|
|
public static readonly MTLTriangleFillMode[] FillMode = new MTLTriangleFillMode[]
|
|
|
{
|
|
|
MTLTriangleFillMode.Fill, // FillMode.Solid
|
|
|
MTLTriangleFillMode.Lines // FillMode.WireFrame
|
|
|
};
|
|
|
|
|
|
public static float DepthBiasScale(MTLPixelFormat format)
|
|
|
{
|
|
|
switch (format)
|
|
|
{
|
|
|
case MTLPixelFormat.Depth16Unorm:
|
|
|
return (float) ((1 << 16) - 1);
|
|
|
|
|
|
case MTLPixelFormat.Depth24Unorm_Stencil8:
|
|
|
return (float) ((1 << 24) - 1);
|
|
|
|
|
|
case MTLPixelFormat.Depth32Float:
|
|
|
case MTLPixelFormat.Depth32Float_Stencil8:
|
|
|
return (float) ((1 << 23) - 1);
|
|
|
}
|
|
|
|
|
|
return 0.0f;
|
|
|
}
|
|
|
|
|
|
public static readonly MTLCullMode[] CullingEnabled = new MTLCullMode[]
|
|
|
{
|
|
|
MTLCullMode.None, // CullMode.None
|
|
|
MTLCullMode.Front, // CullMode.CullClockwiseFace
|
|
|
MTLCullMode.Back // CullMode.CullCounterClockwiseFace
|
|
|
};
|
|
|
|
|
|
public static readonly MTLSamplerAddressMode[] Wrap = new MTLSamplerAddressMode[]
|
|
|
{
|
|
|
MTLSamplerAddressMode.Repeat, // TextureAddressMode.Wrap
|
|
|
MTLSamplerAddressMode.ClampToEdge, // TextureAddressMode.Clamp
|
|
|
MTLSamplerAddressMode.MirrorRepeat // TextureAddressMode.Mirror
|
|
|
};
|
|
|
|
|
|
public static readonly MTLSamplerMinMagFilter[] MagFilter = new MTLSamplerMinMagFilter[]
|
|
|
{
|
|
|
MTLSamplerMinMagFilter.Linear, // TextureFilter.Linear
|
|
|
MTLSamplerMinMagFilter.Nearest, // TextureFilter.Point
|
|
|
MTLSamplerMinMagFilter.Linear, // TextureFilter.Anisotropic
|
|
|
MTLSamplerMinMagFilter.Linear, // TextureFilter.LinearMipPoint
|
|
|
MTLSamplerMinMagFilter.Nearest, // TextureFilter.PointMipLinear
|
|
|
MTLSamplerMinMagFilter.Nearest, // TextureFilter.MinLinearMagPointMipLinear
|
|
|
MTLSamplerMinMagFilter.Nearest, // TextureFilter.MinLinearMagPointMipPoint
|
|
|
MTLSamplerMinMagFilter.Linear, // TextureFilter.MinPointMagLinearMipLinear
|
|
|
MTLSamplerMinMagFilter.Linear // TextureFilter.MinPointMagLinearMipPoint
|
|
|
};
|
|
|
|
|
|
public static readonly MTLSamplerMipFilter[] MipFilter = new MTLSamplerMipFilter[]
|
|
|
{
|
|
|
MTLSamplerMipFilter.Linear, // TextureFilter.Linear
|
|
|
MTLSamplerMipFilter.Nearest, // TextureFilter.Point
|
|
|
MTLSamplerMipFilter.Linear, // TextureFilter.Anisotropic
|
|
|
MTLSamplerMipFilter.Nearest, // TextureFilter.LinearMipPoint
|
|
|
MTLSamplerMipFilter.Linear, // TextureFilter.PointMipLinear
|
|
|
MTLSamplerMipFilter.Linear, // TextureFilter.MinLinearMagPointMipLinear
|
|
|
MTLSamplerMipFilter.Nearest, // TextureFilter.MinLinearMagPointMipPoint
|
|
|
MTLSamplerMipFilter.Linear, // TextureFilter.MinPointMagLinearMipLinear
|
|
|
MTLSamplerMipFilter.Nearest // TextureFilter.MinPointMagLinearMipPoint
|
|
|
};
|
|
|
|
|
|
public static readonly MTLSamplerMinMagFilter[] MinFilter = new MTLSamplerMinMagFilter[]
|
|
|
{
|
|
|
MTLSamplerMinMagFilter.Linear, // TextureFilter.Linear
|
|
|
MTLSamplerMinMagFilter.Nearest, // TextureFilter.Point
|
|
|
MTLSamplerMinMagFilter.Linear, // TextureFilter.Anisotropic
|
|
|
MTLSamplerMinMagFilter.Linear, // TextureFilter.LinearMipPoint
|
|
|
MTLSamplerMinMagFilter.Nearest, // TextureFilter.PointMipLinear
|
|
|
MTLSamplerMinMagFilter.Linear, // TextureFilter.MinLinearMagPointMipLinear
|
|
|
MTLSamplerMinMagFilter.Linear, // TextureFilter.MinLinearMagPointMipPoint
|
|
|
MTLSamplerMinMagFilter.Nearest, // TextureFilter.MinPointMagLinearMipLinear
|
|
|
MTLSamplerMinMagFilter.Nearest // TextureFilter.MinPointMagLinearMipPoint
|
|
|
};
|
|
|
|
|
|
public static readonly MTLPrimitiveType[] Primitive = new MTLPrimitiveType[]
|
|
|
{
|
|
|
MTLPrimitiveType.Triangle, // PrimitiveType.TriangleList
|
|
|
MTLPrimitiveType.TriangleStrip, // PrimitiveType.TriangleStrip
|
|
|
MTLPrimitiveType.Line, // PrimitiveType.LineList
|
|
|
MTLPrimitiveType.LineStrip, // PrimitiveType.LineStrip
|
|
|
MTLPrimitiveType.Point // PrimitiveType.PointListEXT
|
|
|
};
|
|
|
|
|
|
public static int PrimitiveVerts(PrimitiveType primitiveType, int primitiveCount)
|
|
|
{
|
|
|
switch (primitiveType)
|
|
|
{
|
|
|
case PrimitiveType.TriangleList:
|
|
|
return primitiveCount * 3;
|
|
|
case PrimitiveType.TriangleStrip:
|
|
|
return primitiveCount + 2;
|
|
|
case PrimitiveType.LineList:
|
|
|
return primitiveCount * 2;
|
|
|
case PrimitiveType.LineStrip:
|
|
|
return primitiveCount + 1;
|
|
|
case PrimitiveType.PointListEXT:
|
|
|
return primitiveCount;
|
|
|
}
|
|
|
throw new NotSupportedException();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region The Faux-Backbuffer
|
|
|
|
|
|
private class MetalBackbuffer : IGLBackbuffer
|
|
|
{
|
|
|
public int Width
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public int Height
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public MTLPixelFormat PixelFormat
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public DepthFormat DepthFormat
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public int MultiSampleCount
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public MetalTexture Texture
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public IntPtr ColorBuffer = IntPtr.Zero;
|
|
|
public IntPtr MultiSampleColorBuffer = IntPtr.Zero;
|
|
|
public IntPtr DepthStencilBuffer = IntPtr.Zero;
|
|
|
|
|
|
private MetalDevice mtlDevice;
|
|
|
|
|
|
public MetalBackbuffer(
|
|
|
MetalDevice device,
|
|
|
PresentationParameters presentationParameters
|
|
|
) {
|
|
|
mtlDevice = device;
|
|
|
PixelFormat = MTLPixelFormat.RGBA8Unorm;
|
|
|
|
|
|
/* Set these now to prevent a changed event in Create!
|
|
|
* The rest will be set and don't have checks anywhere.
|
|
|
* -flibit
|
|
|
*/
|
|
|
Width = presentationParameters.BackBufferWidth;
|
|
|
Height = presentationParameters.BackBufferHeight;
|
|
|
}
|
|
|
|
|
|
public void Dispose()
|
|
|
{
|
|
|
objc_release(ColorBuffer);
|
|
|
ColorBuffer = IntPtr.Zero;
|
|
|
|
|
|
objc_release(MultiSampleColorBuffer);
|
|
|
MultiSampleColorBuffer = IntPtr.Zero;
|
|
|
|
|
|
objc_release(DepthStencilBuffer);
|
|
|
DepthStencilBuffer = IntPtr.Zero;
|
|
|
}
|
|
|
|
|
|
public void ResetFramebuffer(
|
|
|
PresentationParameters presentationParameters
|
|
|
) {
|
|
|
// Just destroy and recreate from scratch
|
|
|
Dispose();
|
|
|
CreateFramebuffer(presentationParameters);
|
|
|
}
|
|
|
|
|
|
public void CreateFramebuffer(
|
|
|
PresentationParameters presentationParameters
|
|
|
) {
|
|
|
// Update the backbuffer size
|
|
|
int newWidth = presentationParameters.BackBufferWidth;
|
|
|
int newHeight = presentationParameters.BackBufferHeight;
|
|
|
if (Width != newWidth || Height != newHeight)
|
|
|
{
|
|
|
mtlDevice.fauxBackbufferSizeChanged = true;
|
|
|
}
|
|
|
Width = newWidth;
|
|
|
Height = newHeight;
|
|
|
|
|
|
// Update other presentation parameters
|
|
|
DepthFormat = presentationParameters.DepthStencilFormat;
|
|
|
MultiSampleCount = mtlDevice.GetCompatibleSampleCount(
|
|
|
presentationParameters.MultiSampleCount
|
|
|
);
|
|
|
|
|
|
// Update color buffer to the new resolution.
|
|
|
IntPtr colorBufferDesc = mtlMakeTexture2DDescriptor(
|
|
|
PixelFormat,
|
|
|
Width,
|
|
|
Height,
|
|
|
false
|
|
|
);
|
|
|
mtlSetStorageMode(
|
|
|
colorBufferDesc,
|
|
|
MTLStorageMode.Private
|
|
|
);
|
|
|
mtlSetTextureUsage(
|
|
|
colorBufferDesc,
|
|
|
MTLTextureUsage.RenderTarget | MTLTextureUsage.ShaderRead
|
|
|
);
|
|
|
ColorBuffer = mtlNewTextureWithDescriptor(
|
|
|
mtlDevice.device,
|
|
|
colorBufferDesc
|
|
|
);
|
|
|
if (MultiSampleCount > 0)
|
|
|
{
|
|
|
mtlSetTextureType(
|
|
|
colorBufferDesc,
|
|
|
MTLTextureType.Multisample2D
|
|
|
);
|
|
|
mtlSetTextureSampleCount(
|
|
|
colorBufferDesc,
|
|
|
MultiSampleCount
|
|
|
);
|
|
|
mtlSetTextureUsage(
|
|
|
colorBufferDesc,
|
|
|
MTLTextureUsage.RenderTarget
|
|
|
);
|
|
|
MultiSampleColorBuffer = mtlNewTextureWithDescriptor(
|
|
|
mtlDevice.device,
|
|
|
colorBufferDesc
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// Update the depth/stencil buffer, if applicable
|
|
|
if (DepthFormat != DepthFormat.None)
|
|
|
{
|
|
|
IntPtr depthStencilBufferDesc = mtlMakeTexture2DDescriptor(
|
|
|
mtlDevice.GetDepthFormat(DepthFormat),
|
|
|
Width,
|
|
|
Height,
|
|
|
false
|
|
|
);
|
|
|
mtlSetStorageMode(
|
|
|
depthStencilBufferDesc,
|
|
|
MTLStorageMode.Private
|
|
|
);
|
|
|
mtlSetTextureUsage(
|
|
|
depthStencilBufferDesc,
|
|
|
MTLTextureUsage.RenderTarget
|
|
|
);
|
|
|
if (MultiSampleCount > 0)
|
|
|
{
|
|
|
mtlSetTextureType(
|
|
|
depthStencilBufferDesc,
|
|
|
MTLTextureType.Multisample2D
|
|
|
);
|
|
|
mtlSetTextureSampleCount(
|
|
|
depthStencilBufferDesc,
|
|
|
MultiSampleCount
|
|
|
);
|
|
|
}
|
|
|
DepthStencilBuffer = mtlNewTextureWithDescriptor(
|
|
|
mtlDevice.device,
|
|
|
depthStencilBufferDesc
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// Update the Texture representation
|
|
|
Texture = new MetalTexture(
|
|
|
ColorBuffer,
|
|
|
Width,
|
|
|
Height,
|
|
|
SurfaceFormat.Color,
|
|
|
1,
|
|
|
true
|
|
|
);
|
|
|
|
|
|
// This is the default render target
|
|
|
mtlDevice.SetRenderTargets(null, null, DepthFormat.None);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void InitializeFauxBackbuffer(
|
|
|
PresentationParameters presentationParameters
|
|
|
) {
|
|
|
MetalBackbuffer mtlBackbuffer = new MetalBackbuffer(
|
|
|
this,
|
|
|
presentationParameters
|
|
|
);
|
|
|
Backbuffer = mtlBackbuffer;
|
|
|
mtlBackbuffer.CreateFramebuffer(presentationParameters);
|
|
|
|
|
|
/* Create a combined vertex/index buffer
|
|
|
* for rendering the faux-backbuffer.
|
|
|
*/
|
|
|
fauxBackbufferDrawBuffer = mtlNewBufferWithLength(
|
|
|
device,
|
|
|
(16 * sizeof(float)) + (6 * sizeof(ushort)),
|
|
|
MTLResourceOptions.CPUCacheModeWriteCombined
|
|
|
);
|
|
|
|
|
|
ushort[] indices = new ushort[]
|
|
|
{
|
|
|
0, 1, 3,
|
|
|
1, 2, 3
|
|
|
};
|
|
|
GCHandle indicesPinned = GCHandle.Alloc(indices, GCHandleType.Pinned);
|
|
|
SDL.SDL_memcpy(
|
|
|
mtlGetBufferContentsPtr(fauxBackbufferDrawBuffer) + (16 * sizeof(float)),
|
|
|
indicesPinned.AddrOfPinnedObject(),
|
|
|
(IntPtr) (6 * sizeof(ushort))
|
|
|
);
|
|
|
indicesPinned.Free();
|
|
|
|
|
|
// Create vertex and fragment shaders for the faux-backbuffer pipeline
|
|
|
string shaderSource =
|
|
|
@"
|
|
|
#include <metal_stdlib>
|
|
|
using namespace metal;
|
|
|
|
|
|
struct VertexIn {
|
|
|
packed_float2 position;
|
|
|
packed_float2 texCoord;
|
|
|
};
|
|
|
|
|
|
struct VertexOut {
|
|
|
float4 position [[ position ]];
|
|
|
float2 texCoord;
|
|
|
};
|
|
|
|
|
|
vertex VertexOut
|
|
|
vertexShader(
|
|
|
uint vertexID [[ vertex_id ]],
|
|
|
constant VertexIn *vertexArray [[ buffer(0) ]]
|
|
|
) {
|
|
|
VertexOut out;
|
|
|
out.position = float4(vertexArray[vertexID].position, 0.0, 1.0);
|
|
|
out.position.y *= -1;
|
|
|
out.texCoord = vertexArray[vertexID].texCoord;
|
|
|
return out;
|
|
|
}
|
|
|
|
|
|
fragment float4
|
|
|
fragmentShader(
|
|
|
VertexOut in [[stage_in]],
|
|
|
texture2d<half> colorTexture [[ texture(0) ]],
|
|
|
sampler s0 [[sampler(0)]]
|
|
|
) {
|
|
|
const half4 colorSample = colorTexture.sample(s0, in.texCoord);
|
|
|
return float4(colorSample);
|
|
|
}
|
|
|
";
|
|
|
|
|
|
IntPtr nsShaderSource = UTF8ToNSString(shaderSource);
|
|
|
IntPtr nsVertexShader = UTF8ToNSString("vertexShader");
|
|
|
IntPtr nsFragmentShader = UTF8ToNSString("fragmentShader");
|
|
|
|
|
|
IntPtr library = mtlNewLibraryWithSource(
|
|
|
device,
|
|
|
nsShaderSource,
|
|
|
IntPtr.Zero
|
|
|
);
|
|
|
IntPtr vertexFunc = mtlNewFunctionWithName(
|
|
|
library,
|
|
|
nsVertexShader
|
|
|
);
|
|
|
IntPtr fragFunc = mtlNewFunctionWithName(
|
|
|
library,
|
|
|
nsFragmentShader
|
|
|
);
|
|
|
|
|
|
objc_release(nsShaderSource);
|
|
|
objc_release(nsVertexShader);
|
|
|
objc_release(nsFragmentShader);
|
|
|
|
|
|
// Create a sampler state
|
|
|
IntPtr samplerDescriptor = mtlNewSamplerDescriptor();
|
|
|
mtlSetSamplerMinFilter(samplerDescriptor, backbufferScaleMode);
|
|
|
mtlSetSamplerMagFilter(samplerDescriptor, backbufferScaleMode);
|
|
|
fauxBackbufferSamplerState = mtlNewSamplerStateWithDescriptor(
|
|
|
device,
|
|
|
samplerDescriptor
|
|
|
);
|
|
|
objc_release(samplerDescriptor);
|
|
|
|
|
|
// Create a render pipeline for rendering the backbuffer
|
|
|
IntPtr pipelineDesc = mtlNewRenderPipelineDescriptor();
|
|
|
mtlSetPipelineVertexFunction(pipelineDesc, vertexFunc);
|
|
|
mtlSetPipelineFragmentFunction(pipelineDesc, fragFunc);
|
|
|
mtlSetAttachmentPixelFormat(
|
|
|
mtlGetColorAttachment(pipelineDesc, 0),
|
|
|
mtlGetLayerPixelFormat(layer)
|
|
|
);
|
|
|
fauxBackbufferRenderPipeline = mtlNewRenderPipelineStateWithDescriptor(
|
|
|
device,
|
|
|
pipelineDesc
|
|
|
);
|
|
|
objc_release(pipelineDesc);
|
|
|
objc_release(vertexFunc);
|
|
|
objc_release(fragFunc);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
}
|
|
|
}
|
|
|
|