mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2024-11-08 16:48:40 +00:00
884b4e5fd3
* Initial non 2D textures support - Shaders still need to be changed - Some types aren't yet implemented * Start implementing texture instructions suffixes Fix wrong texture type with cube and TEXS Also support array textures in TEX and TEX.B Clean up TEX and TEXS coords managment Fix TEXS.LL with non-2d textures Implement TEX.AOFFI Get the right arguments for TEX, TEXS and TLDS Also, store suffix operands in appropriate values to support multiple suffix combinaisons * Support depth in read/writeTexture Also support WrapR and detect mipmap * Proper cube map textures support + fix TEXS.LZ * Implement depth compare * some code clean up * Implement CubeMap textures in OGLTexture.Create * Implement TLD4 and TLD4S * Add Texture 1D support * updates comments * fix some code style issues * Fix some nits + rename some things to be less confusing * Remove GetSuffix local functions * AOFFI => AOffI * TextureType => GalTextureTarget * finish renaming TextureType to TextureTarget * Disable LL, LZ and LB support in the decompiler This needs more work at the GL level (GLSL implementation should be right) * Revert "Disable LL, LZ and LB support in the decompiler" This reverts commit 64536c3d9f673645faff3152838d1413c3203395. * Fix TEXS ARRAY_2D index * ImageFormat depth should be 1 for all image format * Fix shader build issues with sampler1DShadow and texture * Fix DC & AOFFI combinaison with TEX/TEXS * Support AOFFI with TLD4 and TLD4S * Fix shader compilation error for TLD4.AOFFI with no DC * Fix binding isuses on the 2d copy engine TODO: support 2d array copy * Support 2D array copy operation in the 2D engine This make every copy right in the GPU side. Thie CPU copy probably needs to be updated * Implement GetGpuSize + fix somes issues with 2d engine copies TODO: mipmap level in it * Don't throw an exception in the layer handling * Fix because of rebase * Reject 2d layers of non textures in 2d copy engine * Add 3D textures and mipmap support on BlockLinearSwizzle * Fix naming on new BitUtils methods * gpu cache: Make sure to invalidate textures that doesn't have the same target * Add the concept of layer count for array instead of using depth Also cleanup GetGpuSize as Swizzle can compute the size with mipmap * Support multi layer with mip map in ReadTexture * Add more check for cache invalidation & remove cubemap and cubemap array code for now Also fix compressed 2d array * Fix texelFetchOffset shader build error * Start looking into cube map again Also add some way to log write in register in engines * fix write register log levles * Remove debug logs in WriteRegister * Disable AOFFI support on non NVIDIA drivers * Fix code align
509 lines
15 KiB
C#
509 lines
15 KiB
C#
using Ryujinx.Common.Logging;
|
|
using Ryujinx.Graphics.Gal;
|
|
using Ryujinx.Graphics.Memory;
|
|
using Ryujinx.HLE.HOS.Kernel;
|
|
using Ryujinx.HLE.HOS.Kernel.Threading;
|
|
using Ryujinx.HLE.HOS.Services.Nv.NvGpuAS;
|
|
using Ryujinx.HLE.HOS.Services.Nv.NvMap;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Threading;
|
|
|
|
using static Ryujinx.HLE.HOS.Services.Android.Parcel;
|
|
|
|
namespace Ryujinx.HLE.HOS.Services.Android
|
|
{
|
|
class NvFlinger : IDisposable
|
|
{
|
|
private delegate long ServiceProcessParcel(ServiceCtx context, BinaryReader parcelReader);
|
|
|
|
private Dictionary<(string, int), ServiceProcessParcel> _commands;
|
|
|
|
private KEvent _binderEvent;
|
|
|
|
private IGalRenderer _renderer;
|
|
|
|
private const int BufferQueueCount = 0x40;
|
|
private const int BufferQueueMask = BufferQueueCount - 1;
|
|
|
|
[Flags]
|
|
private enum HalTransform
|
|
{
|
|
FlipX = 1,
|
|
FlipY = 2,
|
|
Rotate90 = 4,
|
|
Rotate180 = FlipX | FlipY,
|
|
Rotate270 = Rotate90 | Rotate180,
|
|
}
|
|
|
|
private enum BufferState
|
|
{
|
|
Free,
|
|
Dequeued,
|
|
Queued,
|
|
Acquired
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential, Size = 0x8)]
|
|
private struct Fence
|
|
{
|
|
public int id;
|
|
public int value;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Explicit, Size = 0x24)]
|
|
private struct MultiFence
|
|
{
|
|
[FieldOffset(0x0)]
|
|
public int FenceCount;
|
|
|
|
[FieldOffset(0x4)]
|
|
public Fence Fence0;
|
|
|
|
[FieldOffset(0xC)]
|
|
public Fence Fence1;
|
|
|
|
[FieldOffset(0x14)]
|
|
public Fence Fence2;
|
|
|
|
[FieldOffset(0x1C)]
|
|
public Fence Fence3;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
|
|
private struct Rect
|
|
{
|
|
public int Top;
|
|
public int Left;
|
|
public int Right;
|
|
public int Bottom;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Explicit)]
|
|
private struct QueueBufferObject
|
|
{
|
|
[FieldOffset(0x0)]
|
|
public long Timestamp;
|
|
|
|
[FieldOffset(0x8)]
|
|
public int IsAutoTimestamp;
|
|
|
|
[FieldOffset(0xC)]
|
|
public Rect Crop;
|
|
|
|
[FieldOffset(0x1C)]
|
|
public int ScalingMode;
|
|
|
|
[FieldOffset(0x20)]
|
|
public HalTransform Transform;
|
|
|
|
[FieldOffset(0x24)]
|
|
public int StickyTransform;
|
|
|
|
[FieldOffset(0x28)]
|
|
public int Unknown;
|
|
|
|
[FieldOffset(0x2C)]
|
|
public int SwapInterval;
|
|
|
|
[FieldOffset(0x30)]
|
|
public MultiFence Fence;
|
|
}
|
|
|
|
private struct BufferEntry
|
|
{
|
|
public BufferState State;
|
|
|
|
public HalTransform Transform;
|
|
|
|
public Rect Crop;
|
|
|
|
public GbpBuffer Data;
|
|
}
|
|
|
|
private BufferEntry[] _bufferQueue;
|
|
|
|
private AutoResetEvent _waitBufferFree;
|
|
|
|
private bool _disposed;
|
|
|
|
public NvFlinger(IGalRenderer renderer, KEvent binderEvent)
|
|
{
|
|
_commands = new Dictionary<(string, int), ServiceProcessParcel>
|
|
{
|
|
{ ("android.gui.IGraphicBufferProducer", 0x1), GbpRequestBuffer },
|
|
{ ("android.gui.IGraphicBufferProducer", 0x3), GbpDequeueBuffer },
|
|
{ ("android.gui.IGraphicBufferProducer", 0x4), GbpDetachBuffer },
|
|
{ ("android.gui.IGraphicBufferProducer", 0x7), GbpQueueBuffer },
|
|
{ ("android.gui.IGraphicBufferProducer", 0x8), GbpCancelBuffer },
|
|
{ ("android.gui.IGraphicBufferProducer", 0x9), GbpQuery },
|
|
{ ("android.gui.IGraphicBufferProducer", 0xa), GbpConnect },
|
|
{ ("android.gui.IGraphicBufferProducer", 0xb), GbpDisconnect },
|
|
{ ("android.gui.IGraphicBufferProducer", 0xe), GbpPreallocBuffer }
|
|
};
|
|
|
|
_renderer = renderer;
|
|
_binderEvent = binderEvent;
|
|
|
|
_bufferQueue = new BufferEntry[0x40];
|
|
|
|
_waitBufferFree = new AutoResetEvent(false);
|
|
}
|
|
|
|
public long ProcessParcelRequest(ServiceCtx context, byte[] parcelData, int code)
|
|
{
|
|
using (MemoryStream ms = new MemoryStream(parcelData))
|
|
{
|
|
BinaryReader reader = new BinaryReader(ms);
|
|
|
|
ms.Seek(4, SeekOrigin.Current);
|
|
|
|
int strSize = reader.ReadInt32();
|
|
|
|
string interfaceName = Encoding.Unicode.GetString(reader.ReadBytes(strSize * 2));
|
|
|
|
long remainder = ms.Position & 0xf;
|
|
|
|
if (remainder != 0)
|
|
{
|
|
ms.Seek(0x10 - remainder, SeekOrigin.Current);
|
|
}
|
|
|
|
ms.Seek(0x50, SeekOrigin.Begin);
|
|
|
|
if (_commands.TryGetValue((interfaceName, code), out ServiceProcessParcel procReq))
|
|
{
|
|
Logger.PrintDebug(LogClass.ServiceVi, $"{interfaceName} {procReq.Method.Name}");
|
|
|
|
return procReq(context, reader);
|
|
}
|
|
else
|
|
{
|
|
throw new NotImplementedException($"{interfaceName} {code}");
|
|
}
|
|
}
|
|
}
|
|
|
|
private long GbpRequestBuffer(ServiceCtx context, BinaryReader parcelReader)
|
|
{
|
|
int slot = parcelReader.ReadInt32();
|
|
|
|
using (MemoryStream ms = new MemoryStream())
|
|
{
|
|
BinaryWriter writer = new BinaryWriter(ms);
|
|
|
|
BufferEntry entry = _bufferQueue[slot];
|
|
|
|
int bufferCount = 1; //?
|
|
long bufferSize = entry.Data.Size;
|
|
|
|
writer.Write(bufferCount);
|
|
writer.Write(bufferSize);
|
|
|
|
entry.Data.Write(writer);
|
|
|
|
writer.Write(0);
|
|
|
|
return MakeReplyParcel(context, ms.ToArray());
|
|
}
|
|
}
|
|
|
|
private long GbpDequeueBuffer(ServiceCtx context, BinaryReader parcelReader)
|
|
{
|
|
//TODO: Errors.
|
|
int format = parcelReader.ReadInt32();
|
|
int width = parcelReader.ReadInt32();
|
|
int height = parcelReader.ReadInt32();
|
|
int getTimestamps = parcelReader.ReadInt32();
|
|
int usage = parcelReader.ReadInt32();
|
|
|
|
int slot = GetFreeSlotBlocking(width, height);
|
|
|
|
return MakeReplyParcel(context, slot, 1, 0x24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
|
}
|
|
|
|
private long GbpQueueBuffer(ServiceCtx context, BinaryReader parcelReader)
|
|
{
|
|
context.Device.Statistics.RecordGameFrameTime();
|
|
|
|
//TODO: Errors.
|
|
int slot = parcelReader.ReadInt32();
|
|
|
|
long Position = parcelReader.BaseStream.Position;
|
|
|
|
QueueBufferObject queueBufferObject = ReadFlattenedObject<QueueBufferObject>(parcelReader);
|
|
|
|
parcelReader.BaseStream.Position = Position;
|
|
|
|
_bufferQueue[slot].Transform = queueBufferObject.Transform;
|
|
_bufferQueue[slot].Crop = queueBufferObject.Crop;
|
|
|
|
_bufferQueue[slot].State = BufferState.Queued;
|
|
|
|
SendFrameBuffer(context, slot);
|
|
|
|
if (context.Device.EnableDeviceVsync)
|
|
{
|
|
context.Device.VsyncEvent.WaitOne();
|
|
}
|
|
|
|
return MakeReplyParcel(context, 1280, 720, 0, 0, 0);
|
|
}
|
|
|
|
private long GbpDetachBuffer(ServiceCtx context, BinaryReader parcelReader)
|
|
{
|
|
return MakeReplyParcel(context, 0);
|
|
}
|
|
|
|
private long GbpCancelBuffer(ServiceCtx context, BinaryReader parcelReader)
|
|
{
|
|
//TODO: Errors.
|
|
int slot = parcelReader.ReadInt32();
|
|
|
|
MultiFence fence = ReadFlattenedObject<MultiFence>(parcelReader);
|
|
|
|
_bufferQueue[slot].State = BufferState.Free;
|
|
|
|
_waitBufferFree.Set();
|
|
|
|
return MakeReplyParcel(context, 0);
|
|
}
|
|
|
|
private long GbpQuery(ServiceCtx context, BinaryReader parcelReader)
|
|
{
|
|
return MakeReplyParcel(context, 0, 0);
|
|
}
|
|
|
|
private long GbpConnect(ServiceCtx context, BinaryReader parcelReader)
|
|
{
|
|
return MakeReplyParcel(context, 1280, 720, 0, 0, 0);
|
|
}
|
|
|
|
private long GbpDisconnect(ServiceCtx context, BinaryReader parcelReader)
|
|
{
|
|
return MakeReplyParcel(context, 0);
|
|
}
|
|
|
|
private long GbpPreallocBuffer(ServiceCtx context, BinaryReader parcelReader)
|
|
{
|
|
int slot = parcelReader.ReadInt32();
|
|
|
|
bool hasInput = parcelReader.ReadInt32() == 1;
|
|
|
|
if (hasInput)
|
|
{
|
|
byte[] graphicBuffer = ReadFlattenedObject(parcelReader);
|
|
|
|
_bufferQueue[slot].State = BufferState.Free;
|
|
|
|
using (BinaryReader graphicBufferReader = new BinaryReader(new MemoryStream(graphicBuffer)))
|
|
{
|
|
_bufferQueue[slot].Data = new GbpBuffer(graphicBufferReader);
|
|
}
|
|
|
|
}
|
|
|
|
return MakeReplyParcel(context, 0);
|
|
}
|
|
|
|
private byte[] ReadFlattenedObject(BinaryReader reader)
|
|
{
|
|
long flattenedObjectSize = reader.ReadInt64();
|
|
|
|
return reader.ReadBytes((int)flattenedObjectSize);
|
|
}
|
|
|
|
private unsafe T ReadFlattenedObject<T>(BinaryReader reader) where T: struct
|
|
{
|
|
byte[] data = ReadFlattenedObject(reader);
|
|
|
|
fixed (byte* ptr = data)
|
|
{
|
|
return Marshal.PtrToStructure<T>((IntPtr)ptr);
|
|
}
|
|
}
|
|
|
|
private long MakeReplyParcel(ServiceCtx context, params int[] ints)
|
|
{
|
|
using (MemoryStream ms = new MemoryStream())
|
|
{
|
|
BinaryWriter writer = new BinaryWriter(ms);
|
|
|
|
foreach (int Int in ints)
|
|
{
|
|
writer.Write(Int);
|
|
}
|
|
|
|
return MakeReplyParcel(context, ms.ToArray());
|
|
}
|
|
}
|
|
|
|
private long MakeReplyParcel(ServiceCtx context, byte[] data)
|
|
{
|
|
(long replyPos, long replySize) = context.Request.GetBufferType0x22();
|
|
|
|
byte[] reply = MakeParcel(data, new byte[0]);
|
|
|
|
context.Memory.WriteBytes(replyPos, reply);
|
|
|
|
return 0;
|
|
}
|
|
|
|
private GalImageFormat ConvertColorFormat(ColorFormat colorFormat)
|
|
{
|
|
switch (colorFormat)
|
|
{
|
|
case ColorFormat.A8B8G8R8:
|
|
return GalImageFormat.RGBA8 | GalImageFormat.Unorm;
|
|
case ColorFormat.X8B8G8R8:
|
|
return GalImageFormat.RGBX8 | GalImageFormat.Unorm;
|
|
case ColorFormat.R5G6B5:
|
|
return GalImageFormat.BGR565 | GalImageFormat.Unorm;
|
|
case ColorFormat.A8R8G8B8:
|
|
return GalImageFormat.BGRA8 | GalImageFormat.Unorm;
|
|
case ColorFormat.A4B4G4R4:
|
|
return GalImageFormat.RGBA4 | GalImageFormat.Unorm;
|
|
default:
|
|
throw new NotImplementedException($"Color Format \"{colorFormat}\" not implemented!");
|
|
}
|
|
}
|
|
|
|
// TODO: support multi surface
|
|
private void SendFrameBuffer(ServiceCtx context, int slot)
|
|
{
|
|
int fbWidth = _bufferQueue[slot].Data.Header.Width;
|
|
int fbHeight = _bufferQueue[slot].Data.Header.Height;
|
|
|
|
int nvMapHandle = _bufferQueue[slot].Data.Buffer.Surfaces[0].NvMapHandle;
|
|
|
|
if (nvMapHandle == 0)
|
|
{
|
|
nvMapHandle = _bufferQueue[slot].Data.Buffer.NvMapId;
|
|
}
|
|
|
|
int bufferOffset = _bufferQueue[slot].Data.Buffer.Surfaces[0].Offset;
|
|
|
|
NvMapHandle map = NvMapIoctl.GetNvMap(context, nvMapHandle);
|
|
|
|
long fbAddr = map.Address + bufferOffset;
|
|
|
|
_bufferQueue[slot].State = BufferState.Acquired;
|
|
|
|
Rect crop = _bufferQueue[slot].Crop;
|
|
|
|
bool flipX = _bufferQueue[slot].Transform.HasFlag(HalTransform.FlipX);
|
|
bool flipY = _bufferQueue[slot].Transform.HasFlag(HalTransform.FlipY);
|
|
|
|
GalImageFormat imageFormat = ConvertColorFormat(_bufferQueue[slot].Data.Buffer.Surfaces[0].ColorFormat);
|
|
|
|
int BlockHeight = 1 << _bufferQueue[slot].Data.Buffer.Surfaces[0].BlockHeightLog2;
|
|
|
|
//Note: Rotation is being ignored.
|
|
|
|
int top = crop.Top;
|
|
int left = crop.Left;
|
|
int right = crop.Right;
|
|
int bottom = crop.Bottom;
|
|
|
|
NvGpuVmm vmm = NvGpuASIoctl.GetASCtx(context).Vmm;
|
|
|
|
_renderer.QueueAction(() =>
|
|
{
|
|
if (!_renderer.Texture.TryGetImage(fbAddr, out GalImage image))
|
|
{
|
|
image = new GalImage(
|
|
fbWidth,
|
|
fbHeight, 1, 1, 1, BlockHeight, 1,
|
|
GalMemoryLayout.BlockLinear,
|
|
imageFormat,
|
|
GalTextureTarget.TwoD);
|
|
}
|
|
|
|
context.Device.Gpu.ResourceManager.ClearPbCache();
|
|
context.Device.Gpu.ResourceManager.SendTexture(vmm, fbAddr, image);
|
|
|
|
_renderer.RenderTarget.SetTransform(flipX, flipY, top, left, right, bottom);
|
|
_renderer.RenderTarget.Present(fbAddr);
|
|
|
|
ReleaseBuffer(slot);
|
|
});
|
|
}
|
|
|
|
private void ReleaseBuffer(int slot)
|
|
{
|
|
_bufferQueue[slot].State = BufferState.Free;
|
|
|
|
_binderEvent.ReadableEvent.Signal();
|
|
|
|
_waitBufferFree.Set();
|
|
}
|
|
|
|
private int GetFreeSlotBlocking(int width, int height)
|
|
{
|
|
int slot;
|
|
|
|
do
|
|
{
|
|
if ((slot = GetFreeSlot(width, height)) != -1)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (_disposed)
|
|
{
|
|
break;
|
|
}
|
|
|
|
_waitBufferFree.WaitOne();
|
|
}
|
|
while (!_disposed);
|
|
|
|
return slot;
|
|
}
|
|
|
|
private int GetFreeSlot(int width, int height)
|
|
{
|
|
lock (_bufferQueue)
|
|
{
|
|
for (int slot = 0; slot < _bufferQueue.Length; slot++)
|
|
{
|
|
if (_bufferQueue[slot].State != BufferState.Free)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
GbpBuffer data = _bufferQueue[slot].Data;
|
|
|
|
if (data.Header.Width == width &&
|
|
data.Header.Height == height)
|
|
{
|
|
_bufferQueue[slot].State = BufferState.Dequeued;
|
|
|
|
return slot;
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (disposing && !_disposed)
|
|
{
|
|
_disposed = true;
|
|
|
|
_waitBufferFree.Set();
|
|
_waitBufferFree.Dispose();
|
|
}
|
|
}
|
|
}
|
|
} |